Recipe 配方开发详解
本文是 Recipe 子系统的完整开发参考。Recipe 是 Familiars 的"单技能自动化图":以 LLM 调用 + 工具执行为节点、变量池为状态、条件边为分支的 DAG 运行时。
1. 概念模型
1.1 什么是 Recipe
Recipe(配方) 把一个 agent 的"一轮对话"映射为一张状态图:
- 起点:
start节点(全局唯一入口锚点) - 终点:
done节点(全局唯一出口),必须绑定output/finish_reason - 中间:
compute/llm_call/tool_call/rag_query/emit_event/for_each节点
每个节点从全局变量池读 inputs,往池子写 outputs;边缘 condition= 是 Python bool 表达式;多条出边可并发分支(host 在活跃分支间 round-robin)。
1.2 与旧 pipeline 的关系
旧的 manifest.chains + hooks 线性链 = 每条边 condition 都是 "True" 的特例图。src/recipes/graph/translator.py 把遗留 manifest 自动编译为 graph.json(缓存为 _auto_graph.json);用户在编辑器里保存一次即写为正式 graph.json,此后 manifest 的 chains/hooks 字段不再读取。
2. 目录与文件
2.1 源码结构
src/recipes/
├── __init__.py
├── manifest.py # RecipeManifest 模型(输入/输出/工具/知识库/客户端/RAG/事件类型/渲染器)
├── store.py # RecipeStore — 磁盘 layout + CRUD
├── context.py # RecipeContext — bind 时上下文
├── runtime.py # Graph 运行期入口
├── catalog.py # 工具目录构建(_build_tools_catalog)
├── chain_catalog.py # 旧链式目录
├── approvals.py # 能力审批
├── audit.py # 审计
├── eval.py # 评估
├── filters.py # 过滤器
├── folder_protocol.py # 文件夹协议
├── heartbeat.py # 心跳 cron
├── marketplace.py # marketplace 索引
├── migrations.py # 迁移
├── packaging_cli.py # CLI 打包
├── reasoning.py # 推理
├── renderer_compiler.py # .vue 渲染器预编译
├── runtime_flags.py # 运行时 flag
├── source_reader.py # 源码读取
├── authoring/ # Copilot 辅助创作
│ ├── orchestrator.py
│ ├── prompts.py
│ └── state.py
├── graph/ # ★ 状态图核心
│ ├── schema.py # RecipeGraph / GraphNode / GraphEdge / GraphVariable / SubGraphTemplate / SubgraphIO
│ ├── validate.py # validate_graph — 20+ 条静态校验
│ ├── runner.py # 状态图执行器(_dispatch_chain_slot, _chain_node, _expand_for_each_in_scope)
│ └── translator.py # manifest chains/hooks → RecipeGraph
└── sandbox/ # 沙箱执行
├── ast_guard.py # AST 白名单(permanently denied / requestable)
├── host.py # 沙箱宿主
├── snippet.py # call_snippet — 把代码包成 def _node_<uuid>(**inputs): ...
└── worker.py # 沙箱 worker2.2 运行时磁盘布局
每个 recipe 在 workspace/agent_recipes/<id>/ 下:
workspace/agent_recipes/<id>/
├── manifest.json # RecipeManifest
├── user_code/
│ ├── graph.json # RecipeGraph(核心,前端编辑器的源真相)
│ ├── *.py # 自定义 compute / args_builder / result_parser 代码
│ └── tools/ # manifest.tools.custom_tools 的 .py 实现
│ └── <tool_name>.py # 顶层函数名必须 == tool key
├── knowledge/ # manifest.knowledge.identity/agents_md/user_md 指向的 md 文件
│ ├── IDENTITY.md
│ ├── AGENTS.md # ★ recipe SKILL 通过 execute_skill 进入 flow,必须存在
│ └── USER.md
├── external_kb/<kb_name>/ # manifest.knowledge.external_kbs 对应的文件目录
├── skills/ # manifest.skills.custom_templates 的 markdown
├── renderers/ # manifest.event_types[].renderer 与 output_renderers[].source_file
│ └── *.vue
└── recipes/ # 归档的 recipe(嵌套)2.3 内置 Recipe
随仓库发布、install_built_ins() 自动注册、前端列在"系统"分组、不可编辑/删除:
| ID | 路径 | 说明 |
|---|---|---|
hello_world | src/recipes/_builtin/hello_world/ | 最小 graph recipe — 一个 compute 节点把用户输入倒序复读然后 done |
default_recipe | src/recipes/_builtin/default_recipe/ | 默认 recipe — generate() 主链路的完整状态图镜像(sensitive → triage → RAG → planner → executor → synthesizer → verifier → replanner) |
recipe_author_agent | src/recipes/_builtin/recipe_author_agent/ | Recipe 创作辅助 agent |
3. Schema 详解
3.1 RecipeGraph(graph.json 顶层)
class RecipeGraph(BaseModel):
version: int = 1
variables: list[GraphVariable] # 全局变量池
nodes: list[GraphNode] # 节点集合
edges: list[GraphEdge] # 边缘集合
subgraphs: list[SubGraphTemplate] = [] # for_each 引用的子图模板(legacy)
entry: str = "" # legacy 单入口(迁移到 entries)
entries: list[str] = [] # 多入口(START 节点存在时被强制为 [start.id])
exits: list[str] = [] # 默认 = 所有 done 节点3.2 GraphVariable
class GraphVariable(BaseModel):
name: str # identifier-like(alnum + underscore)
type: str = "any" # str|int|float|bool|list|dict|any
# + 别名:string|number|integer|boolean|array|object → 规范化
external: bool = False # True = host 在 turn 开始注入(只读)
desc: str = ""默认外部变量(host 每轮自动注入,recipe 不必声明):
| 名称 | 类型 | 说明 |
|---|---|---|
query | str | 用户本轮原始输入 |
history | list | 历史对话消息列表 |
attachments | list | 用户本轮附件列表 |
agent_id | int | 当前 agent id |
dialogue_id | int | 当前对话 id |
3.3 GraphNode
class GraphNode(BaseModel):
id: str # identifier-like
kind: str = "compute" # start|compute|llm_call|tool_call|rag_query|emit_event|for_each|done
inputs: list[str] = [] # 从变量池读
outputs: list[str] = [] # 写回变量池
code: str = "" # compute 节点用(snippet:return {outputs})
args_builder: str = "" # side-effect 节点用(snippet,构造 action args)
result_parser: str = "" # side-effect 节点用(snippet,解析 observation)
desc: str = ""
mode: str = "custom" # custom | native
config: dict = {} # native 模式的结构化配置
render_as: dict[str, str] = {} # done 节点专属:metadata key → 渲染 hint(markdown|image|json|text|raw)
position_x: float = 0.0 # UI 位置
position_y: float = 0.0
# for_each 内联 body(2026-08-07 redesign,优先于 config.body_template)
body_nodes: Optional[list[GraphNode]] = None
body_edges: Optional[list[GraphEdge]] = None
body_entry: str = ""
body_exit: str = ""
body_inputs: Optional[list[SubgraphIO]] = None
body_outputs: Optional[list[SubgraphIO]] = NoneNode kind 语义表
| Kind | 含义 | 必填字段(custom 模式) | 必填字段(native 模式) |
|---|---|---|---|
start | 全局入口锚点 | 无(纯锚点,schema 会自动清空所有字段) | 同 |
compute | 纯本地 Python,无副作用 | code | 同 |
llm_call | host 调用 LLM | args_builder + result_parser | config.system_prompt / user_prompt_template / output_format |
tool_call | host 调用工具 | args_builder(返回 {tool_name, arguments})+ result_parser | config.tool_name + config.argument_map |
rag_query | host 查 KB | args_builder + result_parser | config.source + config.query_input |
emit_event | host 推送事件到前端(可阻塞等待用户响应) | args_builder | config.event_type + config.field_values |
for_each | 运行时 N 次展开(动态循环) | 无(靠 config) | 同 |
done | 分支终止 | args_builder(返回 {output, finish_reason}) | 同 |
3.4 GraphEdge
class GraphEdge(BaseModel):
source: str # 起节点 id
target: str # 终节点 id
condition: str = "True" # Python bool 表达式(AST 白名单求值)
directed: bool = True # False = 双向,任一端完成都触发另一端
source_handle: str = "" # UI: VueFlow handle id
target_handle: str = ""
join_id: str = "" # AND-edge 分组(相同 join_id + 相同 target = 所有 source 都触发才跑 target)3.5 Native 模式 config schema
mode="native" 时,runner 直接调 host action(ConversationSession / ToolDispatcher / Memorizer),不跑 Python 片段。
llm_call native config
{
"system_prompt": str | None, # 内联系统提示
"system_prompt_file": "" | "AGENTS.md" | "IDENTITY.md", # 从 knowledge 文件加载
"user_prompt_template": str, # 支持 {{var}} 模板
"output_format": {
"type": "str" | "markdown" | "json" | "schema" | "schema_ref" | "schema_from_var",
"schema": {...}, # type=schema 时的 JSON Schema
"ref": "plan_spec.json", # type=schema_ref 时的文件名
"schema_var": "my_schema", # type=schema_from_var 时的变量名
"on_parse_error": "soft" | "hard"
},
"output_var": str, # 把 parsed dict 包到一个变量名下(防止字段散到顶层)
"output_map": {ret_field: var_name}, # 把 LLM 输出字段映射到变量名
"tool_whitelist": [str], # 本节点允许调用的工具
"loop_mode": "single" | "tool_loop", # tool_loop 时 LLM 多轮发 tool_call
"max_turns": int, # tool_loop 上限
"history_mode": "none" | "inherit" | "custom",
"client_id": str # 指定 client(覆盖 manifest default)
}tool_call native config
{
"tool_name": str, # 必须存在且可通过 ToolDispatcher 调用
"argument_map": {param: var_name | "literal_value"} # 字面量用非 identifier 字符串
}rag_query native config
{
"source": "memorizer" | "external_kb" | "skills_registry" | "data_registry" | "dialog_history",
"query_input": str, # 必须 in node.inputs
"kb_names": [str], # source=external_kb 时必须,且要在 manifest.knowledge.external_kbs 里
"top_k": int,
"result_map": {field: var_name}
}emit_event native config
{
"event_type": str, # 内建:__log__ | __output__;或 manifest.event_types 里声明的
"field_values": {field_name: value | "=expr"}, # =expr 在运行时对变量池求值
"mode": "custom" | "native" # custom 时会忽略 native config
}3.6 for_each 节点 config
{
"iterable_var": str, # 必须 in graph.variables(要迭代的变量)
"iter_var": str, # 每次迭代绑定的当前项名字(不能 == iterable_var)
"body_template": str, # 引用 SubGraphTemplate(与 inline body 二选一)
# --- 或者 inline body(2026-08-07 设计)---
# node.body_nodes / body_edges / body_entry / body_exit / body_inputs / body_outputs
"mode": "parallel" | "serial", # 默认 parallel
"output_var": str, # 迭代结果收集到哪个变量(必须 in node.outputs)
"reduce_strategy": "list" | "concat" | "merge_dict" | "last",
"result_var": str, # 每次迭代里要收集的变量(merge_dict 时必设)
"input_bindings": {sub_input: parent_var},
"output_bindings": {sub_output: parent_var}
}3.7 RecipeManifest(manifest.json 顶层)
关键字段(pydantic 模型在 src/recipes/manifest.py):
class RecipeManifest(BaseModel):
id: str # 全局唯一,== 目录名
name: str
description: str = ""
version: str = "1.0.0"
author: str = ""
tags: list[str] = []
# 输入输出契约
input_vars: list[RecipeVar] # RecipeVar: name, type, description, required
output_vars: list[RecipeVar]
# 工具
tools: RecipeTools = {
"whitelist": None | [str], # None = 全部;[str] = 只这些
"per_tool_overrides": {tool: file.py},
"custom_tools": {tool: CustomToolDef},
"categories": [ToolCategoryDef],
"tool_categories": {tool: category}
}
# RAG
rag: RecipeRAG = {
"mode": "builtin" | "custom",
"alpha": float, "beta": float, "gamma": float,
"top_k": int, "sliding_window_size": int,
"user_code": str,
"hybrid_sparse_weight": float, "rerank_disabled": bool,
"graph_expand_top": int, "graph_expand_hops": int
}
# 技能
skills: RecipeSkills = {"enabled": [], "custom_templates": []}
# 知识
knowledge: RecipeKnowledge = {
"identity": str, "agents_md": str, "user_md": str,
"external_kbs": [ExternalKB],
"kb_categories": [KbCategoryDef]
}
# 生成管线
generate: RecipeGenerate = {
"image": RecipeGenerateImage,
"report": RecipeGenerateReport,
"digital_human": RecipeGenerateDigitalHuman
}
# 客户端覆盖
clients: RecipeClients = {
"existing": {client_id: ExistingClientOverride},
"custom": [CustomClient]
}
# 自定义事件类型
event_types: list[RecipeEventType]
# 输出渲染器
output_renderers: list[RecipeOutputRenderer]
# 能力
capabilities: {"extra_modules": [str]}
# 执行
execution: {"parallel_mode": "topo" | "..."}
# 历史管理
history: RecipeHistory = {"mode": "builtin" | "custom", "filename": str}
# 长期记忆
memorizer: RecipeMemorizer = {"auto_write": bool}4. REST API
4.1 Recipe CRUD(/api/agent_recipes)
| Method | Path | 说明 |
|---|---|---|
| GET | /api/agent_recipes | 列所有 recipe |
| GET | /api/agent_recipes/{id} | 取一个 |
| POST | /api/agent_recipes | 新建 |
| PUT | /api/agent_recipes/{id} | 更新 manifest |
| DELETE | /api/agent_recipes/{id} | 删除 |
| POST | /api/agent_recipes/{id}/duplicate | 复制 |
| POST | /api/agent_recipes/{id}/set_default | 设为某 agent 默认 |
| POST | /api/agent_recipes/{id}/create_agent | 创建绑定该 recipe 的 agent |
4.2 文档与工具文件(/api/agent_recipes/{id}/...)
| Method | Path | 说明 |
|---|---|---|
| GET | /{id}/docs | 列 knowledge 目录文件 |
| GET | /{id}/docs/{name} | 读一个文件 |
| PUT | /{id}/docs/{name} | 写一个文件 |
| DELETE | /{id}/docs/{name} | 删 |
| GET | /{id}/utils | 列 user_code/ 下工具 .py |
| GET | /{id}/utils/{name} | 读工具源码 |
| PUT | /{id}/utils/{name} | 写工具源码 |
| POST | /{id}/utils/upload | multipart 上传工具 .py |
| GET | /{id}/renderers | 列渲染器 |
| GET | /{id}/renderers/{name} | 读渲染器 .vue 源码 |
4.3 导入导出
| Method | Path | 说明 |
|---|---|---|
| GET | /{id}/export_zip | 导出为 .zip |
| POST | /import_zip | 从 .zip 导入 |
| POST | /preview_zip | 预览 .zip 内容 |
| POST | /import_folder | 从目录导入 |
4.4 开发与发布(/api/recipe_dev)
| Method | Path | 说明 |
|---|---|---|
| POST | /api/recipe_dev/compile | 编译校验(结构) |
| POST | /api/recipe_dev/dryrun | 干跑(无真实模型,用 stub asker) |
| POST | /api/recipe_dev/e2e_test | 端到端测试 |
| POST | /api/recipe_dev/publish_relay | 发布到 relay 市场 |
| GET | /api/recipe_dev/review_status | 查看审核状态 |
| GET | /api/recipe_dev/catalog | 工具目录 |
| GET | /api/recipe_dev/_meta/protocol | 协议元数据 |
4.5 状态图读写
实际通过 PUT /api/agent_recipes/{id} 提交 manifest + graph.json 的合并 payload;前端编辑器直接 POST 编辑后的 graph。
5. 校验器(validate_graph)
src/recipes/graph/validate.py:validate_graph(graph, manifest, agent_id) 返回 violation 列表: {node_id?, field?, code, message, severity}。severity="error" 阻止保存;"warning" 仅提示。
5.1 校验清单
| Code | 严重性 | 含义 |
|---|---|---|
NO_DONE | error | 没有 done 节点 |
MULTIPLE_DONE | error | 多个 done 节点 |
NO_START | warning | 没有 start 节点(legacy 模式仍可用) |
MULTIPLE_START | error | 多个 start 节点 |
START_HAS_INCOMING_EDGE | error | start 节点有入边 |
START_NOT_SOLE_ENTRY | error | entries ≠ [start.id] |
START_IS_EXIT | error | start 同时是 exit |
JOIN_UNDIRECTED | error | join_id 成员是无向边 |
JOIN_MULTIPLE_TARGETS | error | 同一 join_id 指向多个 target |
JOIN_DANGLING | error | join_id 成员引用不存在的节点 |
JOIN_SINGLE_MEMBER | warning | join_id 只有一条边 |
ORPHAN_SUBGRAPH | warning | SubGraphTemplate 没被任何 for_each 引用 |
DUP_SUBGRAPH_ID | error | 重复的子图 id |
BODY_ENTRY_UNREACHABLE | warning | 子图 body_entry 没有入边 |
FE_NO_ITER_VAR | error | for_each 缺 iter_var |
FE_NO_ITERABLE | error | for_each 缺 iterable_var |
FE_ITERABLE_NOT_DECLARED | error | iterable_var 不在 variables |
FE_ITER_VAR_COLLISION | error | iter_var == iterable_var |
FE_NO_BODY | error | 既无 body_template 也无 inline body |
FE_BODY_NOT_FOUND | error | body_template 不在 subgraphs |
FE_BAD_MODE | error | mode 不是 parallel/serial |
FE_BAD_REDUCE | error | reduce_strategy 不合法 |
FE_OUTPUT_NOT_DECLARED | error | output_var 不在 variables |
FE_NO_OUTPUTS | warning | node.outputs 为空 |
FE_BODY_DUP_ID | error | body_nodes 内 id 重复 |
FE_NO_BODY_ENTRY / FE_NO_BODY_EXIT | error | 缺 body_entry / body_exit |
FE_BODY_ENTRY_MISSING / FE_BODY_EXIT_MISSING | error | body_entry/exit 不在 body_nodes |
FE_BODY_EDGE_DANGLING | error | body_edges 引用不存在的节点 |
FE_BODY_DUP_IO | error | body_inputs / body_outputs 重名 |
FE_INLINE_OVERRIDES_TEMPLATE | warning | 同时设了 inline body 与 body_template |
FE_BODY_UNDECLARED_VAR | error | body 节点引用未声明变量 |
FE_BODY_INPUT_MISSING_IN_PARENT | warning | body_inputs 名字在父图无对应变量 |
FE_MERGE_DICT_NEEDS_RESULT_VAR | warning | merge_dict 没设 result_var |
MISSING_OUTPUT_KEYS | error | compute return dict 缺声明的 output keys |
NO_RETURN | warning | compute 没 return |
NON_LITERAL_RETURN | warning | compute return 不是字面 dict |
SNIPPET_UNDEFINED_INPUT | error | snippet 引用了不在 node.inputs 的变量 |
NO_TOOL_NAME | error | native tool_call 没 tool_name |
TOOL_NOT_CALLABLE | error | 工具不在 catalog |
TOOL_NOT_IN_WHITELIST | error | 工具不在 manifest tools.whitelist |
ARG_VAR_NOT_INPUT | error | argument_map 值不是 node.inputs |
BAD_RAG_SOURCE | error | rag_query source 不合法 |
NO_QUERY_INPUT | error | rag_query 没 query_input |
KB_NOT_IN_MANIFEST | error | kb_names 不在 manifest external_kbs |
NO_EVENT_TYPE | error | emit_event 没 event_type |
UNKNOWN_EVENT_TYPE | error | event_type 不在 manifest.event_types |
NO_RESPONSE_TARGET | error | expects_response 但没 outputs / response_var |
OUTPUT_VAR_NOT_DECLARED | error | output_map 值不在 node.outputs |
INPUT_VAR_UNRESOLVED | error | node.inputs 里变量不会被 host 注入也不会被任何节点写入 |
START_INPUT_UNUSED | error | query 没被任何下游节点引用 |
OUTPUTS_NOT_BINDABLE | error | native llm_call 有 outputs 但无 output_format/output_map/output_var |
BAD_SYSTEM_PROMPT_FILE | error | system_prompt_file 不是 ''/AGENTS.md/IDENTITY.md |
BAD_OUTPUT_FORMAT | error | output_format.type 不合法 |
BAD_CLIENT_ID | error | client_id 不是字符串 / 不在 manifest |
TOOL_NOT_IN_MANIFEST | error | tool_whitelist 不在 manifest |
BAD_HISTORY_MODE | error | history_mode 不合法 |
BAD_MAX_TURNS | error | max_turns 不是正整数 |
6. 沙箱与 AST 白名单
6.1 永久禁止
ctypes / pickle / importlib / inspect / code / codeop / pdb / trace / tracemalloc / sys / signal / multiprocessing / threading / asyncio / socket / select / asyncore / asynchat / ssl / mmap / platform / __import__ / eval / exec / compile / open / globals / locals / vars / getattr / setattr / delattr / breakpoint / input / exit / quit
6.2 可申请放行
capabilities.extra_modules 声明后走审批:os / subprocess / requests / aiohttp / httpx / sqlite3 / pathlib / tempfile / hashlib / hmac / base64 / binascii / uuid / secrets / shutil / glob / fnmatch / textwrap / io / copy / pprint / urllib / xml / csv / string / bisect / heapq / operator / itertools / random / fractions / warnings / contextlib / abc / types / argparse
授予 src.x 即解锁整个 src/ 子树 —— 谨慎使用。
6.3 能力审批
每个 (recipe_id, agent_id) 组合独立审批哈希。install_built_ins() 自动批内置 recipe。bind / setDefault 的响应携带 caps_declared + needs_approval,前端弹 CapabilityApprovalPanel。
7. 运行期行为
7.1 节点执行
- compute:把
code包成def _node_<uuid>(**inputs): ...,在 sandbox 内运行;返回 dict 写回变量池。 - llm_call custom:
args_builder构造 messages,host 调 ConversationSession,result_parser解析 observation。 - llm_call native:host 直接根据
config调 ConversationSession(带 tool_whitelist 走 tool loop)。 - tool_call:
args_builder构造{tool_name, arguments},host 调 ToolDispatcher。 - rag_query:host 调 RAG 召回,
result_parser解析。 - emit_event:host yield 事件到前端;如果 event_type 是
expects_response=True,阻塞等待用户响应,响应写回response_var或 node.outputs。 - for_each:在运行期展开,每次迭代新建变量 scope,父池快照 + iter_var 绑定。
7.2 变量池语义
- 全局池:所有节点共享。并行分支写同一变量 → last-write-wins + warning。
- 外部变量:host 在 turn 开始时注入(query / history / attachments / agent_id / dialogue_id)。
- for_each 迭代 scope:自动包含父池快照 + iter_var +
<iter_var>_idx。Inline body 严格 allow-list:body 节点只能读body_inputs声明 + iter_var + body 内其他节点写入。
7.3 Recipe SKILL 协作
Recipe 通过 execute_skill 伪工具调用 SKILL.md;SKILL 在 recipe knowledge/AGENTS.md 存在时生效。Flow 里的 agent 通过 filtered_schemas 看见这些工具,而不是 tools.json。
8. 自定义渲染器
8.1 输出渲染器(RecipeOutputRenderer)
{
"output_renderers": [
{"name": "couplet", "output_var": "final_output", "source_file": "couplet.vue"}
]
}.vue 文件放在 renderers/;保存时后端 renderer_compiler 预编译为 ESM JS,前端在 chat 输出时挂载。组件接收 :value="output_fields[output_var]"。
8.2 自定义事件类型(RecipeEventType)
{
"event_types": [
{
"name": "approval_card",
"renderer": "approval_card.vue",
"fields": [
{"name": "title", "type": "string"},
{"name": "options", "type": "array", "items": {"name": "opt", "type": "string"}},
{"name": "audio_url", "type": "audio"}
],
"expects_response": true,
"response_var": "user_choice"
}
]
}emit_event 节点用 event_type: "approval_card" + field_values: {...}。Runner 解析 =expr、挂载 .vue、等响应写回 response_var。
支持的字段类型:string / int / float / bool / array / object / audio / video / attachment / attachments。媒体类型会生成 {kind, path} 附件 dict,前端用 app-file:// 协议播放。
9. 常见坑
| 坑 | 症状 | 解决 |
|---|---|---|
| tool_loop 节点空转 | LLM 一直不 emit tool_call | system_prompt 显式说"emit ≥1 tool_call/turn",用 markdown output_format 而非 schema |
| output_var 没设 | schema 字段散到顶层,下游 .field 取不到 | 在 config 加 output_var 包一层 |
.get() 在条件边 | 静默判 False | 用 x['key'] 下标 |
| native llm_call 有 outputs 但无 output_format | 下游 done 拿到空字符串 | 加 output_format.type: "schema" 或 output_var |
| 改翻译器后不生效 | graph 行为陈旧 | 重启 bridge 或重跑 install_built_ins |
copilot 用 user_input 不用 query | query 永不被消费 | START_INPUT_UNUSED 会报错;统一用 query |
| merge_dict 没 result_var | 后迭代覆盖前迭代 | 设 result_var 指向每次迭代的输出变量 |
| save_image_to_recipe 超时 | HTTP 600s 不够 | 给 custom tool 设 timeout_s: 1800 |
10. 开发流程(端到端)
- 创意工坊新建 recipe → 编辑器拖节点/连线;
- 编辑节点参数(LLM prompt / 工具 / 表达式 / 渲染器);
- 编译:
POST /api/recipe_dev/compile(结构校验,validate_graph输出 violations); - 干跑:
POST /api/recipe_dev/dryrun(无真模型 deps,用 stub asker); - e2e 测试:
POST /api/recipe_dev/e2e_test(真实 LLM + 工具); - 发布:
POST /api/recipe_dev/publish_relay(进审核队列)。
参考源码
- Schema:
src/recipes/graph/schema.py - 校验:
src/recipes/graph/validate.py - 运行:
src/recipes/graph/runner.py - Manifest:
src/recipes/manifest.py - 工具目录:
src/recipes/catalog.py - REST:
src/bridge/routes/agent_recipes.py、src/bridge/routes/recipe_dev.py
