Tree studio cn
Tree Studio 是 Jianmu 的可视化行为树编辑器,提供拖拽式画布、实时运行调试和标准 BT Skill 导出能力。它由 React 前端和 FastAPI 后端组成,通过 WebSocket 实现运行态节点状态实时同步,核心是将图形化工作流 JSON 编译为可执行的 py_trees 行为树。
架构总览¶
Tree Studio 遵循前后端分离架构。前端基于 ReactFlow 构建可视化画布,后端基于 FastAPI 暴露 REST API 和 WebSocket 端点。系统核心数据模型是 WorkflowDefinition,它描述了节点、边、状态模式、资源配置的完整工作流规范。WorkflowConverter 是这个模型到可执行行为树的关键编译器,它将 JSON 工作流定义编译为 Behaviour 树根节点,交由 Jianmu Agent 运行时执行。
graph TB
subgraph Frontend["Frontend (React + Vite)"]
Canvas["ReactFlow 画布<br/>拖拽节点·连线·配置"]
Sidebar["Sidebar<br/>节点面板"]
ChatPanel["ChatPanel<br/>AI 辅助生成"]
ExportDialog["ExportSkillDialog<br/>BT Skill 导出"]
end
subgraph Backend["Backend (FastAPI)"]
API["REST API<br/>/api/workflows<br/>/api/nodes<br/>/api/tools<br/>/api/chat"]
WS["WebSocket<br/>/ws/{workflow_id}"]
Converter["WorkflowConverter<br/>JSON → Behaviour Tree"]
SkillExport["Skill Export<br/>SKILL.md + tree.py<br/>+ workflow.json"]
AgentRunner["Agent Runner<br/>异步任务 + 广播"]
end
subgraph Core["Jianmu Core"]
BT["py_trees Behaviour"]
StateMgr["StateManager"]
Nodes["Node Registry<br/>AgentLLMNode<br/>ToolExecutor<br/>SkillNode..."]
end
Canvas -->|"PUT /api/workflows/:id"| API
ChatPanel -->|"POST /api/chat/generate-workflow"| API
ExportDialog -->|"POST /api/workflows/:id/export-skill"| API
API --> Converter
Converter --> BT
Converter --> StateMgr
Converter --> Nodes
AgentRunner -->|"broadcast"| WS
WS -->|"node_update<br/>state_update<br/>trace"| Canvas
SkillExport --> Converter
工作流的核心数据流:用户在画布上放置节点、配置端口绑定、连接边,保存后生成 WorkflowDefinition JSON。运行时,Converter 根据 JSON 中的 NodeDefinition.type 查询 NodeRegistry 获取对应的 Python 类,实例化后按边关系组装为完整行为树,注入 StateManager 后交 Agent.run() 执行。
数据模型:WorkflowDefinition¶
整个系统的核心数据契约是 WorkflowDefinition,它是一个 Pydantic 模型,完整描述了一个可视化工作流。
| 字段 | 类型 | 说明 |
|---|---|---|
version |
str |
模式版本,当前 "1.0" |
id |
Optional[str] |
工作流唯一标识(UUID) |
nodes |
List[NodeDefinition] |
节点列表,每个节点包含 id、type、position、config、端口绑定 |
edges |
List[EdgeDefinition] |
边列表,定义父子连接关系(source → target) |
state |
StateDefinition |
状态模式定义,包含字段名、类型、默认值 |
resources |
ResourcesDefinition |
资源配置,包含 Memory 和 MCP Server 定义 |
settings |
Dict[str, Any] |
全局设置 |
NodeDefinition 的 type 字段对应 NodeRegistry 中注册的节点标识符,config 字段承载节点构造函数所需的配置参数,input_bindings 和 output_bindings 定义了节点端口到状态字段的映射关系。EdgeDefinition 仅使用 source/target 表达父子关系(非数据流),所有数据交换通过共享状态完成。
视觉画布与节点体系¶
ReactFlow 画布¶
前端使用 ReactFlow v11 作为画布引擎,将节点按视觉效果分为两类:ControlFlow 节点(Sequence、Selector、Parallel、LoopUntilSuccess)渲染为椭圆形渐变卡片,Action 节点(AgentLLMNode、ToolExecutor 等)渲染为矩形卡片。两类节点均使用 CustomNodes.tsx 中定义的组件。
拖拽创建节点的流程如下:
flowchart LR
A[从 Sidebar 拖拽节点] --> B["onDragStart<br/>设置 dataTransfer<br/>type + label"]
B --> C["onDrop<br/>计算 screenToFlowPosition"]
C --> D["创建 Node 对象<br/>id = type_timestamp<br/>data.nodeType = type"]
D --> E["setNodes 添加到画布"]
E --> F["auto-save 到 localStorage"]
节点 ID 通过 {nodeType}_{Date.now()} 生成,保证唯一性。画布的节点和边通过 localStorage 持久化,页面刷新后自动恢复。
NodeRegistry:节点类型注册中心¶
后端 NodeRegistry 是所有可用节点类型的注册中心。它维护 _nodes: Dict[str, NodeMetadata] 和 _class_map: Dict[str, Type] 两个映射表,前者为 UI 提供元数据(标签、图标、分类、端口定义、配置模式),后者在编译时提供 Python 类引用。
classDiagram
class NodeRegistry {
-_nodes: Dict[str, NodeMetadata]
-_class_map: Dict[str, Type]
+register(cls, id, label, category, inputs, outputs, config_schema) Decorator
+register_metadata(meta: NodeMetadata) void
+get(node_id: str) NodeMetadata
+get_all() List[NodeMetadata]
+get_class(node_id: str) Type
}
class NodeMetadata {
+id: str
+label: str
+category: str
+icon: str
+description: str
+inputs: List[Dict]
+outputs: List[Dict]
+input_aliases: Dict
+output_aliases: Dict
+config_schema: Dict
+node_class: Type
}
NodeRegistry "1" --> "*" NodeMetadata
注册通过 @node_registry.register(...) 装饰器或 node_registry.register_metadata(...) 显式调用完成。当前注册的节点覆盖以下类别:
| 类别 | 节点 | 标识符 |
|---|---|---|
| 控制流 | Sequence, Selector, Parallel, LoopUntilSuccess | Sequence, Selector, Parallel, LoopUntilSuccess |
| Agent | AgentLLMNode, ToolExecutor, FinalizeTurn | AgentLLMNode, ToolExecutor, FinalizeTurn |
| Legacy | OutputParser | OutputParser |
| 工具 | ToolNode | ToolNode |
| 技能 | SkillNode | SkillNode |
| 实用 | Log, Wait | Log, Wait |
config_schema 字段支持声明式 UI 表单生成,类型包括 text、number、boolean、select、textarea、multiselect(带 source 指向 tools 或 skills 数据源)。
状态字段推断¶
工作流的状态模式可通过两种方式确定:显式声明(state.fields 非空且 schema_name 不为 "AutoState")或自动推断(_infer_state_fields)。推断逻辑遍历所有节点的输入/输出端口绑定,为每个绑定的目标字段创建 StateFieldDefinition。特殊字段 messages 使用 Annotated[List[Any], operator.add] 标注,使其在状态合并时采用追加语义。
WorkflowConverter:JSON → 行为树编译¶
WorkflowConverter 是 Tree Studio 的架构核心,它执行以下编译流程:
flowchart TD
A["WorkflowDefinition<br/>(JSON)"] --> B["_create_state_manager<br/>动态创建 Pydantic Schema"]
B --> C["_init_mcp<br/>初始化 MCP 客户端"]
C --> D["遍历 nodes<br/>_create_node 逐个实例化"]
D --> E{"node_def.type?"}
E -->|"Sequence|Selector|Parallel"| F["直接构造 Composite"]
E -->|"AgentLLMNode"| G["组装 ModelClient<br/>+ ContextBuilder<br/>+ ToolSet → AgentLLMNode"]
E -->|"ToolExecutor"| H["解析 config.tools<br/>实例化 Tool 对象<br/>→ ToolExecutor"]
E -->|"ToolNode"| I["根据 tool_id<br/>查找 Tool 类<br/>→ ToolNode"]
E -->|"SkillNode"| J["解析 skill_files<br/>→ SkillNode"]
E -->|"自定义类型"| K["get_class + inspect 签名<br/>→ 通用实例化"]
F & G & H & I & J & K --> L["_apply_bindings<br/>设置 _studio_id, bind()"]
L --> M["_assemble_children<br/>按 edges 组装父子树"]
M --> N["返回 root Behaviour"]
关键编译细节¶
拓扑排序:_assemble_children 首先通过分析 edges 确定 has_parent 集合,找出唯一的根节点(无父节点的节点)。若存在多个根节点或无根节点,抛出错误。子节点按画布位置 (position.x, position.y) 排序后添加到父节点。
ToolExecutor 与 ToolNode 的区别:当 ToolExecutor 的子节点是 ToolNode 时,Tools 不会被添加为行为树子节点,而是通过 parent_node.register_tool(child_node.tool) 注入 ToolExecutor 的工具注册表。这允许 Agent 节点动态选择工具,而非按固定顺序执行。
Duck Typing 工具包装:对于具有 run() 和 description 属性但不继承 Behaviour 的实例(如 MCP 工具),自动包装为 ToolNode。
ModelClient 容错:若环境变量未配置 LLM 密钥,返回一个 _MissingModelClient 占位对象,在实际调用时抛出明确的错误提示,而非在编译阶段崩溃。
端口绑定机制¶
_apply_bindings 方法将 NodeDefinition 中的 input_bindings 和 output_bindings 解析后传递给节点对象的 bind() 方法。同时设置 _studio_id 属性,使 StudioVisitor 能够在运行时通过 ID 追踪节点状态。
端口名称解析使用 _resolve_binding_name 方法,优先级为:显式绑定值 > 端口名与状态字段名匹配 > 别名匹配。当同一输出端口被绑定到多个目标字段时,抛出冲突错误。
运行时调试与 WebSocket 实时反馈¶
运行时调试架构围绕三个组件协作:StudioVisitor(行为树访问者)、WebSocket ConnectionManager(广播管理)和前端状态订阅。
sequenceDiagram
participant Browser as 前端画布
participant WS as WebSocket
participant Server as FastAPI Server
participant Agent as Agent Runner
participant Visitor as StudioVisitor
participant Tree as Behaviour Tree
Browser->>Server: POST /api/workflows/:id/run
Server->>Agent: asyncio.create_task(_run_agent_task)
Agent->>Tree: 注册 StudioVisitor
Agent->>Tree: agent.run()
loop 每个 tick
Tree->>Visitor: initialise() → run(behaviour) × N → finalise()
Visitor->>WS: broadcast(node_update, status_map)
WS->>Browser: JSON { type: "node_update", data: {...} }
Browser->>Browser: setNodes 更新节点颜色
end
Agent-->>WS: broadcast(status, "completed")
WS-->>Browser: 更新 isRunning = false
StudioVisitor¶
StudioVisitor 在每个 tick 周期内收集所有被访问节点的状态(通过 _studio_id 标识),并在 finalise() 中以 100ms 为最小间隔广播 node_update 消息。前端收到消息后通过 setNodes 更新对应节点的 data.status 字段,触发 CustomNodes 组件中的颜色映射(RUNNING→黄色、SUCCESS→绿色、FAILURE→红色)。
广播事件类型¶
| 事件类型 | 触发时机 | 载荷 |
|---|---|---|
node_update |
每个 tick 结束(100ms 节流) | {nodeId: status} 映射 |
state_update |
状态变更信号触发 | StateManager.get().model_dump() |
status |
运行开始/结束 | "running" / "completed" / "stopped" |
error |
运行异常 | {"message": str(e)} |
trace |
遥测事件(带 workflow_id 过滤) | {"event": name, "data": payload} |
log |
Log 节点输出和 loguru sink | {"message": formatted} |
状态变更通过在 StateManager 上注册回调实现——每次状态写入发出信号,触发异步的 on_state_update 广播。这使得右侧面板能够实时展示运行中的状态快照。
运行控制¶
运行通过 asyncio.Task 管理,支持异步取消。POST /api/workflows/{id}/stop 调用 task.cancel(),运行循环中的 CancelledError 处理器会捕获取消信号,广播 "stopped" 状态并清理资源(移除遥测订阅、重置 trace 上下文、关闭 MCP 客户端)。
支持多轮对话模式:设置 reuse_messages: true 时,每次运行结束后缓存 messages 列表,下次运行时作为初始状态的一部分注入,实现对话上下文的连续性。
BT Skill 导出流程¶
BT Skill 导出将可视化工作流转换为 Jianmu 标准的 execution=bt 技能格式,使工作流可以作为 Skill 被其他 Agent 复用。
flowchart TD
A["WorkflowDefinition"] --> B["WorkflowConverter.compile()<br/>(预验证拓扑正确性)"]
B --> C["生成 SKILL.md<br/>(YAML frontmatter)"]
B --> D["生成 tree.py<br/>(build_tree 入口)"]
B --> E["生成 workflow.json<br/>(原始定义的 JSON 序列化)"]
C & D & E --> F["写入目标目录<br/>默认: jianmu/skill/builtin/<slug>"]
F --> G["SkillLoader 验证<br/>确认 execution=bt 可加载"]
G --> H["WorkflowSkillExportResult"]
导出产物¶
| 文件 | 内容 | 说明 |
|---|---|---|
SKILL.md |
YAML frontmatter + Markdown 文档 | 声明 execution: "bt", tree: "tree.py", inputs/outputs/required/tools |
tree.py |
build_tree(ctx) 函数 |
调用 build_exported_workflow_skill_tree() 组装 _InitWorkflowStateNode → compiled_root → _ExportWorkflowResultNode 的 Sequence |
workflow.json |
工作流定义 JSON | 原始 WorkflowDefinition 的序列化副本 |
导出运行时适配¶
导出的 skill 在运行时通过 build_exported_workflow_skill_tree() 函数重新组装行为树。该函数在编译后的工作流树前后插入两个特殊节点:
_InitWorkflowStateNode:将BTSkillContext提供的 inputs 和 messages 写入状态管理器,应用端口绑定映射_ExportWorkflowResultNode:从状态中提取导出结果。在新的导出语义下,对外字段固定为result,而result_state_field(通过output_bindings.result落地)决定实际从 workflow 的哪个 state 字段取值。
这种设计使导出的 skill 对外暴露简洁的输入/输出接口,同时内部保持完整的可视化工作流语义。
导出对话框¶
前端 ExportSkillDialog 组件提供导出配置界面,包括:Skill 名称、描述、input_field(对外暴露的主输入字段)、result_state_field(决定导出后 result 输出实际取自哪个 workflow state 字段)、target_dir、以及覆盖选项。为兼容旧调用,后端仍接受旧的 result_field 请求字段。右侧预览面板展示推断出的导出输入字段及其绑定关系。
LLM 辅助工作流生成¶
Tree Studio 内置 AI 助手功能,通过 POST /api/chat/generate-workflow 端点实现。WorkflowLLM 类使用 Jianmu ModelClient 调用 LLM,根据用户的自然语言描述生成工作流 JSON。
系统提示词(SYSTEM_PROMPT)描述了所有可用节点类型、工作流 JSON 格式规范、ReAct 模式模板和布局指南。发送请求时,系统提示词会根据实际注册的节点和工具动态扩展,确保 LLM 只使用有效的节点类型和工具 ID。
生成的 JSON 通过 applyWorkflow 函数转换为 ReactFlow 节点和边,直接渲染到画布上。
沙箱策略¶
Tree Studio 集成了 SandboxPolicy,控制工具执行的安全边界。通过 jianmu_SANDBOX_CONFIG 环境变量或配置文件可配置:
| 配置项 | 默认值 | 说明 |
|---|---|---|
tools.deny |
["FileReadTool", "FileWriteTool"] |
默认禁止文件操作 |
tools.allow |
[] |
白名单模式(优先级高于 deny) |
network.enabled |
false |
默认禁止网络工具 |
file_access.read_paths |
["."] |
允许读取的路径根 |
file_access.write_paths |
["."] |
允许写入的路径根 |
SandboxPolicy.wrap_tool() 方法将工具包装为 SandboxedTool 代理,在执行前进行调用前检查(路径范围、网络权限),违规调用抛出 PermissionError。被完全禁止的工具则被替换为 BlockedTool。
MCP 集成¶
Tree Studio 支持通过 MCP (Model Context Protocol) 加载外部工具。POST /api/mcp/tools 端点接收 MCP Server 配置(stdio command + args 或 HTTP/SSE URL),调用 MCPClient.list_tools() 获取工具列表,返回带 mcp:{server_id}:{tool_name} 格式 ID 的 ToolMetadata 列表。
编译时,WorkflowConverter._init_mcp() 为 resources.mcp_servers 中的每个服务器创建 MCPClient 实例并缓存。_get_mcp_tool() 根据 mcp: 前缀的 tool_id 查找对应的 MCP 工具实例。
前端 ToolLibrary 组件提供 MCP Server 管理界面,支持添加、移除服务器,以及配置允许列表(allowlist)。
项目结构¶
apps/tree_studio/
├── config.yaml # 服务器配置(端口、CORS、技能目录)
├── backend/
│ ├── __main__.py # CLI 入口
│ ├── main.py # uvicorn 启动逻辑
│ ├── server.py # FastAPI 应用、路由、WebSocket、运行管理
│ ├── config.py # 配置模型加载
│ ├── workflow_schema.py # WorkflowDefinition Pydantic 模型
│ ├── converter.py # WorkflowConverter:JSON → Behaviour Tree
│ ├── workflow_adapter.py # 导出 Skill 的运行时适配器
│ ├── node_registry.py # NodeRegistry:节点类型注册中心
│ ├── agent_nodes.py # FinalizeTurn、OutputParser、ContextBuilder
│ ├── studio_composites.py # StudioLoopUntilSuccess
│ ├── tool_registry.py # 内建工具注册与查询
│ ├── skill_registry.py # 技能发现与解析
│ ├── skill_export.py # BT Skill 导出核心逻辑
│ ├── llm.py # WorkflowLLM:AI 辅助工作流生成
│ ├── sandbox.py # SandboxPolicy 安全策略
│ └── websocket.py # WebSocket ConnectionManager
└── frontend/
├── src/
│ ├── App.tsx # 主画布组件(ReactFlow + 状态管理)
│ ├── api/client.ts # Axios API 客户端
│ └── components/
│ ├── CustomNodes.tsx # 自定义节点渲染
│ ├── Sidebar.tsx # 节点面板
│ ├── NodePanel.tsx # 属性编辑面板
│ ├── ChatPanel.tsx # AI 对话面板
│ ├── LogPanel.tsx # 运行日志面板
│ ├── StatePanel.tsx # 状态查看面板
│ ├── ToolLibrary.tsx # MCP 工具管理
│ └── ExportSkillDialog.tsx # 导出对话框
└── package.json
相关页面导航¶
Tree Studio 作为 Jianmu 的上层可视化应用,与以下核心模块紧密关联:
- 行为树执行内核:基于 py_trees 的异步扩展与 Jianmu 节点模型 — WorkflowConverter 编译出的 Behaviour 树的底层执行引擎
- 节点体系全景:AsyncNode 基类、端口绑定与依赖注入 — 节点端口绑定的运行时语义
- Skill 定义与目录:SKILL.md 解析、行为树技能与 Prompt 技能 — 导出产物的标准格式
- ReactiveRunner:事件驱动的异步 tick 调度与挂起恢复机制 — 运行时 tick 调度与 StudioVisitor 的关系
- Swarm Studio:多 Agent 协作的可视化编排与对话管理 — 同层级的上层应用,面向多 Agent 场景