Skip to content

Recipe 开发教程

面向 Recipe 开发者。本文覆盖核心概念、主链路源码解析、从简单到复杂的实例、以及编程助手(copilot)的实现。


什么是 Recipe

Recipe(配方) 是把单一技能自动化的一套parse_inputs → 规划 → 执行 → 验证 → 合成,以 LLM 调用 + 工具执行为节点,最终产出结构化输出。

📄 src/recipes/graph.py — 图定义与节点类型 📄 src/recipes/worker.py — 节点执行引擎 📄 src/recipes/ast_guard.py — 表达式白名单


主链路:从输入到输出

🧪 Recipe 主链路 —— 创意工坊预览
┌──────────┐    ┌──────────────┐    ┌────────────┐
│ starter   │───▶│ clarify      │───▶│ planner    │
│ parse_    │    │ emit_event   │    │ llm_call   │
│ inputs    │    │ block=true   │    │ → plan_doc │
└──────────┘    └──────────────┘    └─────┬──────┘

                    ┌─────────────────────┤
                    ▼                     ▼
              ┌──────────┐         ┌──────────┐
              │ direct_  │         │ execute  │
              │ answer   │         │ llm_call │
              │ (summary)│         │ tool_loop│
              └──────────┘         └────┬─────┘

                    ┌───────────────────┤
                    ▼                   ▼
              ┌──────────┐       ┌──────────┐
              │ verify   │──yes──▶│synthes-  │──▶ done
              │ llm_call │       │ize       │
              │ →verdict │       │→ report  │
              └────┬─────┘       └──────────┘
                   │ no

              (retry execute)

关键源码位置

文件职责
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: parsed

2. 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: plan

3. 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

⚠️ conditionAST 白名单src/recipes/ast_guard.py),只允许:

  • 变量访问:x.fieldx['key']
  • 比较:==, !=, <, >, in, not in
  • 逻辑:and, or, not
  • 禁止:函数调用、赋值、import

实例一:简单 Recipe —— 文本翻译器

目标:输入一段中文,输出英文翻译。

🧪 translator · 创意工坊
┌───────────────────────┐
│ ● starter             │
│   parse_inputs        │
│   inputs: text,       │
│     target_lang       │
└──────────┬────────────┘


┌───────────────────────┐
│ ● llm_call            │
│   translate           │
│   system: 专业翻译     │
│   output_var:         │
│     translation       │
└──────────┬────────────┘


┌───────────────────────┐
│ ● summary             │
│   done                │
│   outputs: result     │
└───────────────────────┘

完整定义

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。

🧪 doc_summarizer · 创意工坊
┌──────────────────────┐
│ ● starter            │
│   parse_inputs       │
│   inputs: file_path  │
└──────────┬───────────┘


┌──────────────────────┐    extra_modules:
│ ● compute            │    src.utils.doc_tools
│   extract            │───────────────────────
│   doc_tools.read_    │
│   document()         │
│   → doc_content      │
└──────────┬───────────┘


┌──────────────────────┐    output_format:
│ ● llm_call           │    {title, key_points,
│   summarize          │     conclusion}
│   system: 文档分析    │
│   → summary          │
└──────────┬───────────┘


┌──────────────────────┐
│ ● compute            │
│   format             │
│   → markdown_report  │
└──────────┬───────────┘


┌──────────────────────┐
│ ● summary            │
│   done               │
│   outputs: report    │
└──────────────────────┘

关键节点

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
节点数513+
LLM 调用17+(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

图结构

🧪 recipe_copilot · 创意工坊
┌──────────────────────────────────────────────────────────────────────┐
│  ● starter: parse_inputs                                             │
│    inputs: brief, attachments, mode(small_fix|large_refactor)        │
└───────────────────────────────┬──────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────┐
│  ● llm_call: analyze_request                                         │
│    system: 分析用户需求,判断是小修还是大改                             │
│    → mode, scope_summary                                             │
└───────────────────────────────┬──────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────┐
│  ● llm_call: execute_1 · manifest                                    │
│    tools: recipe_read_manifest, recipe_write_manifest                 │
│    system: 根据需求编辑 manifest.json                                │
│    → manifest                                                        │
└───────────────────────────────┬──────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────┐
│  ● llm_call: execute_2 · state_graph                                 │
│    tools: recipe_state_graph_add_nodes, recipe_state_graph_           │
│           add_edges, recipe_state_graph_delete_nodes                  │
│    system: 构建图的节点和边                                           │
│    → graph_nodes, graph_edges                                        │
└───────────────────────────────┬──────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────┐
│  ● compute: verify_1                                                 │
│    recipe_compile(recipe_id)                                          │
│    → compile_ok, compile_errors                                      │
└───────────┬────────────────────────────────────┬─────────────────────┘
            │ compile_ok=true                    │ compile_ok=false
            ▼                                    ▼
┌──────────────────────┐     ┌─────────────────────────────────────────┐
│ 继续 execute_3       │     │ ● llm_call: retry_manifest_fix          │
│                      │     │   tools: 同 execute_1                    │
│                      │     │   system: 根据编译错误修复 manifest      │
│                      │     │   → 回到 execute_2(最多 3 轮)          │
│                      │     └─────────────────────────────────────────┘
└──────────┬───────────┘


┌──────────────────────────────────────────────────────────────────────┐
│  ● llm_call: execute_3 · custom_ui                                   │
│    tools: custom_ui_add_event_types, custom_ui_write_renderer        │
│    system: 生成 .vue 渲染器源码                                       │
│    → renderer_source                                                 │
└───────────────────────────────┬──────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────┐
│  ● compute: verify_2                                                 │
│    recipe_compile(recipe_id) + recipe_dryrun(recipe_id)               │
│    → final_ok, dryrun_output                                         │
└───────────────────────────────┬──────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────┐
│  ● llm_call: summarize                                               │
│    system: 汇总所有变更,生成用户可读的修改说明                        │
│    → changelog                                                       │
└───────────────────────────────┬──────────────────────────────────────┘


┌──────────────────────────────────────────────────────────────────────┐
│  ● summary: done                                                     │
│    outputs: changelog, recipe_id, compile_ok                         │
└──────────────────────────────────────────────────────────────────────┘

关键设计

  1. 三阶段 execute:manifest → state_graph → custom_ui,每阶段独立编译校验
  2. 两次 verify:verify_1 校验结构(compile),verify_2 校验行为(compile + dryrun)
  3. 重试循环:verify_1 失败时回退修复,最多 3 轮
  4. 工具调用:通过 tool_whitelist 限定 copilot 只能操作 recipe 相关 API
  5. 增量编辑:工具支持 patch 模式,不重写整个图

源码位置

文件职责
src/recipes/copilot/driver.py对话驱动入口
src/recipes/copilot/graph.pycopilot 图定义
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

✨ Familiars · 多 Agent AI 桌面应用 —— 对话树记忆 · 长期记忆 · 工具系统 · 数字人 · Broker 生态