news 2026/10/20 6:20:34

AI Agent架构演进:从LLM调用到Harness Runtime的设计与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent架构演进:从LLM调用到Harness Runtime的设计与实践

1. 先从一次LLM调用说起

标题里写的是“AI Agent 架构怎么选”,但真正动手做过的人都知道,绝大多数项目的起点根本不是架构,而是一次朴素的LLM API调用。你接好prompt,丢进模型,拿到一段文本,完事。可是当这个调用需要操作数据库、查询订单、调用内部服务,甚至要根据上一步结果决定下一步动作的时候,事情一下就失控了。

我最早接触Agent就是这个状态:模型能“对话”,能给出看起来合理的答复,但它既不能真正执行操作,也没法在多步骤任务里保持可靠的中间状态。你问它“帮我把上周的异常订单汇总一下,再按金额排个序”,它会给你生成一段SQL,但不会替你跑;你让它“先查询库存,再生成补货单”,它能写好逻辑,但真正调用库存接口、处理返回结果、判断下一步分支,还是得开发者在外面硬编码。

这就是从“一次LLM调用”到“完整Harness Runtime”的演进起点。Harness这个词在这个语境下,指的是一个把大模型、工具调用、上下文管理、策略约束、执行监控全部包起来的运行时容器。它存在的意义不是让模型更聪明,而是让模型在一个受控、可观测、可回滚的环境里安全地干活。这篇文章就把这条演进路径拆开讲:先看单次调用为什么不够,再看多步工具编排的瓶颈,最后落到一个最小可用的Runtime长什么样、各个组件怎么选型。

适合谁看?刚好从“调通API”走向“做Agent功能”的开发者,以及正在纠结“自己写循环还是上Agent框架”的架构师。不需要你已经搭过完整系统,只要写过LLM调用、被多轮工具调用坑过,就能在这篇文章里找到对应的解法。

2. 从裸调用到Agent Runtime:架构演进的三个台阶

2.1 第一个台阶:单次调用与Function Calling的局限

先说单次调用。你把用户问题、系统提示词、Few-shot例子拼在一起,传给模型,返回文本。这种模式解决“一次性问答”没有问题,但它有两个天生的短板:一是模型不产生真实动作,二是没有跨步骤记忆。

Function Calling(工具调用)解决了第一个问题。模型输出一个结构化的工具调用意图(比如search_order(order_id="A123")),由代码来执行这个函数,再把执行结果拼回消息里喂给模型。这个反馈闭环让模型能够“触达”外部世界。但很多团队走到这一步就停住了,因为多轮工具调用的复杂度会指数级上升。

举个实际例子:一个客服Agent,用户说“我要退掉昨天买的那个杯子,退款到原支付方式”。模型第一步需要调订单查询接口,拿到订单号和支付方式;第二步需要判断是否满足退款条件(这可能需要调规则引擎);第三步要调用退款接口;第四步可能还要发通知。每一步的结果都会影响下一步的参数和分支选择。如果这些逻辑全部塞进一个循环里,开发者很快就发现:上下文会越拼越长,模型偶尔会选错工具,超时和异常恢复一团糟,日志里根本看不出是哪一步出了错。

这就是第一个台阶的教训:Function Calling只是给模型一只“手”,但手怎么动、动了之后怎么收回来,需要一个编排层来管,这已经超出了“一个循环里调用LLM”能优雅解决的问题。

2.2 第二个台阶:循环式多步编排的甜与痛

很多人下一步会写一个“Agent循环”,大致逻辑是:把用户消息交给模型,模型返回工具调用,代码执行工具,结果拼回消息,再交给模型,直到模型认为任务完成。这个循环解决了“多步”问题,也让Agent真正具备了行动能力。

这个阶段我一直觉得是“甜痛并存”。甜的地方在于,一套代码就能应对大量任务,模型在ReAct式提示下会自己规划步骤;痛的地方则非常集中,首先是状态管理,循环的中间变量散落在全局或请求作用域里,一旦重试或并发,数据一致性立刻崩。其次是工具路由混乱,当工具数量超过十个,模型选错工具的几率明显上升,没有集中的注册中心去管理描述与参数Schema,排错全靠肉眼。再次是黑盒执行,没有trace、没有耗时统计、没有失败归因,生产环境一报警,只能抓瞎。

我在一个内部数据查询Agent项目里就踩过这个坑。当时工具只有五六个,循环看起来完美,后来工具规模涨到十几个,还引入了并行调用多个工具的诉求,问题立刻暴露:重试时同一个下单接口被调了两次,上下文里塞满了工具返回的长Json,模型开始复读之前的错误,整个调试变得无比痛苦。

2.3 第三个台阶:Harness Runtime带来了什么

走到这里,自然会问:能不能把循环、状态、工具注册、上下文管理、重试与观测这些东西抽出来,做成一个统一运行时?这就是Harness Runtime的由来。

你可以把它理解成Web开发中从“手写AJAX”到“用前端框架”的转变:AJAX本身没错,但如果你要管理路由、状态、组件生命周期、副作用清理,一定需要框架层面的约束。Harness Runtime做的事情类似——它提供一个标准化的执行环境,让Agent在这个环境里跑,而不是每次从零拼一个循环。包括模型接入的统一接口、会话级上下文管理器、工具注册中心、策略控制(超时、重试、并发)、可观测性链路。

这里要强调一点,Runtime不是要把逻辑包装得黑盒化。相反,好的Runtime应当保持“透明”:你给它配置了哪些工具、模型、策略,它按什么顺序执行、每步花了多少钱多少Token,都要能看得清清楚楚。这个透明度正是它和“一堆helper函数”的本质区别。

3. 核心组件拆解:一个完整Harness Runtime长什么样

3.1 调度层:Agent主循环不要靠手写

Runtime的调度层是心脏,它决定了模型、工具、用户消息之间的交互顺序。最简单的实现是“线性循环”:模型输出→如果是工具调用就执行→结果回填→继续。但完整的调度层还要处理并行工具、条件分支、终止条件、异常恢复。

我的建议是,第一版不要设计过于复杂的Planner或DAG调度器。大多数真实业务场景,“线性循环+组内并行”就够用了。并行方向的控制其实不需要模型来决策,而是让工具定义里显式声明concurrent: true,调度层在某一轮把所有标为并行的工具一起发出去。

调度层还必须管理最大迭代次数。我建议默认给一个较低的阈值(比如8步),因为模型在复杂任务里容易出现“来回试”的行为,比如先选错工具、再纠正、再试另一条路径,步骤会被无谓消耗。把上限暴露为配置项,而不是硬编码,是避免线上事故的基本功。

3.2 上下文管理器:Token预算要盯死

Runtime里最容易失控的地方就是上下文。工具返回一个分页查询结果可能有几万Token,连续两三个工具调用就会把窗口塞满,然后模型开始丢三落四。

上下文管理器要做三件事:预算计算、内容裁剪、历史压缩。预算计算是在每次发给模型前估算当前消息序列的Token数,如果超过安全线(建议是窗口的70%,留30%给模型生成空间),触发裁剪策略。裁剪分两层,底层是滑动窗口,只保留最近N轮对话,把最早的历史摘要化;顶层是按消息类型保留,系统提示词永远不删,工具执行结果如果太大要用截断字段替代完整内容。

我踩过的最深的坑是上下文压缩与工具结果不兼容。早期直接把最早的几轮对话扔掉,结果模型在一个需要跨轮状态的任务里完全失去了记忆,最后你都不知道该怪模型还是该怪裁剪策略。后来改成“滚动摘要+关键字段保留”的混合方案:每轮的结论性内容进摘要,工具结果保留必要字段,上下文稳定多了。

3.3 工具抽象层:注册中心与Adapter

工具是Agent的“手脚”,工具层设计直接决定Agent能干什么、出错了怎么办。我强烈建议做一个工具注册中心,每个工具声明元信息:名称、描述、输入参数Json Schema、是否幂等、超时时间、是否可以重试、是否需要人工审批。

描述写得好不好,直接决定模型选对工具的准确率。我有一条经验:描述里不要堆形容词,要写清楚这个工具“在什么场景下用什么参数”,最好带上与相似工具的区分点。例如search_order的描述不要只写“查询订单”,而要写“根据order_id或customer_phone查询订单,在用户要求查看下单记录时使用;如果要查售后单,请联系售后单接口”。

Adapter层处理工具输入输出与LLM消息之间的转换。常见问题是模型生成的工具参数偶尔不符合Schema,比如日期格式不对、ID带上了引号。Runtime应当在Adapter层做一次校验,校验失败的参数做一次轻量纠正而不是直接抛错,这个方案比模棱两可的重试效率高得多。

3.4 执行与治理:超时、重试、幂等、并发

工具执行不像本地函数调用那么干净。网络超时、服务端500、数据暂时性不可用是常态。Runtime的治理层要把这些“脏活”统一做掉。

超时分为两级:单次工具调用超时和整轮Agent超时。单次超时建议按工具类型给默认值,一般外部API给5~10秒,内部服务可以给短一点。整轮超时是硬约束,避免Agent陷入无限循环,我通常设120秒。

重试策略遵循“指数退避+抖动”。基本公式是delay = min(base * 2^attempt, max_delay) + random(0, jitter),基础延迟从200ms起步,最多重试3次。要注意的是,只有幂等工具才允许重试。判断幂等可以看工具是否有唯一的业务幂等键,比如退款单号、请求ID。没有幂等键的外部写操作宁可直接失败也不要重试。

并发治理的焦点不是让Runtime开线程,而是控制Agent同时调用工具的上限,以及限制同一用户/会话的并发Agent数量。否则一个营销活动入口被用户高频触发,底层接口的压力会瞬间拉满。

3.5 可观测性:AI Trace与审计

Agent系统的排查难度比传统接口高一个量级:命令式代码是确定的输入输出,而LLM的输出是概率性的。所以Runtime必须自带AI Trace,记录每一步的输入消息、模型输出、工具调用参数、工具返回摘要、耗时、Token消耗、最终判定。

不要指望事后靠业务日志拼凑Agent的执行过程。一个完整的trace应该是父子结构的:一次用户请求生成一个root span,每次LLM调用和工具执行都挂在对应的span下。这类trace用现有的可观测性系统就能搭,不需要另起炉灶,关键在于产线代码里必须埋点。

审计这里多说一句:如果Agent有权限执行写操作,务必记录“谁在什么时间让Agent做了什么事,Agent实际调了哪些工具”,这个审计链在排查责任时能救你的命。

4. 实操:一个最小可用的Harness Runtime骨架

4.1 Agent主循环的代码骨架

理论讲了那么多,还是看代码实在。下面是一个语言无关风格的最小主循环骨架,我用TS风格写,方便理解核心语义。

async function runAgent( request: UserRequest, runtime: RuntimeConfig ): Promise<AgentResult> { const session = runtime.sessionStore.get(request.sessionId); const maxSteps = runtime.maxSteps ?? 8; const deadline = Date.now() + (runtime.timeoutMs ?? 120_000); let messages = session.messages.concat(toUserMessage(request.input)); messages = runtime.contextManager.trim(messages); for (let step = 0; step < maxSteps; step++) { if (Date.now() > deadline) throw new AgentTimeoutError(); const response = await runtime.llm.chat(messages, { tools: runtime.toolRegistry.listToolSignatures(), }); const toolCalls = extractToolCalls(response); if (toolCalls.length === 0) { session.messages = messages.concat(response.message); return { status: "done", output: response.text, tokenUsage: response.usage }; } const executionResults = await runtime.executor.runAll(toolCalls); // 内部处理并发/超时/重试 messages = messages.concat(response.message, toToolResultMessages(executionResults)); runtime.tracer.traceStep(step, messages, response, executionResults); } throw new AgentStepLimitError(maxSteps); }

这个循环看起来简单,但它把复杂部分都委托给了Runtime里的四个对象:contextManager负责裁剪压缩,toolRegistry负责给模型提供工具签名,executor负责执行治理,tracer负责埋点。这正是Runtime的好处——主循环是稳定的,变化的是策略配置。

4.2 工具注册与路由分派

工具注册中心的核心是一个签名列表和分派函数。

interface ToolDef { name: string; description: string; parametersSchema: JSONSchema; handler: (args: unknown, ctx: ToolContext) => Promise<ToolResult>; retryable: boolean; timeoutMs: number; concurrent: boolean; }

注册时最重要的是描述质量。我给内部工具库写过一个规范:描述要包含“触发场景”“关键参数”“与相似工具的边界”。举个例子,两个工具分别是“导出报表”和“查询报表状态”,如果描述都只写“管理报表”,模型大概率选错。

路由分派注意一点:执行前用Json Schema校验模型生成的参数,不符合就直接纠偏。初期用最笨的“再调用一次LLM纠正参数”就行,成本不高但准确率提升明显,成熟后再换成基于规则的反序列化器。

4.3 上下文预算分配的计算示例

上下文管理一定要“算着用”,不要“等到炸了再处理”。假设模型的上下文窗口是128K Token,我给一个经验分配比例:

消息类型预算占比说明
系统提示词5%包含角色定义与总规则,一般不裁剪
工具Schema定义10%工具数量多时可以按热度优先省略部分描述
最近对话窗口65%保留近几轮完整消息,做滑动窗口
历史摘要10%早期内容压缩成结构化摘要
模型回复预留10%给生成空间留余量

这个分配不是死的,但如果某类消息频繁超预算,就该怀疑是工具描述过长还是工具返回结果没有摘要化。比如一个查询接口返回了200条记录,算下来可能有15K Token,我在生产环境会默认对工具结果做“前10条完整+统计摘要”的截断,并明确告诉模型“如需更多数据,请带分页参数再次调用”。

4.4 重试与超时参数到底怎么给

重试参数不要拍脑袋,按工具类型分档:

  • 只读查询类:重试3次,基础延迟500ms,指数倍率2,抖动200ms
  • 写操作但幂等(有幂等键):重试2次,基础延迟1s,倍率2
  • 写操作非幂等:不重试,直接标记失败,交给Agent决策
  • 模型调用失败:重试2次,基础延迟1s,因为LLM服务的返回错误里很多是瞬时过载

注意一点,Agent失败处理和传统代码失败处理不同:不要一失败就把错误抛给用户,而要把错误信息当成工具返回消息喂回给模型,让它自行选择换参数重试、换工具还是道歉收尾。这个设计能让Agent在边界情况里表现得自然得多。

5. 常见问题与排查实录

5.1 工具选错或参数拗不过

最常见的问题是模型在多个相似工具里选错。我排查过几次,基本原因是工具描述太像。比如一个“查询今日销售额”一个“查询本月销售额”,描述里都写“查询销售额”,模型很容易混淆。解决方法是把差异化词前置:“仅返回当日数据,用于日报场景”和“滚动月累计数据,用于月度分析”,并在描述里补充反例“如果是看月度走势,请不要用本工具”。

参数问题也有规律:模型从用户原话里提取参数时容易带上多余符号,比如订单号"A123"带了引号。Adaptern层加一个sanitize步骤,去掉常见噪音字符,能把这类错误率压到很低。

5.2 循环与Token爆炸

有些任务模型会反复尝试同一工具而不改变参数,导致上下文膨胀、超时爆炸。我在Runtime里加了一个“重复检测器”:如果连续两步的工具调用完全相同,就中断重试,把错误反馈给模型,让它改用新方案。实际效果很好,能把无效Token消耗降下来30%以上。

上下文的另一个坑是工具返回过大。有一回一个搜索工具把20条详情都塞进上下文,直接爆了窗口。后来我给工具包了一层“结果摘要器”,对大列表做字段级裁剪,模型识别的关键信息完全足够。

5.3 并发与幂等问题

生产环境最怕的是Agent重复提交写操作。用户点了一次“生成报销单”,因为网络抖动,前端重试、Agent重试、Http重试三层叠起来,可能把同一报销单提交三次。

解法关键在幂等键。在会话开始时生成一个全局请求ID,每次发起写工具调用时把它作为幂等键传给接口。服务端如果发现同一个请求ID已处理过,直接返回原结果。这个改动成本极低,但在Agent场景里能防住最恶劣的事故。另一个容易踩的点是并发工具之间的共享状态,比如两个工具同时更新同一个订单行,会出现覆盖。如果业务上对这类冲突敏感,尽量把工具标记为串行执行。

问题典型现象排查思路推荐方案
工具选错调了功能相近但错误的接口对比描述差异,检查相似工具的边界说明重写工具描述,加反例约束
参数异常模型参数带引号、日期格式错看trace里的工具入参Adapter层sanitize + Schema校验
上下文超限长对话后模型遗忘早期指令查看发送给模型的消息Token分布滚动摘要 + 结果截断
死循环步骤数打满,Token消耗剧增查看相邻步骤的工具调用是否重复重复检测 + 步骤上限
重复提交数据库出现同内容多次写入审计日志查请求ID全链路幂等键
单点超时工具调用卡死整轮看trace里哪个工具耗时超标分档超时 + 异步化

6. 架构选型的判定标准:别为了Harness而Harness

走到最后,回到标题里的核心问题:架构怎么选?我见过不少人一上来就追求完整的Runtime、复杂的Agent框架,结果任务只有两三个工具,运行时本身反而成了负担。

我的经验是分三层判断。

如果业务只是“一问一答+极少量的工具调用”,比如FAQ助手、简单的内容生成,直接裸调LLM加上一层Function Calling就够了。这个阶段不要引入Runtime,否则光是理解上下文管理和调度策略就要花掉好几天。

如果业务有5~20个工具,且需要多轮推理,这时候框架型方案或轻量自研Runtime都比较合适。框架的好处是轮子齐全,坏处是抽象层较多,出了问题不好定位;自研的好处是逻辑透明,坏处是工程量集中在上下文管理和重试治理上。坦白讲,工具数量在这个区间时,我更推荐自研一个极简版Runtime,就是上面那个骨架再加观测埋点,比套框架可控得多。

如果业务超过了20个工具,或者需要多人并行开发、多个Agent协作、涉及严格审计和权限控制,那确实需要一个正式的平台级Runtime。这个时候选型要重点考察模型接入层是否灵活、工具注册和权限管理是否完善、trace和审计是否完整。框架不满足就自研,自研重点放在治理和观测上,而不是重新发明一个调度器。

最后给一张自检清单,做架构决策之前过一遍:

  • 当前工具数量是否已超过10个?没有的话,先不上Runtime。
  • 是否需要自动化的多步工具编排?只有单步调用就别上循环框架。
  • 是否存在重试后会导致重复写入的接口?有的话先做幂等再谈架构。
  • 排查Agent问题时,能否在10分钟内定位到具体是第几步、哪个工具出错?不能就是观测欠账。
  • 团队有没有能力维护一个内嵌的运行时层?如果不确定,优先选抽象少、依赖少的方案。

我个人实际操作中踩过几次坑之后总结下来,Agent架构选型里最值钱的部分不是某个框架或组件,而是把上下文预算、工具描述规范和幂等治理这些“基本功”做扎实。工具从5个涨到20个、业务从单步字符串生成涨到跨系统编排的时候,能兜住底的不是新框架,而是早就按照这一类原则设计好的运行时骨架。先小步快跑搞清楚自己的真实复杂度,再决定要不要上重型方案,这个顺序基本不会错。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 6:20:23

长江流域shp数据空间可视化分析:从坐标系到实战避坑

简介&#xff1a;这是一套面向GIS从业者、地理科研人员及高校学生的长江流域矢量数据集&#xff0c;以Shapefile格式提供&#xff0c;可直接在ArcGIS等软件中加载&#xff0c;用于空间可视化分析与制图。包内共44个文件&#xff0c;涵盖shp、shx、dbf、prj、sbn、sbx、cpg及xml…

作者头像 李华
网站建设 2026/10/20 6:19:39

PointPillars TensorRT加速实战:FP16精度与Plugin优化部署指南

1. 从零到一&#xff1a;为什么PointPillars的TensorRT加速值得死磕做过激光雷达感知部署的人都有一个共同感受&#xff1a;模型在论文里跑得再好&#xff0c;一上真实车载平台就原形毕露。PointPillars这个模型在点云3D检测领域算是经典中的经典&#xff0c;它把无序的3D点云转…

作者头像 李华
网站建设 2026/10/20 6:19:19

Windows 下 ffmpeg 安装与实战:从解压到批量转码抽帧

简介&#xff1a;这份资源是面向 Windows 平台的多媒体开发工具包&#xff0c;适合音视频处理爱好者、C 开发者以及需要集成编解码能力的工程人员使用。它基于 FFmpeg 4.2.1 的 win32 开发版本&#xff0c;可用于音视频格式转换、流媒体推拉流、剪辑裁剪、滤镜特效以及自定义编…

作者头像 李华
网站建设 2026/10/20 6:19:17

CTF MISC入门:用Python破解图片隐写与压缩包爆破全攻略

很多人第一次接触CTF的时候&#xff0c;都听过一个说法&#xff1a;MISC是入门最容易、也最适合刷成就感的方向。确实&#xff0c;MISC题目不像PWN和RE那样需要啃系统底层和汇编&#xff0c;也不像Crypto那样需要深厚的数论功底&#xff0c;它更多考察的是“信息搜集能力”和“…

作者头像 李华
网站建设 2026/10/20 6:19:06

Linux进程管理实战:从ps/top到systemd服务控制

1. 为什么进程管理是Linux筑基的核心1.1 从"程序"到"进程"&#xff1a;先搞清你在管理什么很多新手刚开始接触Linux时&#xff0c;会有一个很自然的困惑&#xff1a;程序&#xff08;Program&#xff09;和进程&#xff08;Process&#xff09;到底有什么区…

作者头像 李华
网站建设 2026/10/20 6:19:05

ISO 10218:2025解读:工业机器人功能安全设计与性能等级落地

1. 从“看得见的防护”到“看不见的证明”&#xff1a;ISO 10218:2025到底改了什么工业机器人这个行当&#xff0c;以前聊安全&#xff0c;大家第一反应是围栏、光栅、安全门这些“物理隔离”手段。但这两年风向明显变了&#xff0c;尤其是协作机器人、移动操作臂、AI感知系统这…

作者头像 李华