Skip to content

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           # 沙箱 worker

2.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_worldsrc/recipes/_builtin/hello_world/最小 graph recipe — 一个 compute 节点把用户输入倒序复读然后 done
default_recipesrc/recipes/_builtin/default_recipe/默认 recipe — generate() 主链路的完整状态图镜像(sensitive → triage → RAG → planner → executor → synthesizer → verifier → replanner)
recipe_author_agentsrc/recipes/_builtin/recipe_author_agent/Recipe 创作辅助 agent

3. Schema 详解 ​

3.1 RecipeGraph(graph.json 顶层) ​

python
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 ​

python
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 不必声明):

名称类型说明
querystr用户本轮原始输入
historylist历史对话消息列表
attachmentslist用户本轮附件列表
agent_idint当前 agent id
dialogue_idint当前对话 id

3.3 GraphNode ​

python
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]] = None

Node kind 语义表 ​

Kind含义必填字段(custom 模式)必填字段(native 模式)
start全局入口锚点无(纯锚点,schema 会自动清空所有字段)同
compute纯本地 Python,无副作用code同
llm_callhost 调用 LLMargs_builder + result_parserconfig.system_prompt / user_prompt_template / output_format
tool_callhost 调用工具args_builder(返回 {tool_name, arguments})+ result_parserconfig.tool_name + config.argument_map
rag_queryhost 查 KBargs_builder + result_parserconfig.source + config.query_input
emit_eventhost 推送事件到前端(可阻塞等待用户响应)args_builderconfig.event_type + config.field_values
for_each运行时 N 次展开(动态循环)无(靠 config)同
done分支终止args_builder(返回 {output, finish_reason})同

3.4 GraphEdge ​

python
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 ​

python
{
    "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 ​

python
{
    "tool_name": str,                    # 必须存在且可通过 ToolDispatcher 调用
    "argument_map": {param: var_name | "literal_value"}  # 字面量用非 identifier 字符串
}

rag_query native config ​

python
{
    "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 ​

python
{
    "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 ​

python
{
    "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):

python
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) ​

MethodPath说明
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}/...) ​

MethodPath说明
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/uploadmultipart 上传工具 .py
GET/{id}/renderers列渲染器
GET/{id}/renderers/{name}读渲染器 .vue 源码

4.3 导入导出 ​

MethodPath说明
GET/{id}/export_zip导出为 .zip
POST/import_zip从 .zip 导入
POST/preview_zip预览 .zip 内容
POST/import_folder从目录导入

4.4 开发与发布(/api/recipe_dev) ​

MethodPath说明
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_DONEerror没有 done 节点
MULTIPLE_DONEerror多个 done 节点
NO_STARTwarning没有 start 节点(legacy 模式仍可用)
MULTIPLE_STARTerror多个 start 节点
START_HAS_INCOMING_EDGEerrorstart 节点有入边
START_NOT_SOLE_ENTRYerrorentries ≠ [start.id]
START_IS_EXITerrorstart 同时是 exit
JOIN_UNDIRECTEDerrorjoin_id 成员是无向边
JOIN_MULTIPLE_TARGETSerror同一 join_id 指向多个 target
JOIN_DANGLINGerrorjoin_id 成员引用不存在的节点
JOIN_SINGLE_MEMBERwarningjoin_id 只有一条边
ORPHAN_SUBGRAPHwarningSubGraphTemplate 没被任何 for_each 引用
DUP_SUBGRAPH_IDerror重复的子图 id
BODY_ENTRY_UNREACHABLEwarning子图 body_entry 没有入边
FE_NO_ITER_VARerrorfor_each 缺 iter_var
FE_NO_ITERABLEerrorfor_each 缺 iterable_var
FE_ITERABLE_NOT_DECLAREDerroriterable_var 不在 variables
FE_ITER_VAR_COLLISIONerroriter_var == iterable_var
FE_NO_BODYerror既无 body_template 也无 inline body
FE_BODY_NOT_FOUNDerrorbody_template 不在 subgraphs
FE_BAD_MODEerrormode 不是 parallel/serial
FE_BAD_REDUCEerrorreduce_strategy 不合法
FE_OUTPUT_NOT_DECLAREDerroroutput_var 不在 variables
FE_NO_OUTPUTSwarningnode.outputs 为空
FE_BODY_DUP_IDerrorbody_nodes 内 id 重复
FE_NO_BODY_ENTRY / FE_NO_BODY_EXITerror缺 body_entry / body_exit
FE_BODY_ENTRY_MISSING / FE_BODY_EXIT_MISSINGerrorbody_entry/exit 不在 body_nodes
FE_BODY_EDGE_DANGLINGerrorbody_edges 引用不存在的节点
FE_BODY_DUP_IOerrorbody_inputs / body_outputs 重名
FE_INLINE_OVERRIDES_TEMPLATEwarning同时设了 inline body 与 body_template
FE_BODY_UNDECLARED_VARerrorbody 节点引用未声明变量
FE_BODY_INPUT_MISSING_IN_PARENTwarningbody_inputs 名字在父图无对应变量
FE_MERGE_DICT_NEEDS_RESULT_VARwarningmerge_dict 没设 result_var
MISSING_OUTPUT_KEYSerrorcompute return dict 缺声明的 output keys
NO_RETURNwarningcompute 没 return
NON_LITERAL_RETURNwarningcompute return 不是字面 dict
SNIPPET_UNDEFINED_INPUTerrorsnippet 引用了不在 node.inputs 的变量
NO_TOOL_NAMEerrornative tool_call 没 tool_name
TOOL_NOT_CALLABLEerror工具不在 catalog
TOOL_NOT_IN_WHITELISTerror工具不在 manifest tools.whitelist
ARG_VAR_NOT_INPUTerrorargument_map 值不是 node.inputs
BAD_RAG_SOURCEerrorrag_query source 不合法
NO_QUERY_INPUTerrorrag_query 没 query_input
KB_NOT_IN_MANIFESTerrorkb_names 不在 manifest external_kbs
NO_EVENT_TYPEerroremit_event 没 event_type
UNKNOWN_EVENT_TYPEerrorevent_type 不在 manifest.event_types
NO_RESPONSE_TARGETerrorexpects_response 但没 outputs / response_var
OUTPUT_VAR_NOT_DECLAREDerroroutput_map 值不在 node.outputs
INPUT_VAR_UNRESOLVEDerrornode.inputs 里变量不会被 host 注入也不会被任何节点写入
START_INPUT_UNUSEDerrorquery 没被任何下游节点引用
OUTPUTS_NOT_BINDABLEerrornative llm_call 有 outputs 但无 output_format/output_map/output_var
BAD_SYSTEM_PROMPT_FILEerrorsystem_prompt_file 不是 ''/AGENTS.md/IDENTITY.md
BAD_OUTPUT_FORMATerroroutput_format.type 不合法
BAD_CLIENT_IDerrorclient_id 不是字符串 / 不在 manifest
TOOL_NOT_IN_MANIFESTerrortool_whitelist 不在 manifest
BAD_HISTORY_MODEerrorhistory_mode 不合法
BAD_MAX_TURNSerrormax_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) ​

json
{
  "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) ​

json
{
  "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_callsystem_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 不用 queryquery 永不被消费START_INPUT_UNUSED 会报错;统一用 query
merge_dict 没 result_var后迭代覆盖前迭代设 result_var 指向每次迭代的输出变量
save_image_to_recipe 超时HTTP 600s 不够给 custom tool 设 timeout_s: 1800

10. 开发流程(端到端) ​

  1. 创意工坊新建 recipe → 编辑器拖节点/连线;
  2. 编辑节点参数(LLM prompt / 工具 / 表达式 / 渲染器);
  3. 编译:POST /api/recipe_dev/compile(结构校验,validate_graph 输出 violations);
  4. 干跑:POST /api/recipe_dev/dryrun(无真模型 deps,用 stub asker);
  5. e2e 测试:POST /api/recipe_dev/e2e_test(真实 LLM + 工具);
  6. 发布: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

Familiars · 多 Agent AI 桌面应用