跳到主要内容

79 篇博文 含有标签「architecture」

查看所有标签

护栏系统的自研与外购:内容审查 API 已成为安全关键路径上的核心依赖

· 阅读需 11 分钟
Tian Pan
Software Engineer

你为了加快上线速度而购买的托管审核 API,现在已经成了你安全关键路径上的一个同步外部依赖。这句话并非观点——而是被如实重绘后的架构图。在供应商服务降级的日子里,你面临两个选择,且两者都很糟糕:故障开启(fail open),此时护栏在最需要的时候恰恰失效了;或者故障关闭(fail closed),护栏的故障直接导致了功能的停摆。大多数团队是在事故发生时才发现自己选了哪一个,而不是在此之前。

团队选择供应商的原因并非因为懒惰。在内部构建内容分类器、提示词注入检测器和 PII 脱敏工具,看起来像是背离实际产品开发的六个月漫长弯路,而供应商通常提供免费额度和五分钟即可完成的集成。这种集成确实很快。但随之而来的架构后果是,第三方现在介入了每一次面向用户的生成请求路径,其可用性、延迟和行为特征是你无法控制且未曾建模的。

这篇文章的主旨是将这一决定视为架构决策,而非采购决策。

聊天历史是数据库。别再把它当成滚动回溯了。

· 阅读需 12 分钟
Tian Pan
Software Engineer

针对 Agent 类产品,生产环境下最常见的投诉通常是某种形式的“它忘记了我们刚才说的话”。这种投诉往往出现在第 8 轮、第 15 轮或第 30 轮——绝不会在第 2 轮出现。团队的第一反应往往如出一辙:扩大上下文窗口。但这其实是错误的直觉,因为 Bug 不在模型本身,而在于团队将对话历史视为了终端的滚动回放(scrollback)——追加一行、渲染尾部、满了就截断。实际上,他们不知不觉中构建的是一个读多写少的数据库,具有仅追加写入、热工作集、隐藏在截断规则中的淘汰策略,以及取决于所提问题类型的查询模式。一旦你接受了这一点,整个问题的本质就改变了。

滚动回放模式之所以如此诱人,是因为聊天界面看起来就像一份对话记录。消息向下流动,用户自上而下阅读,而喂给模型的自然方式就是将最新的 N 轮对话拼接到提示词中。这种数据结构感觉是“免费”的:没有 Schema,没有索引,没有查询——只需追加、渲染、重复。在最初的几轮对话中,任何架构都表现良好。模型拥有完整的上下文,费用低廉,演示效果极佳。

你的智能体有两条发布流水线,而非一条

· 阅读需 12 分钟
Tian Pan
Software Engineer

我合作过的一个团队在周三下午发布了一个“微小的提示词调整”。同一个 PR 还向智能体注册中心添加了一个新工具——一个对内部管理 API 的便利封装,提示词现在偶尔会调用它。评估套件通过了。金丝雀发布看起来也很正常。到周四早上,由于智能体处理了一个包含提示词注入攻击的支持工单,一名客户的计费记录被修改了。审计追踪显示,管理工具完全按照设计运行。值班工程师的第一反应——回滚提示词——毫无用处,因为凭证已经使用,数据行已经写入。

复盘报告将其定性为安全审查失败。其实不是。这是发布流水线的失败。团队通过相同的审查、相同的关卡和相同的回滚逻辑,发布了两个完全不同的资产类别——对模型的行为引导和授予智能体的新权限,就好像它们是同一种变更一样。它们并不是。一旦你将它们视为两个流水线,大多数关于“智能体治理”的争论就会变得清晰得多。

确定性预算:将随机性视为按层面的分配,而非全局开关

· 阅读需 12 分钟
Tian Pan
Software Engineer

Temperature 之争是 AI 工程中最具宗教色彩的争论,也是最没效率的争论之一。每个团队都会形成两个阵营:决定论者希望将所有地方的 Temperature 都固定为 0,因为他们无法调试不稳定的系统;而创意论者则希望调高它,因为这样输出结果感觉更“有灵性”。两者都错了,因为他们都在错误的层面上回答这个问题。Temperature 不是一个全局设置。它是一项预算——就像任何预算一样,它应该被分配,而不是被宣告。

高效的框架很简单:系统中每个模型调用都有其目的,随机性要么在那个层面(surface)发挥作用,要么就不该存在。决定下一个调用哪个工具的规划器(planner)无法从变化中获益;选错一个工具就是调试噩梦,而且没有任何创意上的好处。如果一万个用户看到的摘要措辞都一模一样,那么为他们总结搜索结果的响应合成层很快就会显得呆板——SEO 团队最终会标记这些样板内容。一个让模型提出备选方案供人类选择的头脑风暴层,在 Temperature 为 0 时表现反而 更糟;多样性本身就是其核心功能。

如果你无法清晰地说明随机性在特定调用位置的作用,你就不应该为此付费。

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

· 阅读需 12 分钟
Tian Pan
Software Engineer

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

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

投机采样(Speculative Decoding)是一项流式传输协议决策,而非推理优化

· 阅读需 14 分钟
Tian Pan
Software Engineer

每一篇关于投机解码(Speculative Decoding)的论文中提到的“等效输出”保证,其实是对 token 分布的保证,而不是对用户所见内容的保证。仔细阅读证明过程,你会发现一个纯粹的数学等效性:拒绝采样的接受标准旨在确保投机后的输出分布与目标模型(target model)独立生成的分布完全一致。这一保证约束的是离开推理引擎的字节流,而对于五百毫秒前已经到达用户屏幕、现在却必须收回的字节,它只字未提。

如果你在小模型生成草稿 token 的那一刻就将其流式传输给客户端,那么每当验证器拒绝某个后缀时,你实际上是在对自己的用户进行 A/B 测试。半个段落会自行重写。函数名在 IDE 已经完成语法高亮后发生改变。语音合成(TTS)可能已经读出了“答案很可能是否定的”,随后验证器却将其替换为“答案是肯定的,但有几点需要注意”。数学逻辑上,最终分布与慢速路径一致;但从用户体验来看,他们亲眼目睹了模型在公开场合“反悔”。

这是投机解码中未被计入加速倍数的部分。它也将所谓的“免费 3 倍吞吐量”变成了一个没人预料到的、长达一个半季度的流式协议开发工作。

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

· 阅读需 13 分钟
Tian Pan
Software Engineer

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

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

95% 可靠性幻觉:为什么你的 10 步 Agent 在 40% 的情况下会失败

· 阅读需 13 分钟
Tian Pan
Software Engineer

在几乎每一个智能体(agent)项目评审中,都有一个会让谈话戛然而止的时刻。有人画了一张小图表:y 轴是端到端任务成功率,x 轴是工具使用的步骤数。曲线急剧下降。全场陷入沉默,因为屋子里的每个人之前都在争论提示词(prompt)、模型和检索策略——而这张图表在告诉大家,所有的这些争论,都抵不过一个简单的事实:这条链条上的环节太多了。

这一数学原理是可靠性工程中最古老的结论之一,如今被移植到了一个自以为是的新领域。如果流水线中的每一步都以概率 p 独立成功,那么 n 个串联步骤的成功概率就是 p 的 n 次方。代入一些在进度报告中听起来还不错的数字:单步可靠性 95%,十个步骤,端到端成功率就只有 60%。二十步降至 36%。三十步则降至 21%。那个“95% 的时间都能正常工作”的智能体,实际上在三分之一的真实用户请求中都会失败,因为真实的用户请求绝非只有单个步骤。

DLP 应存在于你的 AI 网关中,而非生搬硬套到每个应用里

· 阅读需 13 分钟
Tian Pan
Software Engineer

第一个内部 LLM 网关的构建通常是出于那些枯燥的原因:成本归因,以便财务可以回答“哪个团队花了推理预算”;速率限制,防止某个失控的脚本烧掉月度配额;以及供应商故障转移,确保 OpenAI 的小故障不会导致助手挂掉。数据泄露防护 (DLP) 虽然出现在幻灯片上,但交付时却变成了“每个应用团队在调用模型前应自行脱敏敏感字段”。六个月后,生产环境中有九个应用,三个维护得半吊子的脱敏库(带有微妙差异的正则表达式集),两个完全绕过网关“仅用于测试”的原型,以及一起 Prompt 中包含客户数据的事故——而这本该是由每个人的中间件来防止的,因为并没有人的中间件是规范的出站口。

这不是工具问题,而是架构错误。DLP 是一种出站控制,而出站控制只有在路径强制执行时才有效。当你让应用团队负责脱敏时,你就放弃了让 DLP 发挥作用的特性——即敏感数据只能从一个地方流出,且你可以证明流出了什么。2025 年的 LayerX 安全报告用大多数团队尚未意识到的数据说明了问题的规模:2025 年初,与生成式 AI (GenAI) 相关的 DLP 事故增加了一倍多,目前占 SaaS 流量中所有数据安全事故的 14%,员工平均每天向 GenAI 工具粘贴 6.8 次内容,其中超过一半包含公司信息。影子路径默认在胜出。

现在,推理速度已经快过你的数据库了

· 阅读需 12 分钟
Tian Pan
Software Engineer

打开任何 2024 年时代的 AI 功能链路追踪 (trace),模型调用就像是一头巨鲸。八百毫秒的生成时间,包裹在检索、鉴权和数据库查询组成的薄壳中,后者的时间几乎可以忽略不计。那一年的每一个架构决策——缓存、预取、流式 UX——都是为了隐藏那头“巨鲸”。

现在,查看运行在 2026 年推理栈上的相同功能的链路追踪。那头巨鲸已经变成了一只海豚。缓存后的预填充 (prefill) 在 180ms 内返回第一个 token。解码 (decode) 以每秒 120 个 token 的速度流式传输。模型不再是慢节点。你自己的基础设施才是,而且大部分基础设施还没有意识到这一点。

这种顺序重排是今年最重要的性能转变,也是各团队一直反应不足的一个。现在,AI 请求的 p99 底限是由特征存储 (feature store) 调用、鉴权中间件以及那些一直都很慢的 Postgres 查询决定的——在模型占据九成预算时,没人关心这些。

飞行中转向:无需重启即可重定向长时运行的智能体

· 阅读需 11 分钟
Tian Pan
Software Engineer

观察一个开发者使用代理型 IDE 二十分钟,你会看到同样的小剧场上演三次。代理开始了一个长任务。在两次工具调用后,用户意识到他们想要一个函数式组件而不是类,或者想要 v2 接口而不是 v1,亦或是想用 Vitest 而不是 Jest 编写测试。他们手中只有一个杠杆:红色的停止按钮。他们按了下去。代理在编辑中途阵亡。他们复制并粘贴上一个提示词,加上修正,然后为前八分钟的工作支付了两次费用。

中止按钮是错误的交互设计。它将“我想调整计划”和“我想丢弃这次运行”视为同一种动作。在实践中,它们就像方向盘和弹射座椅一样迥异,而将两者混为一谈,正是为什么许多代理产品在任务耗时超过一屏输出时就显得脆弱不堪的原因。

在写第一个 Prompt 之前,先设计好你的 Agent 状态机

· 阅读需 11 分钟
Tian Pan
Software Engineer

大多数工程师在构建第一个 LLM agent 时,都会遵循相同的流程:写一个系统提示词,添加一个调用模型的循环,撒上一些工具调用逻辑,然后看着它在简单的测试用例上运行。六周后,这个 agent 变成了一团难以理解的嵌套条件、粘贴在 f-string 里的 prompt 片段,以及散落在三个文件中的重试逻辑。添加一个功能需要通读整个代码。遇到生产 bug 就得盯着一个上千 token 的上下文窗口,试图重建模型当时在"想"什么。

这就是"意大利面式 agent"问题,在以 prompt 为起点而非设计为起点的团队中几乎普遍存在。解决方案不是更好的提示技巧,也不是换一个框架,而是一种纪律:在写第一个 prompt 之前,先设计好状态机