跳到主要内容

为什么你的提示词库应该是 Monorepo,而不是 Cookbook

· 阅读需 13 分钟
Tian Pan
Software Engineer

我最近合作的一个团队有三个不同的“总结这份合同”提示词。一个存在于 Notion 页面中,法律科技小队将其复制粘贴到他们的服务里。一个存在于客户成功后端的 prompts/ 文件夹中,为了适应他们的语气偏好做了轻微修改。还有一个内联在数据团队 notebook 里的 Python 文件中,被硬编码在两个 f-string 插值之间。当 OpenAI 弃用了它们运行的所有模型时,迁移计划变成了一场 “Slack 考古” —— 必须追踪到每个所有者,重新评估每个变体,其中两个变体在生产环境中默默地出了一周的故障才被察觉。

这就是规模化后的提示词 Cookbook 的样子。对于十个提示词和一个团队来说,Cookbook 是合理的。但当提示词达到一百个、团队达到四个左右时,它们就会变得难以管理。当你运行一个 AI 组织时,你的 prompts/ 文件夹(装满 .md 文件)的表现就像 2008 年那种靠复制粘贴引入的第三方代码:每个消费者都有自己的快照,偏差(drift)是不可见的,而破坏性变更会以不可预测的方式向外扩散。

解决方法不是搞一个更花哨的文件夹结构,而是意识到提示词就是代码 —— 并应用我们已经为代码开发的工程规范:语义化版本控制、依赖图、跨包的原子级重构以及构建时验证。换句话说,你的提示词库应该是 Monorepo,而不是 Cookbook。

Cookbook 失败模式

大多数团队最开始直接将提示词写进应用代码。一个方法调用 client.messages.create(prompt=textwrap.dedent("...")),内联了几百个 Token。这在第二个团队需要同样的功能之前一直运作良好。随后,提示词被复制粘贴,修改开始分叉,几个月内,你就将相同的指令分叉成了四个不同的版本,没人能理得清。

下一个进化阶段是 prompts/ 文件夹。这感觉像是一种进步 —— 至少提示词有了个“归宿”。但一个装着 Markdown 文件的文件夹只是一个 Cookbook,而不是一个库。它没有“生产环境服务正在使用哪个版本的提示词”的概念。没有办法询问“谁在消费这个提示词,如果我修改它,什么会崩溃?”没有自动化检查来确保提示词在修改后依然有效。这个文件夹只是一个共享草稿本,其工程体验(ergonomics)甚至比它所取代的内联字符串更差。

一旦 Cookbook 超过五十个提示词,就会可靠地出现三种失败模式:

  • 静默偏差 (Silent drift):有人为了某个消费方的边缘情况修改了提示词,而另一个依赖于原始行为的消费方则默默地降级了。等到有人察觉时,提交历史已经叠加了三十多个变更。
  • 幽灵依赖 (Phantom dependencies):由于平台团队没人知道哪个服务仍在使用它,一个提示词被删除了。一周后,一个面向客户的流程开始返回空字符串,因为该服务启动时加载的文件消失了。
  • 迁移瘫痪 (Migration paralysis):当底层模型被弃用时,你无法推进,因为没人知道生产环境究竟依赖什么。每个提示词都必须手动追踪到其调用者,并根据未知的成功准则逐一重新评估。

这些都是典型的“引入代码 (vendored-code)”问题。在源代码世界中,我们通过构建依赖图、版本锁定(version pins)和原子重构工具解决了这些问题。提示词值得同样的待遇。

“Monorepo 规范”对提示词究竟意味着什么

把某些东西称为 Monorepo 并不意味着把所有代码都放进一个 Git 仓库。Linux 内核几十年来一直是一个仓库,但这并不是人们所说的 “Monorepo 规范”。这种规范是指当你拥有一个共享历史、一个连贯的依赖图以及能够理解这两者的工具链(tooling)时所呈现出的一系列特性。

转化为提示词,这些特性如下:

  • 锁定版本 (Pinned versions):每个消费方都引用提示词的一个特定、不可变的版本。当你编辑时,你会发布一个新版本。旧版本依然存在。生产环境中的任何内容都不会在未经审核的情况下针对变更静默重新构建。
  • 语义边界 (Semantic boundaries):每个提示词都有一个目的、一个所有者以及一组预期的输入和输出。服务于四个不同用例的多功能提示词会被拆分为兄弟节点,而不是累积条件分支。
  • 反向依赖搜索:对于任何提示词文件,你可以在几秒钟内回答“谁在调用它?”。对于任何服务,你可以在不进行 “grep 考古”的情况下回答“它依赖于哪些提示词版本?”。
  • 跨消费方的原子重构:重命名提示词、更改其输出 Schema 或将其迁移到新模型,只需一个 PR 即可同时更新提示词及其所有消费方。没有分阶段迁移,没有兼容性垫片(shims),也没有长达一周的 Slack 讨论。
  • 构建时验证:每一个修改提示词的 PR 都会在合并前针对新版本运行消费方的评估套件(eval suite)。如果回归超过阈值,将自动阻止合并。
  • 下游影响分析:当你修改提示词时,CI 会告诉你哪些消费方受到影响并运行它们的评估 —— 而不仅仅是提示词本身的测试。

这些特性都不罕见。它们是共享库代码的基本门槛。唯一的新颖之处在于将它们应用于历史上一直存在于 Markdown 文件中的文本产物(artifacts)。

提示词的语义化版本控制

最具体的切入点是将提示词的修改视为库的发布。语义化版本控制(Semantic Versioning)可以完美地映射到提示词管理中:

  • 修订号 (Patch / Z): 修复错别字、格式调整、在不改变评估集(Eval set)表现前提下的澄清说明。用户可以自动升级。
  • 次版本号 (Minor / Y): 在保持向后兼容的前提下增加新功能或指令。现有的输出依然有效,新行为是增量式的。用户在经过快速评估后即可升级。
  • 主版本号 (Major / X): 输出 Schema 变更、模型更换、结构性重写,或任何会破坏下游解析逻辑的改动。用户必须明确选择加入并重新测试。

这并不是为了官僚化而官僚化。它对应着 CI 可以强制执行的真实区分:如果评估套件在容差范围内通过,则允许发布修订版本;次要版本要求通过评估套件并增加新的测试用例;主要版本则要求用户更新其固定的版本号并重新运行他们自己的评估。

一个微妙但重要的推论:版本号不仅仅代表提示词文本。两个文本相同但目标模型不同、温度设置不同或工具定义不同的提示词,也是不同的版本。执行上下文至关重要,因为同一段字符串在 claude-opus-4-7claude-haiku-4-5 上的表现会有显著差异。请固定整个上下文,而不只是文字。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates