「请综合一下」是个坏主意
单个金丹是静态人格,但炼丹炉真正有意思的地方在"炼":把多个金丹扔进炉子,产出一个兼具各方特质的新人格。我最初的实现很朴素,把几个金丹渲染成 prompt 拼在一起,让 LLM “综合一下”。结果非常糟糕:模型倾向于简单拼接,“你既是 A 又是 B 又是 C”,输出人格左右横跳;每次结果都不一样,没法复现,也说不清新人格到底继承了谁的什么特质。
我需要的是一套有结构的融合算子,而不是一句"请综合一下"。Promptbreeder 用进化算法搜索 prompt 的思路给了我启发:把融合看作一代进化,用 crossover 交换特质、用 mutation 产生变异,并且给每个产物记录血统(lineage)。
三类算子
我实现了三类核心算子:
- Crossover(交叉):取两个金丹,按字段维度交换、混合。语气维度(tone)取加权平均;原则(principles)按 priority 去重合并;示例(few_shots)各取若干;硬冲突字段(constraints)交给 LLM 裁决并记录理由。
- Mutation(变异):在一个金丹基础上做小幅扰动,强化某一维度的语气、替换一条 principle 的措辞、增删一条 constraint。变异幅度可控(
mutation_rate),用来在已有满意人格附近做探索。 - Ensemble(集成):不合成单一 prompt,而是生成一个"委员会"人格,遇到问题先判断该由哪种专长主导,再调用对应子人格的风格回答。这个算子对专长差异大的金丹组合更稳。
每个融合任务的输出除了 system prompt,还有一份 lineage:
| |
lineage_hash 是对 parents 的 hash、算子名、参数、算子版本做的 sha256,唯一标识这次"血统组合"。相同输入相同参数重跑会命中缓存(下一篇讲),也能用来判断两个分身是否同源。
crossover:语气加权,原则去重
Crossover 算子(Python 引擎侧)的核心逻辑:
| |
mutation:小步扰动
Mutation 算子,只动一个金丹:
| |
血统在任务完成时落账
血统记录在任务完成时统一构建:
| |
四个坑
交叉不是简单拼接。第一版我直接把两个金丹的 principles 列表 concat,结果 prompt 里出现自相矛盾的两条原则。后来加上 dedup_merge:先用 embedding 相似度去重(数据集管理服务里做向量化那套直接复用),再让 LLM 对剩余冲突逐条裁决。去重这一步很关键,两个"意思一样但措辞不同"的原则就靠它合并掉。
变异太大也会跑偏。mutation_rate 设到 0.5 以上时,产物几乎认不出祖先。我默认用 0.2~0.3,每次变异只动一到两个字段,保住"可辨识的连续性"。这跟遗传算法里探索与利用的权衡是一回事。
Ensemble 有代价。委员会人格每次回答要先做一次路由判断(“这个问题该谁主导”),多一次 LLM 调用,延迟和成本都更高。所以它只在金丹专长差异度(expertise 的 Jaccard 距离)超过阈值时作为默认算子,否则用 crossover。
血统要防篡改。lineage 是用户判断"这个分身靠不靠谱"的依据,所以 lineage_hash 连同产物一起落库,Go 网关读取时校验 hash,防止有人手动改 parents 冒充血统。
后来
多金丹融合的关键是用结构化算子控制组合过程,用血统记录保证可解释、可复现。有了这套机制,炼丹就从开盲盒变成了一个可以迭代、可以追溯的工程过程。下一篇讲怎么基于血统做合成提示词缓存。
封面图:Flocci Nivis / Wikimedia Commons · CC BY 4.0
