跳到主要内容

冰封提示词:当你的团队不敢修改一个仍然奏效的系统提示词时

· 阅读需 15 分钟
Tian Pan
Software Engineer

每个成熟的 AI 产品最终都会演变成一个当前团队中没人能完全理解的系统提示词(system prompt)。起初它只是 40 个 token 的纯英文,20 个月后,它变成了一堵 4,000 token 的“高墙”,堆满了条件句、拒绝模板、格式规则、角色强化、边缘情况警告,以及一句没人能解释的关于周二的奇特句子。每一行都是为了应对特定的失败:客户投诉、法务发来的 Slack 消息、评估(eval)中发现的回归,或者在投资者演示期间出现的偶发 bug。写下第 37 行的工程师已经转岗到其他团队。写下第 112 行的工程师是一名外包人员,他的 Notion 文档已被归档。评估套件覆盖了提示词所主张行为的大约三分之一,但没人确定是哪三分之一。

于是,这个提示词以一种最糟糕的方式成为了系统的“承重墙”:它能用,团队也知道它能用,但团队已经不再碰它了。本该对提示词进行迭代的工程师,反而绕过它来处理变更——这里加一个后处理过滤器,那里加一个 few-shot 封装,或者做一个并行的“v2 提示词”并用特性标志(feature flag)关闭,以防有人哪天有勇气进行 A/B 测试来替换它。提示词不再是软件,而成了遗迹。一旦发生这种情况,提示词就不再是你用来改进产品的杠杆,而是塑造产品的约束条件。

这就是“凝固的提示词”(frozen prompt)失效模式,它正成为生产环境 LLM 系统中最常见的技术债形式之一。最近一项对 LLM 项目中 93,000 多个 Python 文件进行的学术调查发现,提示词设计是该领域中自认技术债(self-admitted technical debt)里最大的单一类别,其中位寿命为 553 天,且移除率在所有债务类别中最低——不到 50%。一旦写下了与提示词相关的 TODO,它几乎永远不会被解决。它只会随着时间慢慢老去。

系统提示词是如何凝固的

“凝固”很少发生在瞬间。它是三种力量随着时间的推移朝同一方向拉动的累积效应。

第一种力量是事件驱动的增生(incident-driven accretion)。当一个由 LLM 驱动的产品在生产环境中遇到行为失效时,最快的补丁几乎总是在系统提示词中加上一句话。“绝不推荐竞争对手的产品。”“如果用户提到退款,务必先确认订单 ID。”“不要将 'leverage' 一词用作动词。”每一句话都解决了一个真实的、可见的问题。但它们都没有对应的评估案例(eval case),因为编写评估比写下这句话耗时更长,而且值班工程师还有另外六张工单要处理。两年后,提示词本质上成了产品历次客户可见故障的账本——但唯一的文档就是提示词本身。

第二种力量是作者轮换(author rotation)。增加第 37 行的那个人在添加的那一刻,非常清楚那一行为什么要在那儿。他们知道是哪个用户投诉触发了它,是哪个模型版本产生了糟糕的输出,以及这一行在暗中补偿哪个下游渲染 bug。他们没有把这些记下来,因为当时觉得显而易见。18 个月后,他们已经去了另一家公司,而当前团队读到这一行时,感觉它可能可以删掉,但也可能是防止集体诉讼的唯一屏障。从提示词本身根本无法分辨。

第三种力量是评估不充分(eval underdetermination)。即便是有着严格评估纪律的团队,也会发现他们的套件只覆盖了提示词实际主张的一小部分。一个写着“除非用户用西班牙语写,否则始终用英语回答”的提示词,至少需要四个评估案例——英文入/英文出、西语入/西语出、西语入伴随英文后续、以及含糊的混合语言情况——而大多数团队只有一个或一个也没有。因此,当工程师考虑编辑提示词时,他们无法确定自己的修改是否安全。评估套件会显示“全部通过”,但他们知道套件并没有涵盖提示词执行的大部分内容。理性的反应就是不去碰它。

这些力量中的每一种单独来看都是可以管理的。但合在一起,它们就会产生棘轮效应。每一个事件都让提示词变得更长;每一次人员轮换都让它变得更晦涩;每一次没有对应评估的提示词修改,都让下一次修改变得更令人恐惧。提示词凝固并不是因为有人决定让它凝固。它凝固是因为没人能证明修改是安全的,而犯错的代价是这种行为回归(behavioral regression)会同时推送到每一位用户面前。

真正的代价是被约束的迭代

关于这个问题的讨论大多集中在破坏事物的风险上。但那是一个错误的框架。凝固提示词的严重代价不是你可能造成的回归——而是你再也无法进行的迭代。

想象一个健康的 AI 产品团队在六个月内的状态。模型得到了升级,出现了新的用例,客户要求新的行为,角色设定在演变,新的工具上线。每一个变化自然都希望通过系统提示词来实现,因为系统提示词是模型所有行为的中枢神经系统。一个能够自信编辑提示词的团队可以在几天内适应所有这些变化。而一个拥有凝固提示词的团队则做不到。

你看到的反而是一种架构扩张(architectural sprawl)。不敢修改提示词的团队开始通过其他层面增加功能:剥离或重写输出的后处理函数、注入额外指令的前处理封装、在基础模型之上叠加的微调模型、处理主智能体无法信任的情况的独立“专家”智能体。这些做法本身都没错。但合在一起,它们创造了一个系统的实际行为由七个层级共同决定的局面,没有哪一个层级是新工程师能够一眼读懂的。

这种情况最隐蔽的版本就是影子提示词(shadow prompt)。团队害怕修改规范的系统提示词,于是开始通过用户消息通道注入“上下文特定指令”——在每个请求的顶部加一段文字,有效地在不修改系统提示词的情况下对其进行补丁。现在,产品的真实行为由两个提示词决定:一个在系统槽位中,一个伪装成用户输入,而这两者的并集就成了谁也不许碰的东西。

当模型升级到来时,所有这一切会同时崩溃。新模型有不同的默认设置、不同的措辞敏感度和不同的失败模式。针对旧模型精心调整的凝固提示词,现在与新模型完全不匹配——但团队仍然不敢修改它。他们回滚升级,错失了能力提升的机会,而提示词不能被触碰的理由又多了一个。

像对待代码一样对待 Prompt,而不是配置

“被冻结的 Prompt”(frozen prompt)的文化根源在于,大多数团队将 Prompt 视为配置:YAML 文件中的一个字符串、一个环境变量,或者是 feature-flag 服务中的一行。配置是用来微调的,而不是工程化的。配置没有代码审查文化,没有出处追踪,也没有测试覆盖率要求。配置是那种产品经理在系统故障期间就能直接修改的东西。

走出“冻结状态”的第一步是认识到:系统 Prompt 不是配置。它是软件。它编码了分支逻辑,守护着不变量,定义了协议,并且在部署的那一刻交付给每一位用户。修改 Prompt 的影响范围相当于修改一个在每次请求中都会被调用的函数。它理应受到与代码变更同样的约束。

实际操作中,这意味着几件事。Prompt 的修改必须通过 Pull Request。 审查者需要像对待请求处理程序的代码变更一样仔细。差异(diff)是用来研读的,而不是扫一眼。需要运行测试。需要获得批准。

Prompt 修改需要附带评估增量(eval deltas)。 在 Prompt PR 中,可供审查的产物不是措辞的改变,而是在旧版和新版 Prompt 上运行的评估套件(eval suite)对比结果,显示哪些案例发生了变化。如果你的评估覆盖范围太窄,无法检测到有意义的行为转变,那么这个缺口就是一个单独的任务(ticket),但添加评估是修改 Prompt 的先决条件。仅凭这一条规则,只要持之以恒地执行,就能防止大多数“冻结”现象。

Prompt 修改需要逐步发布。 生产环境的流量会先分给金丝雀(canary)切片——先是 5%,然后是 25%,最后是 100%——并配合监控仪表盘观察 Prompt 预期影响的指标。运行成熟 Prompt 管理机制的团队报告称,每个阶段的观察窗口为 24 到 48 小时。如果用户满意度信号或约束通过率下降,回滚只需一键操作。当犯错的成本从“所有用户立即撞上 bug”降低到“金丝雀能捕捉到它”时,修改 Prompt 的恐惧感会大幅降低。

Prompt 考古学:逆向工程一个被冻结的 Prompt

如果你已经接手了一个被冻结的 Prompt,首要任务不是开始修改。首要任务是“考古”。

考古的产出是一份逐行的出处图谱(provenance map)。对于 Prompt 中每一个有意义的句子或子句,你需要了解三件事:最初是什么失败触发了它,现在哪个评估案例(如果有的话)覆盖了它,以及如果你删掉它会发生什么。对第三个问题的诚实回答通常是“我们不知道”,而这正是练习的重点——未知项就是工作积压点。

实现机制不如产出重要。有些团队在 Prompt 本身中使用行内注释(渲染时移除),将每个子句链接到 Linear 任务或事后总结(postmortem)。另一些团队则维护一份平行的 Markdown 文档,每个子句对应一个条目,视其为 Prompt 的设计文档。格式可以商榷,但存在与否不容妥协。没有出处图谱的 Prompt 注定会一直处于冻结状态。

一旦图谱建立,你就可以开始运行删除实验。对于每一个有记录失败模式的子句,编写一个能触发该失败的评估案例。然后在有和没有该子句的情况下分别运行评估。可能会出现三种结果:该子句确实起到了宣称的作用,评估增量证明了这一点——保留该子句,但现在它已被覆盖;该子句毫无作用,无论如何评估都能通过——删除该子句,精简 Prompt;该子句的作用与预期完全不同,评估揭示了无人知晓的隐藏行为——在采取行动前先调查清楚。

这是一个缓慢的过程。一个包含 60 个子句的 Prompt 是一项为期数月的考古工程。但每一个梳理完成的子句都不再是处于危险状态的“承重部分”。那些你能证明是必要的子句可以安全保留;那些你能证明是不必要的子句可以安全删除;而那些你无法界定的子句则成为了关注的新焦点。

行为覆盖图谱

一项补充实践是构建行为覆盖图谱:一个矩阵,一轴列出 Prompt 断言的所有行为,另一轴列出练习这些行为的每一个评估案例。单元格显示通过或失败。那些空的行——没有任何评估覆盖的行为——就是你的冰山。

这张图谱能同时起到两个作用。首先,它让评估缺口对领导层可见,这通常是分配人力来填补缺口的唯一方法。“我们有 87% 的评估通过率”听起来很棒,直到图谱显示这 87% 集中在 12 个行为上,而另外 31 个行为完全没有覆盖。其次,它为工程师提供了一个具体答案,来回答“修改这一段 Prompt 安全吗?”。答案是:“如果图谱中对应的行有稳健的覆盖,那就是安全的;否则,安全的做法是修改评估,而不是 Prompt。”

行业内成熟的评估覆盖现在通常关联着 50 到 500 个案例规模的金标准测试集(golden test sets),这些测试集与 Prompt 一起进行版本控制,并在每次 Prompt 变更时运行。达到这一标准的团队描述这种体验是截然不同的:Prompt 修改变得常规化,模型升级变得低压力,而 Prompt 本身也因为冗余和过时子句被清理而开始缩减。

弃用审查是容易被遗忘的规范

大多数关于提示词管理(prompt management)的讨论都集中在“增加”这一面:如何安全地添加指令、如何测试新子句、如何部署新版本。而“弃用”这一面很少被讨论,而这往往是杠杆率最高的地方。

一个处于“冻结”状态的提示词充满了各种子句,但它们原本针对的故障模式早已不再适用。六个月前产生糟糕输出的模型,此后已经更换了两次。需要该变通方案(workaround)的下游渲染错误已在 10 月份修复。那个投诉的客户已经流失了。该子句原本要纠正的人设偏离(persona drift)是针对某个已弃用的模型版本的。这一切都没有触发任何人去移除该子句,因为没有人负责重新审查旧子句。

一个有用的规范是每季度进行一次“弃用审查”(deprecation pass):这是一次例行评审,团队逐条检查提示词子句,并针对每一条询问:“这还是承重结构(load-bearing)吗?”溯源图(provenance map)让这个问题变得有据可查。评估套件(eval suite)让答案变得可证伪。结果就是提示词会随着时间的推移变得更 ,而不只是变长——这本身就改变了团队与这一产物(artifact)的关系。一个明显被编辑过的提示词,才是一个可以被编辑的提示词。

文化转变

被“冻结”的提示词究其本质,更多是一个文化产物,而非技术产物。技术解决方案——版本控制、评估套件、金丝雀部署、溯源图——已经存在多年。团队之所以没有将它们应用于提示词,是因为提示词给人的感觉与代码不同。它们是用英语写的。它们很短。它们看起来很容易编辑。它们存放的位置(如配置文件、功能开关面板)通常没有代码审查(code-review)的文化氛围。

打破这种“冻结”状态的转变,在于意识到系统提示词(system prompt)具有在每次请求中调用的函数那样的杠杆作用、未记录协议的脆弱性,以及数据库模式(schema)变更带来的回归风险。面对这些,你既不会随意修改,也不会拒绝修改。你需要建立一套规范,让修改它们变得常规化。

一个畏惧其系统提示词的团队,其产品迭代速度会被这种恐惧所限制。模型可以变得更聪明,用例可以成倍增加,客户可以要求新的行为——但如果没有团队修改提示词的意愿,这一切的推进速度都会受限。出路不在于写出更好的提示词,而在于建立一种工程实践,让任何提示词的修改都变得安全。一旦你拥有了这种实践,提示词就会回归它从一开始就该有的样子:它是你产品中最重要且最具可塑性的部分,而不是那个没人敢碰的禁区。

References:Let's stay in touch and Follow me for more thoughts and updates