前段时间帮某团队做技术评审方案里赫然写着自研Agent Runtime需求文档几十页架构图三张PPT都装不下。我对着那份文档问了三个问题你现在的LLM调用成功率是多少单次调用P95延迟多少工具调用最长执行多久结果会议室沉默了。我在这里想讲的恰恰是这一件事——AI Agent架构选型绝大多数问题不是出在选哪个框架而是出在连一次LLM调用都还没做扎实就急着去造Harness Runtime。这篇文章我会从一次LLM调用的基础账本讲起逐步拆到完整Agent运行时的核心模块、分层演进策略和落地深水区。适合正在规划Agent平台、想自研中小规模Harness Runtime的团队也适合刚从调API阶段起步、准备往多轮工具调度方向走的开发者。文章不会给一份抄了就成的架构图但会给一套我想了很长时间的判定框架和踩坑清单。1. 先别谈架构把一次LLM调用做到可度量、可预期很多人以为Agent系统的天花板取决于模型能力这在理论上没错但在工程上真正决定体验的是单次调用的稳定性。一个Agent循环里每一步都可能产生一次或多次LLM调用而每一步都有失败概率。假设单次调用成功率为95%十步循环的理论成功率只剩约60%二十步就只剩36%。这种错误率叠加效应是所有Agent系统复杂性的源头。1.1 单次调用的四张账本成功率、延迟、成本、结构稳定性我在多个项目里都要求团队先建立这四张表没有数据之前不谈架构。第一张是成功率。注意这里说的是业务成功率不是HTTP 200率。很多API网关返回200但模型输出不符合预期要么拒绝回答要么答非所问要么JSON解析失败。业务成功率需要你定义自己的验收标准而不是看网关指标。第二张是延迟。P50和P95要分开看P95比P50重要得多。Agent循环是串行的每一步延迟都会累加一个P95高达8秒的接口15步循环的P95理论值要按分钟来算。第三张是成本。token消耗要按单次调用拆开记账输入token、输出token、缓存命中分别计费。我曾经见过一个团队只统计总成本结果发现90%的钱烧在了重复注入的固定上下文上这本来是可以做缓存或压缩省掉的。第四张是结构稳定性。让模型输出合法JSON不难难的是字段语义稳定。比如日期这个字段这轮返回2025-03-14下轮返回3月14日你的解析层就要多写一堆容错代码。我给团队定的参考基线大致是这样指标健康值警戒线说明业务成功率97%92%按业务验收口径统计P95首token延迟2s5s对流式交互尤其重要P95完整响应延迟8s15s复杂推理任务可放宽结构化输出解析失败率1%3%核心任务的硬门槛输出token超预算率10%30%需要运行时强制熔断提示这些指标不是拍脑袋定的而是来自多个线上项目的经验。如果你的任务特别复杂长文档分析、大规模代码生成延迟可以放宽但业务成功率和解析失败率必须死守。1.2 结构化输出不是让模型吐JSON而是约定接口很多初学者让模型输出JSON时就一句返回JSON格式。这在玩具项目里没问题但在Agent工程里这会造成灾难。模型返回的JSON可能键名漂移、值类型不稳定、嵌套层级随上下文变化而你的Harness Runtime却要像调用普通函数一样消费这些数据。我在工程上强制要求三层约定第一层是输出Schema。用JSON Schema严格定义每个字段、类型、取值范围和必填性有些大模型API原生支持这种约束模式有的需要靠提示词加自校验兜底。第二层是解析容错。预解析层处理常见的模型输出问题比如把Markdown代码块包裹剥掉、把非法JSON中的尾逗号修复、把单引号替换为双引号。这些小处理看起来不上台面但能显著降低下游报错率。第三层是语义校验。JSON能解析不等于结果正确。比如工具调用Choice字段必须是gallery、collection、read之一不能是查看图集。我在自校验阶段会给模型一个二次修正的机会把校验错误信息回传让它重新生成。下面这段代码是实际项目中一个简化版的结构化调用封装import json import jsonschema from typing import Any def call_llm_with_schema( prompt: str, schema: dict, max_retries: int 2, ) - dict[str, Any]: raw_output llm_call( # 伪代码实际的API调用 promptprompt, response_formatjson, # 使用模型的json模式 temperature0.1, ) for attempt in range(max_retries 1): candidate parse_and_clean(raw_output) try: jsonschema.validate(candidate, schema) return candidate except jsonschema.ValidationError as e: raw_output llm_call( prompt( f{prompt}\n\n f上一次输出不符合要求请修正{e.message}\n f严格按给定Schema输出JSON。 ), response_formatjson, temperature0.1, ) raise RuntimeError(结构化输出连续失败)这段代码的核心思想是把让模型输出正确结构当作一个可重试的契约来管理而不是靠运气。实际项目中我还加上了日志记录落库每次修正尝试都有trace方便后续分析是Schema设计不合理还是模型总在这一步犯错。2. 临界点什么样的任务真的需要Agent循环我见过两类完全相反的错误。第一类是把简单任务复杂化比如帮我总结这篇文章这在绝大多数情况下一次LLM调用就够了硬上多Agent RAG加反思结果成本翻了十倍质量没有本质提升。第二类是任务明显需要多步工具调度却用一条固定链路硬写结果一遇到分支情况就崩。分辨的临界点其实不玄学我总结成四个问题。第一个问题任务是否需要外部工具如果答案是不需要那大概率一次调用就能完成最多加个RAG检索没必要进Agent循环。 第二个问题是否需要多步推理且步骤依赖外部结果比如对比这三个开源项目的被引情况和许可证你得先搜索、再逐个打开详情页、再整理对比中间某一步的结果会决定下一步怎么做。这是典型的Agent化场景。 第三个问题是否存在条件分支和不确定路径固定链路只能处理已知路径遇到如果搜索结果不足就换个关键词重新搜这种逻辑就必须上了。 第四个问题状态是否需要跨轮持久化如果用户中间改需求要从上一轮结果继续你就需要记忆和状态管理这也是Agent循环的职责。2.1 链式调用与循环调用的本质区别链式调用Chain是把一系列提示词按固定顺序串起来前一个输出作为后一个输入中间没有决策点。循环调用Loop则是在思考-行动-观察的圈子里打转模型每一轮根据新的观察来决定下一步动作。这个区别非常关键。链式调用的复杂度是固定的你可以事先编排出错处理循环调用的深度是动态的每一步都可能改变路径这就带来了不可预测性。我在项目里经常跟人强调动态深度是Agent系统的本质特征也是所有工程复杂性的来源。一个简单的循环核心如下step 0 max_steps 15 state initial_state while step max_steps: observation run_tool_phase(state) # 执行当前计划中的工具 next_action llm_reason(observation) if next_action.is_final: return next_action.answer state.messages.append(next_action.prompt) step 1 raise AgentLoopLimitError(f超过最大步数: {max_steps})这一步逻辑看起来简单但每一个环节都有大学问run_tool_phase超时了怎么办llm_reason返回不可理解的JSON怎么办max_steps之外要不要设总成本上限这些细节直接决定了这个系统能不能上生产。2.2 代价放大器错误率、延迟和成本的指数问题一旦进入循环前面第1节提到的几个指标就会被放大。你不再面对一次失败概率而是面对多次条件概率的乘积。也就是说单次调用做得越稳Agent循环的收益越大单次调用越烂Agent循环的灾难越深。所以我的建议一直很直白你先别急着做Agent框架先退回第1节把单次调用的成功率打磨到97%以上。如果这都达不到Agent循环上线之后你将面对一个没法调试的烂摊子。延迟上的放大更直观。一次工具调用往往要几十秒甚至几分钟比如代码执行或浏览器操作LLM思考又要几秒。你叠在一起就会发现一个看似简单的Agent任务用户等待六十秒是常态。这对产品形态和用户预期管理都是巨大挑战。3. Harness Runtime的组成拆解Agent的手脚、大脑、记忆和保险丝进入正题。Harness Runtime是什么我把它定义为承载Agent运行的运行时环境它管理Agent的感知、决策、执行全过程提供工具调用、记忆存储、状态管理、沙箱隔离和可观测性能力。简单说LLM是大脑Harness Runtime是身体——大脑负责思考身体负责动手并保证不伤害周围环境。我不会给那种标准模块图而是按手脚、大脑、记忆、保险丝这四个维度来拆解。3.1 工具注册表把能力边界显式化Agent要执行操作比如查订单、发邮件、改代码必须先经过工具注册表。这相当于给Agent一份菜单上面写着每个工具的名称、参数Schema、权限等级和执行成本。工具注册表不仅是存一个函数它还承载三件重要的事第一显式的能力声明。Agent只能调用注册表里存在的工具没注册的能力一律不存在这从源头杜绝了模型自发编造工具的行为。第二参数强制约束。每个工具的输入都经过Schema校验LLM给出的参数类型不对、缺字段、超范围都会在执行前被打回而不是等调用之后报错。第三调用白名单与权限等级。低权限等级的工具不携带敏感操作能力比如查询类Agent默认没有写入和删除权限。我用一个成本估算字段标记每个工具的执行开销调度器可以根据预算决定是否调用。一个精简的工具注册数据结构可以长这样dataclass class ToolSpec: name: str description: str parameters_schema: dict permissions: set[str] # {read} / {read,write} estimated_cost: float # 单次调用的折算成本 timeout_seconds: int 30 idempotent: bool False # 是否幂等影响重试策略这层设计带来的直接好处是你可以对Agent做细粒度的成本控制和风险控制而不是让它裸奔在一个无所不能的解释器里。3.2 沙箱与最小权限LLM不应该拥有解锁数据库的权限很多翻车事故都来自一个设计失误让Agent直接对接生产系统。某团队曾经让代码执行Agent运行在宿主机上模型说要安装一个npm包结果是直接在服务器上安装了而且包还带依赖建设——几分钟后被例行安全检查拦下。正确做法是三件事在容器/执行环境中跑工具、网络出网受限、文件系统仅挂载工作目录。凡是涉及外部系统的操作一律通过身份代理网关用短期凭证换取最小权限。我在实际系统里给代码执行工具分配了一个临时容器容器没有宿主网络访问权限只有一条受控通道可以访问白名单内的内部API并且所有终端输出都做脱敏处理。这样做之后模型胡闹的破坏力就被限制在容器内部最多浪费资源不会酿成安全事故。3.3 记忆结构上下文是稀缺资源记忆是分层管理关于记忆我看过最多的误区是把记忆和无限上下文划等号。实际项目的教训是上下文窗口再大也是有限且昂贵的资源。一上来就追求全量记忆很快会烧光预算且引入大量噪声。我把记忆做成三层短期工作记忆当前会话的对话历史和中间状态通常就是上下文窗口里的内容需要动态压缩。长期情景记忆跨会话的摘要化事实比如用户偏好、项目背景每次启动时只注入必要片段靠提取而不是全量回放。程序记忆可复用的任务模板和流程片段一旦某条路径验证有效就固化成模板后续任务直接复用减少重复推理。这里的关键是按需检索、主动淘汰。引入向量检索不是自动的你需要一个调度规则在每次循环开始前判断要注入哪些记忆片段。全量塞不如精选。3.4 可观测性设计循环系统里没有trace就是盲人摸象Agent循环的动态特性决定了调试比普通后端系统难得多。如果没有完整的trace记录你根本复现不了问题。我在Harness Runtime里强制要求如下记录链路每一步的输入输出摘要、token消耗和延迟工具调用的完整参数、返回值和执行时长决策时的成本预估与最终实际开销循环步数、停止原因和重试事件。有了这些数据你才能回答支持团队最常问的一句话为什么走到这一步没有traceAgent系统出了问题只能回滚重试问题定位能力基本为零。4. 架构选型的分层演化策略按需加复杂度面对市场上各种框架和自研方案我的核心态度是先分层再演进不迷信一步到位。系统复杂度应严格匹配业务复杂度先跑通再完善这是工程上最高效的路径。我给团队画的架构阶梯是这样层级形态复杂度适用场景典型代价L0裸LLM调用低文本生成、单轮问答无法处理工具、无状态L1流程编排中固定步骤的任务链路无法处理分支和动态路径L2Agent循环中高多步推理工具调度需要状态管理和成本控制L3多Agent路由高复杂任务拆分、专家Agent协作消息协议和调度复杂度激增L4完整Harness Runtime很高大规模、多租户、生产级Agent平台需要运维、安全、观测全套能力4.1 为什么直接上最强框架往往是最差决策有一类项目PPT上写了构建统一Agent平台直接上L4形态结果搞了三个月还在写基础设施业务Agent一个都跑不通。问题不在于L4不该做而在于没有先验证L2层面的任务可达性。如果你的核心任务在L2上跑不通说明要么单次调用功底不行要么任务定义有问题。在这种状态下直接铺L4等于在烂地基上盖高楼。我习惯的路径是用最小循环L2在一周内跑通一个端到端Demo哪怕丑陋都行然后在不改变业务表面的情况下逐步把工具执行、状态持久化、观测系统替换成Runtime组件最后当你有两三个不同业务的Agent都在跑统一的Harness Runtime才真正有存在的意义。那框架与自研怎么平衡我的建议是用框架的核心理念来指导设计但不被框架绑死。比如工具协议、循环控制、记忆抽象这些都是值得借鉴的标准范式真正需要自己实现的是与业务深度耦合的部分权限模型、领域工具、成本策略和评估集。记住一句话架构选型的目标不是用某一个框架而是让Agent系统的核心组件可替换、可演进。4.2 组件可替换的具体抓手我在做架构时会单独定义一层监狱接口Prison Interface把所有外部依赖都关在接口后面。比如模型接口层、工具执行层、检索存储层每一层都可以单独替换实现而不影响其他层。这个设计让团队不会被某一家模型供应商或某个向量数据库绑架。另一个抓手是评估先行。在选型早期就搭一套离线评测集包含典型任务、边界情况和失败回归用例。每一次架构调整都跑一遍评测集对比质量和成本。没有评估集的架构演进就是一场豪赌。5. 落地部署的深水区上下文、状态与工具可靠性前面讲的都是架构层的概念。真正让团队熬夜的往往是这三个深水区。每一项我都踩过不只一次单独拿出来讲。5.1 上下文窗口管理主打注入即成本很多Agent工程师的第一版都特别大方把全部对话历史、全部工具返回、全部文档片段一股脑塞给模型。结果两个问题一是token成本飙升二是输出质量反而下降因为上下文越长模型越容易抓不住重点。后来我强制引入三层策略。第一层是压缩旧对话按重要性策略摘要只保留关键决定和未完成事项第二层是裁剪工具返回过大时只保留摘要和结构化结果完整结果放存储介质里通过检索按需读取第三层是淘汰超过时效的消息优先淘汰。这里有一条我屡试不爽的规则模型输入里只保留直接推动当前决策的信息。每次循环开始前做一个最小上下文拼装的动作比事后优化省得多。5.2 状态管理与故障恢复Agent会挂Runtime要接住Agent循环天然有状态如果某个工具执行到一半进程崩溃整个会话状态就丢了。我在设计里把状态持久化作为一等公民每完成一步就落一个检查点内容包括当前意图、已完成步骤、待执行步骤、对话历史摘要、各工具返回的持久化引用。恢复逻辑很像数据库日志回放从最近的检查点恢复跳过已完成的步骤重新执行有状态副作用操作。这里最麻烦的是状态副作用的重放所以工具幂等性从第一天就要设计。5.3 工具调用可靠性超时、重试与幂等三件套工具调用失败的坑远比LLM调用本身多。一个外部API超时一个Shell命令挂起一个文件锁没释放都可能让整个Agent循环卡死。我的做法是给每个工具调用设置超时硬上限超时即终止。重试策略区分幂等和非幂等工具幂等工具超时后自动重试非幂等工具需要人工确认或生成补偿动作。同时工具层加熔断器某个工具连续报错超过阈值就暂停调用并通知值班。另外工具调用要记录请求标识和返回快照方便下游排查。这里没有捷径老老实实用调用清单和追踪日志才能支撑起生产级Agent的运维。6. 几个真实项目里的坑成本失控、死循环、上下文爆炸6.1 代码执行Agent一下午烧掉大额费用的教训某次给一个内部工具加代码执行能力规定执行器最长跑30秒结果模型生成了一个长时间运行的脚本单次就把资源占满。我当时特别惊讶的是成本监控居然没触发告警因为按token算它是健康的但算上资源占用和带宽实际消耗是预估成本的十几倍。这条教训直接改变了我对成本的定义成本不能只看模型token还要算工具执行、数据检索、基础设施占用。我在Runtime里给每个工具单独设置资源配额并且做整体成本熔断当一个任务的预估成本超过设定预算就自动降级。6.2 死循环不设硬上限操作系统不如Agent能熬有一次线上任务跑了一整晚第二天早上被人发现。原因就是死循环上限设在配置里但配置被某个流程意外覆盖成了最大值。从那天起我把最大步数、最大token总量和最大时钟时间分别做成Runtime底层的硬常量配置只能调小不能调大任何循环都不会超过物理硬顶。这个经验后来概括成一句话所有循环都必须同时受步数、token和时钟三种上限约束三者全部触及才算停止。这是一种比较保守的设计但它保证了你永远不会遇到一个停不下来的Agent。6.3 工具副作用与幂等设计重复执行要可解释Agent系统的重试特别多模型觉得上一步工具执行不对要求再试一次结果一个发送通知的工具被连续触发两次用户收到两条消息。问题根源在于工具的副作用没有纳入设计考虑。我后来给所有工具做了一等公民的幂等控制。比如发送通知工具要求传入事件唯一ID重复调用时直接返回上次结果。对于无法完全幂等的工具比如真实转账严格执行人工审批节点LLM不能直接操作。这是很多团队最容易忽略的红线也是我推荐任何Agent系统上线前必须解决的问题。6.4 回头再看一次LLM调用写到这里再回头看整篇内容想说的核心其实就是一句话Agent系统的一切复杂性都源于动态深度。这个动态深度放大了单次调用的不稳定、放大了成本、放大了调试难度。而Harness Runtime的价值就是把这种动态深度关进一个可观测、可控制、可恢复的笼子里。如果你现在正准备做Agent架构选型我的建议是三个字先别急。回去把单次调用的四张表做扎实把一个最小循环跑通再考虑是买框架、改框架还是自研Runtime。我见过太多团队是因为别人都在做而去自研平台最后连自己的任务都还没跑通。别让架构的宏伟蓝图成为业务Demo的掘墓人。
阅读完成 · 觉得有帮助?