AI Agent架构选型:从单次LLM调用到Harness Runtime的演进

发布时间:2026/10/20 19:31:59
AI Agent架构选型:从单次LLM调用到Harness Runtime的演进 很多人一聊到“AI Agent 架构怎么选”就把问题变成二选一一边是教程里那种几行代码调一次 LLM 的 Demo另一边是生产环境里听起来很重的 Harness Runtime 全家桶。但我在实际项目里最常看到的纠结恰恰卡在中间那段——明明是几个工具调用组成的循环却不知道该把它当脚本写还是直接上运行时。这个标题把路径写得很清楚从一次 LLM 调用到完整的 Harness Runtime是一个连续演进的过程。没有哪一个功能是凭空出现的你缺的不是某个“高级架构”而是知道当前阶段该补哪块板子。这篇我把自己从纯调用层一路改到轻量运行时、再往里加状态管理和审计链路的过程拆开讲顺便把选型时要问自己的几个问题一起说清楚。适合正在做 Agent 应用、但又对“要不要上框架/运行时”拿不准的团队参考也适合一个人 Solo 时少走弯路。1. 选型先回答低成本阶段的三个问题1.1 你的任务是“问一句答一句”还是“跑一段才能交差”很多 Agent 项目的起点其实是问答类需求用户输入一个问题你把它拼进 Prompt调用一次模型把返回结果展示出来。这种场景严格来说还不是 Agent它只是一个带上下文的 LLM 应用。你不需要运行时不需要任务队列甚至连状态管理都可以往后放。但当你开始让模型调用工具比如查库存、生成图片、持续总结网页内容任务的边界就变了。它不再是一次瞬时的请求响应而是“先规划、再调工具、再根据结果继续决策”的多步执行过程。执行过程可能持续几秒也可能持续几分钟甚至几天。这个区别决定了你的架构最终会长成什么样。我建议团队在立项时就先问自己业务方要的是“一个答案”还是一份“可持续推进的工作”如果是后者你的代码里迟早要有一层专门管理执行状态的模块不管它叫不叫 Runtime。1.2 执行过程中断了能不能“接着跑”这是最容易在早期被忽略的问题。纯 LLM 调用阶段断网了大不了重新请求一次结果基本一样。但多步骤 Agent 不一样假设模型已经执行到第 5 个工具调用这一步把数据写进了某个状态下一步要根据结果再调一个外部接口结果进程崩了。重启之后从哪一步继续怎么知道第 5 步有没有写脏数据外部接口会不会因为重复调用产生了副作用这些问题看起来像“运维该操心的”但实际上它会反过来逼你定义 Agent 架构。没有持久化状态你就只能回到原地重新跑代价小的时候还能忍一旦任务是长流程或者高并发根本跑不下去。所以我有一个很朴素的判断标准当你开始担心“崩了怎么办”你就已经从单次调用模型进入需要 Runtime 思维的阶段了。1.3 状态是“一个人独享”还是“多个人共享”第三个问题决定你要不要引入并发控制和共享存储。个人实验阶段你可以把 Agent 的状态放在一个全局变量里反正没人抢。但只要你把 Agent 暴露成服务多个用户同时发起任务状态就不能再存在内存里了。这时候至少需要任务 ID 与所属用户隔离并发上限和排队机制会话级上下文与长期记忆的区分同时有多个执行实例时互不干扰如果你发现自己开始写“全局队列”“锁”“共享数据库表”这些东西说明你不是在写调用脚本而是在搭建一个小型运行时。认清这一点很多框架选择就变得顺理成章——你需要的不是更聪明的 Prompt而是更可靠的任务执行环境。2. 单次 LLM 调用不简单调用边界里的隐藏状态2.1 一次调用的完整结构远远不止“发出一条 Prompt”很多初学者以为从 LLM 调用到 Agent 架构的差异是“要不要多写几层封装”。其实不是。单次调用本身就能展开出七个必须处理的细节请求构造系统提示词、历史消息、上下文裁剪、采样参数温度、max tokens、结构化输出约束、超时与重试策略、费用与 token 上限、结果校验与归一化以及可观测性信息注入。把这些列出来是想说明一件事即使你只做一个“一次调用”你的代码也已经隐含了一层薄薄的运行时逻辑。只不过这一层还非常浅藏在函数内部而已。一个比较规范的请求体大概长这样{ messages: [ { role: system, content: 你是订单助手只有在你确认信息齐全后才能调用工具。 }, { role: user, content: 帮我查一下昨天的营业额 } ], tools: [ { name: query_sales, description: 按日期查询营业额, parameters: { type: object, properties: { date: {type: string} } } } ], temperature: 0.2, max_tokens: 1024 }注意几点。第一温度参数在这里往往不是“创意开关”而是“稳定性开关”。Agent 的工具调用越依赖确定性温度就越应该调低。第二max_tokens 不是越大越好。它决定了结果长度的上界也决定了你要为溢出预留多少成本。第三工具描述本身就在消耗输入 token一次调用十几个工具描述可能占据几千 token这部分成本会在多轮循环中被放大。这些细节就是单次调用层里隐藏的“状态”——不是运行时的持久化状态而是调用策略、请求格式、预算约束等“配置态”。它们同样需要被管理。2.2 为什么我建议从第一天就给每次调用绑一个 trace_id从架构演进的角度看单次调用层最容易为以后铺路的就是引入 trace_id 和 context_id。trace_id 贯穿一次完整 User Request 的所有底层 LLM 调用和工具调用专门用于日志串联和链路追踪context_id 标识一段会话或一个任务用于把同一份记忆拼在一起我在做一个订单客服 Agent 时早期只记了文本日志结果排查问题时根本对不上号同样一句话可能是第 1 轮也可能是第 8 轮出现的日志散落一地只能靠时间猜。后来怼上 trace_id每个日志条目、每次 token 计费、每个工具返回都挂同一个编号问题瞬间清楚。这一步几乎没有成本但它是我见过性价比最高的“未来架构”。哪怕你后来把整个 Runtime 推倒重写只要 trace_id 体系没变迁移成本会低很多。2.3 从“调用函数”到“执行步骤”的最小抽象当你开始做多轮循环时不要再把核心逻辑写成“调用一次模型”的函数而是把它抽象成“执行一个步骤”。# 早期阶段不需要叫 Runtime只需要把“执行一步”和“处理结果”拆开 def run_step(context, agent_id): request build_request(context) response call_llm(request) return normalize(response, context)这里最关键的变化在于函数不再负责“完成整个任务”它只负责“执行一次决策”。至于这个决策是否结束任务、是否要进一步调用工具都交给上层的调度循环来判断。只有做了这个拆解后面接状态管理、接重试、接审计才有地方下手。我见过不少项目把整个 Agent 逻辑塞进一个几十行的大函数里工具调用也写死在内部。这种代码不是不能运行但它很难测试更谈不上架构演进。把“一步”拿出来架构的后续构思会自然清晰很多。3. 中间态地狱工具循环带来的状态与失控问题3.1 函数调用如何把无状态变成了有状态很多模型本身是无状态的你发一次请求它返回一次结果。但一旦引入工具调用事情就变了模型先返回“我要调用 query_sales参数是 2025-01-01”你的代码去执行这个函数然后把函数的返回结果再塞回给模型模型才能继续输出。这一步“工具结果回填”就是状态产生的瞬间。因为模型的下一次输出依赖于上一次工具调用的结果。你没法再像单次调用那样做到无状态重放。而且更麻烦的是一个任务的循环轮次越多需要往返传给模型的消息就越长上下文会越滚越大成本和延迟都在持续增长。3.2 工具循环失控的三个典型征兆在实际项目里我总结出三个最常见的失控征兆循环不退出模型反复调用同一个工具或者不断说“还需要再确认一次”直到你把步数上限强行打满令牌费用翻倍每轮都把完整历史带回给模型工具返回一大段长文本几轮下来 token 消耗成倍增长上下文爆炸工具返回的内容没有裁剪直接把几十页文档塞进消息里模型上下文窗口很快被占满最后连基本指令都被挤没这三个征兆的共同根源是没有给循环设置边界也没有给上下文设置剪枝策略。为什么会失控因为模型采样本身带随机性它的决策不是一个确定性的状态机。你给它的工具越多、任务越开放它越容易在某个分叉上反复横跳。所以“步数上限”“成本上限”“上下文长度上限”“工具结果长度上限”不是可选项而是必需品。3.3 早期可以用的轻量循环方案在还没搭建完整 Runtime 之前一个轻量级循环就能稳住所多数场景。核心思想是循环 外部状态副本 重试上限 JSON 日志。伪代码如下def agent_loop(task, context, max_steps10): for step in range(max_steps): msg build_request(task, context) resp call_llm(msg) if resp.tool_calls: for call in resp.tool_calls: result execute_tool(call.name, call.args) context.add_tool_result(call.id, result) continue return resp.text raise LoopLimitError(f超过 {max_steps} 轮仍未完成任务)关键点是max_steps一定不能只当“防御代码”写在下面而要作为业务逻辑的一部分放到显眼位置并让上层可以动态调整。对于不同的任务类型模型需要的步数差异很大简单查询 3 步就够复杂研究型任务可能要 20 步。步数上限设太小会提前放弃设太大又会烧钱。同时我建议每次循环结束把step、调用工具、参数、token 用量、关键决策写进 JSON 日志。早期不用费劲做完整可观测性一份结构化日志已经能覆盖 80% 的排查需求。真正到多实例并发时再升级成链路追踪系统也不迟。4. Harness Runtime 的本质把 Agent 变成可运维的系统4.1 一个 Runtime 至少要看住七件事说了这么多终于可以回答标题里的另一半了。一个完整的 Harness Runtime到底在“管”什么我通常这样拆构件解决的核心问题缺少它的代价执行调度器决定下一步执行谁、何时暂停、何时恢复逻辑散落在各种 while 循环里无法控制状态持久层保存任务进度、上下文、中间结果崩溃后只能从头重跑无法恢复工具注册与权限控制模型能调哪些工具、每个工具的参数边界模型可能调出不安全或不该调的接口策略与护栏步数上限、成本上限、token 上限、并发上限一次异常循环就能烧掉大量预算可观测性事件链路、耗时、token、失败原因线上问题根本没法定位上下文与记忆管理长短期记忆、历史裁剪、摘要压缩上下文爆炸模型行为迅速劣化运行时隔离工具执行放到独立进程/沙箱工具崩溃直接拖垮主进程这张表不是“全都得上”的意思而是给你一张对照清单你当前的 Agent 卡在哪个现象上就去补哪个构件。很多团队一上来就想把七个都做满结果反而死在自己写的复杂层里。4.2 一个执行步骤在 Runtime 里的完整数据流理解 Runtime与其看概念不如看数据流。我梳理一下一个步骤从进入到结束发生了什么调度器从状态持久层取出当前任务快照反序列化成上下文对象上下文管理模块按策略裁剪消息把必要时摘要压缩再组装成请求执行器调用底层模型接口并把本次调用的 trace_id、token 用量发给可观测性模块如果返回工具调用工具注册中心校验工具名与参数 schema通过后交给隔离环境执行工具执行结果回到上下文对象同时写入状态持久层作为新一版快照策略引擎检查最新上下文是否触发终止条件如步数上限、会话结束标志如果未终止调度器继续下一轮如果终止持久层把最终结果标记为完成你会发现这里每一步都在做“让模型调用这件事可以被审计、被恢复、被限制”的工作。它没有改变模型本身的智能水平但改变了系统对不可靠组件的容忍度。4.3 为什么不能只写“胶水代码”当运行时有的团队会想我手动把上面的逻辑写成函数不就是个运行时吗其实这里有一个关键区别。胶水代码是把步骤写在一起它的逻辑是线性的、隐式的Runtime 的核心在于把“执行意图”和“基础设施”分开。调度器不关心你的 Agent 具体在解决什么任务它只负责“安排步骤执行”状态层不关心你的业务逻辑它只负责“把这一版进度完整存下来”。这种分层才让 Agent 从“一个函数”变成“一个系统”。还有一个容易被忽视的点审计与合规。当你把 Agent 放到线上尤其是涉及支付、医疗、政务等场景时你需要回答“模型当时为什么做这个决定”“哪个版本的工具被调用”“成本花了多少”。只有把上述数据流完整记录下来并且在架构上形成“默认记录”的机制而不是事后打补丁才能应对审计和复盘的追问。5. 架构选型决策框架薄壳、中间层还是全家桶5.1 四个决定架构形态的维度说了半天 Runtime还是要回到选型。我一般从四个维度评估团队规模是一个人独立开发还是三五个后端一起维护任务时长任务普遍几秒结束还是可能持续数分钟甚至跨小时恢复要求任务失败后能不能接受从头重跑治理要求需要完整审计、权限控制、多租户隔离吗你把这四个问题答完选型空间基本上就收缩了。不要先看框架功能清单先看自己的约束条件。5.2 三档架构对应三种不同的投入产出比根据上面的维度我把常见方案分成三档档位典型做法适合条件主要代价薄壳运行时应用内循环 状态表 步数上限 结构化日志13 人团队任务秒级完成可接受重跑并发和长期任务支持弱中间运行时使用成熟编排框架加上持久化、事件、可观测性接入有专职后端任务分钟级需要断点续跑需要学习成本框架约束较多完整控制平面独立调度服务 工作队列 沙箱执行节点 审计中心多条业务线共用 Agent 能力强治理要求研发和运维成本高不适合小团队我给过好几个团队同样的建议如果没有明确的治理需求不要直接上第三档。第三档的问题不是技术上做不到而是它会让团队陷入基础设施维护没精力优化 Agent 本身的智能效果。很多 Agent 项目死在过度基建上而不是死在模型能力不够。5.3 一个相对保守的默认路径我自己的默认路径其实是“薄壳起步但保留中间运行时的接入点”。要做到后者早期需要坚持三件事把所有 Agent 执行入口收敛到一个接口上避免散落各处状态必须走存储不能只放内存关键动作必须产出事件而不是只打日志这三件事看起来很简单但它们保证了以后从薄壳升级到中间层时不需要重写业务逻辑。我就是靠这个路径把一个客服 Agent 从几百行的脚本逐步改成带持久化、事件和检查点的运行时全程没有推倒重来。6. 迁移中的断层点与实战避坑记录6.1 状态恢复与幂等是最容易被跳过的部分很多团队在把轻量循环升级成 Runtime 时第一个踩的坑就是重复执行。外部工具调用天然不是幂等的发送通知、扣减余额、创建订单跑两次就出大事。解决方向是在执行工具前为每个动作生成幂等键tool_call_id f{job_id}:{step_index}:{tool_name} if store.exists(tool_call_id): result store.load_result(tool_call_id) else: result execute_tool(tool_name, args) store.save_result(tool_call_id, result)还有一个容易漏的点工具的“副作用完成”和“结果保存”不是同一个时刻。如果工具执行成功但保存结果时进程崩溃重启后就会再执行一次工具。安全做法是先执行并立刻保存“已完成”标记再继续后续步骤把保存结果和推进状态分开。千万别把“工具执行成功”当作状态推进的唯一依据。6.2 工具调用失败不能把原始异常直接丢回给模型我在早期版本里犯过这个错工具内部抛出异常我图省事直接把堆栈字符串塞进消息里让模型自己看。模型确实“看”了但它做决策的依据是噪音不是结构化信息。更麻烦的是异常文本可能暴露内部文件路径、数据库结构等敏感信息小范围使用还好一旦对外就有隐患。正确做法是先捕获异常分类成“可重试错误”和“不可重试错误”再构造一条简短、明确的工具结果给模型。例如调用 query_sales 失败日期格式不合法请改为 YYYY-MM-DD 后重试。这样模型能明白错在哪、下一步该怎么做。如果模型连续出现同样的失败调用策略层要主动掐断而不是让它无限试错。6.3 可观测性的低配线和及格线我理解小团队没有条件一开始就上完整 APM但可观测性有一条绝对底线能够回答“这一次任务花了多少钱、走了几步、卡在哪一步”。要做到这一点不需要很复杂的系统。最简单的做法是给每个任务维护一个位点文件记录步骤序号、步内模型调用 token、工具名、消耗时间和状态。中途失败时看一眼最后一行就知道卡点。等并发量上来后再把位点文件升级成事件流加上链路追踪系统。但不管哪一档都要坚持一个约定所有关键动作往事件里写不写纯文本。纯文本日志搜索成本高稍微数据量大一点就废了。7. 我用下来的默认方案与下一步扩展空间这些坑踩完以后我自己现在做新项目都会从这样一个组合起步应用内循环解决执行调度用关系型数据库存任务快照每个任务带幂等键所有关键动作发事件再配一份结构化日志。框架不急着上等某个任务真正开始出现长周期、多实例并发、多业务线复用的时候再把中间层和隔离环境一层层套上去。过程里最值钱的经验其实不是“选哪个框架”而是把“调用”和“执行”拆清楚把“业务逻辑”和“基础设施”拆清楚。这两刀切下去架构自然会长出该有的样子不会走偏。后面如果做多 Agent 协作我会在这个基础上补一组消息通道和共享记忆区域让不同 Agent 之间的通信也进入同一个审计体系。如果非要给一个选择结论小团队从薄壳起步中大型平台直接规划中间运行时但无论哪个阶段把状态、幂等、事件这三件事从第一天做好你的 Agent 就不会因为架构选型而返工。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询