LangChain 最近公开了一个内部付费投放 Agent。它每周读取多个广告平台和数据仓库的数据,在 Slack 里自动生成分析报告、回答运营问题,还能提出关键词、定向和投放配置的调整建议。

六个月后,LangChain 约 20% 的营销商机来自广告投放。同时,他们也在持续优化这个 Agent。其中,每周分析报告流程从最初的要运行约 18 分钟,改造到缩短至 85 秒,成本降到原来的约 1/40。

不过,这些效率改善并不都来自模型能力本身,很多优化其实发生在模型之外。

LangChain 后来的调整重点,更多地落在 Agent 周围那套支撑它工作的系统上:让模型集中处理需要判断的部分,同时把信息获取、执行控制和结果确认交给更稳定的机制。OpenAI、Dropbox、Uber、Anthropic、Intercom 最近公开的生产实践里,也能看到类似的变化。

虽然这些公司面对的业务不同,但都在尝试把模型真正放进一套能够长期运行、受控执行,并且可以追踪结果的系统中。

业务上下文

业务上下文的拆分

LangChain 最初要处理的是一组性质不同的业务信息,包括广告运营方法、公司的销售漏斗、不同平台的数据口径、过去的业务决策,以及每天都在变化的广告活动和销售数据。

如果长期把这些信息都写进同一个系统提示词,内容会越来越长,维护也会越来越麻烦。有些工作方法几个月都不会发生变化,有些业务决策则会持续补充,还有一些数据要在每次请求时重新获取。把它们放在一起管理,不仅会让提示词不断膨胀,也会让历史信息和当前状态混在一起。

LangChain 后面把上下文拆成五层:

  • 系统提示词部分只保留角色、工作方式和导航信息;
  • 六组 Skills 记录可以复用的工作方法;
  • 19 页 Wiki 保存 LangChain 自己的业务知识;
  • 实时工具负责获取当前数据;
  • 计算和固定规则留在代码里。

其中,Skills 和 Wiki 承担的内容并不一样。“怎么读取数据、怎么算指标、怎么生成报告”属于 Skill,这些方法换到另一家公司仍然可以使用;“某个广告活动的目标是什么”“7 月为什么做过这次调整”则属于 Wiki,只和当前公司的业务有关。这样一来,系统提示词可以保持精简,只要告诉 Agent 不同的信息应该去哪里找便好。

OpenAI 内部的数据 Agent 面对的是另一类上下文问题。OpenAI 的数据平台覆盖超过 7 万个数据集,表名、字段和表结构只能描述数据本身,却无法完整解释这些数据为什么这样生成、多久刷新一次、哪些值是唯一标识,以及在业务中应该怎样理解。

因此,除了表结构、数据血缘和历史查询,OpenAI 还加入了人工维护的业务注释,并让 Codex 阅读生成这些数据的代码,从中补充数据粒度、刷新方式和业务规则等信息。随后,系统每天离线整理这些内容,生成统一表示和向量索引;真正回答问题时,再通过检索取回和当前任务相关的信息。

这样处理以后,不同上下文也有了各自的更新方式: 变化较慢的信息可以提前整理,和具体任务相关的内容在运行时检索,持续变化的数据则在需要时实时查询 。

图源:OpenAI, Data Agent 的查询流程图

数据的可信来源

业务上下文拆开以后,还会碰到另一个问题: 同一个指标可能同时存在于多个系统里 。LangChain 同时使用广告平台和自己的数据仓库。广告平台掌握广告花费、展示量、点击量等媒体数据,数据仓库则记录潜在客户、销售机会和销售漏斗等业务数据。

他们没有先建立一个包含所有字段的统一数据模型,而是明确规定每类指标应该以哪个来源为准。广告花费以广告平台为准,进入销售漏斗后的业务结果则以数据仓库为准。

这样的设计源自实际的数据问题。由于 Google 的部分视频广告无法通过关键词与数据仓库中的数据进行可靠关联,曾导致 LangChain 大约 10% 的 Google 广告花费没有出现在数据仓库中。Meta 的情况又有所不同:广告平台知道某次广告带来了一次转化,但只有数据仓库能够进一步判断,这次转化属于“联系销售”还是“注册”。

因此,并不是所有数据都需要强行关联到一起。LangChain 把这套可信来源规则写进 Wiki,同时在工具层限制容易产生错误的数据入口。当不同来源之间无法可靠关联时,Agent 会保留数据来源、时间窗口和归因模型,不自行补齐缺失的信息。

到了真实业务系统里,除了考虑“上下文是否完整”,还要考虑同一个事实出现多个版本时,Agent 应该相信哪一个。

如果这层规则没有提前定义清楚,后面的推理即使没有问题,也可能建立在错误的数据来源上。

模型、工具与运行时

模型与代码的边界

LangChain 第一个版本的 Agent 会把广告活动、关键词、销售漏斗和落地页等原始数据全部交给模型。模型读取数据后,会自行计算广告花费、不同时间段的变化和广告活动表现,再生成最终报告。

这套流程是能运行,但是它生成一份报告大约需要处理 390 万输入 Token,耗时 1,112 秒,成本超过 3 美元。

后来,LangChain 重新拆分了这条处理链路:数据获取、日期窗口对齐、数据汇总、指标比较以及固定规则都交给 Python 处理。完成计算后,只把压缩后的结果提供给模型,再由模型结合相关证据解释数据变化、分析原因并提出下一步建议。

这样调整后,Token 消耗明显减少,一些固定的业务规则也可以从提示词转移到代码中。例如,一个长期贡献较高的广告活动,不应该因为某一周表现下降就被关闭。如果这项限制只写在提示词里,模型每次运行时都需要重新理解并遵守;写进代码以后,它就会成为执行流程中的固定条件。

LangChain 将这套分工概括为: 模型负责判断,代码负责保证一致性 。

这种边界划分也会影响后续的测试方式。同样的输入经过固定代码处理后,应该得到稳定的日期窗口、聚合结果和权限判断,因此可以通过单元测试和回归测试进行验证。模型负责的解释和建议允许存在一定变化,则可以通过评测、人工判断或结果指标来观察效果。

如果把这两类逻辑都放进同一次大模型调用中,一旦结果出现问题,也更难判断究竟是数据计算出了差错、业务规则没有执行,还是模型本身的判断发生了偏差。

工具前面的发现层

工具数量较少时,模型可以一次看到完整的工具定义;但随着工具增加到上百个,工具名称、说明和参数本身就会占用大量上下文。

LangChain 接入的广告平台提供了 200 多个工具。早期,即使只加载一个规模较小的只读工具目录,工具名称、描述和参数也需要占用约 3.8 万 Token。

后来, 他们把工具调用拆成了搜索、读取和执行三个步骤 。Agent 先根据当前任务搜索可能需要的工具,一次最多返回 8 个候选;确定要使用哪个工具后,再读取完整的参数定义,最后才真正执行。

经过这样的调整,首轮上下文从约 3.8 万 Token 降到约 1.2 万 Token。在相关测试中,这套系统的成本降到了完整加载所有工具定义方案的约四分之一,同时评测质量基本保持一致。

这相当于在 Agent 和工具之间增加了一层能力发现机制。Agent 先知道需要到哪里寻找能力,具体工具的参数和使用方式,则等真正需要调用时再进入上下文。

Dropbox Dash 也类似。Dash 接入 Confluence、Google Docs、Jira 等系统以后,模型需要在多个检索工具之间进行选择。Dropbox 的实验发现,随着工具数量增加,模型需要面对的选择也越来越多,工具定义和返回结果还会持续占用上下文空间。

后来,Dropbox 将多个检索入口收敛成一个统一的搜索工具,由 Dash 自己的搜索索引处理底层不同的数据源。随着这个工具承担的能力越来越多,查询改写、字段映射、语义检索以及各种边界情况都需要加入大量说明。 Dropbox 随后又把搜索能力拆成独立的搜索 Agent,让主 Agent 只负责判断什么时候需要检索,具体的搜索过程交给搜索 Agent 完成 。

图源:Dropbox,Dropbox 从多个检索工具收敛到统一搜索工具

工具返回的结果同样会占用上下文。一次接口调用返回几万行数据,这不代表这些内容都需要继续送进模型窗口。完整结果可以先保存在文件、沙箱、数据库或其他工作空间中,Agent 再根据后续任务按需查询和处理。

因此,工具架构需要考虑的范围也不只包括“模型可以使用哪些工具”,还包括工具定义如何按需加载、完整结果保存在哪里,以及哪些信息真正需要进入模型上下文。

多个入口的运行时

LangChain 后来还做过一次架构调整。付费投放 Agent 最初有两套独立的执行流程:每周报告通过定时工作流运行,需要使用沙箱、大模型和 PDF 生成功能,整个任务的执行时间也比较长;Slack 需要在几秒内返回结果,因此采用了更轻量的执行循环和成本较低的模型,只开放只读工具,也不使用沙箱。

这两套流程分别对应不同的使用场景,但运行五周以后,维护上的问题逐渐暴露出来。每增加一项能力都需要开发两遍,Slack 和周报的功能上线进度也很难保持一致。由于 PDF 由另一套流程生成,Slack 既无法处理其中的附件,也无法基于周一生成的报告继续回答问题。

后来,LangChain 将两套流程合并到了同一个运行时中。Slack 消息和周一定时任务仍然从不同入口触发,但每次请求都会重新创建 Agent,并根据运行模式加载不同的能力配置。定时任务只会获得一个 task() 工具,再将具体工作分派给不同平台的 Subagent;Slack 场景则会开放范围更广的读取工具、数据仓库以及广告活动操作能力。

这样一来,Skills、Wiki、沙箱和执行框架都可以在同一套运行时中复用,不同入口之间的差异则通过运行时配置来控制。

这种设计也为后续的权限控制留下了空间。同一套 Agent 运行时中,不同入口、用户和任务仍然可以拥有不同的工具、沙箱、模型和写入权限。

当 Agent 从一条固定工作流逐渐变成长期运行的业务能力后,通过能力配置控制不同场景,会比长期维护多套独立流程简单得多。

Subagent 的隔离不只在上下文

优化方法是使用一个主 Agent 调度多个广告平台的子 Agent,各个平台分别完成分析后,再由主 Agent 汇总结果。

广告媒流(小程序)-小红书

每个子 Agent 都拥有独立的上下文窗口,但实际运行后发现仅仅隔离上下文还不够。两个子 Agent 曾经写入同一个报告路径,同时共用一个 done 状态。第一个子 Agent 完成任务后将状态设为 done,第二个子 Agent 看到这一状态,也可能误以为自己的任务已经完成。

于是,Agent 系统对每个平台分别设置了独立的报告路径和完成状态,同时进一步收窄每个子 Agent 可以使用的工具范围。

除了这个问题,还有子 Agent 无法判断 PDF 是否已经渲染完成,于是不断检查文件、持续消耗 Token,最后甚至开始尝试自己实现 PDF 生成功能的问题。最终,这个子 Agent 能力限制为三项:读取上下文、执行计算和渲染 PDF。渲染工具返回成功后,这个子 Agent 的任务就结束。

其实,上面这些问题会发生主要是运行时中的状态边界没有定义清楚。在一个长期运行的 Agent 系统中,状态可能存在于不同范围,例如一次工具调用、一次运行、一条会话线程、一个用户会话,或者多个 Agent 共享的工作区。

例如,某个 done=true 究竟只代表当前任务完成,还是代表整个工作流结束;某个文件只允许当前子 Agent 修改,还是主 Agent 也可以覆盖;一次失败的计数应该在重试时保留,还是在新的运行中重新计算——这些都属于状态作用域需要明确的问题。

因此,多套上下文窗口只能解决一部分隔离问题。文件、状态、工具、访问凭证以及任务完成条件,同样需要分别定义各自的作用范围。

身份与权限

身份绑定调用链

Agent 只读取数据时,权限模型和普通应用比较接近。但当它开始修改配置、创建 PR 或操作内部系统后,调用链中就多了一层代理关系:请求由 Agent 实际发出,背后却对应某个具体用户发起的任务。

LangChain 的付费投放 Agent 会先在 Slack 中提出修改方案,只有指定成员可以编辑和批准。服务端验证 Slack 用户身份后,才会真正调用写入接口。

到了 Agent 调用 Agent 的场景,身份链还会继续延长。Uber 公开过一个内部案例:值班工程师将告警问题交给值班 Agent,后者再调用调查 Agent;发现问题来自告警配置后,还需要由监控 Agent 修改配置并创建 PR。

如果下游系统只记录最后一个 Agent 的服务身份,那么最初由谁发起任务、中间经过了哪些 Agent,就很难从一次调用中还原出来。

为此,Uber 在内部架构中加入了 Agent 注册中心、安全令牌服务和 MCP 网关。Agent 注册中心记录 Agent 与工作负载之间的关系;安全令牌服务会在每次调用下一跳时签发新的 JWT;MCP 网关则负责执行工具调用相关的访问策略。

这些令牌采用短生命周期设计,并且限定具体的访问对象。比如 Agent A 获得一个用于调用 Agent B 的令牌,这个令牌不能继续拿去访问数据库或其他系统。当 Agent B 再调用 MCP 网关时,还需要重新向安全令牌服务交换新的令牌。

与此同时,身份链也会随着每次令牌交换继续传递。因此,当请求到达 MCP 网关时,系统仍然能够看到完整的调用关系: 用户 → 值班 Agent → 调查 Agent。

随后,网关可以结合用户身份、Agent 身份以及当前工具策略共同进行授权判断。Uber 还把令牌交换和身份链传播封装进标准的 A2A 客户端,让这些过程在 Agent 之间调用时自动发生,避免每个 Agent 开发者都重复实现一套身份传递逻辑。

图源:Uber Engineering,Uber Agent 身份架构

从这套设计中可以区分出三个层面:用户本身拥有哪些权限,Agent 当前代表谁执行任务,以及当前运行时实际开放了哪些能力。这三者可以分别控制。

例如,用户拥有写入权限,当前 Agent 仍然可以只获得读取工具;Agent 有权调用某个工具,工具本身也可以根据完整的身份链拒绝某一次具体请求。

权限之外

权限系统能够决定某次调用是否允许发生,但当 Agent 获得命令行、文件系统和网络访问能力后,还需要处理另一类风险:如果执行出现偏差,它最多能影响多大的范围。

Anthropic 在 Claude Code 的遥测数据中发现,用户会批准大约 93% 的权限请求。当这类确认出现得过于频繁时,逐条人工判断的作用也会逐渐下降。因此,他们把更多限制放进了运行环境。

Claude Code 可以通过沙箱限制文件和网络访问;Cowork 的 Agent 则运行在虚拟机中,对文件挂载、网络出站、凭证和进程分别进行控制。Anthropic 提到,模型、安全分类器和人工确认都属于概率性控制,而文件系统、网络和虚拟机提供了另一层更明确的边界。

Anthropic 还公开过一个具体的修复案例。Cowork 曾允许虚拟机访问 api.anthropic.com ,但这条放行规则同时产生了数据外传路径。后来,他们在虚拟机中增加代理,只允许携带当前虚拟机会话令牌的请求通过,并阻止可能触发服务端请求的请求头。

这里讨论的已经不只是 Agent 有没有访问网络的权限,还包括允许联网以后,哪些请求能够真正离开运行环境。

Anthropic 还提到,隔离也会带来新的问题:虚拟机隔离 Claude 的同时,也让企业的终端检测与响应系统无法像观察普通进程一样看到虚拟机内部行为。目前,他们通过 OTLP 导出事件日志来缓解这一问题,但实时可见性仍然有所不同。

安全边界和可观测性之间,也需要做相应取舍。

图源:Anthropic Engineering,Claude Cowork VM 的隔离机制

写操作的重试与结果验证

Agent 开始执行写操作以后,会碰到一个传统分布式系统问题:如果请求失败,能不能安全重试。

例如,Agent 发出“把预算改为 5000”的请求后遇到网络超时。此时它无法判断请求根本没有到达服务端,还是修改已经成功,只是响应没有返回。如果简单地再次执行,某些操作就可能产生重复效果。

因此,业务操作最好设计成可验证、可重试的形式。可以用目标状态替代增量命令,用幂等键避免重复提交,也可以在重试前重新读取当前状态。

比如,“增加预算 500”在重试时可能执行两遍;“把预算设置成 5000”则可以先检查当前值,再决定是否继续执行。LangChain 在修改广告配置后也会重新读取平台状态,因为接口返回成功只能说明一次调用完成,业务流程真正关心的是最终状态是否符合预期。

在代码场景中,这种验证还会继续向后延伸。Intercom 目前超过 93% 的 PR 有 Agent 参与,超过 19% 可以在没有人工审查的情况下自动批准。但他们并没有一开始就开放整条链路,而是先用 100 多个 PR 进行受控试点。

试点阶段,Intercom 关注的不只是 AI 审查是否给出了正确意见,还会观察 AI 批准后的 PR 是否发生回滚,以及审批时间发生了怎样的变化。试点中的 AI 自动批准 PR 没有发生回滚,P75 审批时间提升约 6~16 倍。扩大范围后的前四周,共有 497 个 PR 完成了 Claude 写代码、AI 审查、批准和进入生产环境的完整流程。

同时,审查 Agent 会拒绝过大的 PR,要求开发者拆分修改;工程师也可以随时切回人工审查。代码上线以后,提交者仍然需要观察生产表现,并在出现问题时负责回滚。

这里的任务完成条件,也从“代码已经生成”继续延伸到了测试、审查、合并、持续集成、部署以及生产环境中的实际表现。

其他业务 Agent 也可以采用类似的验证方式:数据修改后重新读取状态,文件生成后重新解析,发起外部请求后确认目标对象的状态,执行完成后保留操作记录和原始结果。

Agent 给出“已完成”可以作为一个信号,但系统最终是否接受任务完成,还需要由实际结果来确认。

从模型调用到业务运行

这些实践虽然来自不同业务,但都指向了同一类问题:当 Agent 开始进入真实业务流程,系统需要同时处理上下文、工具、状态、身份、权限、运行环境和结果验证。

从“回答问题”走向“完成任务”以后,模型调用只是其中一环。业务事实从哪里来、工具怎样开放、状态如何隔离、身份怎样传递、写操作如何重试,以及最终怎样确认任务完成,都会成为生产 Agent 需要处理的一部分。

参考资料

  • LangChain|How we built LangChain's Paid Media Agent, langchain.com/blog/paid-media-agent
  • OpenAI|Inside OpenAI’s in-house data agent, openai.com/my-MM/index/inside-our-in-house-data-agent/
  • Dropbox|How Dash uses context engineering for smarter AI, dropbox.tech/machine-learning/how-dash-uses-context-engineering-for-smarter-ai
  • Uber|Solving the Identity Crisis for AI Agents, uber.com/us/en/blog/solving-the-agent-identity-crisis/
  • Anthropic|How we contain Claude across products, anthropic.com/engineering/how-we-contain-claude
  • Intercom|AI is approving our pull requests: Here’s how we made it safe, intercom.com/blog/ai-is-approving-our-pull-requests-heres-how-we-made-it-safe/