Skills cn
Skill 是 Jianmu 中最高层级的可复用能力封装单元。一个 Skill 将专业领域知识、工具集成与多步骤工作流打包为自包含的目录,由一份 SKILL.md 文件作为入口声明,框架在运行时发现、解析并将其注入到 Agent 的执行上下文中。Skill 按执行模式分为两大类——Prompt 技能将 Markdown 正文作为 LLM 侧指令注入,行为树 (BT) 技能则通过 tree.py 中的 build_tree(ctx) 函数直接构建可执行的 py_trees.Behaviour 子树。
架构总览:Skill 系统的四层协作¶
整个 Skill 系统由四个职责清晰的层协同工作,形成"发现 → 解析 → 选择 → 执行"的完整链路。下图展示了各组件之间的协作关系:
flowchart TB
subgraph 发现层
CAT[SkillsCatalog<br/>工作空间 + 内建目录扫描]
end
subgraph 解析层
LOAD[SkillLoader<br/>SKILL.md → Skill 对象]
TREE[SkillTreeLoader<br/>tree.py → build_tree() 调用]
end
subgraph 选择层
SET[SkillSet<br/>运行时技能筛选]
end
subgraph 执行层
SN[SkillNode<br/>FlattenedAgentNode 子类]
BTTOOL[RunBTSkillTool<br/>LLM 可调用 BT 技能]
end
CAT -->|SkillRecord / SkillCandidate| SET
SET -->|Skill 列表| LOAD
LOAD -->|Skill 对象| SN
LOAD -->|Skill 对象| TREE
TREE -->|Behaviour 子树| SN
TREE -->|Behaviour 子树| BTTOOL
SN -->|bt 模式| TREE
BTTOOL -->|嵌入 ReAct 工具列表| SN
- 发现层 (
SkillsCatalog):扫描工作空间skills/目录和框架内建builtin/目录,收集所有SKILL.md文件,完成可用性检查(CLI 二进制、环境变量、OS 兼容性),工作空间同名技能覆盖内建技能。 - 解析层 (
SkillLoader+SkillTreeLoader):将SKILL.md的 YAML 前置元数据与 Markdown 正文解析为Skill数据类;对于 BT 技能,动态导入tree.py模块并提取build_tree(ctx)可调用对象。 - 选择层 (
SkillSet):根据运行时配置筛选要加载的技能文件,并通过prompt_skills/bt_skills在 Prompt 技能与 BT 技能之间完成分区。 - 执行层 (
SkillNode+RunBTSkillTool):SkillNode为 Prompt 和 BT/react 技能托管 ReAct,并为单个 BT/direct 技能提供 Direct 执行器;两条 BT 路径共享隔离运行协议。
SKILL.md 文件格式:前置元数据 + Markdown 正文¶
每份 SKILL.md 是技能的唯一入口文件,采用 YAML frontmatter + Markdown body 结构。前置元数据声明技能的身份、执行模式与约束,正文在 Prompt 模式下作为 LLM 指令,在 BT 模式下作为文档性描述。
前置元数据字段一览¶
下表汇总了 SKILL.md 中所有受支持的前置元数据字段:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
name |
string | 是 | — | 技能标识名,用于路由与显示 |
description |
string | 是 | — | 简短描述,展示给模型和操作者 |
execution |
"prompt" | "bt" |
否 | "prompt" |
执行模式:Prompt 注入或行为树执行 |
orchestration |
"react" | "direct" |
否 | "react" |
SkillNode 执行策略;direct 仅适用于 BT 技能 |
tree |
string | 否 | "tree.py" |
BT 技能的树文件路径,相对于 SKILL.md 所在目录 |
tools |
object/list | 否 | [] |
启用的内建工具名称列表(如 builtin: [calculator, bash]) |
tools_entry |
list | 否 | [] |
自定义工具入口文件路径(相对于技能根目录) |
inputs |
object | 否 | — | JSON Schema 风格的输入定义 |
outputs |
object | 否 | — | JSON Schema 风格的输出结构定义 |
required |
list | 否 | — | 必须包含的输出字段名 |
push_to_chat |
bool | 否 | false |
技能输出是否追加到对话历史 |
always |
bool | 否 | false |
是否始终将技能注入 Prompt 上下文 |
role |
string | 否 | — | 关联的 Swarm 角色名 |
constraints |
object | 否 | — | Guard 防护与执行约束配置 |
requires |
object | 否 | — | 环境依赖声明(bins、env、os) |
metadata |
object/string | 否 | — | 扩展元数据(支持 JSON 字符串) |
最小 Prompt 技能示例¶
---
name: concise-brief
description: Turn the latest user request into a short implementation-oriented brief.
execution: prompt
push_to_chat: true
---
You are the `concise-brief` skill.
Read the latest user request and produce a compact brief with exactly three sections:
1. Goal
2. Recommended Path
3. Caveats
在这个例子中,execution: prompt 意味着 Markdown 正文 "You are the `concise-brief` skill..." 会作为 LLM 侧的 skill 指引注入上下文。prompt skill 的正常对外结果仍然是 final_answer。push_to_chat: true 指示将输出也追加到对话历史中。
最小 BT 技能示例¶
BT 技能需要两个文件:SKILL.md 声明元数据,tree.py 提供 build_tree(ctx) 函数。
SKILL.md:
---
name: bt-echo
description: Minimal BT skill for the getting started guide.
execution: bt
tree: tree.py
---
This skill reads the latest user message and produces a deterministic BT result.
tree.py:
from jianmu.node.base import Node
from jianmu.tree import Status
class _EchoSkillNode(Node):
def __init__(self, *, name: str, namespace: str):
super().__init__(name=name)
self.namespace = namespace
def update(self) -> Status:
messages = self.state_manager.get("messages", namespace=self.namespace, default=[]) or []
latest = messages[-1] if messages else None
content = str(getattr(latest, "content", "") or "")
self.state_manager.update(
{"done": True, "final_answer": f"BT skill handled the request: {content}"},
namespace=self.namespace, signal=False,
)
return Status.SUCCESS
def build_tree(ctx):
return _EchoSkillNode(name="BTEcho", namespace=ctx.namespace)
Prompt 技能:将 Markdown 正文注入 LLM 上下文¶
当 execution: prompt(或不指定 execution 字段,默认即 prompt)时,Skill 的 Markdown 正文被提取为 LLM 系统指令。框架通过 ContextBuilder 将技能正文与技能资源文件、对话历史等组装为完整的 Prompt,然后经由 ReAct 节点驱动 LLM 调用。
执行流程:SkillNode._compile_subtree() 检测到 Prompt 技能(非 BT 模式)时,调用 ContextBuilder.resolve_for_llm() 构建包含技能正文的上下文,然后创建标准 ReAct 节点。LLM 在每次推理时都能看到技能的完整指令,按照指令指引进行推理和工具调用。
sequenceDiagram
participant SN as SkillNode
participant CB as ContextBuilder
participant REACT as ReAct 节点
participant LLM as ModelClient
SN->>SN: _load_skills() → 解析 SKILL.md
SN->>CB: resolve_for_llm(system_prompt=skill.prompt)
CB-->>SN: 完整上下文配置
SN->>REACT: create_react_node(context_builder=...)
loop ReAct 循环
REACT->>CB: build_context(messages, tools)
CB-->>REACT: 组装后 Prompt(含技能正文)
REACT->>LLM: invoke(prompt)
LLM-->>REACT: 响应 + 工具调用
end
REACT-->>SN: final_answer
SN->>SN: 向外发布 final_answer
对于 Prompt 技能,正式执行路径是由 SkillNode 托管的 ReAct。技能正文和资源文件列表通过 ContextBuilder 及相关 message provider 注入 LLM 上下文,正常对外结果仍然是 final_answer。
行为树 (BT) 技能:tree.py 与 build_tree(ctx)¶
当 execution: bt 时,Skill 由行为树驱动。tree.py 中的 build_tree(ctx) 接收 BTSkillContext 并返回 py_trees.behaviour.Behaviour。行为树可以由宿主显式运行,也可以在 orchestration: direct 时由 SkillNode 直接运行,或在 orchestration: react 时作为 run_bt_skill 能力运行。
BTSkillContext:标准化的树构建参数¶
build_tree(ctx) 接收的 ctx 是 BTSkillContext 实例,它封装了 BT 技能在运行时需要的所有输入,同时将技能与 Jianmu 内部实现解耦:
| 属性 | 类型 | 说明 |
|---|---|---|
messages |
list[Message] |
当前对话消息列表 |
inputs |
dict[str, Any] |
解析后的结构化输入 |
namespace |
str |
状态命名空间前缀 |
skill_result_key |
str |
BT skill 应写入其结构化结果的命名空间内状态键 |
tools |
dict[str, Any] |
启用的工具名 → 工具对象映射 |
model_client |
Any |
活跃的模型客户端(可选) |
constraints |
Any |
Guard 与执行约束 |
BT 技能执行路径¶
BT 技能支持 direct 和 ReAct 托管两种语义;orchestration 缺省为 react。
路径 A:直接执行 BT
当调用方已经知道应该运行哪个 BT 技能时,可以通过宿主侧显式装配 SkillTreeLoader + BTSkillContext + Agent 来直接构造并运行行为树。这里的 Agent 是底层 ReactiveRunner 之上的公开 facade,因此此路径仍然是完全确定性的,也不需要 SkillNode 做路由。
flowchart LR
C[BTSkillContext] --> E[BT 子树<br/>build_tree(ctx)]
E --> R[skill_result]
SkillNode 也支持同样的 direct 语义:当它只加载一个声明了 orchestration: direct 的 BT 技能时,不创建外层 ReAct,直接执行 BT 并发布 skill_result。
路径 B:ReAct 工具调用执行
SkillNode 将 BT 技能包装为 RunBTSkillTool 并嵌入 ReAct,由 LLM 自主选择要调用的 BT。执行完成后,ReAct 的可选 tool_result_policy 会在写入 observation 之前处置规范化结果:
orchestration: react返回Observe,结果写成 tool message,ReAct 继续;orchestration: direct返回Complete,直接发布skill_result,不写 tool message,也不触发下一轮 LLM 总结。
policy 不负责执行或选择技能,也不自行修改 messages;它只声明 Observe 或 Complete,由现有 ToolExecutor 统一提交。这里不需要 SkillNode 专用 ToolExecutor,也不引入公共 terminal-result 字段。
Complete 同时携带节点最终状态。Complete(status=FAILURE) 会经过 ToolExecutor 向外传播,并由 ReAct loop 解释为“已完成但失败”,而不是可重试的普通 round failure。
SkillNode 将该 policy 的 scope 限定为 run_bt_skill。不包含 run_bt_skill 的普通工具 batch 因而保留原有的自动串并行决策;命中 policy scope 的 batch 才会串行,以保证 Complete 能阻止后续 calls。
sequenceDiagram
participant LLM as LLM (ReAct)
participant TOOL as RunBTSkillTool
participant RUNNER as ReactiveRunner
participant TREE as BT 子树
participant POLICY as tool_result_policy
LLM->>TOOL: run_bt_skill(skill_name="deep_research")
TOOL->>TOOL: 解析 skill_name → Skill 对象
TOOL->>TOOL: 创建 BTSkillContext
TOOL->>RUNNER: ReactiveRunner(bt_root, state_manager, ...)
RUNNER->>TREE: tick 循环
TREE-->>RUNNER: SUCCESS / FAILURE
RUNNER-->>TOOL: _RunTreeSummary
TOOL->>POLICY: 规范化 ToolResult
alt orchestration: react
POLICY-->>LLM: Observe → tool message
else orchestration: direct
POLICY-->>LLM: Complete → skill_result,结束 ReAct
end
工具型 BT 技能:在 tree.py 中直接调用工具¶
BT 技能可以通过 ctx.tools 直接调用 Jianmu 工具,无需 LLM 中介。以下示例展示了一个使用 calculator 工具的 BT 技能:
# tree.py for bt-with-tool
from jianmu.engine.behaviour import AsyncBehaviour
from jianmu.tree import Status
class _CalcNode(AsyncBehaviour):
def __init__(self, *, name, namespace, skill_result_key, tools):
super().__init__(name=name)
self.namespace = namespace
self.skill_result_key = skill_result_key
self.tools = tools
async def update_async(self) -> Status:
calculator = self.tools.get("calculator")
if calculator is None:
return Status.FAILURE
result = await calculator.execute({"input": "2 + 3"})
payload = {"result": str(result)}
self.write_state({"done": True, "final_answer": f"2 + 3 = {result}", self.skill_result_key: payload})
return Status.SUCCESS
def build_tree(ctx):
return _CalcNode(name="BTWithTool", namespace=ctx.namespace,
skill_result_key=ctx.skill_result_key, tools=ctx.tools)
这里三者的边界是:
final_answer:给 agent / chat 层的自然语言对外答案skill_result:runtime state 中规范化的结构化结果槽skill_result_key:BT runtime 里的桥接字段,用来告诉子树该把skill_result写到哪里;它的值通常来自SkillNodeConfig.keys.skill_result这类 runtime key 路由配置
框架注入到 ctx.tools 中的工具对象直接暴露 execute() 方法,BT 节点可以在 update() 或 update_async() 中同步/异步调用它们。
人机交互型 BT 技能:挂起恢复协议¶
BT 技能可以通过 self.interaction.suspend() 向用户请求输入,实现挂起-恢复的交互式工作流。bt_interaction_demo 示例展示了完整的模式:先挂起请求研究范围确认,用户回复后恢复并执行多查询搜索循环。
# 挂起点
self.interaction.suspend(
reason=SuspensionReason.AWAITING_USER_INPUT,
message="请确认研究范围:只看国内方案,还是包含海外方案?",
payload={"kind": "deep_research.confirm_scope", ...},
node_name=self.name, node_namespace=self.namespace, signal=False,
)
return Status.RUNNING
# 恢复点(下次 tick 时检测)
resume_payload = self.interaction.read_resume_payload(default=None)
if isinstance(resume_payload, dict) and str(resume_payload.get("text") or "").strip():
# 处理用户输入,继续执行
self.interaction.clear_resume_payload(signal=False)
return Status.SUCCESS
这种模式让 BT 技能可以插入需要人工判断的环节,同时保持行为树的确定性结构。
内建技能目录¶
Jianmu 在 jianmu/skill/builtin/ 中预置了 6 个内建技能,覆盖深度研究、网页搜索、记忆管理、技能创建指南、Tavily 搜索和最小 BT Skill 示例等场景:
jianmu/skill/builtin/
├── agent_memory/ # 持久化记忆:事实记忆、经验学习、实体追踪
│ ├── SKILL.md # execution: prompt(默认)
│ ├── _meta.json
│ ├── cli/
│ ├── examples/
│ ├── src/
│ └── tests/
├── deep_research/ # 深度研究:大纲规划 → 逐项调查 → 报告生成
│ ├── SKILL.md # execution: bt
│ ├── tree.py # 7 节点行为树(LoadArtifacts → Clarify → Plan → ...)
│ ├── nodes.py # ~2000 行节点实现
│ ├── state.py # DeepResearchState(Pydantic 模型)
│ └── validate_json.py
├── rock_paper_scissors/ # 最小 BT Skill:随机返回石头、剪刀或布
│ ├── SKILL.md # execution: bt, orchestration: direct
│ ├── nodes.py # 随机选择节点
│ └── tree.py # 单节点行为树
├── skill_creator/ # 技能创建向导:指导用户编写有效 SKILL.md
│ ├── SKILL.md # execution: prompt
│ └── references/ # 渐进式参考文档
├── tavily_search/ # Tavily 联网搜索(需 API Key 和 Node.js)
│ ├── SKILL.md # execution: prompt, always: true, requires: [node, TAVILY_API_KEY]
│ └── scripts/ # search.mjs(Node.js 搜索脚本)
└── web_research/ # 网页研究:查询规划 → DuckDuckGo 搜索 → LLM 总结
├── SKILL.md # execution: bt, tree: tree.py
├── tree.py # 从 workflow.json 导出构建
└── workflow.json # Tree Studio 导出的工作流定义
deep_research 是内建 BT 技能的典型代表。在宿主已经明确“这次请求就该跑 deep_research”时,它非常适合 direct BT execution:由 7 个子节点组成的 Sequence 行为树负责项目产物加载、研究意图澄清、大纲规划、逐项执行与报告渲染,整体保持确定性。其 nodes.py 包含约 2000 行节点实现代码,并使用 Pydantic 模型 DeepResearchState 管理结构化中间状态。
rock_paper_scissors 是最小的 direct BT Skill 示例。它无需输入,通过一个同步节点随机选择结果并写入 skill result key,可作为 direct BT Skill 最小 SKILL.md 元数据和 tree.py 入口的参考。
tavily_search 展示了 always: true 和 requires 的用法:它被标记为始终注入上下文,但必须满足 Node.js 二进制和 TAVILY_API_KEY 环境变量两个条件才算"可用"。SkillsCatalog 在扫描时自动执行可用性检查,不满足条件的技能会被标记为 available: false。
web_research 展示了从 Tree Studio 导出的 workflow.json 转换为 BT 技能的模式:它的 tree.py 调用 build_exported_workflow_skill_tree() 将 JSON 工作流定义转换为 py_trees.Behaviour,实现了可视化编辑与代码执行的无缝衔接。
SkillsCatalog:工作空间覆盖内建技能的发现¶
SkillsCatalog 采用工作空间优先的合并策略:先扫描工作空间 skills/ 目录,再扫描内建目录,同名技能以工作空间版本为准。技能目录结构要求每个技能是一个子目录,其中包含 SKILL.md 文件:
skills/ # 工作空间技能根目录
├── my-prompt-skill/
│ └── SKILL.md # execution: prompt
├── my-bt-skill/
│ ├── SKILL.md # execution: bt
│ └── tree.py # build_tree(ctx) 入口
└── ...
SkillsCatalog.resolve_runtime() 按以下优先级确定有效目录对象:
1. PromptRuntimeContext.skills_catalog(运行时显式传入的预加载目录)
2. PromptRuntimeContext.skills_dir(运行时显式传入目录)
3. jianmu.yaml 中的 paths.skills_dir 配置
4. 当前工作目录下的 skills/ 子目录(默认)
可用性检查与 SkillRecord¶
每个扫描到的技能会经过 _build_record() 生成 SkillRecord,其中包含基于 requires 字段的可用性检查:
bins:所需的 CLI 二进制(如node),通过shutil.which()检查env:所需的环境变量(如TAVILY_API_KEY),通过os.environ.get()检查os:支持的操作系统列表(darwin/linux/windows),与当前系统比
任一条件不满足,技能即标记为 available: false,并在 missing_reasons_text() 中生成人类可读的原因说明。
SkillSet:运行时的技能筛选与分类¶
SkillSet 是运行时技能选择的门面,负责将用户指定的技能文件路径、启用的技能名称或技能目录统一解析为 Skill 对象列表。它提供便捷的属性来区分 Prompt 技能和 BT 技能:
skill_set = SkillSet.resolve(
skill_files=["path/to/SKILL.md"],
enabled_skills=["deep_research", "concise-brief"],
skills_dir="./skills",
)
prompt_skills = skill_set.prompt_skills # execution != "bt"
bt_skills = skill_set.bt_skills # execution == "bt"
SkillSet 同时也是 SkillNode 内部加载技能的标准入口,SkillNode._load_skills() 通过它完成实际的 SKILL.md 解析。
SkillNode:统一的技能路由节点¶
SkillNode 是 FlattenedAgentNode 的子类,同时支持 ReAct 与 Direct BT:
flowchart TD
SN[SkillNode._compile_subtree] --> LOAD[_load_skills 解析 SKILL.md]
LOAD --> CHOOSE{是否为单个 direct BT?}
CHOOSE -->|是| DIRECT[Direct BT executor<br/>发布 skill_result]
CHOOSE -->|否| REACT[ReAct executor<br/>Prompt 上下文 + run_bt_skill]
多技能场景继续使用现有 ReAct 选择:BT/react 的结果作为 observation,BT/direct 的结果在 observation 写入前发布为 skill_result 并结束外层循环。
约束合并机制¶
SkillNode 实现了节点级约束与技能级约束的智能合并。当多个技能被加载时,采用取最严格值的策略:
max_iterations:取所有技能中的最小值timeout、max_messages:使用首个非空值guard.tool_policy:合并所有 deny 规则guard.budget.total_tokens:使用首个非空值
在代码中使用技能¶
以下三个场景覆盖了最常见的技能使用模式:
场景一:直接执行 Prompt 技能(需要模型客户端)
from jianmu import Agent
from jianmu.model import ModelClient
from jianmu.node import SkillNode
skill_file = "skills/prompt_briefing/SKILL.md"
model_client = ModelClient.resolve(env_override=True)
root = SkillNode(name="Briefing", skill_files=[skill_file], model_client=model_client)
agent = Agent(root, state_schema=MyState, context=RunContext(model_client=None))
await agent.run(input_data={"messages": [{"role": "user", "content": "..."}]})
场景二:通过 SkillNode 执行 Direct BT 技能
from jianmu import Agent
from jianmu.node import SkillNode
# SKILL.md 声明 execution: bt, orchestration: direct
skill_file = "skills/bt_minimal/SKILL.md"
root = SkillNode(name="BTMinimal", skill_files=[skill_file])
agent = Agent(root, state_manager=state, context=RunContext(model_client=None))
await agent.run()
# 正式结果位于 state["skill_result"]。
场景三:通过 LLM + run_bt_skill 工具调用 BT 技能
root = SkillNode(
name="BTEchoSkill",
skill_files=[skill_file],
model_client=model_client,
config=SkillNodeConfig(
react_config=ReActConfig(
system_prompt="You must call run_bt_skill with skill_name='bt-echo' before giving any final answer.",
)
),
)
在这种模式下,LLM 看到 run_bt_skill 工具并自主调用它,BT 技能在隔离的 ReactiveRunner 中执行后,结果返回给 LLM 进行后续推理。
Prompt 技能与 BT 技能的选择指南¶
| 维度 | Prompt 技能 | BT 技能 |
|---|---|---|
| 执行方式 | LLM 读取 Markdown 指令后自主推理 | 执行确定性的 py_trees.Behaviour 子树 |
| 确定性 | 低——依赖 LLM 推理,结果可能变化 | 高——代码逻辑固定,结果可复现 |
| 灵活性 | 高——LLM 可根据上下文动态调整策略 | 低——遵循预设的控制流分支 |
| 是否需要 LLM | 是 | 可选(direct BT execution 模式下不需要) |
| 人机交互 | 通过 Guard 审批/挂起机制 | 通过 interaction.suspend() API |
| 适用场景 | 知识指导、格式转换、总结、决策建议 | 多步骤工作流、数据管道、结构化研究 |
| 复杂度上限 | 受 LLM 上下文窗口限制 | 受代码复杂度限制(可任意扩展) |
| 工具调用 | LLM 自主选择和调用工具 | 代码中显式调用 ctx.tools[name].execute() |
选择建议:当一个任务需要灵活推理和动态决策时,使用 Prompt 技能。当需要确定性执行、复杂分支逻辑或严格流程控制时,使用 BT 技能。两者可以在同一个 SkillNode 中混合使用——Prompt 技能提供知识上下文,BT 技能提供可调用的工具化能力。
阅读后续¶
- SkillsCatalog:工作空间覆盖内置技能的发现与路由 — 深入了解技能目录的扫描、合并、快照与路由候选构建机制
- 工具与技能节点:ToolExecutor、SkillNode 与约束联动 — 理解 SkillNode 如何在节点体系中与 ToolExecutor 等节点协同
- 行为树执行内核:基于 py_trees 的异步扩展与 Jianmu 节点模型 — 深入 BT 技能的底层执行引擎
- ReAct 节点工厂:LLM 调用 → 工具执行 → 完成的循环回路 — Prompt 技能的底层推理循环机制