Graphical 协议设计规范
设计背景与目标
TFRobotServer 平台通过 @tfs_action 机制,结合 JsonSchema、JMESPath、x-namespace / x-actions / x-ref,实现了图结构(graphical)场景下节点、边等结构化数据的协议标准,支持图谱查询与节点/边/类别的增删改查联动。
本文档以当前代码实现为准,关键数据结构来自:
tfrobotserver/dtos/factory/base.py:BaseGraphical/Graphicaltfrobotserver/dtos/factory/brain/memory/graph_dto.py:Node/Edge/ClassInfotfrobotserver/robot/factory/brain/memory/graph_manage_mixin.py:图谱管理方法(get/add/update/delete)tfrobotserver/robot/factory/tfs_action.py:对category="graphical"+resource_op_type="get_graph"的返回类型约束
基本约定
- 所有图结构相关 Action 需通过
@tfs_action注册。 - 当 Action 用于“返回图结构”(即
resource_op_type="get_graph")时: category必须为"graphical"- 函数返回类型必须继承
BaseGraphical(通常使用Graphical[Node, Edge]) - 每个 Action 应定义清晰的入参 Schema 和返回 Schema,基于 Pydantic/JsonSchema 表达。
- 通过
x-namespace/x-actions/x-ref实现: - 当前节点/边上下文变量注入(
x-namespace) - 可操作动作集合(
x-actions) - 动作参数与上下文变量绑定(
x-ref、x-default)
图结构基础 DTO(现状对齐)
Node
iri: str:节点唯一标识(IRI)label: str:节点展示名称cls: str:节点类别 IRIproperties: dict[str, Any] | 显示为 null:其它属性
Edge
source: str:源节点 IRItarget: str:目标节点 IRIlabel: str:边展示名称(通常为对象属性名)property: str:对象属性 IRI(用于标识“关系类型”)properties: dict[str, Any] | 显示为 null:其它属性
图响应
Graphical[Node, Edge]:nodes: list[Node]edges: list[Edge]
典型接口结构
1. 主图查询Action(get_graph)
- 对应实现参考:
GraphManageMixin.aget_nodes(..., cls=None, include_edges=True) -> Graphical[Node, Edge] - 返回 Schema 建议在
nodes.items/edges.items的元素结构中声明x-namespace,为其它 Action 提供上下文变量。 - 可在每个元素结构中声明
x-actions,表明当前节点/边可用的操作。 x-namespace:- key 为变量名
- value 为 JMESPath 路径(相对于当前元素)或 Options 结构(
labels/keys/values三个 JMESPath 字段;见下方示例)
{
"type": "object",
"properties": {
"nodes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"iri": {"type": "string"},
"label": {"type": "string"},
"cls": {"type": "string"},
"properties": {"type": ["object", "null"]}
},
"required": ["iri", "label", "cls"],
"x-namespace": {
"node_iri": "iri",
"node_label": "label",
"node_cls": "cls",
"user_options": {
"Options": {
"labels": "values[*].display_name",
"keys": "values[*].user_id",
"values": "users"
}
}
},
"x-actions": ["update_node", "delete_node"]
}
},
"edges": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"label": {"type": "string"},
"property": {"type": "string"},
"properties": {"type": ["object", "null"]}
},
"required": ["source", "target", "property"],
"x-namespace": {
"source": "source",
"target": "target",
"edge_property": "property"
},
"x-actions": ["delete_edge"]
}
}
},
"required": ["nodes", "edges"]
}
get_graph 推荐入参(可选)
当前 GraphManageMixin.aget_nodes 支持:
cls: str | list[str] | null:按类别过滤(类别 IRI)include_edges: bool:是否包含边
2. 节点/边操作Action(如add_node、update_node、delete_edge、select_user)
- 入参Schema字段可通过 x-ref 声明依赖,直接引用 namespace 中的变量名。
- 可通过 x-default 声明字段默认值,值为 Jmespath 路径(相对于当前 namespace)。
- 可通过 x-fetch 声明“补全动作”,当 x-ref/x-default 无法从当前 namespace 解析出值时,前端可触发该动作补全 namespace,然后重试解析。
{
"type": "object",
"properties": {
"node_iri": {
"type": "string",
"x-ref": "node_iri"
},
"label": {"type": "string"}
},
"required": ["node_iri", "label"]
}
x-fetch 前端实现要求(图场景)
- 触发条件:仅当
x-ref/x-default均无法从当前 namespace 得到可用值时,才允许触发x-fetch。 - 缓存/去重:前端必须按
(action_name + resolved_params)维度对x-fetch做缓存/去重,避免重复触发相同补全动作。 - 循环/风暴控制:同一次参数解析链路中,
x-fetch最大触发次数为10;超过阈值应中止并提示。
3. 类别列表查询 Action(get_list / get_cls)
对应实现参考:GraphManageMixin.aget_cls(...) -> list[ClassInfo]
ClassInfo 字段:
iri: str:类别 IRIname: str:展示名称properties: dict | null:其它属性
此 Action 不属于 get_graph,如果你希望前端以列表形式渲染,建议注册为:
resource_op_type="get_list"category="pageable"并返回PageableList[ClassInfo]或CursorPageableList[ClassInfo]
如果你只是提供辅助数据,也可以不声明 resource_op_type(由业务自定义使用)。
x-namespace/x-actions/x-ref 与 List 的最佳实践
- 推荐在 nodes/edges 的每个元素 schema 层级声明 x-namespace 和 x-actions,x-namespace 以变量名为 key,所有 Action 共享同一 namespace,便于统一引用。
- x-ref 直接引用变量名,前端自动匹配当前上下文。
- x-default 支持字段默认值从 namespace 动态获取。
- x-fetch 仅填写动作名称字符串,用于在 x-ref/x-default 解析失败时补全 namespace。
- JMESPath 用于表达相对路径,如 "iri"、"label"、"property"。
与当前 GraphManageMixin 的操作语义对齐
add_cls(iri, name, parent_cls?, properties?)update_cls(iri, name?, properties?)delete_cls(iri)add_node(name, label, cls, properties?)update_node(iri, label?, properties?)delete_node(iri)add_edge(source, target, property, properties?)update_edge(source, target, property, properties?)delete_edge(source, target, property)
注意:当前实现里边的增删改均为“异步方法”,通常建议在注册 @tfs_action 时标记 is_async=True(由具体 Setting 的 action alias 决定)。
FAQ
- Q:能否只在根节点声明所有 x-namespace?
- A:不推荐。建议在 nodes/edges 层级声明,便于每个节点/边独立注入上下文。
- Q:如何表达 list 中“对应 index”?
- A:前端遍历时自动注入当前项的 namespace,x-ref 直接写变量名即可。
- Q:x-namespace 的 Options 结构是如何使用的?
- A:Options 结构用于注入选项型变量,labels/keys/values 三个 Jmespath 字段分别对应选项的标签、键和值。
示例
- get_graph返回:
{
"nodes": [
{"iri": "iri://n1", "label": "Node1", "cls": "iri://ClassA", "properties": {"name": "Node1"}},
{"iri": "iri://n2", "label": "Node2", "cls": "iri://ClassA", "properties": {"name": "Node2"}}
],
"edges": [
{"source": "iri://n1", "target": "iri://n2", "label": "knows", "property": "iri://knows", "properties": {"name": "knows"}}
],
"users": [
{"user_id": "u1", "display_name": "User1"},
{"user_id": "u2", "display_name": "User2"}
]
}
- update_node参数:
{
"node_iri": "iri://n1",
"label": "Node1-updated"
}
返回类型约束(与 tfs_action 行为一致)
- 当
resource_op_type="get_graph"且category="graphical"时:返回类型必须继承BaseGraphical。 - 推荐直接使用:
-> Graphical[Node, Edge]。
否则在 @tfs_action 注册阶段会抛出 TypeError(即“返回类型不符合 graphical 协议”)。