<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Agent on 姚玉亮的网络日志</title><link>https://yusanwen-code.github.io/tags/agent/</link><description>Recent content in Agent on 姚玉亮的网络日志</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Wed, 02 Sep 2026 20:00:00 +0800</lastBuildDate><atom:link href="https://yusanwen-code.github.io/tags/agent/index.xml" rel="self" type="application/rss+xml"/><item><title>对话编排层重构：从 Go 业务编排迁移到 Python LangGraph</title><link>https://yusanwen-code.github.io/posts/post-76/</link><pubDate>Wed, 02 Sep 2026 20:00:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-76/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-76-cover.jpg" alt="Featured image of post 对话编排层重构：从 Go 业务编排迁移到 Python LangGraph" /&gt;&lt;h2 id="一条报数指令暴露的问题"&gt;一条报数指令暴露的问题&#10;&lt;/h2&gt;&lt;p&gt;炼丹炉（&lt;a class="link" href="https://github.com/yusanwen-code/alchemy-furnace" target="_blank" rel="noopener"&#10; &gt;Alchemy Furnace&lt;/a&gt;，我的开源项目）的群聊里，我发过这样一条指令：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;@全体成员 全体都有！报数！&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;预期很明确：A、B、C、D 四个道人按顺序报 1、2、3、4。实际跑起来，它被当成了闲聊：有时只让一个人接话，有时人设和记忆指令还能把硬指令覆盖掉，报数报成了自由发挥。&lt;/p&gt;&#10;&lt;p&gt;这不是一个偶发 bug，是一类问题的缩影：当编排逻辑长在业务代码里，&amp;ldquo;确定性指令&amp;quot;和&amp;quot;开放对话&amp;quot;就没有边界。群聊调度全部写在 Go 网关里，意图分类、发言人选择、轮次策略和业务逻辑缠在一起，每加一个编排能力都要改 Go、编译、发布，而编排恰恰是整个产品里变化最快的部分。&lt;/p&gt;&#10;&lt;p&gt;于是我做了这次架构迁移：把提示词构建、群聊调度和模型调用编排，整体从 Go 迁到 Python LangGraph，形成「Go 负责数据与 CRUD，Python 负责智能编排」的分层。&lt;/p&gt;&#10;&lt;h2 id="go-在变成-agent-框架"&gt;Go 在变成 Agent 框架&#10;&lt;/h2&gt;&lt;p&gt;迁移前炼丹炉是双后端：Go 网关（Gin）+ Python 引擎（FastAPI）。这个结构本身没问题，问题在于群聊上线后，Go 里长出了第三种性质的职责。&lt;/p&gt;&#10;&lt;p&gt;一轮群聊对话，Go 实际在做三件事：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;数据：会话、消息、道人、金丹、记忆、模型的存储与 CRUD，凭据加密，这是 Go 的本行&lt;/li&gt;&#10;&lt;li&gt;协议：对前端的 SSE 流、内部服务调用，是稳定的契约面&lt;/li&gt;&#10;&lt;li&gt;智能编排：意图分类、发言人选择、提示词拼装、模型调用、轮次与收敛策略，这部分高频变化、需要快速实验&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;第三类和前两类完全不是一种东西。编排层需要的是图结构、状态机、检查点、可恢复执行、结构化输出校验，这些 LangGraph 生态里全是现成的，Go 里全要手写。让一个后端语言慢慢长成一个 Agent 框架，才是最贵的路线。&lt;/p&gt;&#10;&lt;h2 id="分层go-管什么python-管什么"&gt;分层：Go 管什么，Python 管什么&#10;&lt;/h2&gt;&lt;p&gt;迁移后的职责边界：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;职责&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;归属&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;认证、请求校验、凭据加密存储&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;会话/消息/道人/金丹/记忆/模型的存储与 CRUD&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;正式消息落库（只在 &lt;code&gt;assistant_final&lt;/code&gt; 之后，按 run_id 幂等）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;内部事件 → 前端 SSE 的翻译&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;单聊/群聊路由&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python LangGraph&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;指令分类、确定性路由&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Supervisor 计划、发言人调度、收敛&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;提示词构建、模型调用、重试&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;记忆检索策略与记忆提案&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python 提案 → Go 校验落库&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;说白了：Go 不再选择发言人、不再分类对话意图、不再拼 prompt，也不再管轮次策略。前端仍然只调 Go、只消费 Go 的 SSE。对前端来说契约没变，变的是 SSE 背后的调度从 Go 挪进了图里。&lt;/p&gt;&#10;&lt;p&gt;边界里我最在意的一条：凭据不进图状态。API key、解密后的凭据走 LangGraph 的 &lt;code&gt;context_schema&lt;/code&gt;（RuntimeContext：模型网关、凭据、调试开关、取消旗标），与图状态彻底分离：它们绝不进入图状态、SQLite checkpoint、对外事件和日志。图状态里只有快照：历史、道人、金丹、记忆、模型引用，都是 Go 在发起一轮 run 时传进来的不可变快照。&lt;/p&gt;&#10;&lt;h2 id="图架构一张会话图一张可复用的道人图"&gt;图架构：一张会话图，一张可复用的道人图&#10;&lt;/h2&gt;&lt;p&gt;整个编排是四层图，顶层一张会话图：&lt;/p&gt;&#10;&#10;&lt;pre class="mermaid"&gt;&#10;flowchart TD&#10; A[&amp;#34;ConversationGraph（一次用户轮入口）&amp;#34;] --&amp;gt; B[&amp;#34;hydrate_context&amp;#34;] --&amp;gt; C[&amp;#34;route_session&amp;#34;]&#10; C --&amp;gt; D[&amp;#34;SingleChatGraph&amp;#34;] --&amp;gt; D1[&amp;#34;DaoistGraph（复用子图）&amp;#34;]&#10; C --&amp;gt; E[&amp;#34;GroupChatGraph&amp;#34;] --&amp;gt; F[&amp;#34;classify_directive&amp;#34;]&#10; F --&amp;gt; G[&amp;#34;deterministic_router（确定性指令）&amp;#34;]&#10; F --&amp;gt; H[&amp;#34;supervisor（开放讨论出计划）&amp;#34;]&#10; G --&amp;gt; I[&amp;#34;dispatch_daoists&amp;#34;]&#10; H --&amp;gt; I&#10; I --&amp;gt; J[&amp;#34;DaoistGraph × N&amp;#34;]&#10; I --&amp;gt; K[&amp;#34;convergence 收敛&amp;#34;]&#10; K --&amp;gt; L[&amp;#34;propose_memory 记忆提案 → Go 校验落库&amp;#34;]&#10;&lt;/pre&gt;&#10;&#10;&lt;p&gt;关键设计是 DaoistGraph 作为可复用子图：单聊直接调它，群聊每个发言人各自子运行一张。单聊和每个群成员走的是同一条 prompt 构建 + 模型调用管线：选定记忆、编译人设与金丹行为、调模型、校验响应、流出事件。这一条保证了&amp;quot;行为可解释&amp;rdquo;：单聊什么效果，群聊里同一个道人就是什么效果，不存在两套 prompt 逻辑。&lt;/p&gt;&#10;&lt;p&gt;群聊图里的路由是有讲究的：&lt;code&gt;classify_directive&lt;/code&gt; 先分类这一轮用户输入，确定性指令走 &lt;code&gt;deterministic_router&lt;/code&gt; 直接成计划，开放讨论才走 &lt;code&gt;supervisor&lt;/code&gt; 让模型出计划，这就是下一节的内容。&lt;/p&gt;&#10;&lt;h2 id="混合导演该调模型的调模型不该调的绝不调"&gt;混合导演：该调模型的调模型，不该调的绝不调&#10;&lt;/h2&gt;&lt;p&gt;群聊调度我用的是混合导演策略，核心就一句：确定性的事情用代码，创造性的事情用模型。&lt;/p&gt;&#10;&lt;p&gt;确定性指令永不委托模型。直接 @某个成员、@全体成员、报数、停止/继续，这些必须在代码里走死路径。报数的实现是：路由器为每个成员构造一个不可变任务，里面写死序号；人设、记忆、闲聊风格规则只能影响语气，不能改数字、不能漏人、不能加戏、不能改顺序。报数这种事交给模型去&amp;quot;理解&amp;quot;，就是开头那个 bug 的根源。&lt;/p&gt;&#10;&lt;p&gt;开放讨论交给 Supervisor。这一轮该谁说话、按什么顺序、每人拿到什么任务、发言预算多少、要不要收束，由会话默认模型（非人设，不扮演任何道人）产出一份结构化计划。Supervisor 不可见、不写用户可见的文本，只出计划。它失败或产出无效计划时，确定性回退到被明确 @ 的成员或主成员，导演的错误不传染整轮。&lt;/p&gt;&#10;&lt;p&gt;这样分工还有个实际的好处：明确的群指令不花 Supervisor 的模型调用，单聊用被选中道人自己配置的模型。钱花在真正需要智能的地方。&lt;/p&gt;&#10;&lt;h2 id="可恢复执行中断在发言人边界续跑按差集"&gt;可恢复执行：中断在发言人边界，续跑按差集&#10;&lt;/h2&gt;&lt;p&gt;这是整个迁移里技术含量最高的部分，也是最值得抄的部分。&lt;/p&gt;&#10;&lt;p&gt;每个用户轮有全局唯一的 &lt;code&gt;run_id&lt;/code&gt;，每条终稿回复有唯一 &lt;code&gt;reply_id&lt;/code&gt;，Go 按这两个 ID 做落库幂等。LangGraph 侧用本地异步 SQLite checkpointer 做检查点。用户点停止、前端断连、进程重启，都要能恢复，而且恢复时不能重复已完成发言人的发言。&lt;/p&gt;&#10;&lt;p&gt;做法是把恢复粒度设计在发言人边界：dispatch 节点按计划逐个发言人在子图里跑，每个发言人之前先 &lt;code&gt;await asyncio.sleep(0)&lt;/code&gt; 让出事件循环再查取消旗标：取消请求只能落在 await 点，这样中断恰好落在发言人之间的缝隙里，不会截断一条发到一半的回复。&lt;/p&gt;&#10;&lt;p&gt;续跑靠差集裁决。顶层检查点只携带「计划 + 已完成回复」，恢复重放时：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;计划已存在 → 不重新规划、不重发 &lt;code&gt;plan_created&lt;/code&gt; 事件&lt;/li&gt;&#10;&lt;li&gt;已完成的发言人 → 跳过，只在剩余差集上继续&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;其中藏着一个容易踩的坑，代码里的路由顺序：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;7&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;_route_after_router&lt;/span&gt;(state) &lt;span style="color:#ff7b72;font-weight:bold"&gt;-&amp;gt;&lt;/span&gt; Literal[&lt;span style="color:#a5d6ff"&gt;&amp;#34;dispatch&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;&amp;#34;supervisor&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;&amp;#34;end&amp;#34;&lt;/span&gt;]:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; state&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;speaking_plan&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;dispatch&amp;#34;&lt;/span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 续跑重放：计划已在，按差集续，不重规划&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kind &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; state[&lt;span style="color:#a5d6ff"&gt;&amp;#34;directive&amp;#34;&lt;/span&gt;][&lt;span style="color:#a5d6ff"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; kind &lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;open&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;supervisor&amp;#34;&lt;/span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 首轮开放讨论：默认模型出计划&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;end&amp;#34;&lt;/span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 停止/继续：控制指令，不产计划&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这个判断顺序不能反。如果先看 &lt;code&gt;kind&lt;/code&gt;，开放讨论在续跑重放时会误入 supervisor 再调一次模型；先看计划有无，重放才直接走 dispatch 按差集续跑。可恢复执行的全部难度就在这种毫厘之间：状态里每多带一个通道、路由里每换一个判断顺序，恢复语义就完全不同。&lt;/p&gt;&#10;&lt;p&gt;失败语义同样是发言人粒度：单个道人失败只跳过该发言人，不阻塞剩余计划成员；道人图内部对模型错误做最多 2 次机械重试；Supervisor 失败走确定性回退。三层兜底，任何一层的失败都不会把整轮炸掉。&lt;/p&gt;&#10;&lt;h2 id="迁移不是重写feature-flag--一条真实的提交序列"&gt;迁移不是重写：feature flag + 一条真实的提交序列&#10;&lt;/h2&gt;&lt;p&gt;迁移策略是增量推进，但结局只有一种实现：feature flag &lt;code&gt;orchestration_engine=legacy|langgraph&lt;/code&gt; 是临时迁移控制，不是长期双引擎产品设置。验证通过后默认 LangGraph，短回滚窗口过后删掉 legacy。&lt;/p&gt;&#10;&lt;p&gt;真实提交序列（节选自仓库主干）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docs: design LangGraph conversation orchestration ← 321 行设计文档&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docs: plan LangGraph orchestration migration ← 873 行实施计划&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;build(python): add LangGraph runtime dependencies&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(orchestration): define state and event contracts ← 状态/事件契约先行&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(orchestration): add provider-aware model gateway&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(orchestration): evolve speaking plan to ordered task items&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(orchestration): route explicit group directives&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(orchestration): add reusable Daoist graph&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(orchestration): add hybrid group director&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(orchestration): add resumable conversation graph&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(orchestration): expose internal streaming API&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;feat(chat): persist orchestration runs idempotently&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;节奏是契约先行：状态、事件、模型网关先于任何图节点落地；图是一层一层长出来的（道人图 → 确定性路由 → 混合导演 → 可恢复）。整个编排包最终约 1900 行 Python，14 个文件。&lt;/p&gt;&#10;&lt;p&gt;验收有一条硬场景，就是开头那条报数：四个成员各回自己的序号，人设只能加语气，不能改数、不能漏人、不能加戏。这条过不了，迁移就不算完。&lt;/p&gt;&#10;&lt;h2 id="复盘"&gt;复盘&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;分层不是按语言划的，是按职责性质划的。数据是稳定的，编排是高频变化的。把变化最快的职责放进迭代最快的生态：LangGraph 的图结构、checkpointer、子图、结构化输出校验都是现成的，Go 里全要手写。&lt;/li&gt;&#10;&lt;li&gt;图的节点边界就是恢复边界。LangGraph 的检查点落在节点边界，所以节点怎么切，决定能从哪里恢复。想要发言人粒度的恢复，就得把调度切成发言人粒度的节点。&lt;/li&gt;&#10;&lt;li&gt;确定性与概率性分离，是 Agent 系统的地基。能写成代码的绝不调模型；需要模型的环节（Supervisor）只出可校验的结构化计划，失败可回退。开头那个报数 bug，本质就是没有这层分离。&lt;/li&gt;&#10;&lt;li&gt;契约先行让两端各自演进。11 个类型化事件 + run_id 幂等 + 快照式输入，Go 和 Python 之间只有契约没有纠缠；Go 仍然是数据的事实源，Python 在快照上做编排，记忆用提案-校验的方式回流。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这次重构真正的收益在后面：工具调用、人工审批中断（&lt;code&gt;permission_required&lt;/code&gt; 事件已在契约里预留）、长任务规划、更复杂的多 Agent 协作，这些以后都是&amp;quot;在图里加节点&amp;quot;，不再需要动 Go。架构分层的意义不是当下的优雅，是把未来的变化留在了便宜的地方。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://www.flickr.com/photos/Creativity103/5227842611" target="_blank" rel="noopener"&#10; &gt;Creativity103 / Flickr&lt;/a&gt; · CC BY 2.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>Claude Code 的 Workflow 功能：6 任务分 4 波，多 Agent 并行开发三端功能</title><link>https://yusanwen-code.github.io/posts/post-73/</link><pubDate>Thu, 27 Aug 2026 22:30:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-73/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-73-cover.jpg" alt="Featured image of post Claude Code 的 Workflow 功能：6 任务分 4 波，多 Agent 并行开发三端功能" /&gt;&lt;h2 id="一个-141-行的脚本指挥一群-ai-干活"&gt;一个 141 行的脚本，指挥一群 AI 干活&#10;&lt;/h2&gt;&lt;p&gt;炼丹炉（&lt;a class="link" href="https://github.com/yusanwen-code/alchemy-furnace" target="_blank" rel="noopener"&#10; &gt;Alchemy Furnace&lt;/a&gt;，我的开源项目）最近在做一个大功能：女娲蒸馏的范围收紧 + Skill 导出。它横跨三个端（Next.js 前端、Go 网关、Python 引擎），拆出来 6 个开发任务，有依赖、有先后、还要并行。&lt;/p&gt;&#10;&lt;p&gt;我用的方案是 Claude Code 的 Workflow 功能：写一个 141 行的 mjs 脚本，把 6 个任务分成 4 个波次，每波派多个 AI Agent 并行干活。脚本开头长这样：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;export&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;const&lt;/span&gt; meta &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;nuwa-scope-skill-export&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; description&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;按计划实现女娲炼丹范围收紧 + Skill 导出（6 Task 分波并行）&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; phases&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { title&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Wave A: 并行底座&amp;#39;&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Task1 前端入口 + Task2 Python 链路 + Task3 Skill 渲染器&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { title&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Wave B: 服务端接合&amp;#39;&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Task2b Go/前端错误透传 + Task4 导出接口&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { title&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Wave C: 前端导出 UI&amp;#39;&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Task5 详情导出对话框&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { title&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Wave D: 端到端验收&amp;#39;&lt;/span&gt;, detail&lt;span style="color:#ff7b72;font-weight:bold"&gt;:&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#39;Task6 全量回归与修复&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Workflow 的编排原语就几个：&lt;code&gt;agent()&lt;/code&gt; 派一个 Agent 做一件事，&lt;code&gt;parallel()&lt;/code&gt; 并行跑一组，&lt;code&gt;pipeline()&lt;/code&gt; 流水线，&lt;code&gt;phase()&lt;/code&gt; 分阶段。流程是确定性的（脚本写死的），干活是并行的（agent 同时跑）：确定性编排 + 并行吞吐，这就是 Workflow 和&amp;quot;让 AI 自由发挥&amp;quot;最大的区别。&lt;/p&gt;&#10;&lt;h2 id="分波设计依赖关系决定顺序"&gt;分波设计：依赖关系决定顺序&#10;&lt;/h2&gt;&lt;p&gt;6 个任务不是一把梭全并行，它们之间有依赖。分波的核心原则：同一波内的任务互不依赖且文件隔离，波与波之间靠产物衔接。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Claude Code Workflow：6 Task × 4 波次并行编排" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-73-waves.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;Wave A 三个任务互不依赖：前端改入口、Python 改链路、Python 写渲染器，改动文件完全不重叠，并行跑。Wave B 接合服务端：错误透传要读 Task2 的产物（Python 结构化错误），导出接口要调 Task3 的渲染器，等 A 波完成再动。C、D 依此类推。&lt;/p&gt;&#10;&lt;h2 id="多-agent-协调的六个细节"&gt;多 Agent 协调的六个细节&#10;&lt;/h2&gt;&lt;p&gt;分波只是骨架，真正让 6 个 Agent 不打架的是这些细节：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;COMMON 纪律注入。每个 Agent 的 prompt 都是 &lt;code&gt;COMMON + TASK + 终止条件&lt;/code&gt; 三段拼起来的。COMMON 里写死硬约束：TDD 先红后绿、只改自己任务列出的文件、提交格式 &lt;code&gt;type(scope): 中文描述&lt;/code&gt;、绝不 &lt;code&gt;git add -A&lt;/code&gt;、绝不提交 docs/superpowers/ 和 specs/ 等元文档、i18n 必须中英双语、测试命令、凭据保密。纪律写进 prompt 而不是靠自觉，6 个 Agent 才不会互相污染。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;文件隔离 + 以磁盘为准。每个任务只允许碰自己的文件，但并发任务可能改到同一文件，所以约束里有一条：&amp;ldquo;编辑前先 Read 磁盘最新内容，不要基于记忆&amp;rdquo;。并行和踩脚之间的平衡，靠这条纪律兜底。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;依赖确认靠 git log。Wave B/C 的任务 prompt 里写着：&amp;ldquo;先 git log 确认依赖任务已完成，并读它的接口签名再动手&amp;rdquo;。Agent 之间不通信，通过提交记录对齐。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;终止条件。每个任务都附一段 A3：文件不存在或结构差异巨大、关键依赖无法确认、测试连续 3 次修复失败，立即停止并报告，不许硬撑。这防止 Agent 在不确定的情况下编造&amp;quot;成功&amp;quot;。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;TDD 是硬约束。每个任务第一步都是&amp;quot;写失败测试 → 跑红 → 最小实现 → 跑绿&amp;quot;。6 个 Agent 并行写代码，质量一致性靠测试框架兜底。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;进度可视化。每波完成打一条 &lt;code&gt;log()&lt;/code&gt;：&lt;code&gt;task1=ok task2=FAILED&lt;/code&gt;，失败的任务肉眼可见，可以在下一波前人工干预。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="三端是怎么被协调的"&gt;三端是怎么被协调的&#10;&lt;/h2&gt;&lt;p&gt;这个功能本身横跨三端，Workflow 的波次刚好和端的分工对齐：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;端&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;技术栈&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;对应的任务&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;前端&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Next.js&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Task1 收紧女娲入口、Task5 导出对话框&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Go 网关&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Gin + GORM&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Task2b 错误透传、Task4 导出接口&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Python 引擎&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FastAPI&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Task2 蒸馏链路、Task3 Skill 渲染器&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;有意思的是 Python 端同时有 Task2 和 Task3 两个并行 Agent：它们文件不同（链路 vs 渲染器）所以不冲突，但同端并行也要求 prompt 里把边界写得特别清楚：&amp;ldquo;不要碰 backend/go/ 与 frontend/ 的 UI 文件——那些由另一任务负责&amp;rdquo;。&lt;/p&gt;&#10;&lt;h2 id="复盘workflow-教给我的"&gt;复盘：Workflow 教给我的&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;复杂功能先拆波再并行，依赖决定顺序。先画出任务依赖图，互不依赖的并行，有依赖的排波次。比&amp;quot;一个 Agent 从头做到尾&amp;quot;快得多，比&amp;quot;全并行&amp;quot;稳得多。&lt;/li&gt;&#10;&lt;li&gt;纪律要写进 prompt，不是靠提醒。提交纪律、文件边界、TDD、终止条件，每一条都防止一种 Agent 翻车方式。&lt;/li&gt;&#10;&lt;li&gt;终止条件比任务本身重要。允许 Agent 说&amp;quot;我卡住了&amp;quot;，比让它硬编一个成功报告安全一个量级。&lt;/li&gt;&#10;&lt;li&gt;上下文隔离是并行的前提。每个 Agent 只拿自己的任务片段，看不到其他任务的 prompt：专注 + 不越界。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这个脚本不是银弹：它适合任务边界清晰、文件可隔离、有测试兜底的场景。如果任务互相纠缠，先拆任务再谈并行。项目、计划文档和完整脚本都在 &lt;a class="link" href="https://github.com/yusanwen-code/alchemy-furnace" target="_blank" rel="noopener"&#10; &gt;GitHub&lt;/a&gt; 上，Wave A 的并行底座写法可以直接抄。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://www.flickr.com/photos/richard_clyborne/32874464137" target="_blank" rel="noopener"&#10; &gt;richard_clyborne / Flickr&lt;/a&gt; · CC BY 2.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>智能体工作流编排：从知识库问答到多步任务</title><link>https://yusanwen-code.github.io/posts/post-65/</link><pubDate>Tue, 02 Jun 2026 10:30:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-65/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-65-cover.jpg" alt="Featured image of post 智能体工作流编排：从知识库问答到多步任务" /&gt;&lt;h2 id="单轮-rag-接不住了"&gt;单轮 RAG 接不住了&#10;&lt;/h2&gt;&lt;p&gt;知识库问答服务上线初期只有一条链路：用户提问 → 检索 → 拼 Prompt → 调 LLM → 返回答案。简单可控，但很快被顶到了天花板。&lt;/p&gt;&#10;&lt;p&gt;有人问&amp;quot;帮我查一下上个月引用量最高的三篇文献，对比它们的方法，再导出成 Word&amp;quot;。这一句话里藏着检索、排序、对比分析、文档生成四步，单轮 RAG 根本接不住。&lt;/p&gt;&#10;&lt;p&gt;我当时的判断是两头都不能走极端：全交给 ReAct 让模型自由发挥，可控性和成本都扛不住；全硬编码成固定 DAG，又失去了灵活性。最后落在一个折中上：工作流骨架 + Agent 节点。&lt;/p&gt;&#10;&lt;h2 id="一张图两类节点"&gt;一张图，两类节点&#10;&lt;/h2&gt;&lt;p&gt;我把任务拆成两类节点：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;确定性节点：检索、SQL 查询、文件解析、导出，这些用代码写死，输入输出明确&lt;/li&gt;&#10;&lt;li&gt;Agent 节点：需要推理、选择、总结的环节，交给 LLM 决定下一步&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;节点之间用有向图描述，允许条件分支和循环，运行时由一个轻量编排器驱动。每一步的状态写进统一的 &lt;code&gt;WorkflowState&lt;/code&gt;，节点之间不直接耦合。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="工作流骨架 + Agent 节点的混合编排" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-65-agent-workflow.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;核心结构就这么多：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;18&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;19&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;20&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;21&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;22&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;23&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;24&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;25&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;26&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;27&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;28&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;29&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;30&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;31&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;32&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;33&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;34&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;35&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;36&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;37&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;38&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;39&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;40&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;41&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;42&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;43&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;44&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;45&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;WorkflowState&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Query&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Documents&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]Document&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Draft&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Artifact&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Trace&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]StepRecord&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;mu&lt;span style="color:#6e7681"&gt; &lt;/span&gt;sync.Mutex&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Node&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;interface&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Name&lt;/span&gt;()&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Run&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;st&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;WorkflowState)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Orchestrator&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;nodes&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;map&lt;/span&gt;[&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;]Node&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;edges&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;map&lt;/span&gt;[&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;][]Edge&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// from -&amp;gt; edges&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Edge&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;To&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Cond&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;(st&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;WorkflowState)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;bool&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// nil 表示无条件&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(o&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;Orchestrator)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Run&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;start&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;st&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;WorkflowState)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;{start}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;visited&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;map&lt;/span&gt;[&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;]&lt;span style="color:#ff7b72"&gt;int&lt;/span&gt;{}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;for&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;len(queue)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&amp;gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;name&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue[&lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;]&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue[&lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;:]&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;visited[name]&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;5&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 防止死循环&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;fmt.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;node %s exceeded max retries&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;name)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;visited[name]&lt;span style="color:#ff7b72;font-weight:bold"&gt;++&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;o.nodes[name].&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Run&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;st);&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;for&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;_,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;e&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;range&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;o.edges[name]&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;e.Cond&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;||&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;e.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Cond&lt;/span&gt;(st)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;queue&lt;span style="color:#6e7681"&gt; &lt;/span&gt;=&lt;span style="color:#6e7681"&gt; &lt;/span&gt;append(queue,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;e.To)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Agent 节点内部走 ReAct 循环：模型输出思考 + 工具调用，执行工具后把结果喂回去，直到给出最终答案或触发步数上限。工具调用走 MCP，上一篇讲过。拿&amp;quot;查文献→对比→导出&amp;quot;来说，检索和导出是确定性节点，中间的对比分析交给 Agent 节点。&lt;/p&gt;&#10;&lt;h2 id="四个坑"&gt;四个坑&#10;&lt;/h2&gt;&lt;p&gt;第一个坑，state 越攒越肥。一开始我们把所有中间结果都塞进 &lt;code&gt;WorkflowState&lt;/code&gt;，跑到后面堆满文档全文和历史消息，token 直接爆炸。后来立了规矩：节点只输出下一阶段需要的最小字段，长文本进对象存储，state 里只留 ID 和摘要。&lt;/p&gt;&#10;&lt;p&gt;第二个坑，循环没有刹车。LLM 偶尔会陷入&amp;quot;反复检索但不给出答案&amp;quot;的死循环。现在每个节点有最大重试次数，整图有总步数和总 token 预算，超限直接中断，返回当前最优结果，而不是无限烧钱。&lt;/p&gt;&#10;&lt;p&gt;第三个坑，没有 trace 就是瞎子。多步工作流出问题，不查 trace 根本说不清哪一步偏了。我们在每个节点进入/退出时写结构化日志（节点名、输入摘要、输出摘要、耗时、token 数），用 trace_id 串起来，在 Jaeger 里能看完整的节点瀑布图。代码里的 &lt;code&gt;StepRecord&lt;/code&gt; 就是干这个的。&lt;/p&gt;&#10;&lt;p&gt;第四个不算坑，算一条划边界的原则：流程稳定、容错要求高的（支付、对账、数据同步）用确定性 DAG，甚至上 Temporal；探索性、开放式的任务（调研、写作、分析）用 Agent 节点。两者可以混在一张图里，但关键路径上的不可恢复操作，别让 Agent 碰。&lt;/p&gt;&#10;&lt;h2 id="后来"&gt;后来&#10;&lt;/h2&gt;&lt;p&gt;回头看，从单轮 RAG 走到多步工作流，是把&amp;quot;模型一次想清楚&amp;quot;换成了&amp;quot;系统分步兜底&amp;quot;：确定性节点负责可靠，Agent 节点负责灵活，编排器把两者粘起来，顺便守住成本和循环上限。&lt;/p&gt;&#10;&lt;p&gt;下一步我们在试把常用工作流做成模板，让业务方自己拖拽配置，不用每次都找后端写代码。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://www.flickr.com/photos/39415781@N06/7522618254" target="_blank" rel="noopener"&#10; &gt;ell brown / Flickr&lt;/a&gt; · CC BY 2.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>合成提示词缓存：性格变化时的自动重建策略</title><link>https://yusanwen-code.github.io/posts/post-61/</link><pubDate>Tue, 31 Mar 2026 10:30:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-61/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-61-cover.jpg" alt="Featured image of post 合成提示词缓存：性格变化时的自动重建策略" /&gt;&lt;h2 id="每次对话都重新炼丹太贵了"&gt;每次对话都重新炼丹，太贵了&#10;&lt;/h2&gt;&lt;p&gt;alchemy-furnace 里，每个分身（Agent）对话都要用它的合成 system prompt。这份 prompt 不是写死的，是炼出来的：跑一遍融合算子，先让 LLM 生成新的身份句，再让另一次 LLM 调用裁决冲突。也就是说，光&amp;quot;准备 prompt&amp;quot;这一步就是两次 LLM 调用，好几秒，几千 token。&lt;/p&gt;&#10;&lt;p&gt;而分身的人格在两次金丹更新之间是稳定的。每次对话都重算一遍，钱和延迟都花在重复劳动上。&lt;/p&gt;&#10;&lt;p&gt;那就加缓存。可缓存立刻带来那个经典难题：金丹会改，融合参数会调，算子会升级，缓存什么时候失效？失效太激进，等于没缓存；失效不及时，就出灵异 bug：我明明改了人设，Agent 还是老样子。&lt;/p&gt;&#10;&lt;p&gt;这篇讲我怎么处理这个矛盾。&lt;/p&gt;&#10;&lt;h2 id="让内容自己决定缓存"&gt;让内容自己决定缓存&#10;&lt;/h2&gt;&lt;p&gt;先说结论：失效判断不靠主动删除，靠内容寻址。&lt;/p&gt;&#10;&lt;p&gt;缓存的 value 是渲染好的合成 system prompt，外加工具定义和元数据。key 的主体是上一篇讲的 &lt;code&gt;lineage_hash&lt;/code&gt;：它由父母金丹的 &lt;code&gt;content_hash&lt;/code&gt;、算子名、算子版本、融合参数共同决定。任何一项变了，hash 就变，自然落到新 key 上，旧缓存根本没机会被错误命中。&lt;/p&gt;&#10;&lt;p&gt;key 长这样：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;furnace:synth:{lineage_hash}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;value 是一个 JSON：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;7&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;system_prompt&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;渲染后的完整 prompt&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;tools&amp;#34;&lt;/span&gt;: [&lt;span style="color:#a5d6ff"&gt;&amp;#34;mcp.search&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;&amp;#34;mcp.calc&amp;#34;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;lineage_hash&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;9f8e...&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;built_at&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;2026-03-30T10:12:33+08:00&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;op_version&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;v2&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;缓存放 Redis，TTL 设 7 天。但因为 key 随内容走，实际很少等到 TTL：内容不变就一直命中，内容一变就写新 key，旧 key 靠 TTL 自然回收，不用写任何删除逻辑。&lt;/p&gt;&#10;&lt;p&gt;这里有个前提，也是整个设计里我最满意的一步：金丹更新是 versioned 的，不是原地改。一个分身引用的是 &lt;code&gt;(elixir_id, version, content_hash)&lt;/code&gt;，金丹作者改人设，生成的是新版本，分身的 lineage_hash 随之变化，下次对话自动触发重建。&amp;ldquo;原地更新导致缓存判断复杂&amp;quot;这类问题，从根上就不存在了。&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="合成提示词缓存：lineage_hash 命中与重建链路" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-61-prompt-cache.svg"&gt;&lt;/p&gt;&#10;&lt;h2 id="网关先查引擎兜底"&gt;网关先查，引擎兜底&#10;&lt;/h2&gt;&lt;p&gt;Go 网关在对话入口先查缓存，未命中才请求 Python 引擎：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;18&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;19&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;20&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;21&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;22&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;23&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;24&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;25&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;26&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;27&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;28&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;29&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;30&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;31&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;32&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;33&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;34&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;35&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;36&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;37&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;38&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;39&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(s&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;AgentService)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;BuildSystemPrompt&lt;/span&gt;(&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;context.Context,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;agent&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;Agent,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;error&lt;/span&gt;)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;lineageHash&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;agent.LineageHash&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;key&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;furnace:synth:&amp;#34;&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;lineageHash&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 1. 查 Redis&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;cached,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;s.rdb.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Get&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;key).&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Result&lt;/span&gt;();&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;var&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c&lt;span style="color:#6e7681"&gt; &lt;/span&gt;PromptCache&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;json.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Unmarshal&lt;/span&gt;([]byte(cached),&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;c)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;metrics.CacheHit.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Inc&lt;/span&gt;()&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.SystemPrompt,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.Tools,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 2. 未命中，加载金丹，请求 Python 引擎合成&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;elixirs,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;s.elixirRepo.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;LoadByAgent&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;agent.ID)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;result,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;s.furnaceCli.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Synthesize&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;SynthesizeReq{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Elixirs:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;elixirs,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Operator:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;agent.Operator,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Params:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;agent.FusionParams,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;OpVer:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;OP_VERSION,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&amp;#34;&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 3. 写缓存（SetNX 防并发重复写）&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;payload,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;_&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;json.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Marshal&lt;/span&gt;(PromptCache{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;SystemPrompt:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;result.SystemPrompt,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Tools:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;result.Tools,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;BuiltAt:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;time.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Now&lt;/span&gt;(),&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;s.rdb.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;SetNX&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;key,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;payload,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;7&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;24&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;time.Hour)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;result.SystemPrompt,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;result.Tools,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;Python 引擎侧，&lt;code&gt;Synthesize&lt;/code&gt; 内部也有一层针对子调用的 LLM 结果缓存（比如身份句生成），key 更细：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;async&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;synthesize_identity&lt;/span&gt;(a: dict, b: dict) &lt;span style="color:#ff7b72;font-weight:bold"&gt;-&amp;gt;&lt;/span&gt; str:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#79c0ff"&gt;f&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;furnace:id:&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;a[&lt;span style="color:#a5d6ff"&gt;&amp;#39;content_hash&amp;#39;&lt;/span&gt;]&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;:&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;b[&lt;span style="color:#a5d6ff"&gt;&amp;#39;content_hash&amp;#39;&lt;/span&gt;]&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;:&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;OP_VERSION&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; cached &lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; redis&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(key):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; cached&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prompt &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; IDENTITY_META_PROMPT&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;format(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; identity_a&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;a[&lt;span style="color:#a5d6ff"&gt;&amp;#34;identity&amp;#34;&lt;/span&gt;], identity_b&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;b[&lt;span style="color:#a5d6ff"&gt;&amp;#34;identity&amp;#34;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; expertise_a&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;a[&lt;span style="color:#a5d6ff"&gt;&amp;#34;expertise&amp;#34;&lt;/span&gt;], expertise_b&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;b[&lt;span style="color:#a5d6ff"&gt;&amp;#34;expertise&amp;#34;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; llm_client&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;complete(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; provider&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;deepseek&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; system&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;FURNACE_META_PROMPT,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;prompt,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; temperature&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;0.4&lt;/span&gt;, &lt;span style="color:#8b949e;font-style:italic"&gt;# 身份句用低温度求稳定&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; redis&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;set(key, result, ex&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;7&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;24&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;3600&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; result&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;身份句的 temperature 压到了 0.4，比融合的 0.7 低一截。因为它是命名、概括类的任务，低温度结果更稳，缓存命中也更稳：同一对输入反复炼出同一句话，缓存才有意义。&lt;/p&gt;&#10;&lt;h2 id="踩到的坑"&gt;踩到的坑&#10;&lt;/h2&gt;&lt;p&gt;第一个是缓存击穿。热门分身的缓存恰好过期那一瞬，大量请求同时未命中，全打到 Python 引擎和 LLM 上。用 &lt;code&gt;singleflight&lt;/code&gt;（Go 侧）加 Redis &lt;code&gt;SetNX&lt;/code&gt; 做双保险：同一时刻、同一个 lineage_hash，只放一个请求真正去合成，其余等结果。这跟我在统一认证中心里做 JWT 刷新防雪崩是同一个套路。&lt;/p&gt;&#10;&lt;p&gt;第二个是算子升级。&lt;code&gt;OP_VERSION&lt;/code&gt; 是缓存 key 的一部分，改了算子逻辑（换渲染模板、加字段）就必须递增版本号，否则旧 prompt 会被错误复用。我把它做成引擎启动时的常量，改了算子忘改版本，code review 一眼能看出来。也考虑过对算子代码本身算 hash，但那会让无关的注释改动也触发全量失效，权衡之后用了显式版本号。&lt;/p&gt;&#10;&lt;p&gt;第三个是金丹软删除。作者删了金丹，可分身还在引用它。我不做物理删除，只标记 &lt;code&gt;deleted_at&lt;/code&gt;；加载时发现金丹被删，直接报&amp;quot;分身依赖的金丹已下架&amp;rdquo;，而不是拿残缺的金丹重新合成，那样产出的人格跟缓存里的完全对不上，用户只会觉得莫名其妙。&lt;/p&gt;&#10;&lt;p&gt;第四个是要不要预热。加了个简单策略：分身创建或更新后，网关异步发一个预热请求，把 prompt 先算好写进缓存，用户第一次对话就不卡顿。预热失败不阻塞主流程，对话时按需重建就是了。&lt;/p&gt;&#10;&lt;p&gt;第五个是演示模式。DEMO_MODE 下数据全在内存，缓存也退化成内存 map，进程重启就清空。听起来像 bug，其实是期望行为：演示环境本来就不需要持久化。&lt;/p&gt;&#10;&lt;h2 id="后来"&gt;后来&#10;&lt;/h2&gt;&lt;p&gt;回头看，这套缓存做的事情就一件：把&amp;quot;失效判断&amp;quot;从主动删除变成内容寻址。金丹版本化、算子版本化、参数入 hash，内容一变 key 就变，旧值自然淘汰；配合 singleflight 防击穿、低温度稳定子任务、异步预热，&amp;ldquo;每次对话都炼丹&amp;quot;就变成了&amp;quot;只有人格真正变化时才炼丹&amp;rdquo;。&lt;/p&gt;&#10;&lt;p&gt;对用户的体感差异很直接：分身对话的首 token 延迟，基本退化成一次普通 LLM 调用，而不是一次完整的融合流程。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://www.flickr.com/photos/26354629@N02/4012739993" target="_blank" rel="noopener"&#10; &gt;alexkerhead / Flickr&lt;/a&gt; · CC BY 2.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>多金丹融合：Promptbreeder 风格的变异算子与血统追溯</title><link>https://yusanwen-code.github.io/posts/post-60/</link><pubDate>Mon, 16 Mar 2026 10:30:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-60/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-60-cover.jpg" alt="Featured image of post 多金丹融合：Promptbreeder 风格的变异算子与血统追溯" /&gt;&lt;h2 id="请综合一下是个坏主意"&gt;「请综合一下」是个坏主意&#10;&lt;/h2&gt;&lt;p&gt;单个金丹是静态人格，但炼丹炉真正有意思的地方在&amp;quot;炼&amp;quot;：把多个金丹扔进炉子，产出一个兼具各方特质的新人格。我最初的实现很朴素，把几个金丹渲染成 prompt 拼在一起，让 LLM &amp;ldquo;综合一下&amp;rdquo;。结果非常糟糕：模型倾向于简单拼接，&amp;ldquo;你既是 A 又是 B 又是 C&amp;rdquo;，输出人格左右横跳；每次结果都不一样，没法复现，也说不清新人格到底继承了谁的什么特质。&lt;/p&gt;&#10;&lt;p&gt;我需要的是一套有结构的融合算子，而不是一句&amp;quot;请综合一下&amp;quot;。Promptbreeder 用进化算法搜索 prompt 的思路给了我启发：把融合看作一代进化，用 crossover 交换特质、用 mutation 产生变异，并且给每个产物记录血统（lineage）。&lt;/p&gt;&#10;&lt;h2 id="三类算子"&gt;三类算子&#10;&lt;/h2&gt;&lt;p&gt;我实现了三类核心算子：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Crossover（交叉）：取两个金丹，按字段维度交换、混合。语气维度（tone）取加权平均；原则（principles）按 priority 去重合并；示例（few_shots）各取若干；硬冲突字段（constraints）交给 LLM 裁决并记录理由。&lt;/li&gt;&#10;&lt;li&gt;Mutation（变异）：在一个金丹基础上做小幅扰动，强化某一维度的语气、替换一条 principle 的措辞、增删一条 constraint。变异幅度可控（&lt;code&gt;mutation_rate&lt;/code&gt;），用来在已有满意人格附近做探索。&lt;/li&gt;&#10;&lt;li&gt;Ensemble（集成）：不合成单一 prompt，而是生成一个&amp;quot;委员会&amp;quot;人格，遇到问题先判断该由哪种专长主导，再调用对应子人格的风格回答。这个算子对专长差异大的金丹组合更稳。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;img alt="三类融合算子与血统落账" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-60-fusion-operators.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;每个融合任务的输出除了 system prompt，还有一份 &lt;code&gt;lineage&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;agent_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;agt_xxx&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;operator&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;crossover&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;parents&amp;#34;&lt;/span&gt;: [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#7ee787"&gt;&amp;#34;elixir_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;101&lt;/span&gt;, &lt;span style="color:#7ee787"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;3&lt;/span&gt;, &lt;span style="color:#7ee787"&gt;&amp;#34;hash&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;a1b2...&amp;#34;&lt;/span&gt;},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#7ee787"&gt;&amp;#34;elixir_id&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;205&lt;/span&gt;, &lt;span style="color:#7ee787"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;, &lt;span style="color:#7ee787"&gt;&amp;#34;hash&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;c3d4...&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;resolution_notes&amp;#34;&lt;/span&gt;: [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#7ee787"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;max_length&amp;#34;&lt;/span&gt;, &lt;span style="color:#7ee787"&gt;&amp;#34;chose&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;来自金丹A，因 priority 更高&amp;#34;&lt;/span&gt;},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&lt;span style="color:#7ee787"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;humor&amp;#34;&lt;/span&gt;, &lt;span style="color:#7ee787"&gt;&amp;#34;chose&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;加权平均 0.35&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;params&amp;#34;&lt;/span&gt;: {&lt;span style="color:#7ee787"&gt;&amp;#34;temperature&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;0.7&lt;/span&gt;, &lt;span style="color:#7ee787"&gt;&amp;#34;provider&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;deepseek&amp;#34;&lt;/span&gt;, &lt;span style="color:#7ee787"&gt;&amp;#34;op_version&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;v2&amp;#34;&lt;/span&gt;},&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#7ee787"&gt;&amp;#34;lineage_hash&amp;#34;&lt;/span&gt;: &lt;span style="color:#a5d6ff"&gt;&amp;#34;9f8e...&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;code&gt;lineage_hash&lt;/code&gt; 是对 parents 的 hash、算子名、参数、算子版本做的 sha256，唯一标识这次&amp;quot;血统组合&amp;quot;。相同输入相同参数重跑会命中缓存（下一篇讲），也能用来判断两个分身是否同源。&lt;/p&gt;&#10;&lt;h2 id="crossover语气加权原则去重"&gt;crossover：语气加权，原则去重&#10;&lt;/h2&gt;&lt;p&gt;Crossover 算子（Python 引擎侧）的核心逻辑：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;18&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;19&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;20&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;21&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;22&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;23&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;24&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;25&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;26&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;27&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;28&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;29&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;30&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;31&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;32&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;33&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;34&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;35&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;36&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;37&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;38&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;39&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;random&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;,&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;statistics&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;crossover_operator&lt;/span&gt;(elixirs: list[dict], rate: float &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;0.5&lt;/span&gt;) &lt;span style="color:#ff7b72;font-weight:bold"&gt;-&amp;gt;&lt;/span&gt; str:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;assert&lt;/span&gt; len(elixirs) &lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; a, b &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; elixirs[&lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;], elixirs[&lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 语气：加权平均（priority 高的权重大）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wa &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; a&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;priority&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;100&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wb &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; b&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;priority&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;100&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; total &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; wa &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt; wb&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tone &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; {}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;for&lt;/span&gt; k &lt;span style="color:#ff7b72;font-weight:bold"&gt;in&lt;/span&gt; (&lt;span style="color:#a5d6ff"&gt;&amp;#34;formality&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;&amp;#34;conciseness&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;&amp;#34;humor&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;&amp;#34;empathy&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; va &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; a&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;tone&amp;#34;&lt;/span&gt;, {})&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(k, &lt;span style="color:#a5d6ff"&gt;0.5&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vb &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; b&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;tone&amp;#34;&lt;/span&gt;, {})&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(k, &lt;span style="color:#a5d6ff"&gt;0.5&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tone[k] &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; round((va &lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt; wa &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt; vb &lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt; wb) &lt;span style="color:#ff7b72;font-weight:bold"&gt;/&lt;/span&gt; total, &lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 原则：按 priority 排序去重合并&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; principles &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; dedup_merge(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; a&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;, []),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;, []),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 示例：各取一半&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; few_shots &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; (a&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;few_shots&amp;#34;&lt;/span&gt;, [])[:&lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;] &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;few_shots&amp;#34;&lt;/span&gt;, [])[:&lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 硬冲突：交给 LLM 裁决（这里简化为取 priority 高者）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; constraints, notes &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; resolve_constraints(a, b)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; child &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;identity&amp;#34;&lt;/span&gt;: synthesize_identity(a, b), &lt;span style="color:#8b949e;font-style:italic"&gt;# LLM 生成新身份句&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;expertise&amp;#34;&lt;/span&gt;: list({&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;a[&lt;span style="color:#a5d6ff"&gt;&amp;#34;expertise&amp;#34;&lt;/span&gt;], &lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;b[&lt;span style="color:#a5d6ff"&gt;&amp;#34;expertise&amp;#34;&lt;/span&gt;]}),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;tone&amp;#34;&lt;/span&gt;: tone,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;: principles,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;constraints&amp;#34;&lt;/span&gt;: constraints,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;few_shots&amp;#34;&lt;/span&gt;: few_shots,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;tools&amp;#34;&lt;/span&gt;: list({&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;a&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;tools&amp;#34;&lt;/span&gt;, []), &lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;b&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;tools&amp;#34;&lt;/span&gt;, [])}),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; render_elixir(child)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="mutation小步扰动"&gt;mutation：小步扰动&#10;&lt;/h2&gt;&lt;p&gt;Mutation 算子，只动一个金丹：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;mutate_operator&lt;/span&gt;(elixir: dict, rate: float &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;0.3&lt;/span&gt;) &lt;span style="color:#ff7b72;font-weight:bold"&gt;-&amp;gt;&lt;/span&gt; str:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; child &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; copy&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;deepcopy(elixir)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 随机扰动一个语气维度&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; random&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;random() &lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;lt;&lt;/span&gt; rate &lt;span style="color:#ff7b72;font-weight:bold"&gt;and&lt;/span&gt; child&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;tone&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; key &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; random&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;choice(list(child[&lt;span style="color:#a5d6ff"&gt;&amp;#34;tone&amp;#34;&lt;/span&gt;]&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;keys()))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; delta &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; random&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;uniform(&lt;span style="color:#ff7b72;font-weight:bold"&gt;-&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;0.2&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;0.2&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; child[&lt;span style="color:#a5d6ff"&gt;&amp;#34;tone&amp;#34;&lt;/span&gt;][key] &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; round(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min(&lt;span style="color:#a5d6ff"&gt;1.0&lt;/span&gt;, max(&lt;span style="color:#a5d6ff"&gt;0.0&lt;/span&gt;, child[&lt;span style="color:#a5d6ff"&gt;&amp;#34;tone&amp;#34;&lt;/span&gt;][key] &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt; delta)), &lt;span style="color:#a5d6ff"&gt;2&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 随机替换一条 principle 的措辞（交给 LLM 改写）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; random&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;random() &lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;lt;&lt;/span&gt; rate &lt;span style="color:#ff7b72;font-weight:bold"&gt;and&lt;/span&gt; child&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; idx &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; random&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;randrange(len(child[&lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;]))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; child[&lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;][idx] &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; llm_rewrite_principle(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; child[&lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;][idx])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; child[&lt;span style="color:#a5d6ff"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;] &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; child&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;) &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; render_elixir(child)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="血统在任务完成时落账"&gt;血统在任务完成时落账&#10;&lt;/h2&gt;&lt;p&gt;血统记录在任务完成时统一构建：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;build_lineage&lt;/span&gt;(elixirs, operator, params, notes):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parents &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; [{&lt;span style="color:#a5d6ff"&gt;&amp;#34;elixir_id&amp;#34;&lt;/span&gt;: e[&lt;span style="color:#a5d6ff"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;], &lt;span style="color:#a5d6ff"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: e[&lt;span style="color:#a5d6ff"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;hash&amp;#34;&lt;/span&gt;: e[&lt;span style="color:#a5d6ff"&gt;&amp;#34;content_hash&amp;#34;&lt;/span&gt;]} &lt;span style="color:#ff7b72"&gt;for&lt;/span&gt; e &lt;span style="color:#ff7b72;font-weight:bold"&gt;in&lt;/span&gt; elixirs]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;parents&amp;#34;&lt;/span&gt;: parents, &lt;span style="color:#a5d6ff"&gt;&amp;#34;operator&amp;#34;&lt;/span&gt;: operator,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;params&amp;#34;&lt;/span&gt;: params, &lt;span style="color:#a5d6ff"&gt;&amp;#34;op_version&amp;#34;&lt;/span&gt;: OP_VERSION,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lineage_hash &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; hashlib&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;sha256(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; json&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;dumps(payload, sort_keys&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;True&lt;/span&gt;)&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;encode())&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;hexdigest()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; {&lt;span style="color:#ff7b72;font-weight:bold"&gt;**&lt;/span&gt;payload, &lt;span style="color:#a5d6ff"&gt;&amp;#34;resolution_notes&amp;#34;&lt;/span&gt;: notes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;lineage_hash&amp;#34;&lt;/span&gt;: lineage_hash}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="四个坑"&gt;四个坑&#10;&lt;/h2&gt;&lt;p&gt;交叉不是简单拼接。第一版我直接把两个金丹的 principles 列表 concat，结果 prompt 里出现自相矛盾的两条原则。后来加上 &lt;code&gt;dedup_merge&lt;/code&gt;：先用 embedding 相似度去重（数据集管理服务里做向量化那套直接复用），再让 LLM 对剩余冲突逐条裁决。去重这一步很关键，两个&amp;quot;意思一样但措辞不同&amp;quot;的原则就靠它合并掉。&lt;/p&gt;&#10;&lt;p&gt;变异太大也会跑偏。&lt;code&gt;mutation_rate&lt;/code&gt; 设到 0.5 以上时，产物几乎认不出祖先。我默认用 0.2~0.3，每次变异只动一到两个字段，保住&amp;quot;可辨识的连续性&amp;quot;。这跟遗传算法里探索与利用的权衡是一回事。&lt;/p&gt;&#10;&lt;p&gt;Ensemble 有代价。委员会人格每次回答要先做一次路由判断（&amp;ldquo;这个问题该谁主导&amp;rdquo;），多一次 LLM 调用，延迟和成本都更高。所以它只在金丹专长差异度（expertise 的 Jaccard 距离）超过阈值时作为默认算子，否则用 crossover。&lt;/p&gt;&#10;&lt;p&gt;血统要防篡改。lineage 是用户判断&amp;quot;这个分身靠不靠谱&amp;quot;的依据，所以 &lt;code&gt;lineage_hash&lt;/code&gt; 连同产物一起落库，Go 网关读取时校验 hash，防止有人手动改 parents 冒充血统。&lt;/p&gt;&#10;&lt;h2 id="后来"&gt;后来&#10;&lt;/h2&gt;&lt;p&gt;多金丹融合的关键是用结构化算子控制组合过程，用血统记录保证可解释、可复现。有了这套机制，炼丹就从开盲盒变成了一个可以迭代、可以追溯的工程过程。下一篇讲怎么基于血统做合成提示词缓存。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://commons.wikimedia.org/w/index.php?curid=178341781" target="_blank" rel="noopener"&#10; &gt;Flocci Nivis / Wikimedia Commons&lt;/a&gt; · CC BY 4.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>结构化技能包：把 Agent 人格特质封装为可复用模块</title><link>https://yusanwen-code.github.io/posts/post-59/</link><pubDate>Sat, 28 Feb 2026 10:30:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-59/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-59-cover.jpg" alt="Featured image of post 结构化技能包：把 Agent 人格特质封装为可复用模块" /&gt;&lt;h2 id="十几份-prompt-改不动了"&gt;十几份 prompt 改不动了&#10;&lt;/h2&gt;&lt;p&gt;在知识库问答服务项目里，我们最初给企业客户定制 Agent，就是改一段 system prompt：开头写&amp;quot;你是一个 XX 助手&amp;quot;，中间堆语气要求，结尾补几条禁忌。维护起来非常痛苦：客户 A 要&amp;quot;学术严谨&amp;quot;，客户 B 要&amp;quot;活泼亲切&amp;quot;，两份 prompt 八成内容重复，改一处公共措辞要同步十几份。更糟的是，prompt 写长之后，模型对后半段指令的遵循度明显下降，&amp;ldquo;必须用 markdown 表格回答&amp;quot;和&amp;quot;回答控制在三句话&amp;quot;这种约束经常被忽略。&lt;/p&gt;&#10;&lt;p&gt;做 alchemy-furnace 时我决定从根上换个思路：不把人格写成一段散文，而是拆成结构化字段，每个字段职责明确，最终 prompt 由程序拼装。这就是金丹（Elixir）技能包。&lt;/p&gt;&#10;&lt;h2 id="人格拆成一组字段"&gt;人格拆成一组字段&#10;&lt;/h2&gt;&lt;p&gt;一个金丹是一份版本化的结构化文档，核心字段包括：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;identity&lt;/code&gt;：身份设定，一句话（&amp;ldquo;你是一位资深 Go 后端工程师&amp;rdquo;）。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;expertise&lt;/code&gt;：专长标签数组，用于检索和融合权重（&lt;code&gt;[&amp;quot;Go&amp;quot;,&amp;quot;分布式&amp;quot;,&amp;quot;RAG&amp;quot;]&lt;/code&gt;）。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;tone&lt;/code&gt;：语气维度的结构化打分（&lt;code&gt;formality: 0.8, conciseness: 0.6, humor: 0.2&lt;/code&gt;），而不是&amp;quot;既专业又亲切&amp;quot;这种模糊描述。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;principles&lt;/code&gt;：行为原则列表，每条是一句可执行指令（&amp;ldquo;回答前先判断问题属于哪一层：协议/框架/业务&amp;rdquo;）。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;constraints&lt;/code&gt;：硬约束（&lt;code&gt;max_length&lt;/code&gt;、&lt;code&gt;forbidden_topics&lt;/code&gt;、&lt;code&gt;must_use_markdown&lt;/code&gt;）。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;few_shots&lt;/code&gt;：示例对话数组，结构化存 input/output，而不是埋在正文里。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;tools&lt;/code&gt;：允许调用的工具白名单（对应 MCP / function calling）。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;priority&lt;/code&gt; 与 &lt;code&gt;conflicts_with&lt;/code&gt;：融合时的优先级和冲突声明。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;version&lt;/code&gt; 与 &lt;code&gt;content_hash&lt;/code&gt;：版本和内容指纹，用于缓存和血统。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这个结构的关键在于，它既是给 LLM 看的（可渲染成 prompt），也是给程序看的（可校验、可 diff、可融合）。渲染逻辑由引擎统一控制，用户只填字段，不直接写整段 prompt。&lt;/p&gt;&#10;&lt;h2 id="渲染归引擎用户只填字段"&gt;渲染归引擎，用户只填字段&#10;&lt;/h2&gt;&lt;p&gt;&lt;img alt="金丹技能包：结构化字段经渲染函数拼装成最终 prompt" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-59-elixir-render.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;Go 侧的结构体和 GORM 模型（简化版）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;18&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;19&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;20&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;21&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;22&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;23&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Elixir&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ID&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;int64&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;primaryKey;autoIncrement:false&amp;#34; json:&amp;#34;id&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// snowflake&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Name&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;size:128;not null&amp;#34; json:&amp;#34;name&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Version&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;int&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;not null;default:1&amp;#34; json:&amp;#34;version&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Identity&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;type:text&amp;#34; json:&amp;#34;identity&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Expertise&lt;span style="color:#6e7681"&gt; &lt;/span&gt;StringSlice&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;type:json&amp;#34; json:&amp;#34;expertise&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Tone&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ToneSpec&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;type:json&amp;#34; json:&amp;#34;tone&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Principles&lt;span style="color:#6e7681"&gt; &lt;/span&gt;StringSlice&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;type:json&amp;#34; json:&amp;#34;principles&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Constraints&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Constraints&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;type:json&amp;#34; json:&amp;#34;constraints&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;FewShots&lt;span style="color:#6e7681"&gt; &lt;/span&gt;[]FewShot&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;type:json&amp;#34; json:&amp;#34;few_shots&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Tools&lt;span style="color:#6e7681"&gt; &lt;/span&gt;StringSlice&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;type:json&amp;#34; json:&amp;#34;tools&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Priority&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;int&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;default:100&amp;#34; json:&amp;#34;priority&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ConflictsWith&lt;span style="color:#6e7681"&gt; &lt;/span&gt;StringSlice&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;type:json&amp;#34; json:&amp;#34;conflicts_with&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ContentHash&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;string&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`gorm:&amp;#34;size:64&amp;#34; json:&amp;#34;content_hash&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;CreatedAt&lt;span style="color:#6e7681"&gt; &lt;/span&gt;time.Time&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`json:&amp;#34;created_at&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ToneSpec&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Formality&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;float64&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`json:&amp;#34;formality&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 0~1&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Conciseness&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;float64&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`json:&amp;#34;conciseness&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Humor&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;float64&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`json:&amp;#34;humor&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Empathy&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;float64&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;`json:&amp;#34;empathy&amp;#34;`&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;渲染成 prompt 的函数放在 Python 引擎侧，因为这块会随模型表现频繁调：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;18&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;19&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;20&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;21&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;22&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;23&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;24&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;25&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;26&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;27&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;28&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;29&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;30&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;31&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;32&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;33&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;34&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;35&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;render_elixir&lt;/span&gt;(e: dict) &lt;span style="color:#ff7b72;font-weight:bold"&gt;-&amp;gt;&lt;/span&gt; str:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parts &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; [&lt;span style="color:#79c0ff"&gt;f&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;# 身份&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;e[&lt;span style="color:#a5d6ff"&gt;&amp;#39;identity&amp;#39;&lt;/span&gt;]&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; e&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;expertise&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parts&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;# 专长&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;、&amp;#34;&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;join(e[&lt;span style="color:#a5d6ff"&gt;&amp;#34;expertise&amp;#34;&lt;/span&gt;]))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tone &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; e&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;tone&amp;#34;&lt;/span&gt;, {})&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tone_line &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; []&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; tone&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;formality&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;) &lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;0.7&lt;/span&gt;: tone_line&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;措辞正式严谨&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; tone&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;conciseness&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;) &lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;0.7&lt;/span&gt;: tone_line&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;回答简洁，避免铺垫&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; tone&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;humor&amp;#34;&lt;/span&gt;, &lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;) &lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;0.5&lt;/span&gt;: tone_line&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;可适度幽默&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; tone_line:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parts&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;# 语气&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;；&amp;#34;&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;join(tone_line))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; e&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parts&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;# 行为原则&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;join(&lt;span style="color:#79c0ff"&gt;f&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;- &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;p&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;for&lt;/span&gt; p &lt;span style="color:#ff7b72;font-weight:bold"&gt;in&lt;/span&gt; e[&lt;span style="color:#a5d6ff"&gt;&amp;#34;principles&amp;#34;&lt;/span&gt;]))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; c &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; e&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;constraints&amp;#34;&lt;/span&gt;, {})&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; c:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cons &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; []&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; c&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;max_length&amp;#34;&lt;/span&gt;): cons&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#79c0ff"&gt;f&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;回答不超过 &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;c[&lt;span style="color:#a5d6ff"&gt;&amp;#39;max_length&amp;#39;&lt;/span&gt;]&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt; 字&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; c&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;must_use_markdown&amp;#34;&lt;/span&gt;): cons&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;使用 Markdown 排版&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;for&lt;/span&gt; t &lt;span style="color:#ff7b72;font-weight:bold"&gt;in&lt;/span&gt; c&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;forbidden_topics&amp;#34;&lt;/span&gt;, []):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cons&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#79c0ff"&gt;f&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;禁止讨论：&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;t&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; cons:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parts&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;# 硬性约束&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ff7b72;font-weight:bold"&gt;+&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;join(&lt;span style="color:#79c0ff"&gt;f&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;- &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;x&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;for&lt;/span&gt; x &lt;span style="color:#ff7b72;font-weight:bold"&gt;in&lt;/span&gt; cons))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; e&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;get(&lt;span style="color:#a5d6ff"&gt;&amp;#34;few_shots&amp;#34;&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; shots &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; [&lt;span style="color:#a5d6ff"&gt;&amp;#34;# 示例&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;for&lt;/span&gt; i, fs &lt;span style="color:#ff7b72;font-weight:bold"&gt;in&lt;/span&gt; enumerate(e[&lt;span style="color:#a5d6ff"&gt;&amp;#34;few_shots&amp;#34;&lt;/span&gt;], &lt;span style="color:#a5d6ff"&gt;1&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; shots&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#79c0ff"&gt;f&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;## 示例 &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;i&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;用户：&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;fs[&lt;span style="color:#a5d6ff"&gt;&amp;#39;input&amp;#39;&lt;/span&gt;]&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;助手：&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;{&lt;/span&gt;fs[&lt;span style="color:#a5d6ff"&gt;&amp;#39;output&amp;#39;&lt;/span&gt;]&lt;span style="color:#a5d6ff"&gt;}&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; parts&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;append(&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;join(shots))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#79c0ff"&gt;\n\n&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;join(parts)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;code&gt;content_hash&lt;/code&gt; 在入库时计算，用 &lt;code&gt;sha256&lt;/code&gt; 对规范化后的 JSON 取摘要。任何字段改动都会让 hash 变化，这是后面缓存失效和血统追溯的基础。&lt;/p&gt;&#10;&lt;h2 id="四个权衡"&gt;四个权衡&#10;&lt;/h2&gt;&lt;p&gt;字段过多会吓退用户。第一版我设计了二十多个字段，结果自己填都嫌烦。后来把字段分成必填（identity、expertise、principles）和可选（tone、few_shots、tools），前端编辑器用折叠面板：普通用户只看必填三项，高级用户再展开 tone 滑块和 few-shot 编辑器。&lt;/p&gt;&#10;&lt;p&gt;结构化会损失表现力。有些人格特质确实很难用字段表达，比如&amp;quot;那种老北京茶馆里提笼架鸟的松弛感&amp;rdquo;。我的折中是保留一个 &lt;code&gt;style_notes&lt;/code&gt; 自由文本字段，但明确标注它权重低于结构化字段、仅作补充，渲染时放在最后。让模型既吃结构化的硬指令，又留一点自由发挥的空间。&lt;/p&gt;&#10;&lt;p&gt;few-shot 存哪里。示例对话可能很长，全塞 MySQL 的 JSON 字段会让行膨胀。短示例直接存库；长附件（比如整段对话日志）存 S3，Elixir 里只留 &lt;code&gt;few_shot_refs&lt;/code&gt; 引用，渲染前由引擎批量拉取。这跟我在数据治理服务里做 S3 预签名上传是同一套思路。&lt;/p&gt;&#10;&lt;p&gt;版本与克隆。金丹一旦被某个分身引用就不可原地修改，改动要新建 version，旧分身继续引用旧版本。否则就会出现&amp;quot;我没改 prompt，怎么 Agent 性格变了&amp;quot;的灵异问题。&lt;/p&gt;&#10;&lt;h2 id="后来"&gt;后来&#10;&lt;/h2&gt;&lt;p&gt;把人格从一段大 prompt 重构成结构化技能包，等于把提示词工程拉回了软件工程：字段就是接口，渲染是内部实现，版本和 hash 负责可复现。复用、diff、融合、缓存这些在散文 prompt 上几乎做不了的事，到这里都变自然了。金丹这一层立住了，多金丹融合才有可靠的输入。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;封面图：&lt;a class="link" href="https://www.flickr.com/photos/8790226@N06/3494617614" target="_blank" rel="noopener"&#10; &gt;karen horton / Flickr&lt;/a&gt; · CC BY 2.0&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>炼丹炉 alchemy-furnace：多人格融合 Agent 系统设计</title><link>https://yusanwen-code.github.io/posts/post-57/</link><pubDate>Wed, 28 Jan 2026 10:30:00 +0800</pubDate><guid>https://yusanwen-code.github.io/posts/post-57/</guid><description>&lt;img src="https://yusanwen-code.github.io/images/post-57-cover.jpg" alt="Featured image of post 炼丹炉 alchemy-furnace：多人格融合 Agent 系统设计" /&gt;&lt;h2 id="在-system-prompt-里堆人设撑不住"&gt;在 system prompt 里堆人设，撑不住&#10;&lt;/h2&gt;&lt;p&gt;做知识库问答服务的时候我发现一个现象：用户并不满足于跟一个&amp;quot;通用助手&amp;quot;对话，他们想要更具体的东西，比如一个既懂学术写作、又会写 Go、还带点毒舌的混合体。直接的思路是在 system prompt 里塞几段人设。我试过，效果很不稳定：不同人格的指令会互相打架，模型经常只&amp;quot;记住&amp;quot;最后一段。&lt;/p&gt;&#10;&lt;p&gt;我想要的是一个能把多个人格像炼丹一样&amp;quot;熔&amp;quot;在一起的系统：每个人格是结构化的、可复用的；融合过程可追溯、可演化；最终产物本身也是一个能独立调用的 Agent。alchemy-furnace（炼丹炉）就是干这个的，开源在 &lt;code&gt;github.com/yusanwen-code/alchemy-furnace&lt;/code&gt;。&lt;/p&gt;&#10;&lt;h2 id="金丹丹炉分身"&gt;金丹、丹炉、分身&#10;&lt;/h2&gt;&lt;p&gt;整体分三层，领域对象直接用炼丹的隐喻命名：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;金丹（Elixir）：一个结构化技能包，封装一个人格的全部特质，身份、语气、专长、约束、示例对话、禁用词都在里面。它是融合的最小单位。&lt;/li&gt;&#10;&lt;li&gt;丹炉（Furnace）：融合引擎，接收一组金丹，按策略生成一个新的合成人格。合成过程借鉴 Promptbreeder 的变异算子，支持 crossover、mutation 和 lineage 追溯。&lt;/li&gt;&#10;&lt;li&gt;金丹分身（Agent）：融合产物，持有一份合成后的 system prompt 和一组工具定义，通过 OpenAI 兼容协议对外提供对话。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;img alt="金丹进炉，炼出新分身，血统全程落账" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://yusanwen-code.github.io/images/post-57-furnace-flow.svg"&gt;&lt;/p&gt;&#10;&lt;p&gt;技术栈我做了明确切分：Go（Gin + GORM）做网关和业务编排，Python（FastAPI）做合成与 LLM 调用，Next.js 做前端。为什么这么切，后面单独一篇讲。&lt;/p&gt;&#10;&lt;p&gt;数据模型上，金丹、融合任务、合成产物是独立实体。融合任务会记下 &lt;code&gt;parent_elixir_ids&lt;/code&gt;、&lt;code&gt;mutation_operators&lt;/code&gt;、&lt;code&gt;lineage_hash&lt;/code&gt;，任何一个合成人格都能回溯到它的&amp;quot;祖先&amp;quot;。&lt;/p&gt;&#10;&lt;p&gt;Go 网关侧的关键入口大致是这样：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;18&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;19&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;20&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;21&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;22&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;23&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;24&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;25&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;26&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;27&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;28&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;29&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;30&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;31&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;32&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;33&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;34&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;35&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;36&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// FusionController 触发一次炼丹&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;type&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;FusionController&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;struct&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;fusionSvc&lt;span style="color:#6e7681"&gt; &lt;/span&gt;FusionService&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;furnaceCli&lt;span style="color:#6e7681"&gt; &lt;/span&gt;FurnaceClient&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// Python 合成引擎&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;func&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;(c&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;FusionController)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Fuse&lt;/span&gt;(ctx&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;*&lt;/span&gt;gin.Context)&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;var&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;req&lt;span style="color:#6e7681"&gt; &lt;/span&gt;FusionRequest&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ctx.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;ShouldBindJSON&lt;/span&gt;(&lt;span style="color:#ff7b72;font-weight:bold"&gt;&amp;amp;&lt;/span&gt;req);&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ctx.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;JSON&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;400&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;gin.H{&lt;span style="color:#a5d6ff"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Error&lt;/span&gt;()})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 1. 加载金丹（技能包）&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;elixirs,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.fusionSvc.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;LoadElixirs&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;req.ElixirIDs)&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ctx.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;JSON&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;404&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;gin.H{&lt;span style="color:#a5d6ff"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;elixir not found&amp;#34;&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 2. 提交到 Python 丹炉&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;job,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;:=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;c.furnaceCli.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;Submit&lt;/span&gt;(ctx,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;FurnaceJob{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Elixirs:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;elixirs,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Strategy:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;req.Strategy,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// crossover / mutate / ensemble&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Provider:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;req.Provider,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// deepseek/qwen/zhipu/kimi/...&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;Temperature:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;req.Temperature,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;LineageParent:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;req.ParentAgentID,&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;if&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;err&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;!=&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#79c0ff"&gt;nil&lt;/span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;{&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ctx.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;JSON&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;502&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;gin.H{&lt;span style="color:#a5d6ff"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;furnace unreachable&amp;#34;&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#ff7b72"&gt;return&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#8b949e;font-style:italic"&gt;// 3. 异步落库，前端 SSE 订阅进度&lt;/span&gt;&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#6e7681"&gt; &lt;/span&gt;ctx.&lt;span style="color:#d2a8ff;font-weight:bold"&gt;JSON&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;202&lt;/span&gt;,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;gin.H{&lt;span style="color:#a5d6ff"&gt;&amp;#34;job_id&amp;#34;&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;job.ID,&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;:&lt;span style="color:#6e7681"&gt; &lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;queued&amp;#34;&lt;/span&gt;})&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#6e7681"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;h2 id="元指令让模型当设计师不当演员"&gt;元指令：让模型当设计师，不当演员&#10;&lt;/h2&gt;&lt;p&gt;Python 侧的合成引擎是核心。一个融合任务的骨架如下（FastAPI + 异步）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&#10;&lt;table style="border-spacing:0;padding:0;margin:0;border:0;"&gt;&lt;tr&gt;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 1&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 2&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 3&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 4&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 5&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 6&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 7&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 8&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt; 9&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;10&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;11&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;12&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;13&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;14&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;15&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;16&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;17&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;18&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;19&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;20&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;21&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;22&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;23&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;24&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;25&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;26&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;27&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;28&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;29&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;30&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;31&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;32&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;33&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;34&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;35&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;36&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;37&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;38&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;39&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;40&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;41&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;42&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;43&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;44&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;45&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;46&#10;&lt;/span&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#737679"&gt;47&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%"&gt;&#10;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;from&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;fastapi&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;import&lt;/span&gt; FastAPI, BackgroundTasks&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;from&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;pydantic&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;import&lt;/span&gt; BaseModel&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;import&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;asyncio&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;,&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;uuid&lt;/span&gt;&lt;span style="color:#ff7b72;font-weight:bold"&gt;,&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;hashlib&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; FastAPI()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;_jobs: dict[str, &lt;span style="color:#a5d6ff"&gt;&amp;#34;FusionJob&amp;#34;&lt;/span&gt;] &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; {}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;class&lt;/span&gt; &lt;span style="color:#f0883e;font-weight:bold"&gt;FurnaceJob&lt;/span&gt;(BaseModel):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; elixirs: list[dict]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; strategy: str&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; provider: str&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; temperature: float &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;0.8&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lineage_parent: str &lt;span style="color:#ff7b72;font-weight:bold"&gt;|&lt;/span&gt; &lt;span style="color:#79c0ff"&gt;None&lt;/span&gt; &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#79c0ff"&gt;None&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#d2a8ff;font-weight:bold"&gt;@app.post&lt;/span&gt;(&lt;span style="color:#a5d6ff"&gt;&amp;#34;/v1/furnace/fuse&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;async&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;submit_fusion&lt;/span&gt;(job: FurnaceJob, bg: BackgroundTasks):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; job_id &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; str(uuid&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;uuid4())&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _jobs[job_id] &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; FusionJob(id&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;job_id, status&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;&lt;span style="color:#a5d6ff"&gt;&amp;#34;queued&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; bg&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;add_task(run_fusion, job_id, job)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; {&lt;span style="color:#a5d6ff"&gt;&amp;#34;job_id&amp;#34;&lt;/span&gt;: job_id}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;async&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;def&lt;/span&gt; &lt;span style="color:#d2a8ff;font-weight:bold"&gt;run_fusion&lt;/span&gt;(job_id: str, job: FurnaceJob):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; _jobs[job_id]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;try&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;status &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;synthesizing&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;# 根据策略选择算子&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;if&lt;/span&gt; job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;strategy &lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;crossover&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prompt &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; crossover_operator(job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;elixirs)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;elif&lt;/span&gt; job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;strategy &lt;span style="color:#ff7b72;font-weight:bold"&gt;==&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;mutate&amp;#34;&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prompt &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; mutate_operator(job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;elixirs[&lt;span style="color:#a5d6ff"&gt;0&lt;/span&gt;])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;else&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prompt &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; ensemble_operator(job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;elixirs)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;status &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;calling_llm&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; synthesized &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; llm_client&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;complete(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; provider&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;provider,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; system&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;FURNACE_META_PROMPT,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; user&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;prompt,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; temperature&lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt;job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;temperature,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lineage &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; build_lineage(job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;elixirs, job&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;strategy, synthesized)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;result &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; {&lt;span style="color:#a5d6ff"&gt;&amp;#34;system_prompt&amp;#34;&lt;/span&gt;: synthesized, &lt;span style="color:#a5d6ff"&gt;&amp;#34;lineage&amp;#34;&lt;/span&gt;: lineage}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;status &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;done&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;except&lt;/span&gt; &lt;span style="color:#f0883e;font-weight:bold"&gt;Exception&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;as&lt;/span&gt; e:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;status &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; &lt;span style="color:#a5d6ff"&gt;&amp;#34;failed&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; state&lt;span style="color:#ff7b72;font-weight:bold"&gt;.&lt;/span&gt;error &lt;span style="color:#ff7b72;font-weight:bold"&gt;=&lt;/span&gt; str(e)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;FURNACE_META_PROMPT&lt;/code&gt; 是丹炉本身的元指令，它告诉 LLM：&amp;ldquo;你不是在扮演任何一个人格，你是在把这些人格特质融合成一个新的、内在一致的人格。&amp;ldquo;这段提示词是整个系统里最关键的一段。直接让模型&amp;quot;扮演混合体&amp;rdquo;，它会精神分裂；让它以人格设计师的第三人称视角去合成，输出要稳定得多。&lt;/p&gt;&#10;&lt;h2 id="三个坑"&gt;三个坑&#10;&lt;/h2&gt;&lt;p&gt;第一个坑是人格冲突。一个金丹要求&amp;quot;回答必须简短&amp;rdquo;，另一个要求&amp;quot;给出完整推导&amp;quot;，crossover 之后模型会左右横跳。后来我在金丹结构里加了 &lt;code&gt;priority&lt;/code&gt; 和 &lt;code&gt;conflicts_with&lt;/code&gt; 字段，融合前先做一次冲突检测：硬冲突直接拒绝并提示用户，软冲突交给 LLM 在合成阶段显式裁决，理由写进 &lt;code&gt;resolution_notes&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;第二个坑是融合结果不可复现。同一个组合跑两次，出来的人格可能差别很大。现在我把 &lt;code&gt;temperature&lt;/code&gt;、&lt;code&gt;provider&lt;/code&gt;、&lt;code&gt;model_version&lt;/code&gt;、算子版本、输入金丹的 &lt;code&gt;content_hash&lt;/code&gt; 全部记进 lineage，&lt;code&gt;lineage_hash = sha256(...)&lt;/code&gt;。要复现就用同样参数重跑；碰到满意的结果，更好的办法是把它固化成新的金丹，而不是每次重新融。&lt;/p&gt;&#10;&lt;p&gt;第三个算权衡：要不要把合成逻辑放进 Go。Go 调 LLM 完全可行，但 Promptbreeder 那一套算子迭代快、实验性强，Python 生态（pydantic、各类 prompt 工具、后续可能接 LangGraph）更顺手。所以最后是 Go 做稳定的业务网关，Python 做易变的智能层，两边走内网 HTTP + 签名调用。&lt;/p&gt;&#10;&lt;h2 id="后来"&gt;后来&#10;&lt;/h2&gt;&lt;p&gt;回过头看，炼丹炉解决的核心问题是：把&amp;quot;人设 prompt&amp;quot;从一段不可维护的文本，升级为可组合、可演化、可追溯的结构化资产。这套抽象跑通之后，技能包复用、多金丹融合、缓存重建才有了落脚的地方。后面几篇我会分别展开三段式架构、技能包结构、变异算子、缓存和多供应商接入。&lt;/p&gt;&#10;</description></item></channel></rss>