跳到主要内容

你的 Prompt 拥有外键

· 阅读需 11 分钟
Tian Pan
Software Engineer

重命名一个数据库列,看看会发生什么。编译器会捕获每一个查询构建器。ORM 迁移会捕获模型类。类型检查器会捕获 API 序列化器。集成测试会捕获通过网络读取该字段的两个服务。该列的每一个消费者都会被标记出来——只有一个除外。你的系统提示词(System Prompt)包含了一份为了“让模型理解数据”而精心格式化的表描述,它仍在自信地描述一个早已不存在的列。没有编译器错误。没有失败的测试。没有弃用警告。只有一个开始针对三个迭代(Sprint)前的架构生成查询的模型,以及一个慢慢被格式错误的 SQL 填满的仪表盘。

提示词中包含外键。它们引用数据库架构、工具签名、枚举值、API 结构,以及从生产数据中复制的 few-shot 示例——而这些引用都不参与重构。你粘贴到提示词中的每一个事实,都是在对一个你的工具链根本不知道其存在的表进行连接(Join)。当被引用的制品(Artifact)发生变化时,没有任何级联反应。提示词只是在那原地腐烂。

腐烂之所以难以察觉,是因为一切照常运行

这种失败模式之所以如此顽固,是因为它是静默失败的,而且是概率性地失败。一个过时的导入(Import)会在启动时崩溃。而一个过时的提示词则在采样(Sampling)时发生降级。当提示词说 status 字段接受 active | suspended | churned,而枚举值在上一季度增加了一个 paused 状态时,模型不会报错——它会做出更糟糕的事情。它会以十足的把握,将 paused 客户归类为三个过时值中最接近的那一个,其发生频率取决于你的流量触发该路径的概率。

监控这些系统的从业者描述了同样的轨迹:积累的小型不匹配会导致缓慢的、几乎不可见的退化,最终演变成严重的故障。而转折点通常来自于一个根本没人将其与 AI 系统联系起来的常规变更。一名后端工程师作为清理任务的一部分,将 customer_id 重命名为 account_id。他们的 PR 涉及了 14 个文件,全部通过。而存在于另一个代码库中的提示词模板——或者更糟糕,存在于一个完全脱离版本控制的提示词管理仪表盘中——并不在这些文件之列。

这些过时的引用通常聚集为几种常见的类型:

  • 架构描述 —— 粘贴到 text-to-SQL 或分析提示词中的表和列文档,在编写当天是准确的。
  • 工具签名 —— 关于工具功能及其参数的文字描述,随着后端的演进而偏离了实际函数。
  • 枚举值和业务规则 —— “plan 字段是 free、pro、enterprise 之一”——直到定价团队上线了第四个层级。
  • Few-shot 示例 —— 一年前从生产环境复制的真实输入/输出对,展示了已不存在的格式和字段名。

其中的每一项都是一个依赖。但没有任何一项在工具可见的地方被声明。

工具漂移:当模型的地图与实际疆域发生偏离

由于引用更加密集,智能体(Agent)系统使这个问题变得更加尖锐。智能体的有效性取决于模型对工具的“心理地图”是否与实际工具集相匹配,而这两者是独立演进的。工具注册表增长到了 28 个工具;系统提示词却仍在描述其中的 3 个。对此进行过监测的工程师描述了三种截然不同的症状。

第一,幻觉工具。当提示词的示例提到 search_customersget_invoice_by_id 时,模型会愉快地推断出一个听起来很合理但从未存在过的 search_customer_invoices。提示词中过时的词汇不仅没有起到帮助,反而是在主动教会模型一种会产生无效调用的模式。

第二,孤儿工具。存在于注册表中但在提示词最后一次修改后才添加的工具,很少或从未被调用。它们在每次请求中仍然消耗输入 Token。你在为模型实际上被告知不要使用的功能付费。

第三,模糊选择。当 search_docs_v1search_docs_legal 同时存在,且提示词只描述了拆分前的世界时,工具选择就变成了抛硬币。模型的先验知识填补了提示词留下的空白,而先验知识并不知道你的迁移计划。

过时的 few-shot 示例是最阴险的变体,因为示例的权重往往超过了指令。你可以将指令段落更新为“使用 account_id”,而下方 30 行处一个被遗忘的示例在输出 JSON 中仍显示 customer_id——模型就会遵循示例。关于 few-shot 提示的研究一致发现,不准确的示例不仅没有帮助,反而会主动增加错误率。过时的示例就是带有“资历”的不准确示例。

将提示词中嵌入的事实视为带有清单的依赖项

修复工作始于清点。审视你的生产环境提示词,将每一句话归类到两个桶中:观点事实。观点是提示词真正用途所在——语气、优先级、推理策略、护栏、不确定时该做什么。事实是关于外部系统状态的声明:这个表有哪些列,这个工具接受哪些参数,这个字段接受哪些值,这里有一个有效响应的真实示例。

观点可以由人工编写和维护,因为只有人类持有观点。事实永远不应该由人工维护,因为事实的记录系统(System of Record)永远不是提示词——而是架构、类型定义、OpenAPI 规范或枚举声明。提示词中每一个手写的事实都是一个没有失效策略的缓存。

一旦你看清了这种划分,清单(Manifest)几乎是顺理成章的。为每个提示词声明它依赖什么:哪些表、哪些工具、哪些枚举、哪些示例来源。这种声明不需要奇技淫巧——在提示词模板旁边放一个 YAML 块,列出产物标识符以及它们来源的提交(Commit)或架构版本,这就足以开始了。清单带给你的好处与锁定文件(Lockfile)相同:当上游制品发生变化时,你现在可以 知道 提示词受到了影响,而不是通过客户反馈才发现。

这是应用代码在几十年前就走过的成熟化步骤。没有人会通过将依赖项的源码粘贴到自己的代码库并希望在升级后记得重新粘贴来引入依赖——然而,将架构描述粘贴到提示词中正是这种做法。将提示词视为事实上的代码,并投入相应的工程严谨性,是目前大规模运行 LLM 的团队达成的共识;而依赖清单正是这种严谨性应用于提示词外部引用时的体现。

在部署时生成易变部分

清单(Manifest)会告诉你提示词(Prompt)何时过时。更强有力的做法是从结构上杜绝过时的可能性:停止手动编写易变的部分,转而在构建或部署时从单一真理源(source of truth)中生成它们。

提示词模板不再是一个完整的字符串,而是变得像二十年前的 HTML 模板一样——由静态编写的内容和用于动态内容的类型化插槽组成:

  • Schema 部分在部署时,由交付代码的同一流水线从实时信息 schema 或由迁移管理的模型定义中渲染。
  • 工具部分从工具注册表的 JSON schema(即你已经提供给 API 的那些对象)中渲染,因此文字描述和实际函数签名不会发生偏离。
  • 枚举清单从枚举声明本身渲染。
  • Few-shot 示例是从经过策划和测试的示例库中提取的——每个示例都在 CI 中根据当前的输出 schema 进行了验证——而不是作为字符串字面量粘贴进去。

那些从事严肃 Text-to-SQL 工作的团队出于必要性选择了这种架构:Schema 元数据是在查询时检索并注入的,而不是冻结在提示词中。因为在生产规模下,手动维护的 schema 描述在几周内就会过时。同样的逻辑也适用于其他任何事实类别。如果人类手动将某个事实写入提示词,你这就已经预定了一次事故;唯一的未知数是发生的日期。

生成操作还能缩小提示词的大小,这本身就是一笔划算的买卖。手动维护的部分会防御性地堆积——没有人会删除表的描述,因为没有人能确定它是否未被使用。而生成的各部分仅包含清单中声明的内容,别无他物。

比对提示词声明与实时 Schema 的 CI

生成涵盖了你已经迁移的部分。对于仍然手动编写的所有内容——遗留提示词、顺便提及字段名称的见解部分,以及那个由尚未采用新方案的团队拥有的提示词——安全网就是一个对照现实对提示词进行交叉检查的 CI 任务。

这些检查虽然朴实无华,但非常有效,大致按价值排序如下:

  1. 标识符提取和存在性检查。解析提示词中类似于表名、列名、工具名和枚举值的内容;并根据实时 schema、工具注册表和类型定义对每一项进行验证。引用了一个不再存在的列的提示词会导致构建失败,就像未解析的导入(import)一样。
  2. 反向覆盖率。标记注册表中没有任何提示词提及且没有流量调用的工具。在一千次请求中零调用意味着它是冗余负担,或者是模型无法使用的描述——无论哪种情况,你都在为这些 token 付费。
  3. 示例验证。每个 few-shot 示例的输出必须能根据当前的响应 schema 进行解析。这单项检查就能消除“指令说的是 X,示例演示的是 Y”这类 bug。
  4. 生产环境中的漂移遥测。跟踪无效的工具调用率、超出枚举范围的分类输出,以及引用不存在列的 SQL——并针对趋势而非阈值发出警报。部署后不断上升的无效引用率是提示词中出现“外键约束违规”的运行时特征。

这些都不需要机器学习平台团队。第一项检查只需要一个下午的时间写写正则和 schema 转储。它需要的是心智模型的重新分类:从提示词到 schema 的引用是依赖项,依赖项由机器解析和验证,而没有任何机器验证的引用就是已经在腐烂的引用。

这一切背后令人不安的真相是,整个行业花了两年时间对提示词进行版本管理——注册表、回滚、变更评审——而版本管理只解决了问题中错误的那一半。版本控制告诉你提示词说了什么以及何时更改。它并没有说明提示词所说的内容是否仍然真实。一个版本管理完美、经过仔细评审、六个月未变动的提示词,恰恰最有可能是在描述一个已不存在的世界。引用完整性(Referential integrity)而非变更跟踪,才是保持提示词诚实的属性——是时候让我们的提示词像我们在 1970 年为其他数据所做的那样,获得外键约束了。

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