☰
Agent 生产级三层架构:Harness、Loop 与 Graph 设计实战
2026/10/8 20:57:38 网站建设 项目流程

做 Agent 工程这几年,我最大的感受是:demo 和成品之间,隔着的是 Harness、Loop、Graph 这三层。Agent 单次调用谁都会写,但要把一个 Agent 变成能上生产、能被团队维护、能扛住真实业务流量的系统,绕不开这三层怎么设计、怎么调优、怎么兜底。这篇文章就围绕这个三层架构展开,把我做过的几个真实项目里的设计思路、参数选择和排坑过程完整写出来,适合正在搭 Agent 框架、或者准备把 Agent 接到现有业务里的开发者参考。

打个比方比较好理解:Harness 像车的驾驶舱和外壳,决定你能碰哪些按钮、仪表显示什么、安全带系在哪;Loop 是发动机,让模型反复走“观察—决策—行动—看结果”这个过程;Graph 是导航图,告诉我们在哪个路口转弯、哪些路段可以并行、哪些地方必须停车等人。三层叠加在一起,Agent 才不是一个拿着锤子见啥都敲的黑盒,而是一个可以被设计、被测试、被监控的系统。下面我把三个词先掰开,再分别讲每一层怎么搭、怎么调、怎么排坑。

1. 先把三个词掰开揉碎:Harness、Loop、Graph 到底在说什么

1.1 Harness 不是“提示词包装”,是 Agent 的生产级操作台

很多人第一次接触 Harness,以为它就是写个 system prompt,把模型“框住”。这个理解差得有点远。Harness 这个词在工程领域一直有“装配、捆扎”的意思,比如altium harness指的是电子设计里的线束,物理世界里把电源线、信号线、地线规整地捆在一起,让设备能可靠工作。Agent 领域的 Harness 干的是同一件事:把模型、工具、数据、策略、权限这些原本松散的部件,规整地装配到一个可控的运行环境里。

我自己的定义很简单:Harness 是模型之外的“整套运行框架”,决定模型能用什么工具、能看什么信息、能做什么动作。一个生产级 Harness 至少负责:工具注册与 schema 管理、上下文组装与裁剪、调用前权限校验、调用中沙箱隔离、调用后结果过滤、全链路日志与计费。这些事全部写在模型提示词里是做不到的,因为提示词只是给文本模型看的文本,而 Harness 是真正执行代码、捏着资源、握着开关的那层系统。

很多人搞不清 Harness 和 Agent 的区别。Agent 是一个执行体,它有目标、有行动能力;Harness 是承载这个执行体的环境。同一个 Agent 逻辑,放进不同 Harness,表现会是两个东西:一个可能什么都不让碰,另一个可能让模型随意执行 shell 命令。生产系统选 Harness 时,本质是在选“谁能拥有哪些特权”。

1.2 Loop 是 Agent 的心脏,Graph 是 Loop 的升级形态

Loop 说的是 agent loop,也就是 Agent 里最核心的重复过程:每一步把当前状态交给模型,模型决定是调用工具还是输出结果,然后再把工具返回的观察结果带回去,继续让模型决策。这个过程循环往复,直到模型认为任务完成、或者达到预设终止条件。它是 Agent 产生“自主性”的来源。

Graph 则把 Loop 从“一条直线”升级成了“一张有分叉、有汇合、有等待的地图”。Loop 只有一个隐含的反复过程,Graph 则显式定义节点和边:节点是任务步骤,边是流转条件。比如一个节点执行成功后走到下一步,失败则走降级分支,需要人工确认时挂起等待。很多真实业务并不能用一条直线 Loop 跑完,必须有分支、并行、人工审批这些结构,Graph 就是把这些结构显式化。

三层不是替代关系,是叠加关系。Graph 的每个节点内部可能依然在跑一个 Loop;Harness 为所有 Loop 和 Graph 提供统一的运行时能力,比如工具权限、日志、计费埋点。所以正确理解是:Harness 是运行基座,Loop 是最小执行单元,Graph 是执行拓扑。

1.3 三层架构各自解决什么问题

用一个表格对照,看得更清楚:

层次核心问题主要职责典型事故
Harness它能干什么、不能干什么工具装配、权限控制、上下文管理、安全护栏、可观测性模型乱调工具、越权访问、预算失控
Loop它怎么一步一步把事干完迭代调度、记忆管理、终止检测、重试恢复无限循环、重复操作、上下文爆炸
Graph事多了之后路线怎么编排分支、并行、人工介入、状态流转流程僵化、任务状态丢失、结果不可复现

我见过很多初学 Agent 的人,只搭了一个 Loop 就开始跑业务,结果遇到两个问题:模型在没人管的情况下反复调用同一个工具,把账号余额刷光;或者流程走到一半,模型自己发明了一个不存在的环节。这些都是因为上层的 Harness 没兜底、Graph 没约束。下面我按层拆开讲。

2. Harness 层:边界、安全与工具装配,决定 Agent 能不能上生产

2.1 工具装配的本质:把模型伸进世界的“手”和“眼睛”

模型本身不具备真正调用工具的能力,它只会“请求调用”。Harness 要做的是把模型发出的调用请求翻译成真实的函数执行。这个翻译质量,直接决定 Agent 好不好用。我用过一个天气查询工具,一开始 schema 里对参数 city 的解释写得太简单,模型总是传拼音或者英文名,导致查询失败。后来在参数描述里加了明确的示例:“city 必须是中文城市名,例如‘北京’‘上海’,不要传拼音或英文”,问题立刻消失了。工具描述不是给程序员看的注释,是给模型看的说明书,必须把边界、格式、错误情况写得清清楚楚。

工具注册也不是一个简单的字典。每个工具在 Harness 层应该带五类元信息:名称、版本、参数 schema、权限级别、成本预估。权限级别决定哪些用户/哪些场景下模型可以碰这个工具;成本预估用来在调用前做预算校验。工具多了以后还要考虑分组,一个模型请求里塞上百个工具 schema 是很占 token 的,而且模型会“挑花眼”。我实测超过 80 个工具的时候,需要按场景分组,Harness 根据当前任务先选出一个工具子集,再拿去组装请求。

2.2 安全护栏:越狱防护、危险操作拦截、预算熔断

Agent 安全是上生产之前最容易被忽略的一环。原理很简单:模型是无状态的逻辑体,它不管你的钱、不管你的权限,它只知道“继续干下去”。所以所有跟风险相关的事都要下沉到 Harness 层实现。我习惯做三道防线:

第一道是调用前规则校验。比如禁止执行某些系统命令、禁止读写某些敏感路径、外部请求只放行白名单域名。这些规则用代码写死,不吃模型判断。第二道是调用时隔离。工具函数尽量跑在受限账号或容器里,加上超时限制,防止一个工具把整个服务拖垮。第三道是调用后结果过滤。工具返回值在拼进上下文之前,先检查是否藏了异常内容,比如网页抓取结果里经常有“忽略之前所有指令”这类的注入文本,必须在进入模型上下文前隔离或标注,否则模型容易被带偏。

预算熔断一定要做在 Harness 层。我在一个项目里给每个任务设置了 token 上限和费用上限,达到阈值后 Harness 直接终止 Loop,并生成一条“预算熔断”记录。不做这个,一个 Loop 失控跑半小时,费用能让你怀疑人生。熔断之后还要有恢复机制,最简单的就是保存现场的上下文快照,人工决策后重新发起。

2.3 插件化与技能体系:skill 怎么设计才不翻车

现在的 Agent 框架基本都支持插件和 skill,热词里常提到的deepseek harness、claude agent skills就是这类生态。skill 本质就是一组指令、工具定义、示例和触发条件的打包。一个好的 skill 应该做到:一个 skill 解决一类完整任务,而不是解决一个原子操作。比如“金融研报解读”是一个 skill,它内部包含读取数据、调用计算工具、生成结构化结论这三个步骤;如果把它拆成三个 skill,模型反而不知道怎么串联。

插件加载有一个我踩过的坑。热词里有一条很典型的报错:harness failed to load plugins web boot: 1 entry did not activate huayu-yuan。这类“入口未激活”问题,十有八九是下面几个原因:插件 manifest 里配置的入口文件路径不对、入口函数名没有按约定导出、或者插件依赖缺失导致初始化抛异常。排查顺序我建议是:先看 manifest 的 entry 字段指向的文件是否存在,再确认入口函数是不是用了框架约定的命名和导出方式,最后看插件的依赖包有没有装全。插件目录里不要放多余文件,我就见过一个项目把测试用例打进插件包,入口扫描逻辑找不到主入口,直接报错。

skill 太多也要注意触发冲突。我的经验是给每个 skill 写一个“触发条件”声明,Harness 在组装上下文时先按触发条件做一轮粗筛,能选出 3 到 5 个 skill 再进入模型决策。

2.4 离线环境部署与版本回退:Harness 工程化的最后两公里

很多人部署 Agent 是在隔离网络环境,没有公网访问能力。这时候配deepseek harness之类组件,要注意提前把依赖包全部下载好,用本地源或者离线包安装。我习惯的做法是:在一台有外网的机器上把所有依赖导出成 wheel 文件,连同模型权重一起拷进内网,再用本地路径安装。安装完成后先跑一个最小冒烟测试,确认 harness 能成功加载模型和核心插件,再上业务量。有一个细节容易被忽略:离线包的依赖版本锁定文件装完以后,内网机器上如果之前有旧版本,会出现各种怪异的“冲突但不报错”的问题,所以内网环境最好用干净的基础镜像。

版本回退也值得讲。Harness 的配置、skill 定义、prompt 模板,全都要进 git,发布时打 tag。因为配置类的错误不像代码有编译期检查,往往运行到一半才爆出来。我的习惯是每次发布都记录当前所有组件的版本组合,一旦线上出现问题直接切到上一个 tag。代码回退这件事,看起来不起眼,关键时刻能救命。我有一次只改了一个 prompt 词,上线后模型开始大量重复调用搜索工具,在没有一键回退机制的情况下,只能手动改配置重启,多花了二十分钟。现在每个 harness 项目上线前,我都会先确认回退路径可用。

3. Loop 层:主循环是 Agent 的发动机,怎么调才不熄火

3.1 一次完整的 Agent Loop 里发生了什么

Agent Loop 的每一步,本质上就是“组装上下文 → 调模型 → 执行动作 → 观察结果”这四个动作的循环。我用一个极简的 Python 伪代码说明,实际框架会复杂很多,但核心骨架长这样:

MAX_ITERATIONS = 15 def run_agent(initial_state, harness): state = initial_state for step in range(MAX_ITERATIONS): messages = harness.assemble_context( state.history, state.tool_results, state.memory ) response = model.call(messages) if response.has_tool_call(): result = harness.execute_tool( response.tool_name, response.tool_args ) state.tool_results.append(result) state.history.append(response) continue if response.is_final_answer(): return response.text raise MaxIterationsError("loop exceeded limit")

生产版本还会有重试、超时、重复检测、记忆压缩,但思想上完全一样。每次循环里,模型拿到的输入都要包含“上一步发生了什么”,否则它就无法感知自己的行动结果。这一步缺失是很多 Agent 表现差的根源——模型做了动作,但下一个请求里压根没带上动作结果,它只能在原地空转。

3.2 记忆是怎么“塞”进 Loop 的

记忆是 Loop 能持续工作的燃料,也是容易失控的地方。我一般把 Agent 的记忆分成三层:短期记忆是最近几轮对话和工具调用记录,直接在上下文中完整保留;长期记忆是向量库检索出的相关片段,按相关性动态注入;结构化记忆是用户属性、任务状态这类必须准确的数据,存成 JSON 或数据库记录,不靠模型猜。这三层里最容易出问题的是长期记忆,检索出来的片段如果不做相关性阈值过滤,塞进去一堆无关信息,反而把模型带偏。

记忆在 Loop 里还有一个经典坑:序列化的时候出现循环引用。热词里那条self referencing loop detected for property 'mem_memberinfo' with type 'system'就是典型问题——工具返回的对象里有互相引用的字段,比如 memory 对象里挂了一个指向整个系统对象的引用,框架在序列化时陷入死循环。解决思路很粗暴实用:把敏感对象先转成 DTO,只保留需要的标量字段;或者开启引用循环忽略选项,在序列化层直接跳过重复引用。我在项目里遇到过两次,根源都是工具函数直接返回了 ORM 实体对象,后来统一改成返回字典,问题再没出现过。

记忆还要考虑 token 预算。上下文窗口是有限的,Loop 跑得越久,历史越长。我的做法是设一个阈值,超出后做摘要压缩:把最老的几轮对话交给模型生成一段摘要,保留摘要而丢弃原文。摘要会损失细节,但总比上下文爆炸直接报错强。

3.3 Loop 的终止条件、重试与自引用检测

Loop 最怕的是“停不下来”。终止条件分两类:正常终止是模型自己输出最终答案;异常终止是达到了迭代上限、预算上限或超时时间。迭代上限我一般设在 10 到 20 之间,任务简单设 10,复杂任务设 20,但不建议超过 30——超过 30 步的任务大概率是流程设计有问题,应该用 Graph 拆成多步,而不是指望一个 Loop 硬跑。

比迭代上限更难处理的是“重复循环”。模型在同一个问题上反复调用同一个工具,每次都拿到差不多的结果,它还是不结束。这个现象有点像 ffmpeg 里的 loop 滤镜:同一段画面不停循环输出,如果不给终止条件,就会无限播放。我的解决办法是给 Loop 加一个“指纹检测”:把最近五步的工具名和参数摘要算成一个哈希,如果连续出现相同指纹,就触发降级策略——要么终止,要么换一种提示策略重新尝试。有一次排查线上 Agent 卡死,查日志发现模型在连续 11 次调用一个翻译工具,每次都传一模一样的参数,指纹检测直接把问题暴露了。

工具调用失败后的重试也要限次数。工具报错时,把错误信息回填给模型,让模型修正参数重试,通常有效;但同一工具连续失败三次以上,就不要再让模型硬试,直接跳到降级分支。降级分支可以是告诉用户“工具暂时不可用”,也可以是换备用工具。

3.4 Loop 的观测:看不到循环在干嘛,就没法调

调 Loop 没有日志等于盲飞。每个迭代周期,我会输出一行 JSON Lines 日志,包含:迭代序号、模型输出截断、调用的工具名、工具返回状态、耗时、token 消耗。调试时用 request_id 把所有日志串起来,就能完整回放一次 Agent 的思考链条。

重点关注的指标有三个:平均迭代次数、工具失败率、首次工具调用时间。平均迭代次数突然上升,通常是上下文里缺信息,模型在反复摸索;工具失败率升高,一般是 schema 描述变差了或者上游接口不稳。这些指标配上告警,比肉眼盯日志强得多。有一次告警告诉我工具失败率从 5% 飙到 60%,查下来发现是下游 API 悄悄改了返回字段,要不是指标盯着,用户反馈出来之前根本不知道。

3.5 给模型“退一步”的实话

Loop 调试的经验多了以后,我反而觉得最关键的是承认“模型不是万能的”。上下文越长,模型越容易在后期犯糊涂。我有个习惯:Loop 跑到一定步数后,如果发现模型连续两轮都没有新动作,就把上下文里最老的内容压缩掉一部分,给它“减负”。这就像人工作太久需要停下来换个角度,模型也一样。还有,生产环境里模型参数尽量固定:低温度(0 到 0.3),固定 seed,方便回归对比。看到有人在生产环境把温度拉到 0.8,Agent 的输出一天一个样,测试用例都跑不稳,这基本是把随机性当卖点了。

4. Graph 层:把 Loop 从“直线冲刺”升级成“路线图”

4.1 为什么需要 Graph:线性循环的第一个短板

Loop 只解决“单条路径反复走”的问题。真实业务没这么简单,一个任务里可能要先判断数据质量,质量好走 A 路径,质量差走 B 路径;可能同时要查三个来源再汇总;可能中间需要人工审核。这些结构用 Loop 硬写会很痛苦,因为分支和并行逻辑只能写在提示词里,让模型自己“决定”,结果不可控。Graph 把流程结构从提示词里抽出来,变成显式的代码路径。

举个例子:一个舆情分析 Agent,如果只用 Loop,模型要自己决定“先清洗数据还是先写报告”,很容易乱。用 Graph 就清晰了:清洗节点 → 分析节点 → 报告节点 → 人工审核节点。每个节点是一个独立函数,有明确的输入输出,节点之间的流转条件写死在边里。模型不再负责决定流程顺序,只负责当前节点内的事情。

人工介入放在 Graph 里特别顺手。把“人工审核”定义成一个特殊节点,Agent 跑到这里就挂起,等人工确认后再继续后续节点。这个体验是纯 Loop 很难给的,Loop 里你想暂停,要么靠外部强杀,要么靠模型自觉,都不靠谱。

4.2 可视化编排与 Snap Graph Builder:把图纸变成可运行系统

我做过几个项目是用可视化工具搭 Graph 的,像是热词里提到的snap graph builder,本质上就是把节点和边画出来,自动生成可执行的定义文件。可视化编排最大的价值不是“不用写代码”,而是让产品和业务能直接看到 Agent 的执行路径,而不是打开一堆代码才能理解。评审流程时,对着图讲比对着代码讲效率高一个量级。

这类工具还有一个隐藏功能:快照。把某个节点运行上下文保存成快照,失败时可以拿到快照重放,逐步排查。这就像游戏存档,Loop 时代你想复现一个问题得碰运气,Graph 时代直接读档。我建议哪怕是手写代码做 Graph,也要给每个节点的输入输出做版本化记录,至少保留最近几天的状态快照。调试复杂 Agent 的时候,这个习惯能省你半天时间。

4.3 商图与状态压缩:图论给 Agent 的启发

Graph 层做多了,我反而回头发现图论模型挺有用。热词里的“商图(quotient graph)”原本是图论概念:把等价的顶点合并成一个超顶点,得到一个简化后的图。你可以把它理解成地铁线路图——把相邻的小站合并成一个大站,只看主干走向。拿这个思路看 Agent 运行日志,效果很好:把相同或高度相似的中间状态折叠起来,就能看到 Agent 是不是卡在某个状态绕圈。

有一次调试一个超长任务,我按“工具名+参数摘要”把状态归一化,折叠出商图之后,一眼看出来 Agent 在“搜索→阅读→搜索→阅读”之间反复横跳,没有真正的信息增量。这个循环在原始日志里看不出名堂,因为每一步的文本都不一样,但折叠成商图后,本质状态就几个,问题立刻清楚了。

顺便提一句,图结构在跨领域也很流行,比如脑疾病分类里用局部到全局的图神经网络建模。这提醒我们一件挺本质的事:很多复杂问题都适合先建立局部结构,再聚合出全局结论。Agent 编排也一样,先让各个子任务独立跑局部逻辑,再用一个聚合节点把结果汇总,这种“局部到全局”的模式比让一个 Loop 从头管到尾稳定得多。

4.4 从 Loop 改造成 Graph 的最小步骤

如果现在你的 Agent 是一个线性 Loop,想升级成 Graph,我建议按这个顺序改,不要想着一步到位:

第一步,在纸上画出主流程的 4 到 6 个步骤节点。第二步,标出哪些地方需要判断分支、哪些需要并行、哪些需要人工。第三步,把每一个节点函数化:明确输入、输出、抛错行为,最好做到每个节点可以独立在测试脚本里调用。第四步,引入一个统一状态对象,所有节点只能从状态对象取值,并把执行结果写回状态对象。节点之间不直接传参。第五步,先按串行方式把整个流程跑通,再加入并行的分支和重试机制。

状态对象是 Graph 的核心。节点可以理解为纯函数,输入是状态的一部分,输出是状态的更新。这样设计的好处是:任务中途挂了,只要有状态快照就能重新拉起;节点重试不会产生重复副作用,因为节点只改状态不改外部资源。如果做不到节点幂等,至少要保证节点内部的“有副作用操作”和“状态更新”分开,这样更稳妥。

5. 生产实践:三层架构落地与排坑实录

5.1 最小可行架构长什么样

一个能跑生产的 Agent 服务,代码上至少分成四个模块。Harness 模块负责工具注册、权限、上下文组装和熔断;Loop 模块负责迭代执行、终止检测、重试和重复循环识别;Graph 模块负责流程定义、状态对象和人工审核挂起;运行时模块负责请求队列、并发控制、日志采集和指标暴露。第一版不需要很复杂,但这些边界要清晰。我在项目里吃过亏:前几版把工具调用和流程控制写在了一起,结果加一个工具要动 Loop 的代码,加一个节点也要动 Loop 的代码,改了两周就变成了谁都不敢碰的代码块。

配置化是这里的关键。工具列表、Graph 定义、prompt 模板全部做成配置,代码主体保持稳定。项目上线后你会发现,业务需求大多在“改配置”层面就能满足,不需要每次都发版。只有做 Harness 层的新能力才需要动代码,频率会低很多。

5.2 常见问题排查速查表

现象可能原因解决方案
Loop 无限循环不退出终止条件没写或 max_iterations 没设加迭代上限触摸,配重复指纹检测
工具调用一直报参数错误schema 描述不清、缺少示例补参数描述和示例,限制枚举值
插件加载失败,entry did not activate入口路径错误、入口函数名不符、依赖缺失按 manifest 路径逐项检查,确认入口导出
上下文越滚越大,模型开始糊涂没有做压缩和裁剪滑动窗口 + 摘要机制
工具返回结果带注入指令结果未隔离直接进了上下文在 Harness 层做结果清洗或隔离标注
多步骤任务状态丢失节点用了局部变量,没有统一状态对象引入全局 state,每个节点只读写状态对象
预算超支没有计费和熔断Harness 层记录 token 和费用,超阈值自动终止
序列化时自引用循环报错工具返回了互相引用的对象转成 DTO,只保留标量字段,或开启循环忽略

这张表是我从好几个项目的线上问题里整理出来的,每一条都见过不止一次。排查的时候记住一个原则:先看配置,再看日志,最后才怀疑框架和模型。大部分问题都是配置写错或者环境不一致。

5.3 版本管理与发布回退:给 Agent 上个保险

Agent 项目比普通后端项目更需要版本管理。普通后端代码出问题,报错能定位到行;Agent 里一个提示词调不好,表现是“效果变差”,不是崩溃。所以我把所有可能影响行为的元素都纳入版本管理:prompt 模板、工具 schema、skill 列表、Graph 定义、模型参数。这些东西改了任何一处,都要走 MR 评审和回归测试。

回归测试这件事不能偷懒。我会准备一组合格用例,每个用例有输入和期望的结构化输出。每次发布前跑一遍,对比和上一版的输出差异。模型输出有随机性,所以我会把温度调低,在固定参数下跑回归,至少能挡住大部分明显的回归问题。

回退机制做得简单点:配置存储带版本号,状态对象也记录 graph_version。线上出问题后,一键把配置切回上一版本,再重新发起任务。注意这里要保证状态对象能兼容旧版本配置,所以状态对象里不要存和流程强相关的字段,只存任务业务数据,否则新旧配置之间切换会产生字段对不上。

5.4 团队协作与后续扩展:三层架构的长期价值

三层架构还有一个隐性价值:团队分工变清晰了。有人专门打磨 Harness 层的工具质量和安全策略;有人专门优化 Loop 的效率和记忆策略;有人专门设计 Graph 的业务流程和评审机制。各层的关注点完全不同,硬塞在一个文件里只会互相干扰。工具工程师不会天天去改流程定义,流程设计师也不用深入了解每个工具的入参细节。

后续想做多 Agent 协作的时候,这个架构也更容易扩展。每个 Agent 保留自己的 Harness 和 Loop,通过 Graph 层或者消息队列来协作。这个模式有点像知识库工具里的插件生态,Agent 之间通过定义良好的接口传递任务和结果,而不是互相直接改对方的内部状态。比如在 Obsidian 这种笔记工具里集成助手插件,本质上也是在 Harness 层控制它能查哪些笔记、能改哪些文件,规则清楚,才不会被模型带乱。

踩过几次坑之后,我现在不管做什么 Agent 项目,都会先问三个问题:这个动作需要什么样的 Harness 边界?主循环怎样才能明确判断“完成”?哪些业务路径需要显式地用 Graph 固定下来?把这三个问题答清楚,项目基本不会跑偏。最后分享一个小技巧:每次循环结束,把关键状态 dump 一份到本地日志,带时间戳。这个东西平时不起眼,等你想复盘一次诡异 Bug 的时候,你会无比感激当时的自己。

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

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

立即咨询