☰
Agent工程化实战:Harness、Loop、Graph三层架构详解
2026/10/8 10:55:12 网站建设 项目流程

如果你真正动手做过 Agent 项目,大概率会撞上同一个感受:Demo 跑得飞起,一到生产就翻车。今天能用的对话流程,明天换个工具就崩;明明只是加一个“查天气”的功能,却要把整个主流程的代码翻一遍。我意识到问题不在模型智商,而在工程结构。后来我花了两三个月把项目拆成三层——Harness、Loop、Graph——很多反复出现的疑难杂症才真正消失了。

这篇文章我想完整讲一遍这三层架构,包括每一层负责什么、层与层之间怎么配合、生产落地时具体怎么做,以及我在重构过程中踩过的坑。内容不偏理论,基本都是可以直接复用的设计思路和代码骨架。适合正在做 Agent 应用、或者准备从单体 Agent 代码往工程化方向重构的开发者和技术负责人。

1. 为什么 Agent 工程需要三层架构:从 Demo 到生产的那道坎

1.1 单体 Agent 代码是怎么一步步失控的

很多人写 Agent 的第一个版本,就是在一个 Python 文件里把大模型 API、工具函数、Prompt 模板、状态缓存、重试逻辑全部堆在一起。第一版几十行确实够用,但随着工具增多、业务分支变复杂,这个文件会膨胀到两三千行。此时最典型的问题有三个:

第一,职责完全耦合。模型调用逻辑和业务判断逻辑粘在一起,改 Prompt 要小心翼翼,不然可能影响工具调用的解析;加一个新工具要改主流程,否则模型绕不过去。

第二,无法单独测试。整段逻辑跑起来才知道对不对,单元测试根本无从下手,因为每一步都在依赖全局状态。

第三,崩溃恢复能力为零。一旦某个工具调用超时或者返回脏数据,整个 Agent 会话就废掉,用户只能重新开一个对话。

我见过很多项目死在“能演示”到“能稳定跑”的鸿沟上。三层架构就是为这道鸿沟准备的。

1.2 三层架构的职责边界:一句话说清

这三层我能用最直白的话给你捋清楚:

  • Harness 是 Agent 的“躯体”,负责运行环境、工具注册、插件加载、安全拦截、模型适配。说白了就是让 Agent 跑得起来、跑得安全的那层壳。
  • Loop 是 Agent 的“神经回路”,负责在一个任务内部反复执行“思考-行动-观察”的循环。大模型每一步该调什么工具、看到结果后下一步怎么决策,都在这一层完成。
  • Graph 是 Agent 的“路径规划”,负责多个步骤之间、多个 Agent 之间的编排。任务先干什么后干什么、什么情况下走哪个分支、哪些子任务可以并行,都由 Graph 层决定。

打个比方:Graph 是流水线的传动带和分拣口,Loop 是某一个工位上工人反复完成的操作循环,Harness 是整座车间的照明、通风、安全护栏和电力系统。三者不互相替代,而是各管一段。

1.3 三个设计原则:控制反转、可观测、可恢复

我在重构时给自己定了三条硬性原则,缺一条后面都会痛苦。

控制反转要求 Agent 内部逻辑不直接 new 工具对象、不直接发起网络请求,而是通过 Harness 提供的接口发出意图,由 Harness 决定调用哪个真实实现。这样换工具、加限制、做 mock 都集中在 Harness 一层,不用动业务逻辑。

可观测意味着每一层都要输出结构化日志。接口调用人、调用工具名、消耗 token、循环次数、图节点状态,全部带链路 ID 记录下来。没有这一步,后面排查问题基本靠猜。

可恢复是指任何一次工具调用失败都不应该让整个会话报废。Loop 层要捕获异常并反馈给模型让它重新决策,Graph 层要能重试节点,Harness 要负责把崩溃的会话恢复到最近一个稳定状态。

2. Harness 层:Agent 的躯体,也是安全与扩展的边界

2.1 什么算一个“够用”的 Harness

Harness 这个词在 Agent 生态里出现频率越来越高,但很多人把它和“Agent 框架”混为一谈。我理解的区别是:框架解决“怎么写 Agent”,Harness 解决“怎么安全稳定地跑 Agent”。

一个够用的 Harness 至少要包含四个模块:

  • 模型适配器:统一封装不同大模型 API(OpenAI 兼容接口、DeepSeek、Claude 等),让上层 Loop 不必关心底层是哪个模型。
  • 工具注册中心:维护一个工具名到实现函数的映射表,同时声明每个工具的参数格式和权限级别。
  • 插件加载器:从指定目录按约定动态加载技能包,实现“加一个能力不用改主代码”。
  • 生命周期管理:负责 Agent 启动、会话创建、超时回收、优雅退出。

2.2 Harness 不是 Agent 框架:别再混淆了

我们拿主流 Agent 框架来做对比容易理解。框架一般给你一个 Agent 类和一套抽象工具,你继承后写 Prompt 就能出活。而 Harness 更像一个容器,它不关心你的业务逻辑是什么,只负责提供“模型进来、工具出去、日志留下、异常拦住”的通道。

我用一个真实经历说明为什么需要区分。早期我在项目里给 Agent 加“上传文件解析”能力时,直接在 Agent 类里写了一个函数。后来权限收紧,要求解析前必须做文件类型白名单校验,结果改动牵一发动全身。重构到 Harness 之后,这个校验逻辑被抽成 Harness 层面的拦截器,所有工具调用统一过一遍拦截,安全策略变成可配置项,不再和业务纠缠在一起。

2.3 从零搭一个极简 Harness:插件化设计

下面是我常用的 Harness 骨架,用 Python 写一个最小版本,核心思路是“能力即插件”。

# harness.py import importlib import json import yaml from pathlib import Path from typing import Dict, Callable, Any class ToolRegistry: """工具注册中心:管理所有 Agent 可用的工具""" def __init__(self): self._tools: Dict[str, Dict[str, Any]] = {} def register(self, name: str, func: Callable, schema: dict, permission: str = "read"): self._tools[name] = { "func": func, "schema": schema, "permission": permission, } def call(self, name: str, **kwargs): if name not in self._tools: raise KeyError(f"Tool {name} not registered") tool = self._tools[name] # 安全拦截可以放在这里,校验参数、检查权限、记录审计日志 return tool["func"](**kwargs) class PluginLoader: """插件加载器:扫描 plugins 目录并按约定加载工具""" def __init__(self, registry: ToolRegistry, plugin_dir: str): self.registry = registry self.plugin_dir = Path(plugin_dir) def load_all(self): for manifest_path in self.plugin_dir.glob("*/plugin.yaml"): with open(manifest_path, "r", encoding="utf-8") as f: manifest = yaml.safe_load(f) module_path = manifest["entry"] module_name = f"plugins.{manifest['name']}.{Path(module_path).stem}" module = importlib.import_module(module_name) for tool in manifest["tools"]: func = getattr(module, tool["handler"]) self.registry.register(tool["name"], func, tool["schema"], tool.get("permission", "read")) class ModelAdapter: """模型适配器:统一模型接口,底层可替换""" def __init__(self, base_url: str, api_key: str, model: str): # 这里以 OpenAI 兼容接口为例,DeepSeek、通义等都能用这个方式接入 from openai import OpenAI self.client = OpenAI(base_url=base_url, api_key=api_key) self.model = model def chat(self, messages, **kwargs): return self.client.chat.completions.create(model=self.model, messages=messages, **kwargs) class Harness: """Agent 运行外壳:把插件、模型、会话状态组合在一起""" def __init__(self, config_path: str): with open(config_path, "r", encoding="utf-8") as f: self.config = yaml.safe_load(f) self.registry = ToolRegistry() self.plugin_loader = PluginLoader(self.registry, self.config["plugin_dir"]) self.plugin_loader.load_all() self.model = ModelAdapter( base_url=self.config["model"]["base_url"], api_key=self.config["model"]["api_key"], model=self.config["model"]["name"], ) self.session_state = {} def create_session(self, session_id: str): self.session_state[session_id] = {"history": [], "tool_logs": []} def get_state(self, session_id: str): return self.session_state.get(session_id)

核心在于你把“有哪些工具”和“怎么调模型”全部收口到 Harness 内部。上面这段代码看起来简单,但已经可以支撑一个最小可用的 Agent 外壳:加载 plugins 目录下的技能包,模型走统一适配层,会话状态集中管理。

配置文件大概长这样:

plugin_dir: ./plugins model: name: deepseek-chat base_url: https://api.deepseek.com api_key: ${DEEPSEEK_API_KEY}

2.4 DeepSeek 等模型接入 Harness 时的真实适配经验

现在国产大模型的 API 大多兼容 OpenAI 协议,所以 Harness 的适配层不需要为每家单独写客户端。只要把 base_url 换成对应的服务地址即可。

我在接 DeepSeek 时遇到过一个坑:它的敏感内容拦截策略和 OpenAI 不太一样,某些 Prompt 会触发空回复甚至报错。Harness 层必须捕获这种异常,返回给 Loop 层让模型换一种措辞重新组织答案,而不是直接把异常抛给用户。我把模型调用包装成下面的模式:

def safe_chat(self, messages, max_retries=3): for attempt in range(max_retries): try: resp = self.client.chat.completions.create(model=self.model, messages=messages) if not resp.choices or not resp.choices[0].message.content: raise ValueError("Empty response, may be blocked by safety policy") return resp.choices[0].message.content except Exception: # 退避重试,让上层 Loop 有时间调整 Prompt time.sleep(2 ** attempt) raise RuntimeError("Model chat failed after retries")

另一个经验是插件加载失败的容错。有段时间我经常看到类似“harness failed to load plugins web boot: 1 entry did not activate”的报错,查下来基本都是插件入口的加载时序问题:插件模块在 boot 阶段依赖了尚不存在的全局对象。解决方式是在 loader 里捕获单插件异常,记录错误后继续加载其他插件,不要让一个坏插件拖垮整个 Harness。

3. Loop 层:Agent 的神经回路,也是智能的关键引擎

3.1 ReAct 循环到底在转什么

Loop 层是很多人理解的“Agent 本体”,因为它对应的是 ReAct 模式的核心循环。这个循环可以概括为四步:模型根据当前状态产出下一步思考,然后决定调用某个工具;Harness 执行工具并返回结果;模型观察结果,更新认知;再次进入下一步思考。周而复始,直到达成目标或者触发终止条件。

这就类似视频处理里 ffmpeg 的 loop 滤镜,把一个片段反复循环播放。但 Agent 的 Loop 必须带退出机制,否则就成了死循环。所以 Loop engineering 的核心不是“让模型多转几圈”,而是“让模型在该停的时候停下来”。

3.2 Loop engineering 的核心参数与终止条件

我把 Loop 层最关键的设计参数整理成了表格,方便你对照自己的项目调整:

参数作用建议取值
max_steps单轮任务最大决策次数10~20,视任务复杂度而定
stop_condition用户定义的任务完成判定自定义函数或模型自评
context_compress_threshold触发上下文压缩的 token 阈值模型窗口的 50%~70%
retry_when_tool_error工具报错后是否允许模型换策略重试是,但最多 2~3 次
temperature模型决策采样温度工具调用场景 0~0.3

有个容易忽略的点:不要只靠模型自己判断“任务完成了”。模型的自评很容易产生幻觉,尤其在多步骤任务里,它会在中间某一步误以为目标已达成。所以我通常加一层规则校验,比如必须调用指定工具拿到最终结果后,循环才允许结束。

3.3 用伪代码写一个带反思的 Loop

下面是一个典型 Loop 骨架,加入了反思(Reflexion)机制:当任务失败或偏离方向时,让模型自己生成一段修正总结,下次迭代时带上这段总结重新决策。

# loop.py class AgentLoop: def __init__(self, harness, max_steps=15): self.harness = harness self.max_steps = max_steps self.history = [] def run(self, task: str, session_id: str) -> str: messages = [ {"role": "system", "content": "你是一个能调用外部工具的 Agent..."}, {"role": "user", "content": task}, ] reflection = "" for step in range(self.max_steps): # 把反思结果附加到上下文 if reflection: messages.append({"role": "user", "content": f"反思:{reflection}"}) response = self.harness.model.chat(messages) thought, action = self.parse_decision(response) self.history.append({"step": step, "thought": thought, "action": action}) if action.get("type") == "finish": return action.get("answer") if action.get("type") == "tool": try: observation = self.harness.registry.call( action["tool_name"], **action["args"] ) except Exception as e: observation = f"工具调用失败:{e}" reflection = f"上次调用 {action['tool_name']} 失败,原因是 {e}。请换一种方式。" messages.append({"role": "assistant", "content": response}) messages.append({"role": "tool", "content": str(observation)}) return self.force_summary(messages)

注意我在工具报错失败后,没有直接终止,而是记录 reflection 让模型下一轮修正。这招实测非常管用,很多第一轮参数传错的问题,第二轮模型就能自己纠正过来。

3.4 循环中的状态管理与上下文压缩

Loop 层最痛苦的工程问题是上下文爆炸。模型窗口有限,工具返回结果一长,几千 token 就没了。我的实践分三步解决:

第一步,每次工具调用结果只保留截断版本,比如 500 字以内,超出部分丢弃或写入外部存储。第二步,消息列表定期做压缩,把早期的对话改写为摘要。第三步,压缩后的摘要和最近的原始消息一起送进模型。

def compress_if_needed(self, messages, max_tokens=8000): total = sum(estimate_tokens(m["content"]) for m in messages) if total < max_tokens: return messages # 把最旧的一半消息合并为摘要 old_part = messages[: len(messages) // 2] recent_part = messages[len(messages) // 2:] summary = self.harness.model.chat([ {"role": "system", "content": "请用三句话概括以下对话的已完成信息和关键事实"}, {"role": "user", "content": json.dumps(old_part, ensure_ascii=False)}, ]) return [{"role": "system", "content": f"历史摘要:{summary}"}] + recent_part

这里有一个经验:摘要不能只记录“聊了什么”,要记录“已经完成哪些动作、得到哪些事实”。否则模型会忘记之前已经调用过工具,导致重复调用同一个查询。

4. Graph 层:Agent 的路径规划,调度一切的编排骨架

4.1 从单 Loop 到图编排,什么时候必须升级

单个 Loop 解决“做一件事”,Graph 解决“做一件事的流程里有很多步骤、分支、并行、人工审批”。我把必须上 Graph 的信号列一下:

  • 任务不是一次对话能完成的,包含多个独立阶段,比如“先收集信息,再分析,再生成报告”。
  • 存在条件分支,根据中间结果决定下一步走哪条路径。
  • 需要并行执行多个任务,比如同时搜索多个数据源。
  • 中间步骤需要人工确认,例如付款、发布、删除操作。
  • 需要多个专用 Agent 协作,而不是一个通用 Agent 包办所有事。

没到这些信号之前,我建议你先别引入 Graph 框架。因为图编排的抽象成本不低,简单任务强行上 Graph 反而是过度设计。

4.2 Graph 和 Loop、Harness 是怎么配合的

Graph 层不直接调模型、也不直接调工具,它只做一 件事:维护节点状态和流转。一个节点的内部逻辑,可以是一个 Loop(比如让 Agent 自主完成“资料调研”这个环节),也可以是一个单纯的 LLM 调用,甚至可以是一个人工审批页面。

Harness 在这些节点下面提供公共能力:模型接口、工具调用、上下文存储、权限控制。所以我常说 Graph 是“脑”,Loop 是“神经”,Harness 是“身体”。这个类比帮我和团队沟通时省了很多解释成本。

4.3 一个最小 Graph Engine 的骨架

市面上有 LangGraph、Coze 等工作流引擎,但如果只想理解原理,一个最小执行器就够了:

# graph.py class Node: def __init__(self, name, handler, next_map=None): self.name = name self.handler = handler # 接收 inputs 和 context,返回 (outputs, next_node) self.next_map = next_map or {} class Graph: def __init__(self): self.nodes = {} def add_node(self, node: Node): self.nodes[node.name] = node def run(self, entry_node: str, initial_inputs: dict): current = entry_node inputs = initial_inputs context = {} while current: node = self.nodes[current] outputs, next_node = node.handler(inputs, context) context[current] = outputs current = node.next_map.get(next_node, next_node) inputs = outputs if current == "END": return context return context

配合具体节点:

def collect_inputs(inputs, context): # 这里可以放一个 Loop Agent,也可以直接调模型 return {"collected": "原始信息..."}, "analyze" def analyze(inputs, context): return {"analysis": "分析结论..."}, "report" def report(inputs, context): result = f"报告:{context['collect_inputs']['collected']} / {inputs['analysis']}" return {"report": result}, "END"

实际生产里,每个节点的 handler 就是完整封装的一个执行单元,内部可以包含 Harness 调用和 Loop 循环。这样业务逻辑被拆成了零件,测试和维护都清晰很多。

4.4 Graph 四类高频模式与跨领域图思想的相通之处

我整理了自己项目里最常用的四类 Graph 模式:

顺序链:最简单,A 节点完成进 B 节点,适合流水线型任务。条件分支:根据判断节点输出,决定下一步走左侧还是右侧。并行扇出与汇聚:一个节点同时发起多个子任务,全部完成或部分完成后汇聚。人工审批:任务运行到特定节点时挂起,等待审批结果,通过后继续。

有意思的是,这种“局部子任务各自处理,再汇总到全局结论”的思路,和脑功能网络分析中的 local-to-global 思想很接近。脑科学的图模型也是先构建局部脑区子图,再整合成全局连接模式。Agent 的 Graph 编排同样遵循局部自治与全局协作的平衡。

另外可以提一下商图(quotient graph)概念:当你把一组内部结构复杂的重复子流程看作一个整体时,整个流程图就被大大简化了。我在做 Agent 流程回归测试时,会把“用户提问→信息检索→内容生成→反馈优化”这一整块收缩成一个抽象节点,然后用简化后的商图验证整体路径是否符合预期。这个思路特别适合大型流程图的可视化审查。

4.5 画图可以,别把图画成蜘蛛网

我见过最失败的一个 Graph 设计,节点有 40 多个,边连得密密麻麻,根本看不出主流程。后面我定了几条规矩:

  • 主路径控制在 5 个节点以内,复杂分支用子图包装。
  • 任何节点只能有一个明确的输入来源,避免多人同时拉数据导致状态混乱。
  • 每个节点必须定义超时和失败出口,不能死等一个子任务。

工具层面,如果你喜欢可视化建图,可以找支持 graph builder 的 Agent 编排工具,把节点拖拽出来生成配置。但记住图形化只是辅助,核心还是节点间的数据契约要清晰——每个节点输入什么、输出什么,必须在 Graph 定义里写清楚。

5. 生产实践:三层架构落地的全链路细节

5.1 可观测性:怎么知道 Agent 在干嘛

没做可观测性之前,生产出了问题只能看用户反馈猜。重构后我把可观测性按三层拆开:

  • Harness 层:记录每次模型请求的 token 数、用时、模型名、插件加载清单。
  • Loop 层:记录每一步的思考内容、决策动作、工具名和参数、工具返回摘要。
  • Graph 层:记录节点进入/退出时间、节点状态、分支走向、并行子任务结果。

用 trace_id 贯穿三层,一条用户请求从 Graph 节点到 Loop 循环再到 Harness 调用,全程可串联。我这里分享一个常用格式,把结构化日志写成 JSON 行,后续接 ELK 或 Loki 都方便:

{"trace_id": "abc123", "layer": "loop", "step": 3, "action": "tool_call", "tool": "weather_query", "args": {"city": "北京"}, "cost_tokens": 386}

会话回放也很关键。我会定期把会话状态做快照,存到对象存储。出问题时直接把快照灌回本地环境,复跑一次就能定位根因。这个方法省了我大量排查时间。

5.2 安全防线:工具权限、输出过滤与审计

Agent 的安全问题不能靠模型自觉,要靠在 Harness 层拦截。我实践下来至少有四道防线:

  • 工具白名单:Agent 能用的工具必须在 Harness 注册表里存在,且权限等级要匹配会话级别。
  • 参数校验:工具调用前校验参数格式和取值范围,防止模型生成越界参数。
  • 危险操作拦截:涉及删除、写入、支付的动作,强制走 Graph 的“人工审批”节点。
  • 输出过滤:模型返回内容过敏感词和策略过滤器,再展示给用户。

我踩过最痛的一个坑是:模型在一个内部工具里生成了删除数据的请求,幸好有白名单拦截,否则后果严重。从那以后我把“最小权限原则”写进 Harness 的代码评审规范里。

5.3 评估与回归:没有 eval 就不要上生产

很多人把 Agent 做完就上线,只测了几条路径。这种项目我基本可以断定会在线上出幺蛾子。一套可用的评估体系至少包含三类指标:

  • 任务成功率:最终结果是否满足用户预期,需要人工或裁判模型打分。
  • 过程效率:平均循环步数、工具调用次数、token 消耗,步数异常增多往往说明 Prompt 有歧义。
  • 鲁棒性:对输入变体、错别字、工具返回异常、网络超时的容忍度。

我习惯把评估用例分成三档:正常路径、边界路径、故障注入路径。故障注入就是故意让某个工具返回报错,看 Loop 层能否恢复。实测下来,能在故障注入测试中存活下来的 Agent,上线后稳定性会高很多。

5.4 性能优化:少花钱、少等几秒的具体手法

Agent 生产环境最大的成本往往是模型调用。我有几个省钱又省时间的经验:

  • 用模型路由:简单节点用小模型,复杂推理用大模型。DeepSeek 这类模型性价比高,可以作为默认主力,遇到特别复杂的推理再切换更强大的模型。
  • 工具结果缓存:相同参数的查询结果缓存十分钟,避免重复调用。
  • 并行节点并发执行:Graph 层里互不依赖的节点用 asyncio 并发跑,整体耗时能缩短一半。

另外提一句:LLM 输出 JSON 解析很容易出问题,哪怕让模型“只输出 JSON 不要其他内容”,它偶尔还是会带 Markdown 代码块。我建议在 Harness 层做一次 “JSON 容错解析器”,把 ```json 包裹、前后多余文本全部去掉再解析。我项目里因为这个解析器,工具调用的成功率从 78% 提到了 95%。

6. 问题排查实战:我踩过的七个坑

6.1 最典型的 Agent 故障现场

我把实际操作中反复踩过的七类问题列出来,每条都是真实生产案例。

死循环:Agent 在某个决策点上反复调用同一个工具,参数稍有变化但逻辑完全一样。原因是 stop_condition 没有覆盖“重复尝试无进展”的情况。我最后加了一个“连续同动作次数”计数器,超过 3 次就直接强制终止并向上报告。

JSON 解析失败:模型生成的工具调用参数不合法,常见于参数里出现单引号、换行、注释。解决办法就是上文说的 JSON 容错解析器,以及 Prompt 里给出严格的参数示例。

上下文爆掉:长会话运行到一半,token 数超了模型窗口。靠压缩机制减少了近 60% 的异常中断。

工具幻觉:模型调用了一个实际上不存在的工具名。原因是注册表里的工具描述和用户 Prompt 的意图模糊地带太多。我在 Prompt 里显式列出工具清单,并加了一条工具选择规则:“如果没有匹配工具,直接告诉用户能力边界”。

Graph 节点超时:某个子 Agent 卡了很久不返回,导致整个图挂起。后来每个节点都配超时时间,超时后走失败分支或返回哨兵值。

插件热加载状态丢失:更新插件后,新版本函数使用的内存缓存全部丢失,用户感知就是“功能偶尔失效”。我把插件状态迁移到独立的 Redis 或文件存储,插件只读状态,不再持有内存缓存。

评估通过但生产翻车:测试集覆盖不到真实用户的长尾输入。现在我会每个月拿线上日志的失败样例回灌到评估集,保持测试库跟着生产一起进化。

6.2 快速定位问题的方法与兜底策略

排查 Agent 问题你别一上来就改代码。我的标准流程是先看链路日志,确认问题出在哪一层:如果 Harness 日志显示模型调用失败,那就是适配或网络问题;如果 Loop 日志显示反复调用同一工具,那就是决策逻辑问题;如果 Graph 日志显示某个节点没走完,那就是流程编排问题。

定位到层之后,再借助会话回放复现。我强烈建议所有 Agent 项目上线前都搭一个“回放调试”工具,把线上 trace 文件重放到本地沙箱,比断点调试高效得多。

6.3 附:常见故障速查表

现象可能原因快速排查最终方案
Agent 反复调用同一工具循环终止条件不完善查看 Loop 日志中动作序列增加重复动作计数器
工具参数解析失败LLM 输出 JSON 不干净查看原始模型输出Harness 层做 JSON 容错解析
上下文超限工具结果未截断查看历史消息 token 统计压缩机制+结果截断
调用了不存在的工具工具描述与需求意图模糊查看工具名和注册表显式工具清单+选择规则
图节点长时间无响应子 Agent 或工具未设超时查看 Graph 节点耗时节点超时+失败分支
插件更新后状态异常插件内部状态在内存中查看进程是否重启状态外部化存储
线上 badcase 持续出现评估集覆盖不全回放失败样例每月回灌线上日志到评估集
数据序列化报循环引用Graph 节点状态里引用了父节点对象查看异常堆栈中的序列化函数只存顶层字段,避免引用整个 context

还有一个不太起眼但很常见的坑:上下文压缩时,如果把“已经完成的动作”摘要丢掉了,模型会在后续步骤里重复执行。我的经验是压缩摘要必须包含三类信息:已获取的关键事实、已调用的工具及结果概要、当前任务进度的判断。不满足这三项的摘要,宁可多留一些原始文本,也不要压缩成一句干巴巴的“用户问过天气”。

结尾

这套 Harness、Loop、Graph 三层架构我实践下来,最大的体会是:它并没有增加多少代码量,却让项目的认知负担降了一个量级。以前改一个功能,要在几千行单体代码里找位置;现在只需要明确改动落在哪一层,然后动手改那一个模块。插件、模型、工具、流程都变成了可以独立替换的零件。

如果你现在正被单体 Agent 代码的复杂度折磨,我建议你先别急着换框架,打开你的主文件,把模型调用、工具定义、流程控制分别抽到三个目录里。不需要一次到位,先抽出边界,再慢慢颗粒化。等你的项目开始出现“加一个工具要改三处代码”或者“一个节点不稳,整条流程都挂”这类信号时,三层架构就是你下一步应该走的方向。

最后再分享一个小技巧:每次给 Agent 加新能力,都先在 Harness 层写一个对应的故障注入测试——故意让工具返回错误,看 Loop 能不能自救。这比任何架构理论都更能帮你提前发现问题。

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

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

立即咨询