ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Harness、Loop、Graph:AI Agent 生产级三层架构实践指南

Harness、Loop、Graph:AI Agent 生产级三层架构实践指南 过去半年只要在技术社区里聊 Agent 开发几乎绕不开三个词Harness、Loop、Graph。有人把 Harness 比作 Agent 的骨架把 Loop 比作心脏把 Graph 比作神经系统也有人被这三个概念绕晕——明明都是 LLM 应用为什么非要拆成三层我在自己的项目里从单 Loop 原型一路改到 Harness Loop Graph 分层架构中间踩过插件加载失败、循环死转、并发崩溃和状态序列化报错。这篇文章就讲讲我理解的三层架构以及每一层在生产环境中到底怎么落地。它适合正在做 Agent 原型、准备把 Agent 推上线的开发者也适合被各种框架概念搞得一头雾水、想从底层原理重新梳理一遍的读者。1. 为什么是这三个词Harness、Loop、Graph 各管哪一段这三层不是某个框架的专利而是一种通用的工程拆解方式。最近社区里 harness engineering 这个词被频繁提起很多团队开始意识到Agent 的稳定性不只是模型选得好不好更是工程环境做得牢不牢。要理解这套分层先看每层的职责Harness外层环境负责模型和工具之间的所有工程细节——模型接入、工具注册、插件加载、上下文管理、权限控制、沙箱隔离。它决定 Agent 能不能在一个可控、可观测、可审计的环境里运行。Loop智能闭环负责Agent 的核心智能行为——大模型读取上下文、决定调用哪个工具、拿到观察结果、再次推理。它决定 Agent 聪明不聪明会不会把事情做完。Graph流程编排负责多任务的拓扑结构——把单个 Loop 变成一个节点把多个节点组织成顺序、分支、并行、汇合、人工介入的工作流图。它决定 Agent 能不能扛住复杂任务、能不能规模化。我见过最典型的理解误区是把三者当成三种替代方案来 PK。实际上它们是嵌套关系Graph 的节点里可以跑 LoopLoop 的每一轮工具调用都发生在 Harness 提供的环境里。你可以只有 Loop一个简单的 Chat Agent 就可以但一旦需要上生产Harness 就是必需品一旦任务变长变复杂Graph 就会自然浮现。用工厂打个比方Harness 是厂房和供电系统Loop 是车间里老师傅看一眼、动手、检查的工作方法Graph 是整条流水线的工艺路线图。没有流水线图单个车间也能干活没有厂房车床和老师傅就只能露天作业。这个顺序也对应了大多数团队的实际演进路径先用 Loop 做出原型再用 Harness 把它变得可控最后用 Graph 解决规模化问题。顺带回答一个经常被问到的问题Harness 和 Agent 到底有什么区别我的理解是Agent 是一个完整实体包含策略、记忆、工具使用能力Harness 是承载这些能力的运行环境与装配机制。同一个 Harness 可以跑不同的 Agent 策略同一个 Agent 策略也可以换不同的 Harness。区别在于 Harness 本身没有智能它决定的是智能能不能在受控条件下稳定发挥。2. Harness 层外部骨骼、供电系统与安全边界2.1 Harness 到底箍住了哪些东西Harness 这个词的原意是马具、挽具引申为把分散部件装配在一起的装置。在 Agent 工程里它恰如其分模型本身只是一个千亿参数的大脑要让大脑能看文件、能查数据库、能调用内部系统就必须用一套装配件把它和外界接起来。这套装配件就是 Harness。一个完整 Harness 至少包含五类资源模型接入层统一封装不同模型的鉴权、超时、限流、重试让上层 Loop 不必关心底层用的是哪个模型的哪个版本。工具层工具定义名称、描述、参数 Schema、工具注册表、工具调用的输入校验与输出解析。插件与技能层按需加载的技能包和插件决定 Agent 在不同场景下会什么。这块最容易出问题后面单独讲。上下文管理器系统提示词、历史对话、检索结果、工具返回内容的拼接与裁剪避免上下文窗口被撑爆。权限与审计层哪些工具需要人工批准、哪些路径可读、哪些命令可执行、每一步工具调用的日志留痕。这五块说起来都很基础但每一项在真实环境里都有坑。比如上下文管理器简单拼接 prompt、历史、工具结果的做法在跑几十轮之后必然爆窗口。常见的做法是分层摘要把早期对话压缩成结构化摘要把大段工具返回落进外部存储只留索引给模型。这不是 Loop 的职责也不是 Graph 的职责而是 Harness 的职责——如果 Harness 没做好再聪明的模型也会在第五轮开始失忆。2.2 插件加载失败最高频的 Harness 生产事故搜 harness failed to load plugins 能看到大量报错讨论我自己也遇到过 web boot: 1 entry did not activate 这类问题。根因基本可以归为四类入口激活条件不满足。插件注册时一般会声明 activationEvents比如命令触发、工作区打开、特定工具被调用时激活如果声明和实际触发时机对不上插件就永远处于已注册未激活状态。依赖版本冲突。技能包依赖某个解析库的 A 版本Harness 运行环境里却是 B 版本。这类问题在 Python 环境里尤其常见。清单文件字段错误。插件 manifest 写错一个字段名或类型Harness 直接拒绝加载。处理办法是先做 Schema 校验而不是运行时才发现。符号冲突。两个插件导出同名工具或同名命令注册表里后加载的覆盖先加载的行为变得不可预测。一个典型的插件 manifest 大致长这样{ name: internal-doc-search, version: 1.2.0, entry: src/index.py, activationEvents: [on_tool:doc_search], dependencies: [requests2.31, jieba] }排查这类问题的思路我的建议有两条一是在开发环境强制开启插件诊断日志把加载过程分成解析 manifest、校验依赖、创建沙箱、激活入口、注册工具五个步骤逐步确认卡在哪一步二是给每个插件做个最小冒烟测试加载后立刻调用一个内置自检工具能快速区分是插件本身坏了还是环境不兼容。提示插件加载失败这类问题线上系统最忌讳的就是把错误吞掉后继续启动。宁可启动失败也不要用一个残缺的 Harness 对外服务否则后面所有工具调用都会在运行时莫名其妙地失效排查成本高出几个数量级。2.3 权限边界Harness 是安全最重要的防线LLM 的 tool calling 本质上是把决定权交给了概率模型而 Harness 的权限层就是在概率之上加一层规则保险。我常用的权限模型分三级白名单级只允许调用显式注册过的工具未注册的一律拒绝。人工审批级对涉及写操作、删除、外部发送、支付等敏感动作Harness 拦截调用并推送给人工确认。沙箱级高危命令在隔离容器或受限用户下执行限制网络、文件系统写入范围。这三级的配置必须是 Agent 可读的也就是说权限策略要能通过工具动态查询。否则模型会反复尝试调用被禁止的工具白白消耗轮次。我在实践中还会加一条硬规则工具返回结果必须经过 Harness 的输出清洗大文件截断、敏感信息脱敏都在这一层完成不让脏数据进入上下文。2.4 模型路由一个 Harness 接多个模型时要注意什么生产系统很少只用一个模型。用便宜模型做分类和抽取、用旗舰模型做复杂推理、用专用模型做特定领域任务已经是标准做法。Harness 里做模型路由核心是搞清三件事协议差异。不同厂商的 tool calling 参数格式不一样有的用 JSON Schema有的用自定义格式。Harness 需要一套内部统一的工具描述格式再在适配层转换成各家 API 的格式。退出信号差异。有的模型用特殊标签表示我要停止有的用 end_turn 参数有的干脆生成一段结束语。Harness 必须识别这些信号才能让 Loop 正确地收尾。成本与延迟的可观测性。每次请求的 token 数、延迟、重试次数都要落日志这是后面做 Loop 调优的数据基础。我在把模型从闭源切到开源比如用 DeepSeek 这类国产开源模型做私有化部署时感受最深的就是Harness 层如果协议封装做得好切换成本就只是改一个 adapter而不是重写整个 Loop。这也是为什么我反复强调不要把模型接入细节散落在业务代码里。3. Loop 层思考-行动-观察的智能闭环3.1 最小可运行的 Loop 长什么样Loop 是 Agent 的心脏它的名字已经说明了一切一个反复执行的循环。最小闭环用伪代码表示就是while not done and iteration max_iterations: context build_context(system_prompt, history, tool_results) response model.generate(context, toolsavailable_tools) if response.is_final_answer(): done True break tool_call parse_tool_call(response) observation harness.execute_tool(tool_call) history.append(tool_call, observation)这个循环里每一步都有讲究。build_context 不是简单拼接而是 Harness 的上下文管理器在做裁剪和摘要model.generate 带着 tools 参数让模型知道有哪些工具可用、参数 Schema 是什么parse_tool_call 看似简单实际最容易出错——模型经常会产生格式不标准的工具调用需要容错解析。Loop engineering 的核心就是围绕这个循环调四个参数最大迭代次数、停止条件、工具调用容错策略、上下文压缩策略。我这边的默认配置是 iteration 上限 20 轮超过就停止并输出任务未完成报告工具解析失败时允许模型重新生成一次调用但不允许无限重试上下文达到窗口的 70% 时强制触发摘要压缩。3.2 agent execution terminated due to error 的四种断裂社区里搜 agent execution terminated due to error 能翻出一堆相似案例我归纳过四类典型断裂断裂类型典型表现处理办法工具返回格式异常工具本该返回 JSON结果返回了空字符串或错误堆栈解析器抛异常解析器必须容忍异常把解析失败当作一种 observation 喂回给模型模型陷入自我循环模型反复生成同一个工具调用观察结果没有让状态发生任何推进设置重复动作检测连续 N 轮调用同一工具且参数相同强制打断并提示换策略上下文爆掉长任务跑到十几轮后历史累积超出窗口模型开始忘记原始目标Harness 层做分层摘要Loop 层每隔一定轮次重新注入原始任务目标外部依赖故障工具调用超时或下游服务 5xx模型把错误当成正常结果继续推理在工具执行层统一做超时和重试超过重试次数才把失败信息返回给模型绝大多数报错不是模型笨而是 Loop 的某个环节设计不合理。工具返回格式巨变这个坑尤其隐蔽因为它经常在某个下游系统升级后突然出现。我在工具执行层加了一个通用包装器所有工具输出统一转成结构化对象再交给模型即使工具内部报错也会被规范成 { status: error, reason: ... } 的形式。这样模型面对失败时至少有明确信息可推理。3.3 并发场景AI Agent 怎么扛并发AI Agent 怎么扛并发在社区里是个高频问题。很多人的第一反应是给模型 API 加并发数但真正的问题通常不在模型接口而在 Loop 的状态管理。Loop 是有状态的——它携带上下文历史、工具执行结果、任务中间状态。如果多个请求共享同一个 Loop 实例状态必然串扰。扛并发最基础的原则是一个任务等于一个独立的 Loop 实例等于一份独立的状态。具体落地时我会把状态对象显式建模成可序列化的数据结构保证任意时刻可以被持久化和恢复。这样即使进程崩溃也能从最近的检查点重新拉起 Loop。并发还涉及一个隐藏瓶颈工具执行。模型调用是一路工具调用是另一路。如果工具是调用内部 API并发一上来最先挂的往往是下游系统。所以生产环境的 Harness 必须给工具执行加独立的连接池、超时和限流而不是让每个 Loop 自己裸调。把 Loop 和工具执行解耦是扛住并发的前提。4. Graph 层把单个循环组织成可编排的工作流4.1 单 Loop 的局限什么时候必须上 Graph单 Loop 能解决单线程任务但现实中很多业务场景是单 Loop 搞不定的长流程任务。比如从需求文档生成测试用例、执行测试、汇总报告每一步耗时都长一个 Loop 从头跑到尾任何中间失败都会导致整体重来。并行子任务。比如同时检索多个数据源单 Loop 只能串行Graph 可以把多个检索节点并行执行再汇合。多角色协作。比如规划者、执行者、审查者三个角色各自有自己的上下文和工具集用 Graph 的节点天然表达。人工审批介入。自动化流程中需要等待人工确认的节点Graph 可以挂起等待而单 Loop 很难优雅地处理等待外部事件。判断标准很简单如果你发现自己在单 Loop 里写了大量 if-else 来模拟分支逻辑代码已经乱到没法维护了那就是该上 Graph 的信号。Graph 不是性能优化而是复杂度和可靠性的管理工具。4.2 节点、边与状态Graph 里最基本的三个设计决策Graph 的建模语言在各家框架里大同小异节点node是执行单元边edge是状态流转状态state是所有节点共享的数据容器。三个设计决策会直接影响系统的可靠性和可维护性。第一节点粒度。我的经验是一个节点只做一件事但这件事可以内部嵌套若干 Loop。粗粒度节点会让图变成一堆黑盒细粒度节点会让图变成蜘蛛网。一种平衡做法是把需要模型智能决策的部分包成 Loop 节点把确定性计算的部分做成普通函数节点。第二边的语义。边不只是箭头它通常携带条件。条件有两种确定性条件上一节点的输出字段满足某个规则走 A 分支和模型判断条件由 LLM 决定下一步走哪个分支。模型判断条件必须给定有限选项否则模型会自由发挥节点结构等于没有约束。第三状态设计。所有节点共享一个状态对象但这个对象很容易被节点间隐式依赖搞乱。我见过最多的 Graph 返工都源于状态字段的命名和流转方式没有提前约定。一个实用建议状态用明确的数据类定义每个节点声明自己读哪些字段、写哪些字段。4.3 状态序列化里那个著名的 self referencing loop detectedGraph 要支持断点续跑状态就必须能持久化而序列化是最容易踩坑的地方。社区里搜 self referencing loop detected for property mem_memberinfo 这个词条的人大多是在持久化一个对象模型时碰到了循环引用对象里有一个属性指向它的父对象父对象又持有子对象列表序列化器在遍历时陷入无限循环于是抛出这个错误。这类问题的本质是内存里的对象图和可传输的数据图不是一回事。对象图可以有环但 JSON 这类格式要求树形结构。解决办法无非三种序列化深度截断配置里只序列化到指定层级。引用替换把父对象引用替换成父对象 ID反序列化时再重建关联。DTO 扁平化定义专门的传输对象只包含需要持久化的字段不直接序列化 domain 对象。第三种是我最推荐的。Graph 的持久化状态应当是一份干净的、可版本化的数据契约而不是把运行时的对象模型直接丢给序列化器。设计状态对象时从一开始就避免互相持有引用全部用 ID 关联这个坑基本就不会遇到。4.4 local-to-global从局部子图到全局编排的演进路线在脑影像分析领域有一个研究方向叫 classification of brain disorders in rs-fMRI via local-to-global graph先分析局部脑区的功能连接再组合成全局图做分类。这个思想在 Agent 的 Graph 编排里同样适用不要一上来就画一个包含几十个节点的全局大图而是先用小规模的局部子图验证每个环节的可靠性再把多个局部子图组合成全局工作流。这种演进路线的实际好处是每张子图都能独立测试、独立部署、独立监控。全局图只负责把子图连起来即使某个子图失败也能把任务转到降级路径而不是整条流水线报废。我在项目里就是这样做的先用一个资料收集子图验证检索和解析逻辑再用一个报告生成子图验证总结逻辑最后用全局图把它们串起来并加入人工审核节点和失败重试节点。Graph 的另一个实践要点是可视化。无论用框架自带的画布还是社区里的 workflow builder比如 Snap Graph Builder 这类工具可视化能极大降低团队沟通成本。图结构的一大优势就是可读如果一张图复杂到没人能读那就说明划分粒度出了问题。5. 生产实践把三层架构真正落到可运维的系统上5.1 一套最小可运行的参考架构如果从零搭建我建议直接按下面四个组件来切组件职责关键要求Harness Runtime模型接入、工具注册、插件加载、权限控制无状态可水平扩展Loop Worker跑单个任务闭环从 Harness 拿工具能力从状态存储读任务上下文每任务独立实例状态可持久化Graph Orchestrator解析工作流定义、调度节点、管理状态流转和重试支持挂起、恢复、降级State Store持久化任务状态、检查点和最终结果支持事务可追溯组件间的接口老实用 REST 加消息队列就够了不建议一上来就上服务网格之类的东西。任务提交时把任务描述、工作流定义、初始状态打包成一个任务单Graph Orchestrator 拿到任务单后开始调度Loop Worker 执行节点执行结果写回 State StoreOrchestrator 根据结果决定下一个节点。这个架构的核心思想是把控制流和数据流分开Graph 管控制流谁下一步做什么State Store 管数据流每一步的输入输出落在哪里Harness 管能力和权限每个节点能调用什么。三个层次职责清晰出问题时能快速定位是编排问题、状态问题还是工具能力问题。5.2 技能包离线部署把 skill 装进内网服务器生产环境中很多企业要做私有化部署把整套能力装进内网服务器。以社区里讨论很多的 deepseek harness 附带 skill 怎么部署到内网服务器 这类问题为例核心思路是打包、校验、注册三步。打包技能包除了代码还必须包含依赖清单和环境要求。部署时不是把代码拷进去就完了而是要先在干净环境里验证依赖能否完整安装。离线环境常见做法是维护一个内部依赖仓库把技能包所需的所有第三方库提前拉取入库。校验技能包上传后做三件事校验清单文件格式、校验依赖完整性、跑一次冒烟测试。校验通过后才允许注册进 Harness 的技能目录。注册技能注册不是往文件夹里丢文件而是要在 Harness 的技能注册表里登记版本号、启用状态、允许访问的工具范围。切换版本时用新版本影子启用的方式先让新版本在灰度流量下跑一段时间稳定后再切换。这套流程听起来重但一旦技能数量超过十个没有版本管理的内网部署就会变成灾难——你根本不知道线上正在跑的是哪个版本的技能包。5.3 可观测性每次 Loop 都在消耗真金白银Agent 系统的可观测性比普通 Web 服务更关键因为每次循环都在消耗 token也就是直接产生成本。我的监控清单包括每个任务的 Loop 轮数、token 总数、总耗时、工具调用次数。每个节点的重试次数、失败原因分类工具错误、模型格式错误、超时、上下文溢出。每个任务的状态流转轨迹也就是 Graph 实际走过的路径。这些指标不仅用于排障更是成本优化的依据。我看到过实际案例一个任务平均跑 15 轮其中 6 轮都在重复尝试同一个失败的工具调用。在 Harness 层给这个工具加一次前置参数校验后轮数直接降到 8 轮成本几乎减半。这个例子说明可观测性不是可选项而是优化 Agent 成本的第一入口。5.4 错误恢复与人工兜底再完善的分层架构也无法保证模型不犯错。生产系统必须假设每一步都可能失败并且定义好恢复策略。我的做法分三层节点级重试带指数退避、图级降级失败节点转入人工处理子图、任务级兜底超过最大重试次数后任务进入待人工审核队列由人查看完整轨迹后决定重跑还是中止。人工兜底是很多人容易忽略的环节。Agent 跑错了不可怕可怕的是跑错了没人知道、没人能干预。Graph 编排里一定要预留人工介入节点这类节点挂起等待时不影响其他任务同时把失败上下文完整展示给操作人员。6. 容易被忽略的实践顺序与我的个人体会最后聊点个人体会。如果你刚开始做 Agent我的建议是严格按 Loop、Harness、Graph 的顺序推进先用一个最朴素的 Loop 验证核心业务逻辑通不通当模型能稳定完成任务时再用 Harness 把工具、权限、插件、上下文管理这些工程细节补上最后等出现并行、分支、人工审批等需求时再上 Graph。反过来做则会非常痛苦图也画好了、编排也做好了结果发现核心 Loop 根本不稳所有节点都在同一个错误上来回重试。另一个体会是三层架构的每一层都要保持薄。Harness 不要做业务判断Loop 不要管多任务调度Graph 不要直接调用工具。边界一旦模糊系统复杂度会指数上升。每次我在代码里发现一个越层调用都会把它当成一次架构债记下来集中找时间修掉。如果你手头正在做一个 Agent 项目不妨先对照三层架构盘点一遍你的环境装配够不够可控、你的智能闭环有没有防死循环、你的流程编排是不是已经复杂到需要一张图。这三件事每件都值得单独打磨打磨的顺序决定了你后面要还多少技术债。
返回列表