跳到主要内容

当你的智能体框架成为 Bug 时

· 阅读需 10 分钟
Tian Pan
Software Engineer

高层级智能体框架承诺将三天的集成工作转化为三小时的原型开发。这个承诺是真实的。问题在于接下来发生的事情:在一家开发 AI 驱动的浏览器测试智能体的公司中,工程师们在进入生产阶段六个月后发现,他们花在调试 LangChain 上的时间竟然和开发功能的时间一样多。他们的解决方案很彻底——完全弃用了框架,并回退到模块化的构建块。“一旦我们移除了它,”他们写道,“我们就不再需要将需求转化为符合 LangChain 规范的方案。我们可以直接编码。”

他们并不孤单。大约 45% 尝试使用高层级 LLM 编排框架的开发者从未将其部署到生产环境。另有 23% 的开发者在上线后最终将其移除。这些数字并不意味着框架是糟糕的工具——它们意味着框架是具有特定有用范围的工具,而那个范围比演示中展示的要窄。

README 中没人提及的抽象税

每个框架都会征收一种“税”。问题在于,它提供的抽象是否值得你付出的代价。

在项目的早期阶段,答案通常是肯定的。你只需几行代码就能获得检索链、内存管理、工具调用和智能体循环。框架处理了你目前还不想编写的样板代码。迭代速度很快。

当进入生产阶段时,这种“税”就变得显而易见了。框架不透明的内部机制——隐藏的重试逻辑、自动的上下文窗口管理、幕后的提示词注入——开始以一种你无法再推导的方式运作。而且由于这些机制是隐藏的,当出错时,错误会出现在你的代码中,而根本原因却深藏在三层抽象之下。

团队中反复出现三类典型的失败:

隐藏的重试放大。 许多框架会自动执行重试逻辑,而没有 Token 预算意识或抖动(jitter)。对一个 4,000 Token 的提示词进行简单的重试,如果重试两次,就会产生 12,000 个 Token。在多步流水线中,这种计算会产生复合效应:在五层服务链的每一层重试三次,每一个原始用户请求就会产生 243 次后端调用。一个团队在智能体循环失控运行 11 天后,发现他们的 API 支出从每周 127 美元飙升至 47,000 美元。框架的重试逻辑没有断路器,没有预算上限,也没有告警机制。

上下文窗口的不可见性。 为你管理对话历史的框架通常都很慷慨——它们保留所有内容。在多轮对话的第十条消息时,你可能为了接收一个 100 Token 的响应而发送了 40,000 个 Token。用自定义实现替换框架默认内存组件的团队通常报告称,在质量不下降的情况下,成本降低了 30%。框架的默认设置并不能说完全错误——它只是优化了信息的完整性而非成本,且从未告知你它在这样做。

需要阅读框架源码的调试。 当 LangChain 的 LCEL 管道操作符通过其内部的 invoke() 机制路由执行时,在步骤之间没有自然的插入点来放置标准的 Python 日志。工程师们最终不得不向框架本身的源代码添加 print 语句,以追踪发生了什么。这是一个迹象,表明抽象已经倒置:它不仅没有让你的工作变得更轻松,反而让你在错误的层级上看到了框架的内部工作。

提示你向底层迁移的信号

这些问题不会一下子全部出现。它们是累积的。早期预警很容易被忽略,因为每一个看起来都像是配置问题,而非架构问题。以下是需要注意的信号:

成本爆炸式增长,超出预期。 如果你实际的 API 支出始终是初步估算的 2–3 倍,那么框架正在注入你未意识到的 Token。这包括自动注入的系统提示词模板、无论是否相关都包含在内的工具模式,以及为支持调试输出而添加的冗长内部链描述。

调试需要特定框架的知识。 当故障发生而你无法使用通用的 Python 调试技巧进行诊断时——当你必须阅读框架源码、理解其内部原理或在 Issue 列表中搜索时——抽象已经不再为你提供帮助了。你在支付开销,却没有得到生产力收益。

你的需求不符合框架的模型。 前面提到的团队需要动态生成子智能体、协调专家智能体,并在执行过程中观察智能体状态。LangChain 的链式模型无法干净地适配这些模式。每一项新功能高度依赖于将他们的心理模型转化为符合 LangChain 规范的抽象,这增加了摩擦而非消除摩擦。

性能没有随着优化而提升。 一项基准测试研究发现,CrewAI 消耗的 Token 几乎是其他框架的两倍,耗时超过三倍,因为它的多智能体模式为每次智能体调用都注入了冗长的角色描述、目标陈述和内部独白。如果在明显的提示词优化后延迟仍然居高不下,框架的开销可能就是你无法通过优化突破的底线。

升级会不可预测地破坏功能。 在迈向 1.0 版本的过程中,快速更迭的框架在不同发布版本之间引入了破坏性变更。如果升级框架版本需要审计整个应用程序的变更,那么你已经积累了这种抽象原本应该防止的隐性耦合。

深入技术栈底层的真实样貌

“降低抽象层次”并非单一的动作,而是一个光谱。目标并不是从头重写一切,而是找到一个层级,让你的团队能够清晰地推导正在发生的事情,并控制需要控制的部分。

从最痛苦的接缝处(Seam)开始。 如果隐藏的重试逻辑是你的问题所在,解决方案不是完整的框架迁移,而是用显式的、可观测的代码替换掉管理重试的那一个组件。大多数框架都提供了足够的“逃生门”(Escape hatches),让你可以在不放弃框架其余部分的情况下完成此操作。

在进行任何更改之前,让 Token 成本变得可观测。 在优化之前,先进行插桩(Instrument)。你需要确切地知道,对于每种请求类型,系统提示词贡献了多少 Token,工具模式(Tool schemas)贡献了多少,对话历史贡献了多少,以及实际的用户内容贡献了多少。如果没有这些细分数据,任何优化都是盲目的猜测。兼容 OpenTelemetry 的 LLM 追踪库可以通过极少的代码改动,将 Token 计数附加到 Span 中。

用显式状态替换内存管理。 框架管理的对话历史几乎从来不是生产环境的正确默认选择。编写你自己的总结逻辑,或者实现滑动窗口,或者将会话状态存储在 Redis 中并有意识地进行重构。显式版本虽然代码更多,但这是你可以理解并测试的代码。

针对关键路径构建直接的 SDK 调用。 Anthropic 和 OpenAI 的 SDK 设计得非常好。许多模式——提示词链(Prompt chaining)、结构化输出、工具调用、流式传输——都可以通过直接调用 SDK 在 20 到 50 行代码内实现。对于延迟敏感或成本敏感的路径,这通常是正确的答案。你会获得可预测的行为、清晰的错误暴露,以及对重试、超时和上下文构建的完全控制。

无需推倒重来的迁移

如果你将迁移视为逐个接缝处的替换,而不是一次性重写的大工程,那么迁离高级框架会容易得多。

通用模式如下:

  1. 首先增加可观测性。 在动任何代码之前,对系统进行插桩,以捕获 Token 使用情况、每一步的延迟以及错误暴露。这会告诉你哪些环节实际上是昂贵的。

  2. 一次替换一个组件。 从内存管理开始(通常是最不透明的部分),然后是重试/错误处理,最后是提示词构建。每个替换的组件都应该是可以独立测试的。

  3. 在非关键路径上保留框架。 快速原型设计受益于框架抽象。你不需要迁移内部管理工具和实验性功能——将精力集中在服务用户的生产路径上。

  4. 在边界处使用类型化接口。 在每个组件边界处使用 Pydantic 模型处理输入和输出,为你提供一个迁移检查点。当组件被替换时,接口保持稳定。

  5. 测试你要保留的行为,而不是代码。 不要编写检查旧框架实现细节的单元测试。编写断言系统应有行为的行为测试:给定此用户查询,响应是否包含正确信息?它是否调用了预期的工具?是否保持在预算范围内?

对于那些想要框架但希望更轻量级的团队,已经出现了像 Pydantic AI 这样类型安全、原生 Python 的替代方案。它们支持多个供应商,内置测试注入,并且不会注入你未要求的 Token。它们并非魔法——你仍然需要理解发生了什么——但它们为提供的脚手架收取的“税收”要小得多。

何时框架才是正确答案

这一切并不意味着框架是错误的。它们在特定条件下是正确的:

  • 当团队正在探索,且迭代速度优于运营控制时
  • 当框架的内置集成无需定制即可覆盖你的实际用例时
  • 当框架开销的成本对于你的流量和延迟要求而言确实可以接受时
  • 当你的团队有精力在依赖框架内部机制之前先了解它们时

错误不在于使用框架。错误在于当框架的运营成本超过替换成本时,仍坚持使用它。留下的成本是隐性的(被吸收到调试时间、无法解释的 API 账单以及表现为缓慢的架构约束中),而迁移的成本是显性的且预付的。这种不对称性正是团队坚持太久的原因。

问题不在于“我是否应该使用框架?”,而在于“我的系统各部分应该位于哪一层?” 答案对于你的原型、生产 API 和内部工具都会有所不同——并且随着系统的成熟,答案也会随之改变。现在就构建好逃生门,你迟早会用到它们。

References:Let's stay in touch and Follow me for more thoughts and updates