核心概念
手机号 → 钉钉用户。13800000001。最小示例
# 一段 SNL:声明包、事物、关系,并把关系落到具体事物上 package demo.dingtalk object 钉钉用户 userid 手机号 relation 钉钉定位(A, B) A->B=1 钉钉定位(userid, 钉钉用户) 钉钉定位(手机号, 钉钉用户)
不同人开发的软件各自描述自己的 网络,再用 转换方法 打通,网络之间凭关系自动路由——不再逐家对接 API。
三步上手
注册网络
一段 SNL 描述你的模型,到工作台「注册新网络」,校验通过即入库。
注册转换方法
一段整包转换 SNL(字段等同 / 事物等同),说明你的网络与别的网络如何打通。
登记实现
录入整段实现语句(如 @buildMap),用「就绪检查」看缺实现边,用「执行区域」跑脚本验证。
SNL 语法速查
声明包。package com.example.erp
导入其它包,可起别名。import com.example.common as c,之后用 c:用户 限定寻址。
声明事物。object 省份 城市
声明关系与数量约束。relation 包含(A, B)(一省多市)。
A->B=[0,)
把关系落到具体事物。包含(省份, 城市)、钉钉定位(userid, 钉钉用户)。
同名事物跨包分立,歧义时用 包名:事物 钉定。demo.bridge:客户。
映射表实现(给边挂数据)。@buildMap((源, 目标), ((源值, 目标值), …))。
具体路径映射:把一条边实现为「沿指定链行走」。@buildChainMap((国家, 城市), with(国家).get(省份).get(城市))。
常量实现。@buildConstMap((状态, 默认状态), "激活")。
等同实现(目标取来源之一的值)。@buildEqualMap((A, B, A), A)。
网络请求实现(内置求解器,引 cURL/jq 语法)。正文一行管道命令,${源概念} 执行时替换为源值。JSON 提取 | jq -r '.a.b[0]'。
能力引用:把一条边实现为一次能力调用,正文恰一行「能力名 + 可选版本」。未注册 = 挂起(就绪检查列缺实现)。
查询。@take(省份: "浙江", 城市)。
隐藏中间事物。@hideObject(中间概念)。
多源注入与视图缩放(进阶)。
注释。
实现与执行脚本
实现不是逐关系录入,而是给网络的一个实现录入整段 SNL 实现语句:
package test @buildMap((aa, bb), ((u1, 张甲)))
执行区域在「网络 + 该实现」装配出的图上跑取数脚本(一行一条,ITakeCommand 直译,行末打印):
from(aa, u1).get(bb) # 单跳:aa 为 u1,取 bb -> 张甲 from(a, a1).get(m).get(c) # 连跳:值逐跳流转 from(a, a1).get(m).with(b, b2).get(c) # 中途补源:该跳源集 = 上一结果 + (b, b2)
打包化:网络 = 清单 + 包集合
当一个平台有几百上千个接口(如飞书 1513 个接口),就把它组织成一个网络而不是一堆网络:
com.feishu.contact_v3.user.create → contact_v3。.common 结尾,存放共享事物,装配时随模块一起加载。按模块切片加载
大网络整段装配很慢,编辑页「结构图」与脚本执行只装配「公共包 + 当前模块」:
在 network-edit.html 三页签(包编辑 / 清单 / 结构图)里选模块即可;工具栏支持新增模块、删除模块、新增包、删除包。
能力脚本:四种解释器与统一框架
SNL 里一条边可以声明为能力引用(script capability(源, 目标) begin 能力名 v版本 end),
执行时把「当前关系 + 来源值」交给能力脚本,脚本算出目标值返回。
四种解释器
统一脚本框架(所有解释器同契约)
能力脚本执行时能拿到「当前关系 / 来源值」,并设置目标值:
["手机号"])。"钉钉用户")。设置目标值 = 脚本返回列表(JS 的 invoke() 返回值;独立脚本 print 结果 JSON)。
各解释器完整样例(获取关系 / 来源值 / 返回目标值)
统一口径:获取关系=fromNames(源概念名)/ toName(目标概念名);
来源值=sources(按名)/ values(按位置,与 fromNames 同序);
返回目标值=列表(单值包一层 [x])。
① JavaScript(Nashorn)
// 全局已注入:fromNames, toName, sources, values;invoke() 的返回值 = 目标值
function invoke() {
var from = fromNames; // 获取关系:源概念名数组,如 ["手机号"]
var to = toName; // 获取关系:目标概念名,如 "钉钉用户"
var v0 = values[0]; // 获取来源值:按位置(与 fromNames 同序)
var byName = sources["手机号"]; // 获取来源值:按源概念名
return [v0 + " -> " + to]; // 返回目标值:数组
}
② MicroPython / ③ GraalPy(独立脚本,stdin → stdout)
# 读 stdin 请求 {"fromNames","toName","sources","values"},写 stdout 结果
import sys, json
req = json.loads(sys.stdin.read())
from_names = req["fromNames"] # 获取关系:源概念名
to_name = req["toName"] # 获取关系:目标概念名
v0 = req["values"][0] # 获取来源值:按位置
by_name = req["sources"].get(from_names[0], "")
print(json.dumps({"ok": True, "values": [v0 + " -> " + to_name]})) # 返回目标值
MicroPython 与 GraalPy 脚本形态完全一致;区别只在解释器能力——
MicroPython 是沙箱(子集标准库),GraalPy 可 os/platform/socket 读宿主、import java 互操作。
④ WebAssembly(Chicory,内存 JSON)
WASM 能力不是「源码脚本」而是模块:导出 alloc(len) / invoke(ptr,len) / result_len(),
请求 JSON(含 fromNames/toName/sources/values)已写入模块内存,模块解析后把结果 JSON 写回内存。
宿主信息面:显式 import env.hostInfo/env.hostInfoLen 才注入(只读)。WAT 模板见
data/capabilities/本机信息.*/module.wat(本机信息已改 GraalPy,模板仍在引擎
HostInfoWasmModule)。
服务:网络 + 实现 对外调用
服务是把「网络(契约)+ 实现(应答)」组合发布成的一个可开关的对外调用单元—— 外部系统不接触 SNL,只按服务名查值。工作台「我的服务」可发布、停用/启用。
外部调用协议(POST,无需登录)
# 结构化查询:from(手机号, 13800000001).get(钉钉用户) 的等价外部调用 POST /api/registry/services/通讯录服务/query {"object":"手机号","value":"13800000001","get":["钉钉用户"]} # -> {"success":true,"data":{"service":"通讯录服务","network":"钉钉网络","impl":"default","values":["…"]}} # 原样脚本(进阶:连跳 / with 补源 / path 诊断) POST /api/registry/services/通讯录服务/query {"script":"from(手机号, 13800000001).get(钉钉用户).get(昵称)"}
实现文本由网络实现提供(@buildMap 表或 script curl 指向你自己的 HTTP 端点)——服务查询实际执行的就是「该网络 + 该实现」装配图上的链式取数。
常见报错与参考
SNL 校验失败时错误信息会附带正确参考示例:
# 报错:buildConstMap 参数不足。参考示例:@buildConstMap((源对象, 目标对象), 常量值) @buildConstMap((aa, bb), "激活") # 正确:源、目标两个对象 + 一个常量值 # 报错:buildMap 来源/目标参数不足。参考示例:@buildMap((源, 目标), ((源值, 目标值))) @buildMap((aa, bb), ((u1, 张甲))) # 正确:多源时平铺为 (源1, 源2, ..., 目标)
完整的 SNL 语言规范见仓库《语义网络语言规范_SNL.md》。