☰
Agent工程三层架构:Harness、Loop与Graph的生产级实践指南
2026/10/1 6:00:32 网站建设 项目流程

这话题我琢磨了很久,一直想找机会把 Agent 工程里那些看似玄乎、其实有章可循的东西整理出来。市面上聊 Agent 的文章不少,但大多数要么停在概念层,讲 ReAct、讲工具调用,要么一上来就甩一堆框架代码,让人看完更糊涂。今天我打算换一个切入角度,从Harness、Loop、Graph 这三层架构出发,把 Agent 从“能跑通一个 demo”到“能在生产环境稳定干活”的完整路径拆开讲清楚。

这套三层架构不是我发明的,而是我在实际项目中反复对比、踩坑之后总结出来的分层方式。简单说,Harness 管底座——模型接入、工具注册、插件加载这些都是它的活;Loop 管脑子——Agent 的思考与行动循环、记忆读写、上下文管理都在这一层;Graph 管流程——复杂任务怎么拆、多步操作怎么编排、多个子 Agent 怎么协作,由它统一调度。三层各司其职,才能让一个 Agent 项目既好开发、又好维护、还好扩展。不管你是刚接触 Agent 开发的新手,还是已经在做 agent 框架选型的工程师,这篇文章都值得花几分钟读完,我会把每层要解决的核心问题、选型思路、以及生产环境中真正会遇到的坑都讲透。

1. 三层架构为什么是 Agent 工程的必然选择

1.1 从单体脚本到分层架构的演进逻辑

先聊一个很多人忽视的问题:Agent 工程到底难在哪?

如果你只是写一个调用大模型 API 的脚本,那确实不难。定义好 system prompt,把用户问题丢给模型,拿到结果再返回,完事了。但一旦你的 Agent 需要调用工具、访问知识库、操作外部系统、在多次推理中保持目标一致性,事情就完全不一样了。你会发现代码开始变得混乱:模型调用逻辑和工具执行逻辑挤在一起,上下文管理散落在各处,任务编排和单步执行纠缠不清。

我最早做 Agent 项目时就吃过这个亏。一个多步骤的客服机器人,代码写了两千多行,所有逻辑塞在一个类里。后来要加一个“查订单”的新工具,光是理清楚这个工具应该在哪个环节被调用、调用结果怎么回填到上下文,就花了一个下午。这不是工具难加,是架构上根本没给“工具执行”留出独立的位置。

分层架构解决的就是这个问题。每一层只关心自己那一件事,层与层之间通过明确的接口通信。这跟 MVC 三层架构的思想一脉相承,但具体到 Agent 领域,分层的方式又完全不同。

1.2 Harness、Loop、Graph 各自的职责边界

先说我最喜欢的一个类比:把 Agent 想象成一家餐厅。

  • Harness 是厨房的硬件设施。水电气、灶台、冰箱、厨具,这些基础设施都在 Harness 层搞定。对应到 Agent 工程里,就是模型 API 的接入、工具 API 的定义与注册、插件系统的加载、沙箱环境的搭建、密钥与权限的管理。没有这套底座,后厨没法开火。
  • Loop 是厨师的操作节奏。洗菜、切菜、看火候、尝味道、调整下一步动作——这套“观察-思考-行动-再观察”的循环,就是 Loop 层要实现的。它决定了 Agent 是机械地走流程,还是能根据实际情况灵活调整策略。
  • Graph 是餐厅的菜品流程单。一道菜从准备食材到装盘上桌,哪些步骤必须串行、哪些可以并行、哪一步需要等主厨确认、哪一步出错了要重做——这些都是 Graph 层编排的内容。复杂任务不是靠一个循环硬扛到底,而是拆成一张有依赖关系的执行图。

这三层不是望文生义的三个模块,而是三个不同变化频率的层次。Harness 最稳定,换模型、增工具都是局部改动;Loop 相对核心,但逻辑一旦成熟就很少动;Graph 是变化最频繁的层,因为业务流程、任务拆解策略每天都在迭代。把它们分开之后,你改业务流程的时候不用碰模型接入代码,换模型供应商的时候也不用重构任务编排,这才是分层最大的收益。

1.3 没有分层时你会遇到的三类典型问题

为了让大家更直观地理解分层的价值,我说几个实际项目里没做好分层时遇到的典型问题。

第一类是模块间耦合过深。模型输出格式稍微变一下,工具解析逻辑跟着改;工具返回结构变了,又要回去改模型调用的 prompt。一个改动引发连锁反应,改一个地方炸三个地方。

第二类是并发与隔离困境。生产环境的 Agent 服务往往要同时处理多个用户请求。没有独立的 Harness 层来管理会话级资源,两个请求之间就可能互相串上下文,A 用户的工具调用结果被 B 用户看到了,这是相当危险的。

第三类是调试无从下手。整体逻辑混在一起的时候,出了问题只能从头到尾打日志。到底是模型答错了?工具执行失败了?还是编排逻辑有 bug?没有清晰的层次边界,你连问题的归属都定位不了。

所以三层架构的本质,是为了让 Agent 这个系统的复杂度可管理、故障可定位、能力可扩展。下面我逐层深入,讲清楚每一层具体怎么做、怎么选型、怎么避坑。

2. Harness 层:把模型、工具与运行环境收拾利索

2.1 Harness 到底要管哪些事情

Harness 这个词在 Agent 工程里没有特别统一的定义。有的框架把它叫做 runtime,有的叫 engine,有的叫 sandbox,但管的事情大差不差。我把我的理解整理成一张清单:

职责领域具体内容出问题时的典型症状
模型接入大模型 API 配置、密钥管理、流式输出、超时重试API 调用频繁超时、密钥泄漏
工具注册工具函数的 schema 定义、参数校验、结果格式化模型生成非法参数、工具返回乱格式
插件系统插件发现、依赖解析、安全加载、版本管理插件加载失败、插件互相冲突
运行环境沙箱隔离、资源限制、文件系统、网络策略代码执行越权、资源耗尽
观测埋点日志、链路追踪、指标采集、会话快照出问题查不到线索、无法复现

这里面每一块展开都是一个大话题,但我要重点说几个生产环境中容易被忽略的点。

2.2 工具注册的正确姿势:用 schema 说话

工具注册是 Harness 层最核心的活,也是新手最容易写歪的地方。很多人在接工具的时候,只是简单地在 prompt 里写“你可以调用以下函数:XXX”,然后把函数的 Python 文档字符串贴在系统提示词里。这种方式在小 demo 里能跑通,但一换模型、一上生产就露馅——不同模型对工具描述的解释能力差异极大,靠自然语言描述工具参数,模型经常生成 JSON 格式错误的调用请求。

我在生产环境里的做法是:严格使用工具 schema(JSON Schema 格式)来定义每一个工具的入参、出参和描述。给模型喂的是机器可解析的结构化定义,而不是自然语言段落。举个例子,定义一个“查询天气”的工具:

{ "name": "get_weather", "description": "查询指定城市当前天气情况", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名称,中文全称,如:北京市" }, "date": { "type": "string", "description": "查询日期,格式为 YYYY-MM-DD,默认为当天" } }, "required": ["city"] } }

这样做有几个立竿见影的好处。第一,模型对参数的理解更准确,幻觉参数(比如把日期格式写错)的概率大幅下降。第二,工具执行层可以对模型传进来的参数做严格校验,不合法就直接返回错误,而不是崩溃。第三,新增工具变成了一件纯声明式的工作——写好 schema,实现函数体,注册进去,完事,根本不动 Loop 层的代码。

2.3 插件系统与“harness failed to load plugins”问题

插件是 Harness 层里一个功能强大但坑最多的设计。好的插件机制能让 Agent 的能力像积木一样拼接,但实现不好就是一场灾难。

我先说热搜里那个典型的报错:harness failed to load plugins。这个错我在本地环境遇到过不下十次,绝大多数原因就三类。

第一类是依赖冲突。插件 A 依赖了 requests 2.28,插件 B 依赖 requests 2.31,两个插件同时加载时,依赖树解析失败。解决思路:给每个插件创建独立的依赖环境,或者在加载时做依赖版本隔离,不要把所有插件的依赖打进同一个环境里。

第二类是路径问题。插件目录没在 sys.path 里,或者插件的入口文件命名不规范,加载器找不到启动点。建议约定插件必须提供统一的 manifest 文件,里面写清楚入口类名和初始化函数,加载器按 manifest 去找,而不是靠猜。

第三类是安全校验失败。某些框架的插件加载器会校验插件签名或来源白名单,如果插件是从网上下载的且没有经过签名注册,直接会被拒之门外。这个在本地开发时最容易误伤,需要把开发模式的安全校验级别调低。

还有一个容易被忽视的细节:插件加载顺序。如果插件之间存在依赖关系(插件 B 依赖插件 A 先加载),那不能按照文件名排序来加载,而要按依赖拓扑排序。我的做法是让插件在 manifest 里显式声明 depends_on 字段,加载器解析完整依赖图后再决定顺序。

2.4 Harness 层的安全边界

最后说安全,这个必须单独拿出来讲。Agent 的 Harness 层是工具与外部世界的接触面,权限问题不处理好,后果会很严重。

一个核心原则:最小权限。Agent 需要读数据库,那就给它一个只读账号;需要调内部 API,那就给它一个限定 scope 的 token;需要操作文件系统,那就把它锁在沙箱目录里,绝对不能让它能读写整个服务器的文件。

代码执行类的 Agent(比如让它写代码、跑代码的 Agent)必须有沙箱。本地开发可以用容器来做沙箱,生产环境建议用更严格的隔离方案。每一次工具调用都要有审计日志,记录谁在什么时间调用了什么工具、传了什么参数、返回了什么结果。不是说要监控用户,而是当 Agent 行为异常时,你能从审计日志里还原出完整的决策链。

3. Loop 层:Agent 的心跳与大脑

3.1 单循环执行机制的核心设计

如果说 Harness 是 Agent 的底座,那 Loop 就是 Agent 的灵魂。所谓 Loop,指的是Agent 反复执行“思考-行动-观察”这一循环,直到完成目标或到达终止条件的过程。最常见的模式是 ReAct,模型先生成下一步行动计划,选择一个工具执行,观察执行结果,再根据新信息生成新的行动,以此往复。

Loop 层设计得好不好,直接决定了 Agent 是“聪明地干活”还是“盲目地绕圈”。我重点讲三个核心设计点:终止条件、最大步数限制、上下文窗口管理。

终止条件很容易想当然。新手往往只设一个条件:模型认为任务完成了就停止。但生产环境里模型经常“自以为是”,任务根本没完成,它却说完成了。所以我在项目里会设计显式的终止判定:一是模型主动输出 END 标记;二是任务目标状态被外部校验函数确认为达成(比如“已经调用下单接口并且返回成功”);三是达到最大步数后强制终止,并对未完成任务做降级处理。

最大步数限制这个必须有。没有步数上限的 Agent,遇到复杂问题时会在几个失败方案之间死循环,浪费大量 token 和时间。我一般把默认最大步数设置为 10~15 步,超过就停止循环,返回一个部分完成的状态,由 Graph 层决定继续还是换策略。

上下文窗口管理是最容易被忽略的。每个循环产生的工具返回结果、模型推理过程都堆积在上下文里,撑不了几轮就会触达上下文上限。我的经验是:每轮循环结束后,对上下文做一次压缩或裁剪——把特别长的工具原始返回(比如查询数据库的全部行)缩写成关键摘要,把已经过时的中间推理过程丢弃。这里有个技巧:不要用同一个 tokenizer 截断,而是让模型自己总结,用一次额外的模型调用把长内容浓缩成结构化摘要,虽然多花了点 token,但长远看不亏。

3.2 记忆机制:短期、长期与工作记忆的配合

Loop 层的另一个重头戏是记忆。Agent 记忆这个话题在热词里出现了很多次,说明大家都很关心。我的理解是,Agent 的记忆可以分成三个池子。

短期记忆就是当前对话窗口内的上下文。它不需要额外设计,模型天然支持。难点在于不要让无关信息挤占短期记忆空间。

工作记忆是当前任务执行过程中产生的中间状态。比如一个“制定旅行计划”的任务,Agent 已经查了两个城市的酒店,这些查询结果在任务切换之前要能取用。工作记忆通常以结构化的任务状态变量来存储,放在 Loop 层的内存里,任务结束就清掉。

长期记忆是跨会话、跨任务持久化的知识和用户偏好。比如用户之前说过“我不吃辣”,那下次做餐厅推荐时就应该自动避开辣味餐厅。长期记忆存储的核心是向量化 + 检索,把历史对话切片嵌入到向量数据库里,每次新会话开始时就做一次相关性检索,把最相关的记忆片段捞出来注入上下文。

我遇到过不少团队在记忆上过度设计。记忆不是越多越好,检索回来的噪声反而会干扰模型判断。一个务实的做法是:长期记忆的召回数量严格限制在 3~5 条,每条不超过 200 字,并且只在任务开始时注入一次,不在每轮循环中都反复检索——后者既耗时又容易让模型丢失焦点。

3.3 并发控制与限流策略

热词里有“ai agent 怎么扛并发”,这个问题我相信每个做生产的团队都会遇到。Agent 的循环执行天然比普通 API 调用更耗资源,因为一次完整任务可能要调用模型十几次。并发一旦上来,模型 API 的限额、后端的连接数、内存占用都会成为瓶颈。

我的并发策略核心是三件事:限流、排队、降级。

限流要分两层。第一层是对单个用户的并发限制,防止一个用户开太多会话;第二层是对模型 API 总调用频率的限制,防止触发供应商的 rate limit。这个不是简单地做一个计数器,而是要做成令牌桶,在 Loop 层每次发起模型调用前都去取令牌,取不到就说明当前速率已满,循环要等待。

排队要设计优先级。普通用户请求可以排队,而管理员或测试请求可以插队。队列要做持久化,服务重启了任务不能丢。

降级是最重要的兜底方案。当模型 API 整体不可用时,Agent 应该返回什么?我的做法是:配置一套降级链路——先试供应商 A 的模型,失败切供应商 B,再失败切本地小模型,最后如果还是不行,就返回一个“当前服务繁忙”的明确错误,而不是让用户无限等待。

3.4 Loop 层的可观测性:别让你的 Agent 变成黑盒

最后说 Loop 层的运维问题。Agent 的推理过程是一步步进行的,如果中间某一步决策错了,最终结果就会错得离谱。所以生产环境的 Agent 必须有完整的推理过程日志。

我习惯给每一轮循环记录一条结构化日志,包含:本轮输入摘要、模型生成的动作类型、选择的工具、传入的参数、工具返回状态、本轮耗时。这些日志统一推送到链路追踪系统里,按会话 ID 聚合。当用户反馈“Agent 答错了”的时候,我能直接翻出这个会话每一步的推理过程,定位是在哪一步开始跑偏的。

这里有一个非常实用的经验:除了日志,还要保存“会话快照”。也就是说,在任务执行完的瞬间,把完整的上下文、工具调用序列、最终结果打包存成一份 JSON 快照。会话快照有几个用途:一是做问题复现,直接拿快照重放;二是做评测集,把历史真实会话筛选出正例和负例,当新的记忆策略或模型版本上线时,拿这批数据做回归测试。没有会话快照,Agent 项目的迭代基本是在裸奔。

4. Graph 层:复杂任务的编排与多 Agent 协作

4.1 什么时候需要 Graph:从线性链路到有向无环图

不是所有 Agent 都需要 Graph 层。如果你的任务就是一个单轮的问答,或者一个固定三步的流水线(比如:理解用户意图 → 查询数据库 → 返回结果),那用简单的顺序执行就够了,引入 Graph 反而是过度设计。

但现实中的复杂任务往往不是线性的。举一个我做过的人力资源助理 Agent 的例子:用户说“帮我安排下周二下午和产品团队的评审会议”。这个任务至少包含:查团队成员忙闲状态、找空闲会议室、创建会议邀请、给参会人发通知。这四步之间有依赖关系——必须先确认大家都有时间,才能定会议室;必须先定好时间地点,才能发邀请。但“查忙闲”这一步要查的人是可以并行的,不需要串行等待。

这种带条件分支、并行节点、依赖关系的任务,就是 Graph 层要解决的。业界典型的做法是用有向无环图(DAG)来描述任务流程。每个 DAG 的节点是一个执行单元(可以是调用一次模型、执行一个工具、运行一个子 Agent),节点间的边定义了依赖关系。

4.2 把任务拆成图:节点与边的设计要点

在 Graph 层,最核心的设计决策就是:节点的粒度到底切多大?切太细,图变得无比复杂,节点之间的状态传递开销很大;切太粗,每个节点内部又塞了一堆逻辑,退回到单体模式。

我总结的一个实用原则是:一个节点应该对应一个“可独立验证的原子动作”。什么叫可独立验证?就是节点执行完,你有一种明确的方式判断它是否成功。比如“查询会议室状态”这个节点,它的输出就是一份会议室空闲列表,你能明确判断这个输出是否合理;“调用创建会议 API”这个节点,它的输出是 API 返回的会议 ID,成功与否一目了然。而“安排会议”就不适合做节点,因为它包含多个原子动作,没法单一验证。

Graph 中还有一个经常被忽略的设计——状态传递。每个节点执行完,会把新的数据写入一个共享状态区,后续节点从这个状态区读取所需数据。这个共享状态区在多 Agent 协作时尤其重要。我见过不少失败的 Graph 实现,问题都出在状态区里没有一个明确的数据 schema,节点 A 写入的字段叫meeting_time,节点 B 却读的是time,结果运行时就报 KeyError。所以在 Graph 设计之初,就要先定义好一张状态数据字典,每个字段叫什么、什么类型、由谁写入、由谁读取,全部列清楚。这个文档看起来笨,但能省掉后面数不清的调试时间。

4.3 人机协同:Human-in-the-loop 的正确插入点

Graph 层的另一个重要能力是支持人工介入。不是所有步骤都应该由 Agent 自动执行。比如最终确认要发给客户的合同,或者要对外发布的公告文案,这种高风险动作必须留一个人工审批节点。

我在生产实践中的做法是:Graph 里定义一种特殊类型的节点,我管它叫“checkpoint 节点”。Agent 执行到 checkpoint 时,暂停自动流程,生成一个审批请求,推送到人工审批系统(可以是企微机器人、邮件或专门的审批后台)。人工通过后,流程继续;驳回时,Agent 根据驳回理由调整策略,重新提交。

这里最容易踩的坑是审批超时处理。人工审批可能有五分钟、也可能有五小时,Graph 节点如果一直阻塞等待,整个流程就会卡死。我的方案是:checkpoint 节点设置一个最长等待时间(比如 30 分钟),超时后自动降级为“默认通过 + 事后人工抽查”,或者“自动挂起并通知管理员”。具体用哪种降级策略,取决于业务风险等级。合同审批这种高风险场景,超时就挂起并且发强提醒,绝不自动通过;内部文档审阅这种低风险场景,超时就默认通过,事后抽查。

4.4 多 Agent 协作:从单 Graph 到 Graph 嵌套

当任务足够复杂时,单个 Graph 也会变得臃肿。这时候可以考虑多 Agent 架构:由一个主 Graph 负责顶层调度,其中的某些节点不是简单的函数调用,而是启动一个子 Agent、由子 Agent 自己跑一个内部的 Loop 或子 Graph。

打个比方,主 Graph 是“项目经理”,子 Agent 是“专项负责人”。项目经理不关心专项具体怎么做,它只关心专项负责人给出的结果是否达标。这种结构的优势在于:每个 Agent 的职责单一,prompt 设计更聚焦,工具的暴露面也更小。

多 Agent 协作的难点在于结果融合。多个子 Agent 各自返回结果后,谁是最终答案的裁判?我的实践是:不要指望让主 Agent 简单拼接子结果,而是要给主 Agent 一个明确的“汇总模板”。比如子 Agent A 返回的是技术方案、子 Agent B 返回的是风险清单,主 Agent 要按模板最终生成一份方案评审报告。这个模板要提前设计好,而不是让主模型自由发挥。

另外要提醒一句:多 Agent ≠ 更好。每个 Agent 都有独立的上下文和模型调用开销,协作时还要花额外的 token 做任务交接和信息同步。我见过一个项目拆了 8 个 Agent,结果跑一个任务的时间是单 Agent 的 6 倍。能用单 Agent 解决的,就不要拆。只有当你发现单 Agent 的上下文窗口实在装不下任务所需信息,或者任务类型差异实在太大导致 prompt 互相干扰时,才值得拆。

5. 生产环境落地:从 Demo 到可运维的完整路径

5.1 工程化之前先想清楚的四件事

很多团队做 Agent 项目,卡壳的地方不是模型选型,也不是功能实现,而是不知道该以什么标准来结束开发、开始上线。我给大家一个建议:在写第一行业务代码之前,先回答四个问题。

第一,评测怎么做?Agent 的打印不像传统 CRUD 项目,有明确的输入输出规范。你要提前准备一批评测用例,每类任务至少 20 条,覆盖正常情况、边缘情况、恶意输入。评测有两个层面:离线评测在开发环境跑,看准确率;在线评测是灰度期收集真实流量,看用户反馈。

第二,失败怎么兜底?Agent 一定会失败,问题是你打算怎么面对失败。我的标准做法是:定义三级兜底——第一级,Agent 重试(换个更好的 prompt 再试一次);第二级,降级到简化流程(放弃复杂工具链,只用基础问答能力回答);第三级,转人工(把会话无缝转交给真人客服)。

第三,成本怎么控制?模型调用是 Agent 项目最大的成本项。大任务跑一次要调用几十次模型,单价看着不高,总量非常惊人。建议做两层:一层是 token 用量监控看板,实时掌握每个会话的 token 消耗;另一层是预算限制,比如设定单个用户单日最多消耗多少 token,超了就自动降级到便宜的小模型。

第四,数据怎么回流?Agent 项目最值钱的资产是运行中产生的真实会话数据。从设计第一天起,就要让数据能自动回流到评测集和记忆系统里。没有数据闭环的 Agent 项目,永远只能停留在 demo 阶段。

5.2 部署架构:单体服务还是微服务拆分

聊一下部署问题。Agent 项目的部署架构因规模而异,我给出三个档位的参考。

低流量档(每天几百次任务):单体服务足够。把所有层放到同一个进程里,数据库用 SQLite 或简单的 PostgreSQL 就够了。这个档位最重要的是快速迭代。

中流量档(每天几千到几万次任务):建议把 Harness 层与业务 API 解耦。模型调用和工具执行放到独立的 worker 进程,业务 API 只负责接收请求、写入队列、查询结果。这时的架构是“生产者-消费者”模型,能扛住比较明显的波峰。

高流量档(每天十万次以上任务):这时候 Graph 层必须有独立的编排服务,状态的存储也要改用高可用的分布式存储。每个子 Agent 可以是独立部署的服务,通过消息队列通信。这个档位还有一个必须做的事:把模型调用做成独立的 gateway 服务,统一负责密钥管理、限流、重试、模型供应商切换,业务服务不再直连模型 API。

无论哪个档位,有一条不能妥协:配置必须外置。密钥、模型名称、工具白名单、Prompt 模板,全部放到配置中心或环境变量里,绝不能硬编码在代码里。Agent 项目迭代频繁,今天换个模型、明天改个 prompt,配置外置之后变更成本可以降到极低。

5.3 评估体系的搭建:没有评测就没有迭代

我见过太多 Agent 项目死在了“感觉还行”这四个字上。为什么?因为 Agent 是概率系统,同样的输入,同一套代码,每次输出都可能不一样。你凭感觉觉得“还行”,但下一次可能就完全不达预期。没有结构化的评估体系,你根本说不清楚某个改动到底是变好了还是变差了。

给新手一个可落地的评估框架,分三个维度:

成功率:任务是否完整跑完并达成目标。这个必须有明确的判定标准,不能依赖人工主观判断。建议为关键任务配置自动验证器——比如任务是“创建订单”,验证器直接查数据库,确认订单记录存在且字段正确。

效率指标:完成任务消耗的步数、token 数、时长。同样的任务,如果某个版本平均步数从 8 步降到了 5 步,说明改动有效。

体验指标:用户侧的感受,比如响应时间、中断次数、转人工率。收集方式可以是埋点,也可以是人工抽检。

评估不是一次性的,要沉淀成日常迭代的一环。每次要改模型版本、调 prompt、换记忆策略之前,先在固定评测集上跑一遍,拿新旧版本对比分数。分数达标再上灰度,不达标就回去调。这个过程枯燥,但它是 Agent 项目从“运气好”走向“稳定交付”的唯一路径。

5.4 一个可复用的最小工程模板

最后给一个可以直接抄作业的工程结构。不一定适合所有项目,但作为起步模板非常实用。

agent-service/ ├── harness/ # Harness 层:模型接入、工具注册、插件管理 │ ├── llm_client.py # 模型 API 封装,含重试、限流、切换逻辑 │ ├── tool_registry.py # 工具注册中心,管理 schema 与执行函数 │ └── plugins/ # 插件目录,每个插件一个独立子目录 ├── loop/ # Loop 层:执行循环、记忆管理 │ ├── agent_loop.py # ReAct 循环主体 │ ├── memory.py # 短期/工作/长期记忆的统一管理 │ └── context.py # 上下文压缩与裁剪策略 ├── graph/ # Graph 层:任务编排 │ ├── orchestrator.py # DAG 调度器 │ ├── nodes/ # 节点实现,每个节点一个文件 │ └── state_schema.py # 共享状态数据字典定义 ├── common/ # 公共组件 │ ├── config.py # 配置加载 │ ├── logging.py # 结构化日志 │ └── tracing.py # 链路追踪埋点 ├── evals/ # 评测集与评测脚本 │ ├── cases/ # 测试用例,JSON 格式 │ └── run_evals.py # 离线评测入口 └── api/ # 对外服务接口 └── server.py # FastAPI 应用

这个骨架把三层边界划得很清楚:harness 目录不依赖 loop 和 graph,loop 只通过接口调用 harness 的工具,graph 调起 loop 执行子任务。依赖方向永远是单向的,从 graph 指向 loop、loop 指向 harness。只要守好这条依赖规则,后续扩展就不会乱。

6. 生产实践:典型问题排查与避坑实录

6.1 插件加载失败的四种场景与应对

前面提到了harness failed to load plugins这个报错,这里我把生产环境中遇到的具体场景和排查思路系统地整理一下。

第一个场景:Python 依赖冲突。现象是加载插件 A 成功、加载插件 B 失败,报 ImportError 或 ModuleNotFoundError。排查思路:进入插件目录,单独pip list看依赖版本,对比两个插件的 requirements 是否有版本重叠但不兼容的包。解决方案:要么统一版本,要么给插件做独立环境。

第二个场景:入口发现失败。插件目录结构不符合加载器的预期。比如加载器要求每个插件有__init__.py且其中包含register()函数,你的插件结构不对自然加载不上。排查思路:看加载日志里是否有扫描到目录但未发现入口函数的记录。解决方案:对照加载器的规范重新整理插件目录结构。

第三个场景:安全校验拦截。这在企业级框架里很常见,插件需要有签名或者来源白名单。排查思路:加载失败日志里如果出现 verify、signature、policy 之类的关键词,基本就是这类问题。解决方案:开发环境用开发者模式绕过签名,或者把自己的插件加入白名单。

第四个场景:循环依赖。插件 A 的初始化代码依赖插件 B,但加载顺序是 A 在前,于是 A 加载时引用 B 的模块直接报错。排查思路:把插件逐个加载测试,找个能稳定复现失败的顺序,就能看出依赖方向。解决方案:在 manifest 里声明依赖关系,让加载器按拓扑排序。

6.2 上下文超限的三种处理策略

上下文超限(context overflow)是 Loop 层最高频的问题。模型有固定上下文窗口,你塞进去的内容太多就会报错或丢信息。

策略一:定时摘要压缩。每执行 N 轮循环(我习惯 N=3),就把之前的对话压缩成摘要。压缩的 prompt 要明确要求保留“关键决策、已获取的事实数据、未完成的事项”,而不是简单地说“总结一下”。压缩后的摘要替换掉原始对话,上下文重新变得宽敞。

策略二:工具返回裁切。很多工具返回的内容非常长,比如搜索返回 20 条网页摘要。在进入上下文之前,先对工具返回做一次裁剪,只保留与当前任务相关性最高的 3~5 条。这个裁剪逻辑可以放在 Harness 层的工具执行之后。

策略三:外置化全文,上下文只留索引。如果任务确实需要用到很大的文档(比如读取一份 50 页的 PDF),不要全文塞进上下文,而是先把文档按章节切块做向量化,上下文里只放入查询结果片段。

三个策略可以组合使用。我的经验配置是:全局开策略一,关键工具开策略二,大文档类工具开策略三。三者一起来,上下文超限的概率能降到接近零。

6.3 Agent 陷入死循环的判断与干预

Agent 在一个错误行动上来回打转,是 Loop 层最难调试的问题之一。典型的死循环长这样:模型反复调用同一个工具,每次都得到同样的失败结果,然后又调用同一个工具,再来一遍。

根因分析下来通常是三种。第一种是错误反馈不充分。工具返回的报错信息太抽象,模型根本不知道自己错在哪。解决:改工具的错误返回,把失败原因写得具体,比如“参数 city 格式不正确,请使用中文城市全称”,模型看到这个就能自我修正。

第二种是循环终止条件太宽松。模型认为只要没有成功就不算结束,但实际上这条路根本走不通。解决:引入步数上限的同时,再加一个重复动作检测——如果最近的 3 步里调用了同一个工具且参数基本相同,就判定为死循环,中断循环并触发备选策略。

第三种是任务本身在模型能力之外。比如让一个只能做文本处理的模型去做需要图像识别的任务,模型再怎么绕也绕不出来。解决:在 Graph 层做“能力预检”,任务分配前先判断此任务类型是否有对应工具链支持,没有就直接走降级流程,不浪费循环资源。

6.4 评测与快速验证的几个实用技巧

最后分享几个评测阶段特别实用的技巧,都是我实操中总结出来的。

技巧一:评测用例用 JSON 管理,不要用代码写死。每个用例包含输入、期望行为描述、验证规则。这样非工程背景的产品同事也能直接往评测集里加用例,测试集越养越肥。

技巧二:离线评测不要用真实模型跑全量用例。太贵也太慢。我的做法是:先用确定性规则筛掉明显不通过的,再对高价值用例跑真实模型。筛选规则可以是关键词匹配、格式校验、工具调用序列比对。

技巧三:给评测集分层。分成“冒烟层”(10 个用例,每次改动都跑)、“回归层”(100 个用例,每周跑一次)、“全量层”(所有用例,发布前跑)。这样既能保证快速反馈,又不至于让评测拖住发布节奏。

技巧四:重点关注“死惨案例”。每次上线后,把生产环境中表现最差的 20 个会话整理出来,人工分析失败原因,挑出共性模式,转化为新的评测用例。这个循环转起来之后,你的产品会越用越稳。

7. 关于这套架构的几点个人体会

写到最后,说点纯个人的经验之谈。

这几年做了不少 Agent 项目,我最大的感受是:Agent 工程与其说是算法问题,不如说是架构问题。模型能力进步很快,今天觉得困难的事,半年后模型自己就能搞定。但架构不会自动变好,Harness、Loop、Graph 这三层边界如果一开始没划清楚,后面想重构的成本极高。

我也观察到不少团队一上来就追求花哨的多 Agent 协作、复杂的记忆系统,结果基础的三层都没搭稳。我给的建议一直是:先把单 Agent 的 Harness 和 Loop 打磨到能稳定完成任务,再考虑 Graph 编排;先能用线性流程解决,再引入并行和分支;先手动评测积累数据,再上自动评测系统。一步一步来,比一步到位稳妥得多。

还有一点,也是我反复提醒自己和团队的:Agent 的边界感很重要。不要试图让它做所有事,也不要让它在没有把握的场景里硬撑。好的 Agent 系统,知道什么时候该自主执行,什么时候该停下来问人,什么时候该承认自己做不了。这套边界意识,需要从架构层面去设计,而不是指望模型自己领悟。

最后分享一个小技巧:如果你正在设计一个新的 Agent 项目,我建议第一周什么都不要写,先把三层架构各自要处理的接口契约定义出来——Harness 层对外提供哪些工具接口,Loop 层接收什么输入、产出什么输出,Graph 层的状态 schema 长什么样。接口定清楚了,后面的编码基本就是填肉。反之,接口模模糊糊就开写,越写越痛苦,最后几乎必然推倒重来。

希望这篇文章能帮你少走一些弯路。如果你在实践中有不同的分层思路,或者踩过我没提到的坑,欢迎留言交流。搞 Agent 工程,大家都是摸着石头过河,互相分享经验,才能让这条路越走越宽。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询