跳到主要内容

702 篇博文 含有标签「llm」

查看所有标签

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

· 阅读需 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)违规:一个依赖项在生产环境中自动升级,却没有更改其版本字符串,随后最终用户的错误报告接踵而至,主题还带着轻快的“我们这边什么都没改”。

推理预算委员会:Token 支出突破七位数时的治理之道

· 阅读需 13 分钟
Tian Pan
Software Engineer

在每月 50,000 美元的水平时,你基础设施账单上的“计算 + Token”这一项只是可以忽略不计的零头。但当每月达到 5,000,000 美元时,它就是一个 CFO 级别的问题。这两个阶段之间的转变并不是渐进的——它是组织讨论模型支出方式的一种“相变”,而大多数工程组织对于随之而来的社会和政治工作都准备不足。账单依然是那简单的一行;但围绕它的对话却不再简单。

改变的是谁有资格问“为什么”。当三个产品团队共享一个 API Key 和一个预留容量时,每一个配额争论的结构都是相同的:某人正以牺牲他人的利益为代价获胜,而没有中立方来主持公道。当一个团队的发布第一次因为另一个团队上线了一个“话痨”智能体(agent)而受到限制时,整个工程组织会立刻感受到治理机构缺失带来的痛苦。在压力之下召开会议并凭空发明流程,是设计流程最糟糕的时机。

Prompt 迭代中的“局部最大值”陷阱:如何判断你调错了地方

· 阅读需 12 分钟
Tian Pan
Software Engineer

在一个严肃的 LLM 项目进行到第六周时,总会有那么一个时刻,Prompt 迭代日志开始变得像一本心理治疗日志。每一次微调都是在用一种失败模式交换另一种。增加一个更严格的 “do not”(不要)条款,模型在以前能处理的情况下就开始回避。放软语气,另一类幻觉又回来了。评测得分板在三四个点的范围内徘徊,拒绝突破。有人说,“让我再试一次重新排序,”于是又是半天时间烟消云散。

这就是局部最优陷阱(local-maximum trap)。团队正在爬山,但这山头已经到顶了。残忍的是,这座山是真的——每一次 Prompt 的改动确实会在某些案例子集上产生可衡量的变化,而这正是让每个人持续微调的信号。被忽略的是:上方的天花板根本不是 Prompt 的天花板。

主权崩塌:记录你的 Prompt 究竟去了哪里

· 阅读需 11 分钟
Tian Pan
Software Engineer

监管机构问了一个简单的问题:“对于上周二 UTC 时间 14:32 提交的这个特定用户 Prompt,请证明该请求及其派生状态经过了哪些管辖区。”

你的应用日志显示 model=claude-sonnet-4-5, region=eu-west-1, latency=2.1s。你的网关日志也显示同样的内容。供应商的发票确认了请求确实发生了。但这些都无法回答上述问题。该请求进入了一个由欧盟托管的网关,被转发到美国区域的主端点,但在一次区域性故障期间故障转移到了新加坡,并预热了一个第三方 GPU 池上的 KV 缓存,而该 GPU 池的数据驻留声明仅存在于供应商的脚注中。你所需要的审计追踪存在于一个你的团队并不掌握的层级中。

这就是主权崩溃:即你的合同中关于数据位置的承诺与你的运行时在事后能实际证明的情况之间的差距。合规主张的强度取决于链路中最薄弱的那行日志。

RAG 流水线中被你忽略的查询重写层

· 阅读需 12 分钟
Tian Pan
Software Engineer

当 RAG 系统回答错误时,大多数团队的第一反应是归咎于编码器(encoder)。更换更大的嵌入模型(embedding model)。尝试针对特定领域微调过的模型。增加维度。三个迭代周期(sprint)后,召回率曲线只提升了几个百分点,而用户的投诉看起来还是老样子。

诊断错了。大多数检索失败并非嵌入失败。它们是查询形状(query-shape)失败——在编码器运行之前就存在的词汇不匹配,无论如何调整向量都无法修复。

用户输入“如何取消”。相关的文档标题却是“订阅生命周期管理”,并使用了“终止”、“计费周期结束”和“服务停用”等词汇。世界上没有任何编码器能靠词汇运气将这两个字符串拉入同一个邻域。余弦相似度(cosine similarity)的差距是真实存在的,它存在于输入中,而非模型中。位于检索之前的查询重写层是大多数流水线跳过的步骤,随后他们却要花一个季度的时间试图在下游进行补偿。

Agent 的链路追踪采样:每日千万级 Span 中哪些值得保留

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个 Web 服务请求在繁忙时段产生 5 个 Span。而一个现代的 Agent 会话产生 50 个,如果 Planner 决定递归,有时甚至会产生 1000 个。你们平台团队从微服务时代复制粘贴过来的 1% 均匀采样器,从定义上就会丢弃你真正关心的稀有故障——因为故障是稀有的,而均匀采样对稀有性没有任何判断力。

“我们对 Agent 拥有完全的可观测性”的真实版本听起来与营销版本不同。它听起来应该是:我们保留重要的 Trace,丢弃不重要的,并且我们预先知道哪些是哪些。这句话中的每一个词都至关重要,而那些在账单寄来之前一直忽视采样设计的平台团队,现在正被迫反向学习这一学科——在成本压力下,以及在经历了一个季度的故障之后,这些故障本应“在数据中”,但在有人查看之前就被剔除了。

智能体无法察觉的死锁:生成计划中的循环工具依赖

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个规划器智能体输出了七个步骤。每一个看起来都很合理。编排器分发了这些步骤,前三个返回了值,第四个在等待第五个,第五个在等待第七个,而第七个——埋藏在规划器散文般描述的第三行里——正静静地等待着第四个。没有任何东西被锁定。没有触发过任何 EDEADLK。智能体消耗了 40,000 个 token 来推理为什么第四步“花费的时间比预期长”,最终以一个温和、合理的道歉向用户宣告放弃。

这就是你的智能体无法察觉的死锁。它不是操作系统课程中的那种经典死锁——这里没有互斥锁(mutex),没有内核可以内省的资源图,也没有你的技术栈中任何人能识别的持有者或等待者。依赖关系存在于规划器生成的英语句子中,循环形成于潜在语义而非任何数据结构中,而故障模式看起来与“模型正在努力思考”无异。经典的死锁检测在这里毫无用处,但代价是相同的:工作流停滞,token 蒸发,而你的 trace 什么也不会告诉你。