跳到主要内容

720 篇博文 含有标签「llm」

查看所有标签

推理力度预算编制:当思维 Token 成为财务账单的独立细目

· 阅读需 13 分钟
Tian Pan
Software Engineer

当你的财务团队第一次问,为什么单个用户在回答一个价值 0.1 美分的问题时产生了两美分的账单,那个电话讨论的不会是模型,而是发票上那行十二个月前还不存在的项目:推理 Token (reasoning tokens)。在账单上它们看起来像输出 Token,在大多数服务商那里也按输出 Token 的费率计费,而且它们没有天然的上限。一个在非推理模型上只需产生 400 个 Token 回复的查询,可能会悄无声息地消耗 8,000 个内部思考 Token 才能得出答案——唯一注意到这一点的人是核对支出的人。

在 API 时代的大部分时间里,“使用的 Token 数”是一个诚实的数字。你输入提示词,得到响应,账单是两者的清晰函数。推理模型打破了这种直觉。模型现在在发出调用者将阅读的答案之前,会生成一个隐藏的、可计费的、仅内部可见的思维链,而该链的大小取决于模型自身对问题难度的评估。用户可见的输出可能只有一句话,而账单可能长达十页。

重新规划而非重试:为什么大多数智能体错误并非瞬时性的

· 阅读需 12 分钟
Tian Pan
Software Engineer

一次日历写入返回了 409 Conflict。框架默认的错误处理器开始介入:退避 200ms,重试。同样的冲突。退避 400ms,重试。同样的冲突。退避 800ms,重试。等到智能体放弃并告诉用户“我无法预订会议”时,它已经浪费了三秒钟的延迟预算,去证明第一条响应就已经告诉它的事实:该时段已被占用。世界没有改变。它也不会在 800 毫秒内改变。重试永远不会奏效,因为这个错误中没有任何瞬时性的成分。

这是智能体系统中最为常见的错误处理 bug,而且它就隐藏在当今几乎每一个发布的框架之中。带有指数退避的重试模式是从无状态 HTTP 客户端中照搬过来的——在那里这种模式完全正确——但被引入到有状态的规划循环中时,它就完全错误了。对于智能体中的工具错误,正确的默认处理方式不是重试,而是重新规划。

采样参数继承:当 0.7 的温度从规划器泄露到验证器时

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个在 8% 的情况下会推翻自己答案的验证器(verifier)并不是一个表现不稳定的模型。这是一个由于框架默认采用继承机制而进入生产环境的采样配置 Bug。规划器(planner)需要 temperature=0.7 来头脑风暴子任务的分解。而验证器 —— 其全部工作就是针对答案是否符合评分标准给出低方差的“是”或“否” —— 却是通过同一个 harness 调用实例化的,并默默地沿用了相同的温度设置。没有人故意这么设置。甚至根本没有人去设置它。

这是你的技术栈中最昂贵却无人认领的参数。它在调用树中不断累积:验证器上方的总结器、下方的结构化输出提取器,以及包裹整个流程的重试循环,都像使用全局变量一样沿用着规划器的“保持创意”旋钮。这笔账会同时体现在三个地方:评估的不稳定性、Token 支出,以及资深工程师花半天时间对一个结果发现根本不是退化的“性能退化”进行二分法排查。

AI 功能指标陷阱:为什么 DAU 和留存率在随机化表面 (Stochastic Surfaces) 上会产生误导

· 阅读需 13 分钟
Tian Pan
Software Engineer

一位产品经理(PM)带着一张幻灯片走进 AI 功能评审会,上面写着:“参与度 +12%,会话时长 +8%,留存率上升 3 个百分点。” 房间里的人纷纷点头。而在两个工位之外,客服主管正盯着另一张图表:涉及 AI 界面的工单增加了 22%,最常见的处理逻辑是“用户放弃,由人工客服协助”。这两个数字都是真实的,且都来自同一个产品。PM 的仪表盘建立在这样一个假设之上:AI 功能产生的事件形状与它所取代的按钮完全相同。事实并非如此。仪表盘统计的数据与用户实际体验之间的差距,正是 AI 功能在众目睽睽之下悄然失败的原因。

确定性功能的操作手册将交互视为点击流:用户触发一个事件,系统做出反应,用户继续操作。AI 功能具有不同的事件形状——一个包含阶段、重试、转向人工以及遥测数据永远无法看到的离线判断的“任务弧”(Task Arc)。将确定性仪表盘套用在这个任务弧上,就像是用 2018 年的面试流程来筛选 2026 年的工作职位。数字在上升,但这些数字本应预测的结果却在下降。

你的 stop_reason 在说谎:构建生产环境故障排查真正需要的停止分类法

· 阅读需 14 分钟
Tian Pan
Software Engineer

运维工程师调出一个 trace。模型已返回,span 正常关闭,API 调用显示 stop_reason: end_turn。从平台提供的各种信号来看,这是一次成功的生成。3 分钟后,客户报告说 Agent 煞有介事地写了半个配置文件,宣布操作完成,然后就继续下一步了。Trace 里没有任何预警信号,因为预警信号不在 API 协议里 —— 供应商提供的停止原因只有四到七类,而你的事故排查所需要的答案,恰恰隐藏在这些类别的缝隙之中。

停止原因(Stop reasons)是工程师在故障排查时最先查看的字段,也是最具误导性的字段。这些值是为运行时(runtime)设计的,旨在决定下一步该做什么:这一轮是否完成了?是否请求了工具?是否超出了预算?安全检查是否介入了?它们并不是为了让开发者重建答案出错的原因而设计的,而这两者之间的差异,正是生产团队耗费整个下午的时间所在。

流式 JSON 解析器:Token 与类型化对象之间的鸿沟

· 阅读需 13 分钟
Tian Pan
Software Engineer

模型正在逐个 Token 地输出 JSON。你的 UI 希望在字段出现的那一刻就进行渲染 —— 在冗长的回答正文之前显示置信度得分,或者在模型填充工具调用参数时实时显示它们。接着,有人尝试在每个数据块(chunk)上调用 JSON.parse,结果整个系统就崩溃了,因为 JSON.parse 是“全或无”的。它需要一个结构完整的文档才能返回任何结果。在模型输出闭合括号之前,你什么也显示不出来。

这不是一个可以通过 try/catch 解决的解析器问题。标准 JSON 解析器是针对内容长度已知的 HTTP 响应设计的。部分输入并不是它所建模的状态 —— 而是被视为“输入错误”。当你将 Token 流视为 HTTP 正文处理时,你继承了三十年来“文档要么完整,要么无效”的传统,而你的 UI 则为此付出了代价。

并行工具扇出的结构化并发:谁来负责部分失败?

· 阅读需 13 分钟
Tian Pan
Software Engineer

当你的智能体(Agent)扇出五个并行工具调用——跨三个索引进行搜索、查询两个数据库、调用一个外部 API——的那一刻,你已经跨越了一道无形的界限。你不再是在编写“提示-响应”(prompt-and-response)代码,而是在编写一个并发程序。大多数智能体框架都假装你没有在这样做,而账单会在凌晨 2 点准时送达。

这种假象是令人愉悦的。规划器(Planner)发出一个工具调用列表,运行时环境(Runtime)启动它们,收集返回的任何结果,最后由规划器消费这些汇总数据。从万英尺的高空俯瞰,这就像一个扇出 / 扇入(fan-out / fan-in)流水线,大多数团队在生产环境给他们上课之前,也确实是这样对待它的。问题在于,二十年的并发编程研究——部分失败语义(partial-failure semantics)、结构化取消(structured cancellation)、背压(backpressure)、确定性错误归因(deterministic error attribution)——已经解决了你即将重新发现的那些失败模式。而你的智能体框架在默认情况下,没有引入其中的任何一项。

系统提示词作为代码、配置或数据:影响全局的架构决策

· 阅读需 13 分钟
Tian Pan
Software Engineer

上个季度我交流过的一个团队发布了一个客户支持智能体,其系统提示词存储在 Postgres 的一行中,每个租户一行。这个方案听起来很合理:企业客户要求定制语气,“让提示词可编辑”是实现这一目标最廉价的方式。六个月后,发生了三件事。评估套件从 200 个案例膨胀到了 11,000 个,因为每个租户的提示词现在都需要自己的回归测试集。提示词更新工作流悄然变成了一个没有审核的写入路径,因为产品负责人被赋予了对表的直接访问权限。此外,由于部署流水线根本不知道提示词发生了变化,一个韩国租户提示词中损坏的 UTF-8 字符导致该租户的聊天机器人下线了两天,却没人察觉。

这些结果都不是需求强制导致的。它们是由一个无人刻意做出的架构决策所强制导致的:系统提示词存放在哪里?在代码中?在配置文件中?还是在数据库行中?团队选择了“数据库”,因为这是实现功能最快的路径,而后果在接下来的几个月里级联影响到了每一个相邻系统。

Tokenizer Churn:你的“兼容”模型升级中隐藏的破坏性变更

· 阅读需 13 分钟
Tian Pan
Software Engineer

供应商声称这次升级是无缝替换。API 契约保持不变。配置中的模型名称几乎没变。一周后,你的上下文窗口防御机制(context-window guard)开始在以前从未触发过的提示词(prompts)上报警,你的停止序列(stop-sequence)正则匹配在了错误的位置,而你的少量样本(few-shot)示例之一开始产生一个极其自信的错误答案,而你的评估套件恰好没有覆盖到这一点。没有人动过提示词。没有人动过温度参数(temperature)。有人悄悄重新训练了分词器(tokenizer)。

分词器更改是供应商不会称之为“破坏性”(breaking)的破坏性更改。API 层面保持了字节级稳定,SDK 没有升级主版本,发布说明中提到了“改进的指令遵循能力”——但从你的输入字符串到模型实际看到的整数序列的映射函数已经被替换了。你的代码关于文本如何转换为标记(token)的每一个假设现在都出现了微妙的偏差。这种隐形代价是,在有人重新通过 count_tokens 运行标准提示词并发现答案之前,你会经历两周“感觉模型不太一样”的困惑期。

拒绝还是上报:置信度门控 AI 中的双阈值问题

· 阅读需 14 分钟
Tian Pan
Software Engineer

大多数生产环境中的 AI 功能在发布时只带有一个置信度阈值。在阈值之上,模型给出回答;在阈值之下,用户会得到一句生硬的“我不确定”。这个单一的数值同时承担着两个完全不同的任务,这就是为什么即便你对已回答查询的准确率看起来不错,但信任度指标却已经连续两个季度下滑的原因。

正确的设计至少应该有两个切分点。一个“弃权”(abstain)阈值设在低位:低于该值时,模型拒绝回答,因为此时保持沉默比给出任何答案都更有价值。一个“升级”(escalate)阈值设在中间:在两个切分点之间,系统将案例交给人工审核员,而不是直接将其丢弃。将它们合并成一个刻度盘,你发布的产品在出错时和不确定时会让人感到同样无用——在用户只需打开另一个标签页就能找到免费替代品的市场中,这是最糟糕的处境。

这并不是什么新鲜想法。拒绝选项分类器(reject-option classifier)的文献自 20 世纪 70 年代以来就一直在主张拆分阈值,将“歧义”拒绝(输入介于已知类别之间)与“距离”拒绝(输入远离任何训练数据)区分开来。生产环境中的 AI 团队总是在以惨痛的方式重新学习这一教训,通常是在首次发布大约六个月后,当支持队列中挤满了询问“这玩意儿是坏了还是怎么了”的人时。

供应商可迁移性税:为什么“我们可以更换模型”是每季度的成本项,而非一个勾选项

· 阅读需 12 分钟
Tian Pan
Software Engineer

在过去六个月中,我审计过的每一个团队都声称自己是供应商无关的。但实际上没有一个团队能做到。在评估套件中得分最高的系统提示词之所以表现出色,是因为它倾向于单一供应商的分词器行为、JSON 模式协议、拒绝节奏以及停止序列处理 —— 而编写该提示词的团队甚至无法说出其中哪些偏差在起作用。当 CFO 询问为什么采购清单上更便宜的模型不能直接替换时,诚实的回答是:这需要两个工程师季度的提示词重新调优以及对每个评估进行完全的基准重设。这不仅仅是一个勾选项。它是一个季度的成本项。

一直困扰团队的心智模型是将供应商可移植性视为一次性的架构决策。你添加了一个抽象层,在配置中写了一个 model: 字段,以此自我庆贺,然后继续前进。一年后,当供应商提价、发布弃用通知,或者在你关注的某个类别上出现了一周糟糕的拒绝服务时,你发现那个抽象层只是一个围绕特定模型提示词的薄包装。你买到的可移植性是语法上的。而你真正需要的是行为上的可移植性,且行为上的可移植性在你停止支付的那一刻就会衰减。

你的模型更新是一次破坏性变更:你欠集成商的“行为变更日志”

· 阅读需 14 分钟
Tian Pan
Software Engineer

某家厂商在周二下午向模型别名推送了一个“小幅更新”。到了周四,四家客户公司正在进行事件响应。他们本周都没有部署代码。他们的仪表板上没有任何关于延迟、错误率或任何其他基础设施维度的指标退化。改变的是,在他们固定的别名背后的模型开始返回略有不同的句子、略有不同的 JSON 以及略有不同的拒绝——而他们的团队针对旧行为编写的每一个提示词(Prompt)现在都成了一份没人履行的合约。

这种不对称性就是问题的核心。供应商将这次发布视为一次部署:经过内部测试,通过了一些聚合评估,并在维护窗口内逐步推向 100%。而消费端将其视为一次语义化版本(semver)违规:一个依赖项在生产环境中自动升级,却没有更改其版本字符串,随后最终用户的错误报告接踵而至,主题还带着轻快的“我们这边什么都没改”。