资讯动态

Agent工程三层架构详解:Harness、Loop与Graph实战指南

发布时间:2026/10/4 12:37:05 来源:尧图企业网站定制
1. 一层一层拆开为什么 Agent 工程需要三层架构做 Agent 开发的朋友应该都有同感项目一开始全都靠“提示词 一个模型 API”demo 跑得飞起一旦进入生产环境立刻被一堆问题锤懵。工具调用偶尔失灵、上下文越滚越乱、分支流程根本控不住、并发一高系统直接躺平。我自己经历过好几个项目从原型到落地的完整过程深深觉得问题不是“模型不够聪明”而是工程结构没搭对。所谓 Harness、Loop、Graph 三层架构其实就是把 Agent 系统按照不同的抽象层级切分开Harness 管环境与工具Loop 管单轮目标的持续推进Graph 管多阶段流程的整体编排。这三层各司其职又能无缝嵌套。你可以把它理解成一个工厂流水线Harness 是工位和工具架Loop 是工位上那位不断检查进度、决定下一步动作的师傅Graph 则是整条流水线的图纸和传送带——哪道工序做完该往哪走哪些工序可以并行哪道工序出问题要回流都由图纸说了算。这套结构也不是我凭空发明的它其实是 Agent 领域这些年在工程实践中逐渐沉淀出来的通用范式。LangChain 的 Agent 框架、Claude Code 的 Harness 工程实践、DeepSeek Harness 这一类开源项目本质上都在围绕这三个概念做文章。只是不同项目叫法不同、侧重不同——有的把 Harness 做成完整插件系统有的把 Loop 内置在框架里有的把 Graph 外包给可视化编排工具。把这三层彻底理解清楚你去看任何一款 Agent 框架都会觉得豁然开朗。这篇文章适合谁如果你正在用现成框架写 Agent但总觉得“加了功能就崩、接了业务就乱”那这篇文章就是给你准备的。如果你打算从零搭一套 Agent 服务或者正在团队里做技术选型这套三层模型也能直接当设计蓝图用。我会把每层拆开讲原理再给出一套可以照着抄的组装方案最后把生产环境里那些文档里从来不写的坑也一并倒出来。2. HarnessAgent 的“容器”与“工位”2.1 Harness 到底是什么Harness 这个词在 Agent 工程里指的是承载 Agent 运行的一整套基础设施包括模型接入、上下文管理、工具注册、权限控制、沙箱隔离、可观测性等一系列能力。它不是模型本身也不等同于业务逻辑而是那个“让模型能安全地调用工具、稳定地持有上下文、可控地对外输出”的外壳。我经常用一个比喻大模型像是刚入职的实习生脑子聪明但经验少容易乱来。Harness 就是他的工位——桌上摆好了公司规定的工具书、电脑里装好了批准使用的软件、墙上贴着“什么能做、什么不能做”的清单旁边还坐着一位只负责盯流程的督导。实习生不需要关心网络怎么接通、电脑怎么维护、软件从哪里采购他只需要专注干活。放在实际代码里Harness 通常承担这么几件事模型接入层处理与不同 LLM Provider 的通信包括 API Key 管理、重试、超时、流式与批量模式的切换。上下文管理会话历史的组织、裁剪、摘要压缩以及把长期记忆注入到每次请求的 System Prompt 中。工具注册表声明 Agent 可用的一组工具包括工具名称、描述、JSON Schema 参数定义。执行沙箱工具函数的调用环境决定工具代码跑在什么权限级别、能访问哪些文件与网络。观测与控制每次调用的日志、token 消耗统计、中断信号处理、成本配额控制。很多人会把 Harness 和 Agent 当成同一个东西。其实边界很清楚Agent 是那个“感知-决策-行动”的执行体Harness 是承载执行体的运行环境。DeepSeek Harness 这类开源项目之所以流行就是因为它把 DeepSeek 模型接入、工具系统、会话管理这些通用能力做成了开箱即用的运行环境开发者只要写自己的业务工具然后注册进去就能得到一个能干活的 Agent。2.2 Harness 的关键配置与安全边界实操层面配置 Harness 最核心的是三块工具白名单、上下文窗口策略、安全边界。工具白名单是所有 Agent 工程第一条要立的规矩。只给 Agent 暴露它真正需要的工具每多一个工具模型误调用的概率就大一分。比如一个只做文档摘要的 Agent给它接数据库查询工具就完全没有必要——这不仅是功能冗余更是攻击面扩大。生产系统里我见过一次事故Agent 被诱导调用了一个与当前任务无关的文件删除工具好在沙箱权限封住了没有酿成大祸。所以工具注册表里每一个工具都要问一句这个工具非有不可吗如果没有它Agent 还能完成目标吗上下文窗口策略决定 Agent 能记住多少东西。模型输入有长度限制而真实的业务对话往往很长。常用的做法有三种截断丢掉最旧的消息、摘要压缩把旧消息浓缩成摘要、向量检索只把相关片段塞进窗口。实际项目里这三种通常是组合使用比如系统提示词固定占用 2000 token窗口总计 32000那么最近 10 轮对话全量保留再往前的内容走摘要。具体数字要根据模型能力和业务特点反复调没有一劳永逸的方案。安全边界是 Harness 最容易被忽略但最致命的部分。生产环境里要考虑工具执行是否沙箱化容器隔离还是进程级隔离、敏感信息是否脱敏之后再拼进上下文、外部输入是否做了注入防护、模型输出是否经过合规过滤。很多刚接触 Agent 开发的人以为安全是部署阶段才考虑的事其实 Harness 的设计阶段就要把这些定下来否则后面想改架构成本高到让你想重写。2.3 DeepSeek Harness 与开源生态的选择现在开源社区里 Harness 类项目越来越多DeepSeek Harness 之所以被频繁讨论是因为它把“本地可部署的 Agent 运行环境”这件事做得比较彻底——自带 skill 管理、插件加载机制、会话管理能力配合 DeepSeek 系列模型能跑出不错的性价比。如果你是个人开发者或者小团队我建议这么选想快速验证想法直接用现成框架LangChain、Claude Code、DeepSeek Harness的默认配置别自己造轮子。想深度定制工具链基于开源 Harness 做二次开发把工具注册、沙箱、日志三块扩展好。有性能极致要求的场景可以看看 Rust 生态里的一些 Agent 框架实现用 Rust 写的 Harness 在并发和内存占用上确实有优势但开发成本也相应更高。我自己的经验教训是不要一开始就迷信某个框架先花时间把你的领域工具定义清楚。工具定义的质量直接决定 Agent 能力的上限。Harness 说到底是个容器你装进什么工具它就能干什么活。框架选型只是顺手的事工具设计才是真正需要花心思的地方。3. LoopAgent 的核心驱动循环3.1 从一次问答到多轮“干活”Agent 和普通 API 调用的本质区别就在于多轮循环。普通的大模型调用是你问一句、它答一句Agent 则是给定一个目标让它自己决定要调用哪几个工具、按照什么顺序调用、每步结果如何影响下一步。这个“思考-行动-观察-再思考”的过程在学术上叫 ReAct 范式在工程上就是 Loop。拆开来看一次完整的 Agent 循环包括五个环节。目标注入把用户提出的目标、约束条件、可用工具列表写进上下文。推理决策模型阅读当前状态决定这一步是直接回答、还是调用某个工具。工具执行Harness 层接收模型发出的工具调用请求执行对应函数拿到结果。结果回传把工具执行结果包括可能出现的异常作为新的上下文喂回给模型。继续或退出模型根据新信息判断任务是否完成完成则输出最终答案否则回到第 2 步。这五步之间的状态管理是 Loop 工程的核心难点。模型每轮推理会输出一段文字可能夹杂着工具调用指令Harness 需要从这段输出中精确解析出“哪些是普通回复、哪些是要执行的函数调用”执行完再把结果拼接回上下文重新发起一次模型请求。这个“请求-响应-执行-拼接-再请求”的过程在代码上就是一个 while 循环因此业界管它叫 Agent Loop。3.2 循环的终止条件与防失控设计循环本身不难实现难的是怎么让循环在正确的时间停住、以及防止循环失控。我在生产项目里遇到过最典型的问题是Agent 陷入死循环同一批工具调用反复执行白白消耗 token。表现就是日志里出现连续七八次近乎相同的工具调用记录直到某个重试上限被触发才强制退出。这个问题根因通常是模型对工具返回结果产生了“误解”以为任务没完成或者工具本身没有返回足够明确的“完成信号”。解决方案有两层。第一层是给每个工具设计清晰的完成语义工具执行成功就返回结构化结果并且明确标注“任务已完成”、“无需后续操作”这类标志执行失败则返回错误码和错误详情。第二层是在 Loop 层设置硬性上限最大循环次数比如 8 次、最长执行时间比如 120 秒、最大 token 消耗。任何一个指标超限立即终止循环、回退到人工处理或者降级策略。这里有个小技巧把“终止条件”设计成模型可感知的提示词内容。比如说在系统提示里告诉模型——“你有且仅有 5 次工具调用机会如果 5 次内无法完成任务请直接回复‘需要人工介入’”。实测下来模型在明确知道自己“次数有限”之后决策会更加谨慎无效循环显著减少。这比单纯在代码层面硬切断要优雅得多因为它是从行为源头约束模型而不是从结果上打补丁。3.3 Loop 的并发模型与“多窗口”处理当多个用户同时在线上问问题你的 Agent 服务不可能一个循环跑完再跑下一个。这里就涉及 Loop 的并发模型。最朴素的实现是“每用户一个会话上下文串行跑循环”。这种方式实现简单但吞吐量很低一个慢请求会堵住后面的请求。稍微好一点的是“多线程/多协程 每会话独立上下文”每个请求分配到独立的执行环境互不干扰。再进一步可以用异步事件驱动的方式把模型的推理等待时间和工具执行时间都让出去让服务也能处理夹杂的短请求。我见过不少团队在这一步踩坑模型 API 并发上限很低Loop 内部还会多次调用模型一个用户请求就可能消耗 35 次模型调用服务端一扛不住就会出现“更新 Agent 沙盒失败”、“无法发送消息”这类问题。其实核心原因就是并发控制没做好。生产环境里我建议这样设计用信号量控制同时进行的循环数量给模型 API 留出余量。给单次循环内的模型调用加超时比如单次请求 30 秒没返回就自动终止。对不同优先级用户的请求做队列隔离避免一个高消耗任务拖垮所有人。还有一些 Agent 框架支持所谓“多窗口 Loop”——指同一次任务里并行开启多个子循环分别处理不同子目标最后汇总。这个能力在复杂任务中非常有用但工程复杂度也随之成倍增加我建议团队先把单循环跑稳再考虑并行子循环。4. Graph把流程从“自由发挥”变成“按图施工”4.1 为什么单靠 Loop 搞不定复杂业务Loop 很灵活但灵活性也有代价不可预测。同一个任务模型这次按 A 路线走下次可能按 B 路线走。如果业务上有强制的流程要求——比如“先审核再执行”、“多个分支并行、全部完成后汇总”纯靠 Loop 里的模型自由发挥迟早出乱子。举个例子。一个客服工单系统用户提交工单后要先做自动分类然后判断是否属于紧急情况紧急的进快速通道非紧急的走常规流程最后都要经过人工复核才能关闭。这种流程如果用单个 Loop 实现模型决策的不确定性会让工单可能绕过某些环节。这显然不能接受。Graph 解决的就是这个痛点。Graph 把 Agent 的流程从“模型自由决策的循环”升级为“有向无环图DAG上的节点流转”。每个节点是一个确定性的处理步骤节点之间的边定义了流转条件。模型依然可以发挥智能——比如在一个“文本分类”节点里让模型判断类别——但整条流程的骨架是确定的、可审计的、可观测的。4.2 图的节点、边与状态传递按工程习惯一个 Agent Graph 通常由这么几个要素组成节点Node一个原子处理单元。可以是 LLM 调用也可以是普通函数还可以嵌套一个子 Agent内置一个完整的 Loop。边Edge节点之间的有向连接可以带条件表达式也可以无条件顺序传递。状态State跨节点传递的数据载体。通常是一个结构化的 JSON 对象记录任务输入、中间结果、当前进度等信息。路由Router一种特殊的边逻辑根据当前状态的值决定下一步跳转到哪个节点。用代码表示最简单的顺序链大概长这样state {input_text: ..., category: None, result: None} # 节点1分类 def classify(state): state[category] llm_classify(state[input_text]) return state # 节点2按类别走不同分支 def route(state): if state[category] refund: return refund_handler else: return general_handler # 节点3、4不同分支的处理函数 def refund_handler(state): state[result] call_refund_api(state[input_text]) return state def general_handler(state): state[result] call_normal_api(state[input_text]) return state图编排框架比如 LangGraph、Snap Graph Builder或者自研的轻量状态机解决的问题是在节点执行失败时如何重试、在并行节点之间如何合并状态、在条件路由时如何避免死循环、以及在任意节点中途崩溃时如何恢复现场。4.3 三种高频 Graph 模式切片、并行、检查点从实际项目经验看真正用得上的 Graph 模式就三类。模式一顺序流水线。输入经过多个节点依次处理每个节点只负责一件事。比如一个内容审核 Agent“敏感词检测”——“模型安全评估”——“结构化输出”。这种模式最简单工程上几乎零风险适合大多数标准化业务。模式二并行扇出/汇聚。一个任务被分解为多个子任务并行交给多个子 Loop 处理最后汇聚结果。典型场景是数据采集一条新闻进来同时触发“摘要生成”、“标签提取”、“情感分析”三个节点三个结果合并后再进入下一步。并发节点之间需要状态隔离汇聚节点要处理“部分节点失败”的情况。模式三人工审核检查点。在自动流程的某个关键节点上暂停等待人工确认后才继续。这在金融、医疗、涉政内容处理等强合规场景中是刚需。实现上一般是让图引擎支持“中断-恢复”语义把当前状态持久化存储等外部系统回调确认后再从这个节点继续往下走。我个人强烈建议默认优先用 Graph只在确实需要“完全开放的智能探索”时才用裸 Loop。Graph 看起来增加了一层抽象似乎更麻烦但它在可观测性和可控性上的收益远远大于那一点实现成本。生产环境里排障的时候“流程卡在哪个节点”一句话就能说清楚如果换成纯 Loop你得翻几十页对话日志才能定位到问题。5. 三层联动一个可以照抄的组装样例说了这么多原理还是得上一个实际的组装样例。我用一个“工单自动分类 RPA 操作 人工复核”的场景来展示 Harness、Loop、Graph 怎么串联成一个完整可用的系统。这个样例我在多个项目里用过结构上可以直接搬走替换成你自己的业务函数即可。第一层是 Harness 的准备工作定义工具、配置模型。# harness.py class LegacySystemTool: 对接旧系统的操作工具 staticmethod def get_ticket(ticket_id: str) - dict: 根据工单ID查询工单详情 return query_legacy_system(ticket_id) staticmethod def update_ticket_status(ticket_id: str, status: str) - bool: 更新工单状态 return update_legacy_system(ticket_id, status) staticmethod def execute_rpa_flow(ticket_id: str, action: str) - dict: 执行RPA流程比如自动退款、自动同步数据 return rpa_runner.submit(ticket_id, action) TOOL_REGISTRY { get_ticket: { description: 查询工单详情输入工单ID返回结构化详情, params_schema: {ticket_id: {type: string, required: True}}, handler: LegacySystemTool.get_ticket, }, update_ticket_status: { description: 更新工单的状态如 pending/processing/closed, params_schema: { ticket_id: {type: string, required: True}, status: {type: string, required: True}, }, handler: LegacySystemTool.update_ticket_status, }, execute_rpa_flow: { description: 执行RPA操作action 包括 refund、sync、archive, params_schema: { ticket_id: {type: string, required: True}, action: {type: string, required: True}, }, handler: LegacySystemTool.execute_rpa_flow, }, }第二层是 Loop 执行器负责任务目标的持续推进。这一层封装了“让模型决定调用哪个工具、执行、回传结果、再决策”的循环逻辑。# loop.py class AgentLoop: def __init__(self, model_client, tool_registry, max_steps5): self.model_client model_client self.tool_registry tool_registry self.max_steps max_steps def run(self, goal: str, history: list[dict]) - dict: messages self._build_messages(goal, history) for step in range(self.max_steps): response self.model_client.complete(messages) content response.content if response.tool_calls: for call in response.tool_calls: tool self.tool_registry.get(call.name) result tool[handler](**call.arguments) messages.append({role: tool, tool_call_id: call.id, content: json.dumps(result, ensure_asciiFalse)}) else: return {answer: content, steps: step 1} return {answer: 需要人工介入, steps: self.max_steps, timeout: True}第三层是 Graph把多步骤、多分支的流程固化下来。每个节点内部嵌入一个 AgentLoop保证“节点内部智能、节点之间可控”。# graph.py def build_ticket_graph(): graph TicketGraph() # 简化表示 # 节点1利用 Loop 分类工单 graph.add_node(classify, AgentLoop(goal判断工单类别refund/consult/complaint, ...), on_successroute) # 节点2路由 graph.add_node(route, lambda state: route_func(state), branches{ refund: refund_loop, consult: respond_loop, complaint: manual_review, }) # 节点3退款场景先执行RPA然后必过人工审核 graph.add_node(refund_loop, AgentLoop(goal调用 execute_rpa_flow 完成退款actionrefund然后更新工单状态, ...), on_successmanual_review) # 节点4人工复核检查点 graph.add_node(manual_review, HumanReviewNode(), on_successarchive) return graph请求进来后整体执行路径是收到工单 IDHarness 注入系统提示与工具注册表。classify 节点启动一个 AgentLoop自动调用 get_ticket 查询详情把详情交给模型判断工单类别。route 节点按类别跳转。若走 refund 分支refund_loop 节点再启一个子 Agent自动调用 execute_rpa_flow 执行退款随后 update_ticket_status。进入 manual_review 节点系统把关键状态推给人工审批等待人工确认后工单关闭并归档。这套结构的好处很明显每个环节都是可观测、可测试、可替换的。哪个节点出了问题直接改哪个节点不会波及其他流程。而且 Graph 的节点可以复用下次加一个新业务流不需要重写 Agent 逻辑只需要新增几个节点和边。6. 生产环境避坑实录文档里不会写的那些事6.1 并发打满与接口限流Agent 服务跟普通 API 服务的压力模型差异很大。普通 API 一次请求一次模型调用Agent 一次请求可能触发多次模型调用还有工具执行的时间开销。如果按普通服务的经验去预估并发大概率会翻车。我碰到过的最典型事故是上线第一天十几个测试用户同时操作后端的模型 API 直接返回 429 限流错误循环里的重试逻辑又把请求堆积得更严重。排查后发现问题出在“循环是内嵌在同步请求里”的而 Flask 的默认线程池只有 8 个线程每个线程还死死占着等待模型响应。修正方案有三条按成本从低到高排列提高同步线程池上限但注意别超过模型 API 配额。把 Agent 循环改造成异步任务请求先入队轮询获取结果。在入口做并发控制用信号量限制同时运行的 Agent 任务数。我建议中小团队直接选第三种成本最低、效果最明显。不要试图让 Agent 服务扛无限并发而是主动控制并发上限、让超出的请求排队反而对用户体验更好。6.2 上下文膨胀与 token 失控Loop 每跑一轮工具调用结果就会拼回上下文。如果工具返回一个特别大的 JSON多轮之后上下文会迅速膨胀。这不仅是费用问题更严重的是模型会被大量无关信息干扰导致决策质量下降。我见过一个团队给 Agent 接了一个返回全文文档的工具一轮循环下来上下文就满了。解决这类问题核心原则是给每个工具的返回结果设上限。工具返回前先做截断、摘要、只保留关键字段。对于必须完整传入的场景建议用 “分块 向量检索” 的方式按需捞取相关片段而不是一次全量塞进去。另外可以监控每轮循环的 token 消耗。一旦发现某个环节单轮消耗超过阈值马上告警。这个指标能帮你快速找到上下文管理的瓶颈。6.3 工具调用失败的回复机制模型调用工具时参数偶尔会不合法字段名拼错、必填项缺失、ID 格式不对。很多框架默认遇到这种情况是直接抛异常然后整个 Agent 循环中断。这其实不是最优做法。更好的机制是工具执行失败也要返回结构化结果并把这个错误结果回传给模型让它自行修正或决定放弃。比如说执行 execute_rpa_flow 时ID 不存在工具返回 {error: ticket_not_found, code: 404}。模型看到这个错误在下一轮循环里自然就会去调用 get_ticket 重新查一下或者向用户说明原因。这个机制跟人类工作很像——你让助手去跑腿助手回来说“门锁了”你自然会重新交代一句或者换种方式而不是直接解雇他。实现这一步的关键是工具层不要把异常抛给上层执行器而是把错误信息变成普通返回值。对模型来说“错误提示”也是一种有用的信息输入。6.4 Harness 插件加载失败这类问题怎么排查很多 Harness 框架支持插件skill/plugin动态加载但生产环境部署时经常碰到“Failed to load plugins”这种报错。这类问题 90% 是环境因素不是插件代码本身的问题。我自己踩过的坑最常见的是这么几类路径问题插件安装在容器里的目录但 Harness 进程的当前工作目录不在预期位置导致相对路径找不到文件。依赖缺失插件依赖了某些系统库但 Docker 镜像里没装。这个在 Rust/Python 混合生态里特别常见。权限问题插件需要写临时目录或者读取用户目录下的配置但容器里以非 root 身份运行权限不够。排查方向很简单第一步看日志里详细报错信息第二步检查插件目录结构和环境变量第三步在容器里手动执行插件入口脚本复现基本都能定位。这类问题倒不算难但在快速迭代时很容易被忽略建议在部署脚本里加一个“插件自检”步骤启动时先验证所有插件能正常加载再对外提供服务。6.5 可观测性给每一次循环留证据最后这条是我最想强调的。Agent 系统跟普通系统最大的区别是不可确定性。同样的输入不同时间跑出来的路径可能完全不同。这就意味着传统“复现 bug”的思路完全失效——你没法保证重启后能复现同一条路径。所以生产环境里必须有完整的 trace 链路。我在项目里是这么做的每一次 Agent 循环都打一个 trace_id串联目标、初始上下文、模型响应、工具调用、工具结果、下一轮上下文。所有工具调用的入参和出参都记录日志不仅记成功也记失败。把 token 消耗按“目标-工具-轮次”维度聚合方便定位成本黑洞。对可疑流程比如异常多轮、异常长耗时自动录制现场方便事后复盘。这些日志平时不产生业务价值但一旦线上出问题它们就是你唯一的侦探工具。尤其是当用户投诉“Agent 干了不该干的事”没有 trace 你根本说不清楚是哪一步触发的。我宁可业务代码写得糙一点也绝不允许日志链路少一环。最后分享一点个人经验做 Agent 工程这两年我最大的体会是不要试图让模型解决所有问题要让架构来解决模型解决不了的问题。模型负责理解、推理、生成架构负责流程的确定性、工具的安全边界、排障的可观测性。Harness、Loop、Graph 三层架构本质上就是把“模型的能力”和“工程的确定性”做了一个清晰的划分。如果你正在规划一个新项目我的建议是不要贪多。先按“一个 Harness 一个 Loop 三个节点的 Graph”起步把工具定义、循环终止、节点流转跑通再逐步加复杂的并行和人工审核节点。架构这东西前期多花一天做设计后期能省十天的返工。我的项目组现在立了一条规矩任何新的 Agent 需求必须先画 Graph 草图再讨论 Harness 怎么配工具最后才轮到底层模型选型。顺序一旦反了后面基本都要推倒重来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑