跳到主要内容

96 篇博文 含有标签「architecture」

查看所有标签

无人负责的对话:问责制如何在智能体移交链中消散

· 阅读需 10 分钟
Tian Pan
Software Engineer

一个支持请求发送了进来:“我的订阅被扣了两次费,请取消其中一个并退款。”一个路由代理将其归类为计费问题并进行了移交。一位计费专员查看了账户,确认了两笔扣费,并将退款操作移交给了一个工具调用子代理。该子代理执行了一次退款,随后一个总结代理给客户回信:“搞定了——你的重复扣费已退还。”每一个步骤单独看都是正确的。但实际上客户被扣了三次费,而不是两次。其中一笔重复扣费仍在他们的卡上,而总结代理无从得知这一点,因为当对话传导到它那里时,“ 2 ”这个数字已经在上游的四个步骤中固化成了事实。

让我们进行一次复盘。这是谁的 Bug?路由代理分类正确。计费专员读取了它收到的两笔扣费。工具代理准确地按照指令进行了退款。总结代理进行了准确的总结。单独调出每个代理的 Trace,每一个都通过了。系统失败了,但没有任何组件出错。这就是“无主对话”,它是多代理架构中定义性的操作性失败——不是模型太笨,而是责任在移交链条中不断扩散,直到没有任何一个代理需要对用户实际体验到的结果负责。

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

· 阅读需 12 分钟
Tian Pan
Software Engineer

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

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

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

Agent 的端口与适配器架构:为什么你的工具 Schema 应该比供应商更长寿

· 阅读需 11 分钟
Tian Pan
Software Engineer

这是一个在每一个发布 Agent 的团队中都会重复发生的迁移故事。你基于某个供应商的 SDK 构建了你的 Agent。工具定义以该供应商预期的 JSON Schema 形式存在。工具结果被格式化为该供应商的消息结构。接着,某些原因迫使你做出改变 —— 竞争对手发布了更好的模型,采购部门为了冗余要求引入第二个供应商,或者一个 MCP 服务器取代了手写的集成 —— 然后你发现迁移的真正工作量:它不仅仅是一个 API 客户端。它是代码库中的每一个工具定义、每一个结果格式化器、每一个重试处理器以及每一个测试固件。

失败的原因并不是你选错了供应商。而是你让别人的序列化格式变成了你的内部架构。针对这个问题,早在二十年前就有了一个答案 —— Alistair Cockburn 的六边形架构,更广为人知的名字是“端口与适配器” —— 而 Agent 系统是它多年来最引人注目的新用例。

让所有模型都变得平庸的抽象层

· 阅读需 11 分钟
Tian Pan
Software Engineer

在你的代码库中的某个地方,存在一个网关,让你只需更改一个字符串就能将 claude-sonnet 替换为 gpt-5。你的架构评审对此赞不绝口。你的 CTO 因此睡得更香。然而,它正在悄悄让你付出代价:损失了 90% 的缓存折扣、Schema 强制输出,以及那些区分优秀生产模型与平庸模型的推理努力(reasoning-effort)调节旋钮。

这就是统一 LLM API 未被宣传的代价。每一个承诺“一行代码切换供应商”的抽象层,都是通过将每个供应商的功能投射到它们共有的子集来实现的——而那些落在这个子集之外的功能,恰恰是供应商竞争最激烈的地方。Prompt 缓存语义、结构化输出强制、扩展思考预算、服务端工具执行:这些才是决定你实际成本和质量曲线的关键杠杆,而你的网关可能正在默默地将它们弃之不用。

你的 Agent 从未读过的 ADR

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的团队在 2024 年拒绝了微服务拆分。当时开了一个两小时的会议,Slack 上的讨论异常激烈,还有一份对比运营成本的电子表格。结论是坚定的:在平台团队交付多区域部署之前,保持单体架构不变。当时在场的每一个人都还记得这件事。

你的编码 Agent 当时并不在场。上周二,它再次提议拆分——语气自信,带着一份干净的迁移计划和一份论据充分的设计文档。再上个周二,另一个 Agent 会话建议提取计费服务。下周,第三个 Agent 会建议用你评估过并两次拒绝的消息中间件来替换你定制的任务队列。就它们所能看到的信息而言,它们都没有错。它们只是看不到决策的价值所在,因为解决这些问题的推理过程存在于已过期的 Slack 线程、未记录的会议以及两名工程师的脑海中,而其中一人已经离职了。

这是 Agent 辅助工程的一种悄然发生的失败模式:已解决的问题正以机器速度被重新审议。而解决办法是大多数团队在十年前归类为“官僚主义、有则更好”的一项实践——架构决策记录(ADR)。

黑板模式回归:1980 年代的 AI 如何处理多智能体协作

· 阅读需 12 分钟
Tian Pan
Software Engineer

如果你的 Agent 团队通过一个共享的计划文件、仓库或设计文档进行协作,每个人都在上面读写,那么恭喜你:你重新发明了黑板架构 (blackboard architecture)。这种架构在 1975 年曾是顶尖技术。令人尴尬的不是“重新发明”本身——好的想法值得回归。尴尬之处在于,原始架构有三个承重组件,而大多数现代 Agent 技术栈只重建了其中之一。

Hearsay-II 是 1971 年至 1976 年间在卡内基梅隆大学构建、由 DARPA 资助的语音理解系统。它面临着一个听起来很熟悉的问题:许多专业但不可靠的专家——声学分析器、语法预测器、语义评分器——没有一个能独立解决问题,它们都需要建立在彼此的局部推测之上。由此产生的架构包含一个共享工作区(黑板)、独立的专家(知识源)以及一个调度程序,调度程序在每一步决定接下来应该执行哪个专家的贡献。五十年后,将 LLM Agent 连接在一起的团队正趋向于同样的形态——一个主 Agent、一组执行者、一个共享产出物——并陷入了黑板架构文献在大多数人出生前就已命名并解决的失败模式。

你的智能体需要的是监督者,而不是重试循环

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的 Agent 在一个包含十二个步骤的任务的第七步挂掉了。框架捕获了异常,经过指数退避(exponential backoff)后进行了重试。它重试了该步骤 —— 但使用的是同样的上下文窗口(context window),其中已经堆积了三次失败的工具调用、一段解析了一半的错误消息,以及一个模型已经放弃的计划。当然,重试也失败了,因为重试是一场赌注,赌的是世界发生了变化,而那个 Agent 的世界没有任何改变。需要改变的是 Agent 的状态 —— 而任何 Agent 框架中的重试策略都不会做出这种决定。

Erlang 的 OTP 库在三十年前就为必须运行数十年的电话交换机制定了这项决策。主管树(supervisor trees)背后的洞察力从来不是“在崩溃时重启”。而是“如何恢复”是一个与“执行工作”完全解耦的关注点,它由一个独立的进程负责,并排列在一个层级结构中,每一层都对恢复的含义有更深一点的理解。现如今的大多数 Agent 框架将重试强加到单个调用上,这就像在电话交换机的每一行代码周围都套上 try/catch 一样。它们真正需要的是这种层级结构。

你的内部框架是一种低资源语言

· 阅读需 10 分钟
Tian Pan
Software Engineer

让编程智能体(coding agent)构建一个 React 组件,它第一次尝试就能写出地道的、基于 Hook 的、带有无障碍标注的代码。让同一个智能体使用你公司的内部 ORM —— 那个平台团队维护了六年、拥有出色文档和上百个内部用户的框架 —— 它就会幻觉出不存在的方法,从其他库里发明配置选项,并自信地交付出基于它臆造的 API 编写的代码,而这些代码根本无法通过编译。

这种差异并非源于质量。你的 ORM 可能比模型能完美处理的一半开源库设计得都要好。差异在于训练数据。React 背后有数百万个公开仓库;而你的框架则一个都没有。在自然语言处理(NLP)的术语中,你的内部框架是一种低资源语言(low-resource language) —— NLP 研究人员针对低资源语言记录的每一个后果,现在都适用于你的代码库。

自研还是采购的界限已然改变:当供应商原语吞噬你的基础设施时,如何抉择 AI 功能

· 阅读需 11 分钟
Tian Pan
Software Engineer

18 个月前,“我们构建了自己的检索流水线”在架构评审中还是一个非常合理的说法。你拥有分块策略、经过基准测试的嵌入模型、调优过的向量数据库、重排序器,以及一个由 3 名工程师耗时一个季度才搞定的上下文填充启发式算法。那一套技术栈曾是真正的差异化基础设施。而今天,同样的能力只需一次托管的工具调用:将文件上传到向量数据库,将其附加到请求中,供应商就会完成解析、分块、嵌入、存储、检索和重排序——所有这些都隐藏在一个 API 背后。曾经需要 3 名工程师开发一个季度的成果,现在只是一个配置对象。

这就是目前构建 AI 产品令人不安的模式。自建与外购之间的界限并非固定,它在移动,而且只向一个方向移动。每隔几个月,模型供应商就会发布一个原生功能(primitive),蚕食掉你曾经拥有的一个层级:记忆、检索、结构化输出、工具路由,甚至是多步编排。上个季度还让你引以为傲的基础设施,这个季度就成了竞争对手可以免费获得的东西,而且默认配置更好,延迟底线更低,因为它就运行在供应商自己的数据中心内部。

一种本能反应是将其视为需要防范的威胁。但这种思维框架是错误的。供应商吸收通用基础设施实际上是在帮你——它帮你删除了你本就不想要的维护工作。真正的问题在于,你选择构建的东西是位于不断上升的水位线之上还是之下。大多数团队从未明确做出这个决策。他们为了演示需求构建了一切,一年后才发现 70% 的代码库是在重新实现供应商现在提供的原生功能,而那 30% 真正具有防御性的部分反而因为缺乏关注而枯萎。

那些将模型未完成的残缺回答存入数据库的流式 UI

· 阅读需 12 分钟
Tian Pan
Software Engineer

这份事后分析读起来像是一份幻觉报告。一名用户根据一份语气笃定的建议采取了行动,但结果证明该建议是错误的——这种错误在模型正常完成输出的情况下是不会出现的。然而,追踪记录显示模型并未完成输出。在预期的 800 个 Token 中,供应商连接在第 412 个 Token 时断开了。客户端的错误处理程序记录了这次失败。但随着 Token 的到达,持久化的部分消息已被写入对话历史,在用户的 UI 中看起来与其他完整的回答毫无二致。于是用户采信了它。支持团队将该工单归类为内容质量问题,花了整整两周时间才将其转交给平台团队。

这条链路中没有任何环节属于模型故障。模型对生成的 412 个 Token 表现得非常正确。失败的原因在于流式 UI 和持久化对话历史在“什么才算是一条消息”的问题上产生了隐秘的分歧。而正是这种流式传输本应缓解的故障模式,导致这一分歧成为了权威记录。

这是乐观渲染(Optimistic Rendering)与持久化存储之间的契约。大多数聊天产品只是从教程或框架中继承了这种模式,而从未将其视为一项契约,这种鸿沟最终表现为一系列看似模型 Bug 实则不然的尾部故障。

你的 LLM 抄不准的那个账号

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个客服智能体读完工单、拉出账户、总结了最近的活动、发起了退款。退款落到了错误的账户上。不是被凭空捏造出来的账户——是一个真实存在的、只差一位数的账户。模型写下了 acct_7H9j2,可这位客户的真实记录是 acct_7H9j3。trace 干净得无可指摘:搜索调用拿到了正确的记录,总结调用产出了正确的摘要,退款调用毫无报错地完成。每一步都成功了。钱落进了错的人手里。

这并不是事故复盘里通常说的那种"幻觉"。模型没有凭空发明一个客户。它把一个真实存在的客户的两个字符换错了位置——这是另一类失败,一类你的评测集大概率从没抓到过的失败,因为你测试样本里的合成标识符在构造上本就是唯一的。两个账号同时出现在上下文里、前三个字符相同,而语言模型——一个从未被训练成"忠实复制随机字符串"的 token 预测器——挑错了那个。

教训是结构性的,不是行为性的。模型没有任何专门为标识符设计的注意力机制。在模型眼里,acct_7H9j2 只是一串子词 token,它们的延续概率会随窗口中其他每一个 token 漂移。一旦上下文里出现了一个"近亲"标识符,模型就只差一次坏采样,就会做出一次悄无声息的替换——而 harness 会毫不犹豫地把它执行下去。

无法收敛的验证器循环

· 阅读需 12 分钟
Tian Pan
Software Engineer

代理系统里最贵的 bug 是那种没有任何报错的 bug。Worker 提出一个草稿。Verifier 用一段反馈把它驳回。Worker 修改。Verifier 再次驳回。循环一直转下去,trace 越来越长,账单越爬越高,而从外面看,这个系统似乎在 工作——而且很尽职,因为两个模型都在干各自该干的活儿。没有人定价进去的是:验证器的接受标准在不同调用之间并不固定。worker 在追的那个目标本身在动,而循环没有任何收敛保证。

你以为自己交付的是"迭代到满意为止",其实你交付的是一次对极值可能根本不存在的空间的搜索。