Appearance
Recipe 开发教程
面向 Recipe 开发者。本文覆盖核心概念、主链路源码解析、从简单到复杂的实例、以及编程助手(copilot)的实现。
什么是 Recipe
Recipe(配方) 是把单一技能自动化的一套图:parse_inputs → 规划 → 执行 → 验证 → 合成,以 LLM 调用 + 工具执行为节点,最终产出结构化输出。
📄
src/recipes/graph.py— 图定义与节点类型 📄src/recipes/worker.py— 节点执行引擎 📄src/recipes/ast_guard.py— 表达式白名单
主链路:从输入到输出
关键源码位置
| 文件 | 职责 |
|---|---|
src/recipes/graph.py:GraphNode | 节点定义(type, config, edges) |
src/recipes/graph.py:GraphEdge | 边定义(condition 表达式) |
src/recipes/worker.py:execute_node() | 单节点执行入口 |
src/recipes/worker.py:_run_llm_call() | LLM 节点执行 |
src/recipes/worker.py:_run_compute() | 计算节点执行 |
src/recipes/ast_guard.py:safe_eval() | 条件表达式求值 |
src/recipes/compiler.py:compile_recipe() | 编译期校验 |
节点类型详解
1. compute(表达式计算)
python
# src/recipes/graph.py
class ComputeConfig(BaseModel):
expression: str # Python 表达式(AST 白名单)
output_var: str # 输出变量名
extra_modules: list[str] # 可导入的模块白名单示例:把用户输入转为结构化数据
yaml
node:
type: compute
name: parse_user_input
config:
expression: |
{
"question": input.brief.strip(),
"has_attachments": len(input.attachments) > 0,
"attachment_types": [a["kind"] for a in input.attachments]
}
output_var: parsed2. llm_call(调用大模型)
python
# src/recipes/graph.py
class LLMCallConfig(BaseModel):
system_prompt: str
user_prompt_template: str # 支持 {{变量}} 模板
output_format: dict | None # JSON Schema
output_var: str # 收口到一个变量
tool_whitelist: list[str] # 可用工具
model: str | None # 覆盖默认模型示例:规划阶段
yaml
node:
type: llm_call
name: planner
config:
system_prompt: |
你是一个任务规划专家。根据用户问题,拆解为可执行步骤。
每步必须包含:目标、工具、预期输出。
user_prompt_template: |
用户问题:{{parsed.question}}
附件信息:{{parsed.attachment_types}}
请输出执行计划(JSON 数组):
```json
[
{"step": 1, "goal": "...", "tool": "...", "expected": "..."}
]
```
output_format:
type: array
items:
type: object
properties:
step: {type: integer}
goal: {type: string}
tool: {type: string}
expected: {type: string}
output_var: plan3. emit_event(交互/阻塞)
python
# src/recipes/graph.py
class EmitEventConfig(BaseModel):
event_type: str # ask_user / show_info / render
content_template: str # 支持 {{变量}}
block: bool # 是否阻塞等待
output_var: str # 阻塞时存用户响应4. branch(条件分支)
yaml
node:
type: branch
name: route_by_plan
config:
cases:
- condition: "plan[0]['tool'] == 'search'"
target: search_execute
- condition: "plan[0]['tool'] == 'write'"
target: write_execute
default: direct_answer⚠️
condition走 AST 白名单(src/recipes/ast_guard.py),只允许:
- 变量访问:
x.field、x['key']- 比较:
==,!=,<,>,in,not in- 逻辑:
and,or,not- 禁止:函数调用、赋值、import
实例一:简单 Recipe —— 文本翻译器
目标:输入一段中文,输出英文翻译。
完整定义
python
# workspace/recipes/translator/recipe.json
{
"id": "translator",
"name": "文本翻译器",
"version": "1.0.0",
"nodes": [
{
"id": "parse_inputs",
"type": "starter",
"config": {
"inputs": [
{"name": "text", "type": "string", "required": true},
{"name": "target_lang", "type": "string", "default": "en"}
]
}
},
{
"id": "translate",
"type": "llm_call",
"config": {
"system_prompt": "你是一个专业翻译。只输出翻译结果,不要解释。",
"user_prompt_template": "把以下{{target_lang}}翻译:\n\n{{text}}",
"output_var": "translation"
}
},
{
"id": "done",
"type": "summary",
"config": {
"outputs": [
{"name": "result", "from": "translation"}
]
}
}
],
"edges": [
{"from": "parse_inputs", "to": "translate"},
{"from": "translate", "to": "done"}
]
}运行
bash
# 编译检查
python main.py recipe compile translator
# ✓ 结构正确
# 干跑(不消耗 token)
python main.py recipe dryrun translator --input '{"text": "你好世界"}'
# → {"translation": "Hello, World!"}
# 发布
python main.py recipe pack translator
python main.py recipe sign translator-1.0.0.broker.zip
python main.py recipe publish translator-1.0.0.broker.zip实例二:中等 Recipe —— 文档摘要生成器
目标:上传文档 → 提取关键信息 → 生成摘要 → 输出 Markdown。
关键节点
python
# extract 节点:调用文档解析工具
{
"id": "extract",
"type": "compute",
"config": {
"expression": "doc_tools.read_document(input.file_path)",
"output_var": "doc_content",
"extra_modules": ["src.utils.doc_tools"]
}
}
# summarize 节点:LLM 生成摘要
{
"id": "summarize",
"type": "llm_call",
"config": {
"system_prompt": "你是文档分析专家。提取核心观点,生成结构化摘要。",
"user_prompt_template": "文档内容:\n{{doc_content[:3000]}}",
"output_format": {
"type": "object",
"properties": {
"title": {"type": "string"},
"key_points": {"type": "array", "items": {"type": "string"}},
"conclusion": {"type": "string"}
},
"required": ["title", "key_points"]
},
"output_var": "summary"
}
}实例三:复杂 Recipe —— 编程助手(recipe_copilot)
编程助手本身就是一个大型 Recipe,用于帮助用户创建其他 Recipe。它是当前代码库里最复杂的单个 Recipe —— 3 阶段串行 execute + 2 轮 verify + 重试循环。
为什么它复杂
| 维度 | 实例二(文档摘要) | recipe_copilot |
|---|---|---|
| 节点数 | 5 | 13+ |
| LLM 调用 | 1 | 7+(analyze + 3 execute + 2 verify + summarize) |
| 工具调用 | 1(doc_tools) | 8+(recipe_read/recipe_write/recipe_compile/recipe_dryrun…) |
| 循环 | 无 | verify_1 → execute_1 重试,最多 3 次 |
| 阶段 | 单阶段 | 3 阶段:manifest → state_graph → custom_ui |
图结构
关键设计
- 三阶段 execute:manifest → state_graph → custom_ui,每阶段独立编译校验
- 两次 verify:verify_1 校验结构(compile),verify_2 校验行为(compile + dryrun)
- 重试循环:verify_1 失败时回退修复,最多 3 轮
- 工具调用:通过
tool_whitelist限定 copilot 只能操作 recipe 相关 API - 增量编辑:工具支持
patch模式,不重写整个图
源码位置
| 文件 | 职责 |
|---|---|
src/recipes/copilot/driver.py | 对话驱动入口 |
src/recipes/copilot/graph.py | copilot 图定义 |
src/recipes/copilot/tools.py | 专用工具(recipe_*) |
workspace/manager/recipes/copilot/recipe.json | 图配置 |
工具示例
python
# src/recipes/copilot/tools.py
@tool("recipe_state_graph_add_nodes")
def add_nodes(
recipe_id: str,
nodes: list[dict], # 节点定义列表
edges: list[dict], # 边定义列表
mode: str = "append" # append | replace
) -> dict:
"""向 recipe 的 state_graph 添加节点。"""
# 实现...常见坑与调试
| 问题 | 原因 | 解决 |
|---|---|---|
| LLM 节点空转不发工具调用 | prompt 不够明确 | 加 "你必须调用至少一个工具" |
output_format 字段散到顶层 | 未设 output_var | 加 "output_var": "result" |
condition 静默判 False | 用了 .get() | 改用 x['key'] |
| 改翻译器后不生效 | 缓存未刷新 | 重启 bridge 或重跑 install_built_ins |
| 干跑卡死 | 默认 asker 调真实模型 | 传 ask=stub_ask |
调试命令
bash
# 编译检查(不消耗 token)
python main.py recipe compile <recipe_id>
# 干跑(用 stub asker)
python main.py recipe dryrun <recipe_id> --input '{...}'
# 查看详细 trace
python main.py recipe trace <run_id>
# 查看编译报告
python main.py recipe compile-report <recipe_id>更多
- 完整源码:
src/recipes/ - 内置示例:
src/recipes/_builtin/ - 编程助手图:
workspace/manager/recipes/copilot/recipe.json - Flow 编排(跨 Recipe)→ Flow 教程
- Broker 打包发布 → Broker 教程
📄 完整来源:
RECIPE_DEVELOPMENT.md
