跳到主要内容

164 篇博文 含有标签「prompt-engineering」

查看所有标签

无法回滚的功能开关:提示词

· 阅读需 9 分钟
Tian Pan
Software Engineer

你对生产系统的每一次更改都遵循某种纪律。代码在 feature flag 后发布,灰度测试(canary)到 1% 的流量,当仪表盘变红时可以一键回滚。Schema 迁移是分阶段且可逆的。即使是 CSS 的微调也要经过人工阅读的 pull request。然而 Prompt 却并非如此。有人在文本框中编辑一段文字,点击保存,你产品的行为就会立即对所有用户发生改变——没有灰度测试,没有经过审查的 diff,也没有真正能让你回到先前状态的回滚按钮。

令人不安的是,这并非粗心的团队所导致的疏忽,而是工具链产生的默认结果。Prompt 被归类为“配置(configuration)”,因为它们是存在于编译后的二进制文件之外的字符串,而配置一直以来被认为是可以快速更改且无需完整发布周期的东西。但 Prompt 并不是配置。它是一个用英语编写的程序,由一个你无法控制的非确定性解释器编译,其行为你只能通过统计学来观察。将它视为配置值,是导致一整类生产事故的范畴错误(category error)。

语义差异:当行级对比毫无意义时,如何评审 Prompt 变更

· 阅读需 9 分钟
Tian Pan
Software Engineer

一位队友提交了一个拉取请求(PR)。Diff 只有三个单词。一行变红了 —— Do not add information not present in the source.(不要添加原文中不存在的信息)—— 另一行变绿了 —— Make your best guess if the source is incomplete.(如果原文不完整,请做出最佳猜测)。改动很小,意图也合理,代码审查只用了 11 秒。你批准了它。一周后,你的客服机器人开始自信地编造根本不存在的退款政策,而你正在翻阅日志,试图查出幻觉率是从什么时候开始翻倍的。

Git Diff 完美地完成了它的工作。它准确地向你展示了哪些字符发生了变化。但它无法展示唯一重要的事情:这些字符背后的行为已经从“不确定时拒绝”变成了“不确定时编造”。对于代码,文本差异是行为差异的忠实代理 —— 将 < 改为 <=,审查者可以推断出后果。而对于提示词(Prompts),文本差异和行为差异几乎没有任何关系。

两个模型厂商,一个功能:用冗余换来的连贯性噩梦

· 阅读需 12 分钟
Tian Pan
Software Engineer

你接入了第二个模型供应商,因为第一个挂了。那是一个星期二,主供应商的状态页在几个小时内一片通红,你的功能也随之陷入瘫痪。所以你做了件负责任的事:添加了一个备选方案。现在,如果 OpenAI 无法访问,你就路由到 Anthropic。如果 Anthropic 对你进行了频率限制,你就回退到 Gemini。架构图看起来干净且成熟。可靠性问题,解决了。

可惜你添加的不是一个副本。你添加的是第二种意见。而操作“第二种意见”与操作“第二个副本”是完全不同的两回事。

你引入的心理模型来自“无状态服务”的策略:在负载均衡器后运行三个相同的实例,如果一个挂了,另外两个会提供完全相同的响应。这之所以有效,是因为副本之间在字节级别是完全等效的。但来自两个供应商的两个语言模型并非如此。它们是在不同的数据上训练的,针对不同的目标进行了微调,而且它们在用户最容易察觉的输入点上——会产生系统性的、而非随机的——分歧。

你的 Prompt 拥有外键

· 阅读需 11 分钟
Tian Pan
Software Engineer

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

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

你不是选择了一个模型,而是和它结婚了:无人预估的提示词级锁定

· 阅读需 10 分钟
Tian Pan
Software Engineer

询问任何工程主管,他们是否被模型供应商锁定了,他们都会指向抽象层。“我们通过网关路由所有内容。更换供应商只是配置更改。”端点只有一行代码。Base URL 是一个环境变量。在纸面上,迁移只需要一个周二下午。

然后他们尝试了。他们将配置切换到不同的模型系列,集成测试依然通过,但生产环境却悄然崩溃。原本总能解析的 JSON 现在被包裹在 Markdown 代码块中。准确率曾达 94% 的分类器降到了 80% 出头。一个稳定运行了一年的提示词开始因为无人能复现的原因,在每 20 个请求中拒绝 1 个。端点在几秒钟内完成了切换,但行为并没有随之迁移。

这就是没人预料到的锁定。它不在你的合同或 SDK 中,而是在你的提示词(prompts)中——你的团队为了适应单一模型系列的特性,一次又一次地做出的数千个细微调整。你选的不是模型,而是与之“联姻”,而“婚前协议”就是你发布过的每一个提示词。

Token 预算是变相的人员编制决策

· 阅读需 11 分钟
Tian Pan
Software Engineer

最近我交流的一个团队花了三个工程师周的时间,将平均 Prompt 从 4,000 Token 削减到了 2,600 Token。他们以此为荣 —— 纯 35% 的降幅,实打实的数据,幻灯片里漂亮的图表。然后有人反向算了一笔账:这笔节省每月大约为 1,800 美元。而他们投入的三个工程师周,折算成全额薪酬成本大约是 25,000 美元。按照这个月度消耗率,这笔优化大约需要 14 个月才能回本 —— 这还是假设 Prompt 永远不变、模型永远不降价,且那些工程师没有更有价值的东西可做的情况下。

然而,这些假设一个都没成立。下个季度 Prompt 改了两次。他们使用的模型本身就降价了 40%。而那些工程师那个月没能发布的特性,恰恰是最大客户一直询问的功能。

团队上线了新提示词模板,评估框架却还在测昨天的旧版本

· 阅读需 10 分钟
Tian Pan
Software Engineer

事件时间线清晰可见。9:02,你的平台团队将 prompt-template@v38 推送到了配置服务。11:14,你的仪表板显示一切正常。16:51,支持团队有人标记了升级件数的激增。17:03,你打开了评估套件,发现回归分数为 0.34,于是进行了回滚。复盘报告称:“在 8 小时内捕获,除了 0.04% 看到该问题的客户外,未造成进一步损害。”工程领导层对响应速度表示赞赏。

但这是错的。回归在 0 小时内就被捕获了。17:03 运行的评估套件与 09:03 运行的是同一个。它一直指向的是 v37。评估框架在进程启动时从配置服务加载了模板,将渲染后的 Prompt 以 Python 对象的形式缓存到了模块级作用域中,并且从未重新读取源文件。你的线上流量在上午 9 点切换到了 v38。而你的评估直到 17:03 有人重启了 Worker 池来“重新运行回归”时才发生变化。在长达 8 小时的时间里,客户交互是基于从未经过评估打分的 Prompt 进行的,而评估系统却一直在给生产环境中根本没人在用的 Prompt 打分。

那些被你的提示词工程师转变为生产环境 Few-Shot 示例的评估集

· 阅读需 12 分钟
Tian Pan
Software Engineer

评估仪表盘连续三个迭代(sprints)都在攀升。困难切片的质量提升了 6 个百分点,回归切片提升了 9 个百分点,而在支持团队根据上季度最糟糕的工单亲手整理的切片上,质量提升了 12 个百分点。团队据此发布了模型升级。两天后,一位客户提出了一个与评估集中的任何内容都不沾边的问题,结果得到的答案比六个月前还要糟糕。

一旦有人想到进行排查,原因很快就浮出水面了。提示词工程师(prompt engineers)一直与评估团队在同一个代码仓库中工作。他们发现了那些精心策划的示例——这些示例来之不易,有的甚至是某人为了一个理想答案的措辞争论了一个小时才定下来的。在几个迭代中,他们把其中最有代表性的示例以 few-shot 演示的形式直接复制到了生产环境的系统提示词(system prompt)中。仪表盘持续攀升,是因为模型在推理时处理的正是它曾经逐字见过的输入。没有人指出这个问题。没有人负责划定“用于衡量质量的示例”与“用于发布到提示词中的示例”之间的界限。两个团队都准确地完成了他们被雇佣来做的工作。

系统提示词为他人调优的备选模型

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的可靠性仪表盘显示为 99.95% 。但你的支持收件箱却在诉说另一番景象。每周有那么两次,每次持续 10 到 20 分钟,极少数用户会遇到一个说话风格完全像另一家公司的产品版本。拒绝响应读起来很奇怪。一个原本总是渲染为整洁双栏卡片的结构化字段,现在变成了一个塞满了项目符号的段落。语气从“冷静的专家”变成了“热情的助手”。没有人会为此提交工单——他们只会直接关闭标签页,稍后再试。

你的供应商宕机了。故障转移生效了。延迟保持在 SLO 之下。错误预算没有变动。然而,用户在那个窗口期获得的体验,并不是你真正发布的那款产品。

大多数团队在采用多供应商架构时所持的心智模型是:系统提示词(System Prompt)是可移植的——它是一份与“能力出众的模型”这一抽象概念达成的协议,任何理解 LLM 方言的模型都能读懂。这种模型是错误的。系统提示词是一个经过调优的产物(Artifact)。它是针对特定模型的偏好、拒绝语法、格式习惯和指令遵循偏差进行调优的。当故障转移发生时,你并不是将同样的合同交给一个对等的签约方,而是将一份用主模型(Primary Model)的习语编写的合同,交给了一个阅读习惯完全不同却依然强行签字的模型。

被你的模型视为“约束性判例”的 Few-Shot 示例

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户提交了一个问题。你的模型生成了一个答案,这个答案以一种非常具体的方式“自信地出错”:格式完美,推理结构严密,并且出现了一个特定的限定词——这个限定词完全不适用于这个问题——它出现的位置,恰好是你系统提示词(system prompt)中示例三出现类似限定词的地方。这既不是幻觉,也不是提示词注入。模型只是精确地执行了示例教它的操作,尽管这些示例原本并非为了涵盖这个问题。

这就是 Few-Shot 提示主动诱发的故障模式,而大多数评估套件(eval suites)在结构上对此是视而不见的。你的示例并不是“优秀范式”的中立演示。它们是判例法(case law)。模型通过表面 token 选择最匹配的项,并将该先例——包括其限制条件——应用到眼前的任何案例中。

法律免责声明如何从答案泄露到工具调用参数中

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的法律顾问批准了一个单行的系统提示词指令:在每一个涉及受监管领域的回答中附加 “此信息不构成法律建议,不应以此为依据”。三周后,一个用户提交了一个 Bug,因为他们的日历事件描述字段以该行开头,随后是智能体本应放入会议邀请的合同摘要。智能体并没有发生故障。它完全按照系统提示词的要求执行了操作,结果发现这种行为涵盖了模型输出文本的每一个渠道——包括它调用的下一个工具的 JSON 参数。

该指令是一条内容格式规则,而模型也将其视为一条规则。它没有区分 “面向用户的回答” 和 “工具调用参数”,因为提示词中没有任何内容告诉它这些是不同的表面。免责声明最终出现在日历中、邮件草稿中,以及你的智能体代表用户发布的 Slack 消息中。这些中的每一个都是独立的下游系统,其作者根本不知道会有合规字符串被注入到结构化字段中,且每个系统的清理成本各不相同。

系统提示词提供的人格,模型每次都会以同样的方式选择

· 阅读需 11 分钟
Tian Pan
Software Engineer

我最近接触的一个产品团队针对回复人格设定(简洁、详尽、对话式)进行了一项为期三周的 A/B 测试,覆盖了所有用户群组。系统提示词描述了这三种设定,并要求模型选择最匹配用户的那一种。当他们打开数据集编写分析报告时,一个数字让他们愣住了:“详尽”组占据了 91% 的流量。另外两组的比例小到几乎可以忽略不计。

他们的实验平台没有标记任何异常。没有触发任何警报。流水线完全按照他们的指令运行。三周所谓的“多人格测试”产生了一个只能告诉他们关于“详尽”信息的数据集。另外两组样本太少,根本无法进行任何统计推断。

房间里的第一直觉是提示词需要改进——更好的指令、更清晰的人格区分、为对话式场景提供更刻意的示例。如果是在十年前基于规则的路由器中,这个诊断是正确的。但对于模型来说,它是错误的。提示词不是变量,路由器才是。