跳到主要内容

639 篇博文 含有标签「llm」

查看所有标签

标注员校准差距:当人类评分者悄然失去一致性时

· 阅读需 12 分钟
Tian Pan
Software Engineer

控制面板显示评估者间一致性(Inter-rater agreement)为 0.71。模型团队正在庆祝,因为新提示词的得分比基准高出两分。没人注意到,六个月前,同样的 0.71 是由对评分标准(Rubric)理解完全一致的标注者产生的。而今天,这个数值是由三位标注者产生的,他们对“有帮助”(helpful)的定义存在默契的分歧,而这些分歧恰好在指标上相互抵消。你的评估工具已经分化为一组隐性标准的联盟,而仪表盘上的数字只是他们博弈后的加权平均值。

这就是标注者校准差距(Annotator Calibration Gap)。这是一种失败模式:为了对 LLM 评测器无法可靠处理的案例进行评分而建立的人工评估池,逐渐偏离了团队原本设定的衡量目标。模型并没有变差,是评估工具变差了。由于指标依然呈现为一个整洁的数字,没人会察觉,直到发布出现偏差,事后分析才发现,在过去的两个季度里,“有帮助”对三位不同的标注者意味着三种完全不同的东西。

你的评估套件就是你拒绝编写的产品需求文档

· 阅读需 11 分钟
Tian Pan
Software Engineer

打开本季度发布的任何 AI 功能的 PRD。注意那些形容词。助手应该是有帮助的 (helpful)。回复应该是自然的 (natural)。智能体应该理解 (understand) 用户的意图。摘要应该是准确 (accurate)简洁 (concise) 的。每一个这样的词都是团队放弃决策的地方。他们并没有决定这个功能要做什么。他们只是决定了在会议中如何向彼此描述这个功能,然后——在没人点破的情况下——悄悄地将实际的产品定义移交给了编写评估集的人。

这不是文档问题。评估集就是规格说明书。PRD 是一份在产品诞生前撰写的官方新闻稿。文档中模糊的形容词在评估集中变成了明确的行为断言,否则它们就毫无意义——模型会自行挑选一种解释并发布,而团队在三个月后才会发现,“简洁”对审核者、用户以及在上一个 Sprint 调整 Prompt 的人来说,含义完全不同。一个评估集薄弱的 AI 功能,其产品定义也同样薄弱。模型并没有失败。团队从未决定过成功意味着什么。

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

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

隐藏的 SDK 重试机制:为什么你付了两倍的钱却浑然不知

· 阅读需 12 分钟
Tian Pan
Software Engineer

打开 OpenAI Python SDK 的源代码,你会发现一行安静的代码:DEFAULT_MAX_RETRIES = 2。Anthropic SDK 也采用了同样的默认设置。大多数 TypeScript SDK 也是如此。两次重试,指数级退避(exponential backoff),在连接错误、408、409、429 以及任何 5xx 错误时自动触发——这些都在你的代码看到失败之前就执行了。你没有配置它。你没有选择加入。你通常甚至不知道它的发生,因为你的应用记录的指标是 request_count(请求数),而不是 attempt_count(尝试数),并且你的追踪器(tracer)唯一能看到的 span 是 SDK 在最后一次尝试后关闭的最外层 span。

这在大多数情况下都没问题,直到出问题为止。如果在该 SDK 调用之上再添加一个应用级的重试装饰器——那种每个团队在遇到第一个 429 错误后都会写的代码——你就构建了一个 3x3 的风暴:SDK 尝试三次,你的包装层围绕 SDK 又尝试三次,在服务商降级期间,一个单一的用户请求会扇出为九次推理调用。服务商的账单会计算每一次尝试。而你的仪表盘只记录了一次。当最终有人进行账实对账时,那将是一场谁都不会喜欢的季度末谈话。

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

· 阅读需 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 看起来 毫无害处,因为它读起来像英语,而人类正是用眼睛来审阅英语的。让代码评审发挥作用的规范 —— 运行测试、检查影响范围、对 “小改动” 保持适当的怀疑 —— 并没有悄然转化。措辞变好了,但行为变差了,直到用户发现之前,没人注意到。

RAG 中的新鲜度与相关度权衡:为什么你无法在查询时同时优化两者

· 阅读需 13 分钟
Tian Pan
Software Engineer

一名用户询问你的助手公司的育儿假政策。机器人返回了 12 周,并附带了引用。被引用的文档是 2023 年的正确答案;而人力资源部门在上个季度发布了更新,将其延长到了 16 周。这两个版本都在你的知识库中。由于旧版页面的表述更简洁且模棱两可的内容较少,余弦相似度给 2023 年版本的评分是 0.87,而 2024 年版本的评分是 0.84。较新的文档以 3 个百分点的差距落败,用户得到了一个看似经过审计的错误答案。

这就是时效性与相关性的权衡(freshness-relevance tradeoff),令人不安的是,这在查询时并没有完美的解决方案。如果你增加时效性的权重,检索结果就会偏向于昨天刚编辑过的任何内容——在大多数知识库中,这些通常是高频变动的嘈杂区域,不应作为事实来源。如果你不增加时效性的权重,你给出的答案将基于几个月前就被取代的文档。没有一个全局按钮能同时搞定这两点,大多数团队只有在一些令人尴尬的答案绕过评估套件泄露出去后,才会发现这个问题。

检索级联失效:文档删除如何毒害你的 RAG 流水线

· 阅读需 11 分钟
Tian Pan
Software Engineer

一个用户询问你的支持机器人退款期限何时结束。机器人带着愉快的自信给出了“60 天”的回答并附带了引用。然而,那个写着“60 天”的策略页面早在三个月前就从 CMS 中删除了。新策略是 14 天。直到有客户投诉,你的团队中才有人意识到机器人出错了。

这就是检索级联失效(retrieval cascade failure):文档已从事实源中消失,但其嵌入(embedding)仍留在索引中,在余弦相似度排名中依然靠前,不断为模型提供一个“幽灵”。RAG 流水线将向量索引视为源内容的缓存,但大多数团队在构建缓存时并没有构建失效机制。插入操作得到了所有的工程关注,而删除操作只得到了一个 TODO 注释。

“展示过程”的 UX 陷阱:当推理链只是披着产品外壳的调试输出

· 阅读需 11 分钟
Tian Pan
Software Engineer

推理模型会输出思维链(chain-of-thought)轨迹,因为这是它的计算方式。产品团队在 UI 中渲染该轨迹,是因为隐藏它感觉像是丢掉了用户付费购买的 token。这是两个不同的决定,而产品端几乎没有人意识到他们做了第二个决定。于是,轨迹变成了面板,面板变成了功能,功能有了文档页面。六个月后,有人在季度回顾中问,为什么支持队列里全是用户在反驳推理过程,而不是针对答案本身。

推理轨迹本质上是调试输出。它的存在是为了让工程师了解模型为什么选择某个工具、在日期上含糊其辞,或者在段落中间悄悄切换了角色。在没有经过设计审查的情况下将其推给终端用户,等同于在生产环境中留下 console.log 调用并称之为“透明度”。它看起来像个功能,渲染成本几乎为零,但它会以团队构建的任何仪表盘都无法显示的方式悄悄削弱信任。

小模型,大账单:为什么单 Token 成本更低反而更贵

· 阅读需 10 分钟
Tian Pan
Software Engineer

由财务主导的“切换到更小模型”的指令,是让你的 LLM 账单季度环比增长最可靠的方式之一。采购团队盯着的仪表盘——单次调用成本、每次请求的平均 token 数——一直在下降。与此同时,发票金额却在不断攀升。当有人终于把这两者对上账时,团队已经花了六个月的时间进行提示词(prompt)迭代,以补偿那个在任务处理上表现更差的模型,而且团队已经陷得太深,如果不承认最初的切换是个错误,就无法走回头路。

错误不在于定价,而在于计量单位。当推理深度、重试次数和提示词大小都随模型而异时,单 token 价格是一个具有误导性的维度。正确的指标是“单次成功完成所需的 token 数”,在这个维度上,更便宜的模型往往会输。

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

· 阅读需 12 分钟
Tian Pan
Software Engineer

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

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

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