news 2026/10/1 13:39:48

Agent工程三层架构:Harness、Loop与Graph的生产实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent工程三层架构:Harness、Loop与Graph的生产实践指南

前一阵子帮团队重构了一个 Agent 项目,代码从最开始的两千行左右,膨胀到了差不多一万行。业务方倒是很高兴,因为多 Agent 协作、工具权限、失败重放这些需求都上了,但我自己清楚,真正让这个项目活下来的,不是功能变多,而是我们把整个 Agent 的工程结构强制拆成了三层:Harness、Loop、Graph。这篇文章想把这三层到底各管什么、在真实生产里怎么配合、有哪些从事故里换来的经验,一次讲清楚。

1. 先把混乱的 Agent 拆成三层:Harness、Loop、Graph 到底指什么

大部分团队的 Agent 项目都是这么起步的:一个主循环,一段长长的 system prompt,再加上十几个工具函数,全部堆在一个文件里。Demo 阶段很正常,跑起来也挺像回事。但业务需求一变多,问题就全冒出来了——有人要加权限控制,有人要限制工具调用次数,有人要让多个 Agent 接力干活。你用最朴素的while True去满足这些需求的时候,代码会迅速变成一坨没法改的面条。

我自己踩过这个大坑之后,才真正认同一个观点:Agent 工程不是写一个聪明的循环,而是把复杂度分到不同的层里去消化。我后来遵循的分层方式是这样的:

  • Harness 层:给 Agent 提供"运行环境"。管的是工具怎么注册、权限边界在哪、上下文怎么组装、插件怎么加载、错误怎么统一暴露。
  • Loop 层:管单个 Agent 的"思考—行动—观察"循环。目标怎么拆、步骤怎么规划、工具调用的结果如何影响下一步决策、什么时候该停下来。
  • Graph 层:管多个 Loop、多个 Agent 之间的"编排"。节点怎么连接、分支走到哪、失败怎么回退、任务状态怎么持久化和重放。

打个比方,Harness 是给赛车手准备的座舱和仪表盘,Loop 是赛车手在赛道上的每一脚油门刹车,Graph 是整场比赛的路线图和进站策略。你可能只需要一辆能开的车,但真上了赛道、还要争成绩的时候,这三样东西缺一不可。

这三层强制切分之后,最大的收益不是代码变漂亮了,而是问题的归属权变清晰了。插件加载不了,去 Harness 里查;模型陷入死循环,去 Loop 的状态机里看;多 Agent 之间的任务衔接出问题,去 Graph 的节点流转记录里找。排查效率完全不是一个量级。

2. Harness:让 Agent 在“可控环境”里行动

2.1 工具注册不是写个 List,而是给每个工具建立运行契约

很多初学者容易低估 Harness 的复杂度,觉得"我聚合了十个工具函数就是有 harness 了"。但真正能进生产的 Harness,工具注册这件事本身就是一道完整的工程。

我的做法是,每个工具进来都要带一份运行契约。什么是契约?就是一组声明:参数类型和必填项、返回值的格式、超时时间、是否有副作用、是否允许在同一次任务里被重复调用、调用成本估算。这些信息不只是给开发者看的,更是给上层的 Loop 做规划和决策用的。

举个真实例子。我们有段时间在 Harness 里接了一个搜索接口,没声明"重复调用会触发配额计费"。结果某个任务里,模型对同一个关键词反复搜索,几十次调用直接把当天的 API 配额打完了。后来我们给工具注册表加了两个字段:max_repeat_count和cost_estimate。Loop 在规划阶段看到同一参数已经被连续调用过两次,就会主动切换到另一个工具或者换一个搜索词,这类事故基本绝迹。

2.2 权限边界:默认拒绝,而不是默认放行

权限是 Harness 层最容易出安全事故的地方,也是最容易被开发同学忽略的地方。大多数人觉得"工具反正都是我们自己写的,有什么权限好控的"。但别忘了,真正调用工具的是 LLM,而 LLM 的单次输出永远有可能偏离预期。

我的经验是把每个工具按风险等级分成三类:

等级含义示例
allow低风险、高频、无副作用查天气、读文档、向量检索
confirm有一定副作用,需要用户确认发邮件、改配置、写数据库
deny明确禁止,任何情况下都不放行删除生产数据、提现、修改权限

这个分类不能只写在系统 Prompt 里。Prompt 只是给模型的"建议",代码强制拦截才是真正的安全边界。所有工具调用统一走 Harness 的调度器,调度器会先查权限,再决定是直接执行、弹确认框还是拒绝。经验是,默认拒绝原则永远比"默认放行但事后审计"安全得多。

2.3 上下文组装:好结构比大塞量重要一万倍

Harness 的另一项核心工作是把上下文组装好再交给 Loop。大家都懂"模型上下文窗口有限"这个道理,但实际做上下文管理的时候,很多人犯的错是:只关心"装了够不够多",不关心"装进去之后长什么样"。

我做过一个对照实验。同一批对话历史和工具返回结果,一种做法是全部按时间顺序拼接成一个大字符串塞进 Prompt,另一种做法是按"时间线 + 角色来源 + 工具返回摘要"整理成结构化的段落再塞进去。在复杂任务上用同样的模型跑 20 轮测试,后者的任务成功率大概高了三成左右。

后来我们把上下文管理的逻辑统一收口到 Harness 的 Context Assembler 组件里。所有工具返回先做摘要,摘要按来源和类型分类存放,Prompt 里只放摘要和引用 ID。模型如果觉得某个摘要不够详细,可以主动调用一个read_full_result的工具去拉完整内容。这样做的好处是,token 控制住了,同时因为每一步都有引用 ID,后面 Graph 做重放和审计也有了锚点。

2.4 插件机制:每个插件都必须是封闭的扩展点

很多团队都有过harness failed to load plugins之类的报错。插件加载失败的原因五花八门:依赖版本冲突、命名空间覆盖、生命周期回调没绑定、插件 A 和插件 B 注册了同名的工具 ID。

我自己的血的教训是:插件不能想怎么写就怎么写,必须实现固定的生命周期接口。我们后来强制要求每个插件实现load、validate、register、unload四个阶段。加载顺序是:先验证依赖和命名空间,再静态注册工具签名,再动态绑定事件回调。任何一步失败,Harness 都能把整个插件从运行环境中安全移除,不影响主进程和其他插件。

这套机制虽然让插件开发者多写了几个函数,但在生产环境里换来的收益是决定性的——插件更新不用重启主服务,单个插件崩溃不会拖垮整个循环。

3. Loop:单个 Agent 的“大脑节奏”控制

3.1 一个循环的五个环节,缺一不可

Harness 把环境和工具准备好了,接下来真正产生智能的是 Loop 层。不管你的 Agent 对外表现是"会写代码"还是"会做研究",内部跑的都是同一个模式:分析目标、拆解步骤、调用工具、观察结果、反思调整。

我这里特别想强调"反思调整"这一步。很多团队的 Loop 结构是"执行—返回—再执行",没有"对比预期与实际差异"这个动作。结果就是模型在一个错误方向上越走越远。比如明明已经拿到了完整的答案,它还在继续搜索更多资料,因为它的循环里根本没有"够了,该停了"这个判断。

好的 Loop 必须在每轮结束后做一次自我校验:当前结果是否足够回答最初的目标?还有哪些关键信息缺失?下一步是继续深入,还是切换到新方向,或者直接收尾。这一步做不做,直接决定了 Agent 像不像一个靠谱的同事。

3.2 为什么循环要状态机,而不是 while True

如果你直接把 Agent 的主循环写成while True,那只是"轮询"。真正的 Agent 循环是有状态的。它处于"正在规划"、"正在调用工具"、"正在观察结果"、"正在反思"这些状态时,对 Prompt 结构、可用工具列表、拦截规则的要求完全不一样。比如在"正在调用工具"的状态,你可以直接允许工具执行;如果它还处于"正在规划"阶段,你就不想让一些高成本工具白白触发。

生产环境里我采用的是有限状态机的结构:IDLE → PLAN → ACT → OBSERVE → REFLECT → (回到 PLAN) 或进入 DONE / FAILED / NEED_HUMAN。每个状态有独立的处理函数,状态之间的跳转条件由上一状态的输出决定。

状态机带来的最大好处是可观测性。任务跑了两小时还没结束,打开状态日志,一眼就能看出它是不是在 PLAN 和 ACT 之间反复空转。没有状态机的时候,你只能从一坨日志里靠猜。

3.3 循环里的三个保命参数

如果你问我 Loop 层最值钱的工程参数是什么,我会说三个:最大步数、重复结果检测、单步超时。

  • 最大步数:不同的任务类型给不同的额度。我们内部的经验是:简单问答 8 步,文档处理 30 步,复杂的研究类任务 80 步。超过步数直接进入 FAILED 状态,转人工处理。
  • 重复结果检测:把每一步的工具返回结果做哈希,连续几步哈希相同,判定为无效循环。这个机制救过我好几次。有一次模型反复调用同一个报错接口,返回的错误信息一模一样,它硬是拿同样参数重试了十几次,有了检测之后,走到第 3 次就能主动跳出并反思。
  • 单步超时:每个工具调用必须设置独立的超时时间,超时后 Harness 返回一个统一的 timeout 错误码,Loop 捕获后可以选择换一个工具或者换一种调用方式。

这三个参数单独看都很简单,但放在一起就构成了 Loop 层的安全网。没有它们,Agent 的"聪明"可能会变成"灾难性的耗钱机器"。

4. Graph:从单线程到多 Agent 协作编排

4.1 单个 Loop 的边界,就是 Graph 的起点

单 Loop 做得再完善,也只能处理"一个人从头干到尾"的任务。可真实业务里的任务,经常需要多个 Agent 角色接力:一个做需求拆解,一个写实现方案,一个做结果验证,甚至还要分两条线并行调研,最后把结论合成。把这些前后依赖、并行分支、条件跳转画成一张有向图,就是 Graph 层要做的事。

Graph 层一个经常被误解的地方是:使用图不是为了"看起来高级",而是为了引入确定性。Loop 层充满随机性,模型每次输出都可能不一样。但图本身是确定的——A 状态之后到达 B 还是 C,由程序根据明确条件决定,不会凭空跳到 D。这个特性让测试回归变得可行,也让生产链路可以被审计。

4.2 节点状态持久化:中断了,从哪里继续?

做多 Agent 协作的时候,一个复杂任务可能要跑几十分钟甚至更久。中途任何一个环节挂了,你的选择只有两个:从最开始重新跑,或者从最后一个完成节点接着跑。显然后者才是人该干的事。

我的设计要求每个 Graph 节点在完成后,把一个完整的节点快照写入持久化存储。快照内容包括:当前节点的输入、输出、涉及的工具调用 ID、消耗的 token 数、时间戳。这样任务中断之后,恢复程序可以扫描最后一个未完成节点的上游,从那个位置继续。

这个设计最初只是为了容灾,但后来发现它还是审计利器。线上出现用户纠纷,说"Agent 动了不该动的数据",你可以直接从持久化快照里还原出当时每一步的操作序列。这种能力不是产品需求评审时你能想到的,但真出事的时候,它就是救命稻草。

4.3 分支和回退:图的拓扑结构要固定,不要交给 LLM

Graph 层最容易犯的错,是让 LLM 来决定图的结构。比如说"模型觉得任务太复杂,就动态插入一个新节点"。听起来灵活,实际完全不可控。你那不是编排,是让演员自己改剧本。

我的铁律是:图拓扑固定,节点内自由。运行前,Graph 已经把整条路径以及所有可能的分支都定义好了。模型只能在某个节点内部做决策,比如在"方案设计"节点里,决定用方案 A 还是方案 B;节点之间走哪条边,是程序根据确定性条件判定的,不由模型临时发挥。

回退策略也一样要提前画好。比如"验证失败 → 退回方案设计节点 → 最多重试两次 → 再失败转人工"。在 Graph 里,这就是一条带重试计数的回边。重试次数达到上限后,状态自动迁移到 NEED_HUMAN,等待人工接手。这两条规则保住了生产系统的可预测性,让排障和演练都成为可能。

5. 生产环境里的联动、并发与事故复盘

5.1 三层的接缝处,才是真正的工程难点

架构图看起来清爽,但真实系统的复杂度和坑,几乎都藏在层与层的接缝里。

我自己对分工有一个明确的约定:Harness 管"能不能"(权限、上下文、工具合法性),Loop 管"怎么做"(规划、执行、反思),Graph 管"该不该继续"(节点跳转、分支、回退、转人工)。边界清楚了,遇到问题才不会互相甩锅。

一个特别容易踩的接缝是工具返回结果的格式。如果有的工具返回 Markdown,有的返回 JSON,有的返回纯文本,Loop 层的解析逻辑会变成一团乱麻。后来我强制规定:所有工具必须返回带 schema 的结构化 JSON。出错时统一返回error_code + error_message,模型永远看到的是干净的数据结构,解析成功率显著提升。

5.2 并发到底在哪一层扛

"AI Agent 怎么扛并发"是我见过被问得最多的问题之一。很多团队的直觉是让 Loop 层支持多线程,同一个模型实例同时处理多个任务。这个做法后患无穷:上下文串线、工具调用顺序混乱、token 成本失控。

我的答案是:Loop 层保持串行,Graph 层做并发。单个 Loop 内部严格串行,保证一个任务流里的上下文和工具调用顺序完全可控。多个互相独立的 Loop 节点,在 Graph 层被并行调度:每个 Loop 实例拥有独立的上下文和状态存储,跑完之后把结果汇总给父节点。这样并发度上去了,但每个并发单元内部依然是单线程的,Bug 边界非常清晰。

5.3 记忆架构:工作记忆和长期记忆要分开

关于记忆,我也经常被问。指望单个 Loop 里的上下文承载所有记忆,是反工程的。上下文窗口是稀缺资源,随着任务推进会不断被消耗。

我采用的方式是双层记忆。工作记忆跟着当前 Loop 走,只保留当前任务必需的信息,任务结束就归档或清空。长期记忆存在 Graph 层的全局存储里,按任务维度和实体维度建索引。新任务启动时,Graph 会把相关的长期记忆预取到 Harness 的上下文组装器里,让模型"想起"历史对话的关键结论。

这个设计把记忆问题从"模型能力问题"变成了"存储检索问题",一旦完成这个转换,可用性和可控性都大幅提升。

5.4 一次插件加载失败事故的完整复盘

写这篇文章之前,我刚处理过一起 "harness failed to load plugins" 的事故,正好拿来复盘。

现象是线上服务报错,说有两个插件条目没有激活。第一反应是检查插件文件,都在;第二反应是看依赖版本,也都满足要求。最后排查到根因时有点意外:插件 A 在注册时绑定了一个全局事件监听,插件 B 加载时也注册了同一个事件,后加载的 B 把 A 的监听函数覆盖掉了。于是 A 的某些功能在代码层面看起来一切正常,但运行时已经静默失效。

这个坑给我们带来的改进是:插件加载必须做静态冲突检测。加载前扫描所有已注册的工具 ID、事件监听器、命名空间,发现有冲突直接拒绝加载并给出明确的冲突报告。从那次之后,我才算真正把插件机制从"能用"变成了"稳"。

5.5 状态双写与审计日志

Graph 层状态该放内存还是放磁盘?我的答案是都要。热状态放内存,供调度器快速读取;全量快照写持久化,供审计和重放。很多团队只做内存态,觉得"重启就重启吧,从最开始重新跑"。但一个跑了 40 分钟的复杂任务,一旦重启就要从头来,这代价是用户接受不了的。

快照的粒度也要讲究。不必每个步骤都写盘,我通常是在节点级别打点,也就是某一整个环节完成后再写一次快照。这样磁盘压力不大,中断恢复的精度也够用。

6. 选型建议:你的项目到底需不需要三层架构

说了这么多,我必须强调一句公道话:不是所有 Agent 项目都该一上来就上三层架构。如果你只是做一个单轮问答 Demo、工具不超过五个、流程完全固定的小工具,一个写得很干净的循环加一个工具列表,完全够用。强行上 Graph 只会增加维护负担,让本来简单的事变得复杂。

但也别走另一个极端。我的建议是在项目第一天就把目录结构划成三块:harness/、loop/、graph/。哪怕里面现在各只有一个文件,这个目录本身就是一种工程契约,它强迫你在写代码的时候思考"这个逻辑该属于哪一层"。

如果你打算从零搭建,我推荐的落地顺序和大多数人想的正好相反:先做 Loop,再做 Harness,最后上 Graph。

顺序很重要。先跑通单个 Loop,让它能在给定目标和工具的情况下稳定完成单任务;然后补 Harness,把工具权限、上下文结构、错误码体系这些基础设施夯实;最后再加 Graph,把多步骤编排和任务状态管理做起来。Graph 的引入是渐进式无痛改造——Loop 还是那个 Loop,只是调用方从主函数变成了图节点。

另外有几个加分项,是我在实际项目中反复确认过的:

  • 给每次模型调用记录 token 成本和耗时,按任务维度汇总。没有成本数据,你判断"这个反思步骤到底值不值"就没有依据。
  • 给每个工具调用打 trace_id,把 Harness、Loop、Graph 三层日志串起来。线上排障时,这个 ID 让你能在三层的日志之间来回跳转,而不是在几个系统里搜半天。
  • 把暂停、恢复当作一等公民来设计。用户随时可能打断一个长任务过几天再继续,你的架构必须支持在 Graph 的任意节点上暂停和恢复。
  • 对模型输出做 schema 校验,不要默认模型每次都会输出合法 JSON。校验失败就让它重试一次,连续几次失败就降级给人工处理。

聊到这里,其实已经把三层架构的核心讲得差不多了。最后说一点我个人的体会:Agent 工程的上限确实取决于模型的聪明程度,但它的下限,也就是稳定性和可控性,几乎完全是由工程架构决定的。Harness、Loop、Graph 这套分层,真正解决的是"模型不可控"这个先天问题——把疯狂的部分关进笼子里,把确定的部分用工程手段固化下来。这也是我做 Agent 项目以来,觉得最值得分享的一条经验。

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

MADDPG多智能体博弈对抗实战:Python源码精读与训练调优

简介:面向计算机专业毕业设计、课程设计及强化学习入门者,这是一份基于MADDPG(多智能体深度确定性策略梯度)的博弈对抗算法Python完整项目。代码覆盖经验回放缓冲区、Actor-Critic网络、DDPG训练流程、环境交互与测试模块&#xf…

作者头像 李华
网站建设 2026/10/1 13:39:34

交叉编译报错 cannot find crt1.o 的原因与彻底解决方案

干过交叉编译的人,十有八九都撞见过这个报错:arm-linux-gnueabihf-gcc main.c -o main /usr/lib/gcc-cross/arm-linux-gnueabihf/9/../../../../arm-linux-gnueabihf/bin/ld: cannot find crt1.o: No such file or directory collect2: error: ld return…

作者头像 李华
网站建设 2026/10/1 13:38:41

2000张行人图像够用吗?YOLOv5小数据集落地临界点解析

简介:本资源是一份专为YOLOv5目标检测模型训练与评估打造的行人检测数据集,面向计算机视觉初学者、算法工程师及智能安防项目开发者,解决行人检测任务中高质量标注数据匮乏的问题。数据集包含2000张真实场景行人图像(JPG格式&…

作者头像 李华
网站建设 2026/10/1 13:38:39

Agent开发核心五件事:从编排到安全的工程化实践指南

1. 为什么“五件事”这个说法值得认真对待 做了近两年的Agent开发,我最大的感受是:这个领域表面上热闹得不行,新框架、新概念、新论文几乎每周都在刷屏,但真正落到工程里,能决定一个Agent项目是死是活的,来…

作者头像 李华
网站建设 2026/10/1 13:38:27

推理框架与AI编译栈:模型部署的底层逻辑

我最近翻了一些推理框架的源码,比如 ONNX Runtime、TensorRT、TVM、llama.cpp 这类项目,最大的感受是:这些不是一个个单独的库,而是整套串起来的“栈”。很多人在问“模型怎么才能跑起来”的时候,卡住的原因往往不是模…

作者头像 李华
网站建设 2026/10/1 13:38:21

Arch/Manjaro 上运行企业微信:AUR、Docker 与虚拟机实战指南

先说个真实场景。我司在某个版本更新之后强制要求全员使用企业微信处理审批和消息,而我工作机装的是 Manjaro,官方下载页翻遍了只有 Windows、macOS、Android、iOS 四个选项,网页版又被管理员限制得只剩一个壳子,在线文档、音视频…

作者头像 李华