跳到主要内容

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

查看所有标签

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

· 阅读需 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 测试来替换它。提示词不再是软件,而成了遗迹。一旦发生这种情况,提示词就不再是你用来改进产品的杠杆,而是塑造产品的约束条件。

负面提示词是代码异味:为什么系统提示词中的每个 “不要” 都是技术债

· 阅读需 11 分钟
Tian Pan
Software Engineer

打开任何已经上线超过三个月的生产环境 AI 功能的系统提示词(system prompt)。数一数其中的负向条款——“不要”、“绝不说”、“避免”、“在任何情况下都不”、“你绝不能”。如果计数达到了两位数,你看到的就不是一个系统提示词。你看到的是一个坟场。每一块墓碑都标志着一个特定的用户投诉、一个特定的事故报告,或者一条来自利益相关者的 Slack 消息,因为他们看到模型做出了一些令人尴尬的事情。团队在表面打了补丁后就继续前进了,现在这个提示词读起来就像一份被强行嫁接了人格的法律免责声明。

负向提示词是代码异味(code smells)。并非隐喻意义上的,而是字面意义上的。它们在提示词工程中相当于吞掉异常的 try/except 块、没有文档的配置标志,或者是 2022 年留下的 // TODO: refactor this。它们在某种程度上有效,直到它们失效。而且它们所掩盖的失败模式,几乎总是比它们被添加用来压制的那个失败更有趣。

策略文件:为什么你不应该把拒绝规则写在系统提示词里

· 阅读需 13 分钟
Tian Pan
Software Engineer

上个季度,一家金融科技初创公司的安全审核员在系统提示词(system prompt)中添加了四行内容。这次修改包含一条拒绝规则,旨在防止助手为公司未获得运营许可的司法管辖区提供具体的税务建议。这听起来很合理、范围明确且符合审计要求。该规则在周二上线。到周五时,评估套件显示在与税务完全无关的客户入职流程中出现了 7 个点的下降——模型开始对任何提及国家的提问都模棱两可,甚至包括“这个账户持有哪种货币”。产品团队撤回了修改。安全团队在下周以略有不同的措辞重新发布了它。三周后,同样的退化以不同的形式再次出现,而接下来的安全修改又破坏了另一个无关的流程。

这里的 bug 不在于措辞,而在于拒绝规则放错了位置。它被挤进了一个 2,400 token 的构件中,该构件还包含助手的对话语气、格式契约、任务指令以及其他六项策略条款——对其中任何一项的修改都是对所有内容的行为修改,因为模型无法分辨哪句话是策略,哪句话是风格。生产环境中的系统提示词之所以变成了一坨乱麻的单体,是因为三个正交的关注点伪装成了一个整体。没有将它们解耦的团队在每次修改时都在支付“集成税”。

Prompt 修改不只是措辞变动:将 Prompt 视为软件的代码审查规范

· 阅读需 13 分钟
Tian Pan
Software Engineer

周二下午,一个只有六行代码的系统提示词(system prompt)编辑出现在了一个 Pull Request (PR) 中。Diff 只是普通的英文。两位评审者扫了一眼新的措辞,觉得读起来更自然,于是点击了批准。PR 在不到一分钟内合并。到了周五,客服开始收到关于智能体的工单:它突然拒绝总结超过一定长度的文档,不再引用来源,并莫名其妙地在每句回复开头都加上 “Certainly!” —— 这种行为没人要求过,Diff 中也无法预见。

当一个花了十年时间学习如何评审代码的团队,在面对提示词这一产物时,竟然退化到了第一周的水平,结果就是这样。Diff 看起来 毫无害处,因为它读起来像英语,而人类正是用眼睛来审阅英语的。让代码评审发挥作用的规范 —— 运行测试、检查影响范围、对 “小改动” 保持适当的怀疑 —— 并没有悄然转化。措辞变好了,但行为变差了,直到用户发现之前,没人注意到。

停止序列的“自毁”陷阱:当用户输入与分隔符发生冲突

· 阅读需 12 分钟
Tian Pan
Software Engineer

一位用户将一段 Markdown 粘贴到你的支持代理中。他们粘贴内容中的第一个标题是 ### Steps I tried。你的提示词模板(prompt template)使用 ### 作为停止序列(stop sequence)。模型尽职地读取了用户的输入,开始回答,并生成了 ### 作为其结构化响应的一部分——结果 API 返回了两句自信的回复,随后便是沉默。工单以“模型质量退化”的名义进入你的队列。其实不然。修复方法只是网关中的一行代码。

停止序列是生产级 LLM 技术栈中极其关键却又常被忽视的调节开关。它们通常是在最初编写提示词的那一周选定的,那时输入还是整洁的工程示例,还没有人粘贴过 JIRA 工单的堆栈信息。十二个月后,用户内容的分布已经远远超出了提示词作者的想象,曾经整洁的分隔符现在变成了潜伏在每三百个用户粘贴中就有一个的隐患。没有任何告警。评估套件(eval suite)依然能够通过。受影响部分的 CSAT 指标下降了 0.5 分并维持在那里。

这不是模型的问题。这是一个伪装成模型问题的输入契约(input-contract)问题,它的形态类似于典型的分布式系统 Bug:为一方的内容分布选择的分隔符被强制应用于另一方的内容分布,且在边界处没有任何监控。

你的系统提示词还在用英文:AI 本地化不完全的隐形成本

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的团队发布了一项 AI 功能。你为本地化工作感到欢欣鼓舞:每个按钮标签、工具提示和错误消息都被翻译成了 12 种语言。产品经理签了字。该功能在全球上线。

然而,六周后,一位德国用户发布了一张截图。AI 的回答用词正确但语域(Register)不对 —— 在非正式的客服场景中显得过于生硬。一位日本用户反映,结构化输出中的日期格式为 MM/DD/YYYY,这导致他们的下游工具出现故障。一位巴西的支持工程师注意到,AI 在对复杂查询进行推理时,偶尔会在句子中途滑入英语。这些并不是基础设施故障。你的仪表盘显示一切正常。但对于非英语用户来说,产品正在悄无声息地变得更糟。

根本原因几乎总是一样的:团队翻译了 UI 字符串,但却让系统提示词保留为英文。这看起来像是本地化,但事实并非如此。

大多数团队在无意中做出的上下文格式选择:JSON vs Markdown vs 纯文本

· 阅读需 11 分钟
Tian Pan
Software Engineer

大多数团队在开发初期选择一次上下文格式后,就再也不会重新审视它。一位开发者选择 JSON 是因为它看起来结构化且机器可读。另一位开发者则选择 Markdown,因为他们在 README 文件中一直使用它。当似乎没有其他必要时,普通文本(Plain text)就成了默认选择。这些并不是工程决策——它们只是习惯。并且它们在无形中塑造了你的模型如何进行推理。

你传递给 LLM 的格式并非死板的包装。它本身就是一条指令。结构化的 JSON 上下文会引导模型进入遵循模式(schema-following)的行为。Markdown 鼓励层次化的综合。普通文本则开启了更灵活的推理。即使只是选错了一个格式类别,也可能导致准确率下降 40% 或更多——而且你无法在日志中查看到这种错误。

LLM 系统中的软约束与硬约束:为什么失配会导致真正的失败

· 阅读需 12 分钟
Tian Pan
Software Engineer

大多数 LLM 系统故障并非源于模型出错。而是源于系统误判了模型能够强制执行的约束。当你在系统提示词中写下“绝不泄露客户数据”并将其等同于“撤销数据库凭据”时,你引入了一个范畴错误。这最终会导致安全事件、可靠性故障或受损的用户体验——而你直到故障在生产环境中发生时才会察觉。

软约束与硬约束之间的区别是架构层面的,而非风格层面的。搞错这一点不会导致风格退化,而是会导致安全漏洞。

系统提示中的冲突指令:无人负责的隐性故障模式

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的 AI 功能在上线时运行良好。六个月后,它有时给出简短的一两句话,有时写出五段长文,偶尔还会拒绝回答上个季度毫无障碍地处理过的问题。代码库中没有任何变化——至少你是这么认为的。实际上,系统提示在悄然改变,经过四名工程师跨两个团队提交的十一个拉取请求,逐步演变。每次改动单独来看都合情合理,但合在一起,却将你的提示变成了一台矛盾制造机。

这就是指令冲突问题。它不会抛出异常,不会出现在错误日志中,而是以行为漂移的形式表现出来——模型在细微不同的情境下做出细微不同的事,难以复现,更难溯源。等到用户提交 bug 报告时,提示可能已经被再次打过两个补丁了。

AI 入职差距:为什么工程师无法学习他们无法测试的东西

· 阅读需 12 分钟
Tian Pan
Software Engineer

一名新工程师加入了一个重度依赖 AI 的团队。入职第三天,他们发现系统指令中有一个措辞别扭的双重否定。看起来像是个 bug。于是他们把它清理了——这是任何合理的人都会做的小小优化。两小时后,一条关键流水线的客户端分类准确率从 91% 跌至 74%。没有人知道原因。

这种情景以某种形式发生在几乎每一个基于 LLM 构建系统的团队中。新工程师并不粗心。那个提示词看起来确实有问题。但那个双重否定在某种意义上是"承重墙"——只有写下它的人才真正理解,而那是在经过数周实验之后才领悟到的。他们从未把这种理解写下来。

这就是 AI 入职差距:AI 代码库表面上的行为与实际行为之间的鸿沟,以及为什么这个鸿沟在有人掉进去之前是不可见的。

上下文窗口是一个 API 界面:像对待合约一样对待你的提示词结构

· 阅读需 11 分钟
Tian Pan
Software Engineer

在一个生产环境中的 LLM 功能上线半年后,一名工程师提交了一个 bug:模型在上个季度的某个时间点开始给出错误的输出。没人记得改过提示词(Prompt)。Git blame 显示它为了“提高可读性”被清理过。之前的版本已经找不到了。调试工作只能从零开始。

就在这一刻,团队才发现他们的上下文窗口(context window)从未被真正工程化过——它只是被拼凑出来的。

上下文窗口是你的系统与模型之间的契约。进入其中的每一个标记(token)——系统指令、检索到的文档、对话历史、工具架构、用户查询——都是对一个函数调用的输入,这个调用既费钱又耗时,且会产生非确定性的输出。然而,大多数团队将上下文组合视为实现细节,而非 API 表面。提示词被就地编辑,没有版本控制。各部分通过累加增长。没有人负责布局。变化在无声无息中传播。调试体验比 LLM 时代之前的任何东西都要糟糕,因为至少堆栈跟踪(stack traces)会告诉你什么是改变了的。

超参数幻觉:为什么 Temperature 和 Top-P 应该最后才调

· 阅读需 10 分钟
Tian Pan
Software Engineer

当 LLM 的输出感觉不对劲时,工程师会本能地去调 temperature。这是调试手册里最早的动作之一——调低它以获得更强的一致性,调高它以获得更多创意。这感觉很有效,因为改动简单且效果立竿见影。但这几乎从来都不是正确的做法。

Temperature 和 top-p 只是输出质量最后 10% 的因素,而不是前 90%。真正决定模型成败的变量是上下文质量、指令清晰度和模型选择——依次排列。在一个有问题的提示词之上再调整采样参数,就像给一道还没煮熟的菜调味一样。根本问题并没有消失。