1. 先搞清楚:为什么我现在看 Agent 项目,习惯拆成三层
这几年 Agent 相关的项目我接触了非常多,从最早用 Prompt 硬堆的 Demo,到后来带工具调用、带记忆、带多智能体协作的生产级系统,踩过的坑绕起来能绕地球半圈。坦白说,大部分 Agent 项目翻车,都不是模型能力不够,而是工程边界没划清楚。你让一个 Agent 又管对话、又管工具执行、又管任务编排、又管失败重试,全塞在一坨代码里,初期确实跑得欢,一旦上点规模就会各种妖蛾子:一个工具超时把整个对话拖死,一个循环判断失误让 Agent 无限自我对话,一个上下文被塞爆导致模型开始胡言乱语。
后来我逐渐形成一套自己的分析框架,就是标题里写的这三层:Harness(执行容器)、Loop(循环驱动)、Graph(任务编排)。这个分法不是从某篇论文里抄来的,而是我在实际排查问题、重构系统时反复摸索出来的。这三层对应的是三个截然不同的问题域:Harness 解决的是"Agent 在什么环境里跑、能碰什么、不能碰什么",Loop 解决的是"Agent 怎么自我驱动、怎么决定下一步做什么",Graph 解决的是"多个步骤、多个分支、多个 Agent 怎么被组织成一条可控的流水线"。
把这三层分开看,最大的好处是定位问题快。比如某个 Agent 在执行工具时权限报错,那这是 Harness 的配置问题;比如 Agent 在一个问题上反复打转出不来,那这是 Loop 的终止条件设计问题;比如多步骤任务里前置节点失败导致后续全部乱套,那这是 Graph 的容错编排问题。分层之后,每一层都可以独立测试、独立优化、独立替换实现,这比"整个 Agent 一起调"要高效太多。
这篇文章我会结合我自己的实战经验,把每一层是什么、解决什么问题、有哪些关键设计决策、生产环境里容易踩哪些坑,全部掰开揉碎讲一遍。内容不追求面面俱到,但求每个点都对得起"生产实践"四个字。
2. Harness:Agent 的执行容器,决定了它"能做什么、能碰什么"
2.1 Harness 到底是什么
Harness 这个词,直译是"挽具",就是套在马身上用来连接马车的那套装备。在 Agent 工程里,它指的是包裹在 Agent 核心逻辑外面的一整套执行环境与能力边界,包括模型接口的调用方式、可用工具的注册与封装、环境变量的注入、上下文窗口的管理、安全策略的约束,以及日志和审计的采集。
我经常跟团队里的同学打一个比方:模型本身就像一个能力很强但毫无执照的实习生,你给他一台电脑他什么都能干,但你不能真让他随便碰生产服务器。Harness 就是给这个实习生配的办公位——桌上有哪几台机器、能登录哪些系统、能调用哪些内部平台、操作需要什么审批,全部由办公位这个"环境"来决定。同样的模型,放在不同的 Harness 里,表现出来的能力边界完全不同。
这也是为什么现在社区里讨论"DeepSeek Harness"这类项目时,很多人会产生困惑。他们以为 Harness 是某个具体的工具或插件,其实它更像是一套把 Agent 封装进可管控执行环境的工程范式。DeepSeek 系模型被频繁拿来讨论,是因为它的开源权重让很多团队可以本地化部署,而本地化部署之后,你反而更需要一个正经的 Harness 来管理它——模型文件放哪、推理服务怎么起、工具脚本怎么注入、外部 API Key 怎么隔离,这些都是 Harness 的活。
2.2 工具封装:统一的执行接口是底线
Harness 层最核心的工程产物,是工具注册表(Tool Registry)。不管你的 Agent 是要查数据库、调内部 API、执行 Shell 命令还是操作浏览器,所有能力都应该以"工具"的形式注册进来,并且暴露给模型的是一套统一的 JSON Schema。这样做有三个明确的好处:
第一,模型调用工具的格式是确定的。你可以让模型输出一个结构化的调用请求,而不是自由文本。比如:
{ "tool": "database_query", "params": { "sql": "SELECT * FROM orders WHERE status = 'pending' LIMIT 10", "timeout": 5000 } }第二,你可以在 Harness 层做统一的校验、鉴权和审计。所有工具请求都会经过一个统一的入口,你可以在这里检查参数合法不合法、调用者有权限没权限、频率超没超限,然后再分发到具体的执行器。这一层是安全策略落地的最佳位置。
第三,后续加新能力非常方便。团队里任何一个人开发了新工具,只要按工具注册表的规范写一个描述文件,注册进去,Agent 立刻就能用,不需要改 Agent 核心逻辑。
我自己在项目里通常会给每个工具写三样东西:功能描述(给模型看,决定什么时候用它)、参数 Schema(给模型看,决定怎么调它)、执行配置(给 Harness 看,决定怎么跑它,包括超时时间、重试策略、运行环境)。这三样东西缺一不可,描述写得不清楚,模型就会在工具选择上犯傻;参数 Schema 设计得不好,模型就会传错参数;执行配置没有超时保护,一个卡死的工具调用就能毁掉整轮对话。
2.3 环境隔离与安全边界:Harness 的底线工程
Harness 的另一半职责,是把 Agent 关进一个可控的笼子里。这不是限制 Agent 的能力,而是防止它在不可控的情况下造成不可逆的后果。
我见过很多团队做的 Agent Demo,让模型直接执行 Shell 命令,rm -rf这种危险操作也没有任何拦截,看着很酷,但稍微想想就知道这在生产环境里根本没法用。一个专业的 Harness 至少应该具备这几层安全机制:
- 执行环境隔离:工具执行最好跑在独立的容器、沙箱或子进程中,即使工具本身有 bug 或者被恶意利用,也不会拖垮核心服务。我自己倾向于用进程级隔离加资源限制,比如限制内存上限、CPU 配额、网络访问范围。
- 权限最小化:给 Agent 的 API Key、数据库账号、文件系统权限,都应该按"这次任务需要的最小范围"来授予。不要给一个只需要查订单状态的 Agent 配上能删库的权限。
- 操作审批门:对于高风险操作(删除、覆盖、批量修改、支付等),Harness 层应当支持"需要人工审批才能放行"的机制。模型可以提出请求,但真正执行要等审批通过。
这一块在实践里特别容易翻车。举例来说,我们曾经给一个 Agent 配了操作内部 Wiki 的能力,本来只想让它读取内容辅助回答,结果工具描述里写得模糊,模型开始调用更新接口修改 Wiki 页面,差点把一整个知识库改乱了。后来我们在 Harness 层把工具分成只读和可写两类,并且强制对可写工具加了"二次确认"逻辑,这个问题才彻底解决。
2.4 Harness 的选型:自研还是用框架
现在社区里讨论 DeepSeek Harness、Harness Anything 这类项目时,总有人纠结要不要自研 Harness。我的观点很明确:早期项目别自研,直接站在成熟实现之上做裁剪。
一个可用的 Harness 看起来简单,但工程细节非常多——工具调用怎么流式返回、上下文窗口怎么管理、日志怎么结构化、错误怎么分类重试,这些表面看不出来、遇到才知道疼的破事,成熟框架早就替你趟平了。优先选一个社区活跃、有真实用户在生产环境使用的框架,然后把它裁剪到适合你的场景。
需要裁剪的部分通常集中在三个方向:去掉你用不到的抽象(比如你只需要单 Agent,就不用引入复杂的多智能体通信层);补上你特有的工具(比如你们内部的监控系统、工单平台、知识库 API);调整安全策略(比如审批流、敏感操作拦截规则、数据脱敏逻辑)。这样既不会从零开始踩坑,也不会被框架的重量压垮。
3. Loop:Agent 的心脏,自我驱动的循环机制
3.1 从 ReAct 到 Agentic Loop:让 Agent 学会"自己推自己"
如果说 Harness 是 Agent 的骨架和边界,那Loop(循环)就是 Agent 的心脏和发动机。Agent 和普通 ChatBot 最大的区别就在于它有一个"自己驱动自己"的循环:模型输出一个决策,系统执行这个决策,观察结果,把结果喂回模型,模型再输出下一个决策,一直循环到任务完成为止。
这个模式最早可以追溯到 ReAct 那个经典范式(推理 + 行动交替进行),但在生产级 Agent 里,Loop 的复杂度要远远超过 ReAct 论文里的玩具示例。真实的 Agentic Loop 至少包含这么几个环节:
- 推理/规划:模型基于当前状态和最终目标,决定下一步要做什么。
- 行动:调用一个工具、询问用户、或者输出一段内容。
- 观察:获取行动的结果(工具返回值、错误信息、用户反馈)。
- 状态更新:把新信息写入上下文或记忆系统。
- 循环判断:决定继续循环还是终止。
这个循环看似简单,实现起来却处处是坑。最简单的实现方式,就是在代码里写一个while循环,每次迭代把"历史对话 + 工具结果"拼进 Prompt,发给模型,拿回结果,判断要不要继续。这个方式原型阶段够用,但到生产环境就有很多问题需要认真处理。
3.2 循环失控:死循环、自我对话和上下文爆炸
Loop 层翻车率最高的几个问题,我一个个说。
死循环是最经典的:Agent 在同一件失败的事情上反复尝试,每次都返回同样的错误,但它偏不放弃,也不向用户求助,就这么一直打转到 token 耗尽。产生死循环的根本原因,通常是模型缺少对"重复失败"的敏感度,或者循环终止条件太弱。解决思路通常有几种:设置最大迭代次数(比如 20 轮,超过就强制终止);设置"连续相同错误"检测(同一个工具、同一个错误超过 N 次就切换策略);在 Prompt 里明确给模型说"如果同一件事失败两次,停止当前方案,换一种思路或向用户求助"。
自我对话失控是我在带多智能体系统时踩过的大坑。你可以让两个 Agent 互相"沟通"来协作解决问题,但如果它们之间的消息传递没有边界约束,它们就能无限地你来我往,每一轮都在客气地问候、确认、复述,就是不出实质结果。这个问题本质上还是 Loop 的终止条件没设计好——你应该在架构上就约定好:一次协作最多几个来回,超过这个数就必须由仲裁者介入。
上下文爆炸是 Loop 层最隐蔽的杀手。每一轮循环都会产生新的中间结果,你把它们全部丢回上下文中,很快窗口就满了。窗口满了以后,模型的注意力开始涣散,早期的重要指令被"挤"出有效范围,Agent 的行为会变得极其不可预测。解决思路是给 Loop 加一道"记忆管理"层:区分短期上下文(当前任务正在使用的信息)和长期记忆(重要结论、已完成步骤、用户偏好),长期记忆可以存到外部向量库或结构化存储,只在需要时检索召回。
我这里有一个实战案例。某个 Agent 做数据分析任务,每轮循环会把查询结果全量塞回上下文,结果做到第 7 步时,模型开始忘掉最初的分析目标,生成了一大段和原始问题无关的洞见。排查下来发现,最初的用户指令已经被几千行中间结果挤到了上下文边缘。后来我们加了两道措施:一是在每次循环时把原始目标重新压制到系统 Prompt 的最前部;二是对中间结果做摘要,而不是全量保留。问题立刻缓解。
3.3 循环的可观测性:让 Loop 变成可诊断的
生产环境的 Loop 必须做到"每一步都可以被追踪"。我强烈建议在 Loop 的每轮迭代里记录这么几个字段:迭代序号、输入摘要、模型决策、工具调用、工具结果摘要、当前置信度、是否异常。这些日志不仅是为了排障,更是为了分析 Agent 的行为模式——哪个环节耗时最长,哪个工具最容易失败,哪个 Prompt 导致模型反复横跳。
我自己常用的做法,是把每一轮 Loop 的决策和观察,用结构化的方式落一条日志:
{ "loop_id": "loop_8f3a2", "iteration": 4, "agent_id": "order_query_agent", "thought": "用户想查最近三个月的退货单,我先确认订单状态字段的含义", "action": "tool_call", "tool": "metadata_lookup", "tool_input": {"table": "orders", "field": "status"}, "observation_summary": "status 字段有三个枚举值:pending/completed/refunded", "next_plan": "用 refunded 状态过滤最近三个月数据" }有了这种结构化日志,你再去看一个 Agent 为什么表现不佳,就是在看一份清醒的行为记录,而不是靠猜。配合可视化界面把 Loop 的轨迹渲染出来(这就是后面要讲的 Graph 的雏形),排查效率会提升一个数量级。
4. Graph:把循环变成流水线,把混乱变成可控
4.1 为什么要 Graph:单循环撑不起复杂任务
单个 Loop 能解决"一个 Agent 自主完成一件事",但真实业务里,任务往往不是单线到底的:可能要分多个阶段,某些阶段要并行,某些阶段要等待用户确认,某些阶段要回退重试。用一个大循环把所有事情串下来,不是不行,但你会面临两个很现实的痛苦:
一是Prompt 越来越长。所有阶段的指令、知识、规则都塞进一个系统 Prompt 里,模型在长上下文里的表现会越来越不稳定,而且每次迭代都在重复消费这些信息,成本高得吓人。
二是维度灾难。多阶段任务的中间状态非常多,如果全靠模型自己管理,你几乎没有办法精确控制它"现在到底在哪个阶段"。你告诉它"先做 A 再做 B 再分情况做 C 或 D",它照着做了一轮,结果发现 A 的结果不符合预期,需要回到 A 重新做,这个回退逻辑在大循环里极难表达。
Graph 就是来解决这两个问题的。Graph 把任务拆成节点(Node)和边(Edge),每个节点代表一个工作单元(可以是单独的 Prompt 调用、工具执行、Agent 循环,甚至是一个人审批),边代表状态转移和依赖关系。有了这张图,你就从"把一切都交给模型临场发挥",转向了"把结构化的流程定义好,让模型在边界内发挥"。后者才是生产级 Agent 的常态。
4.2 常见的节点类型与编排模式
我做了这么多 Graph 编排的实践,发现无论业务花样怎么变,常用的节点类型就那么几种:
- LLM 节点:单纯的模型调用,比如写摘要、做分类、生成回答。
- 工具节点:调用外部工具或服务,比如查库存、发消息、写文件。
- Agent 节点:把一个完整的"子 Loop"封装在节点里。这个子 Loop 可以有自己的 Harness、自己的工具集、自己的上下窗口,跑完以后只向父级返回一个结构化结果。这是 Graph 和 Loop 交互的关键接口。
- 条件分支节点:根据前置节点的结果,决定走哪条边。
- 并行节点:同时触发多个子节点,等全部完成或部分完成后汇合继续。
- 人工审核节点:把任务挂起,等人在 UI 上确认或修改后再继续。
- 重试/回退节点:定义错误处理策略,比如失败后重试、降级、回退到前置步骤。
编排模式上,我见得最多的是这么几种:
链式编排:一个节点接一个节点,前一个输出是后一个的输入。适合流程相对固定的场景,比如"接收查询 -> 语义解析 -> 查数据库 -> 生成报表 -> 发送邮件"。它的优点是简单、可控、好排查,缺点是灵活性不足,遇到分支就只能靠模型在单节点内部做判断。
路由编排:一个"路由"节点先判断任务的类型,然后分发到不同的子流程。比如客服场景里,先判断是退款问题、物流问题还是商品咨询,然后分别走各自的处理 Graph。
子图复用:把一些通用的子流程提出来做成子图,主图通过引用子图来复用。比如所有 Agent 任务都需要的"用户信息校验"子流程,抽出来以后,每个主图都在相应位置挂接它。
动态扩展:就是在 Graph 里留出"动态节点"的插槽,Agent 运行时发现任务需要额外步骤,可以动态往插槽里塞一个子图。这个模式最灵活,但也最难控制,通常需要有严格的验证和沙箱机制。
4.3 状态管理与可恢复性:Graph 生产化的关键
Graph 层比 Loop 层更重视"状态"这个概念。Loop 的状态是隐式的(靠上下文),Graph 的状态应该是显式的——你能清晰地回答出:当前在执行哪个节点?前置节点的输出是什么?变量怎么引用的?事件发生了什么?
我推荐的做法是给 Graph 引入一个全局状态存储,每个节点从里面读输入,向里面写输出。状态存储可以用内存(简单场景)、Redis(跨实例共享)、或者数据库(持久化场景)。状态结构可以参考这样设计:
{ "graph_id": "report_flow_v3", "run_id": "run_20250218_001", "current_node": "db_query", "status": "running", "variables": { "user_query": "...", "parsed_intent": "query_sales_report", "db_result": "...", "report_file_path": "" }, "history": [ {"node": "intent_parsing", "status": "success", "duration_ms": 320}, {"node": "db_query", "status": "running", "started_at": "..."} ] }有了显式状态,你就拥有了可恢复性。某个节点崩溃了,可以从持久化状态里恢复,跳过已完成节点,重新执行失败节点,而不是整个任务从头再来。这个在生产环境里极其值钱——一个跑了 10 分钟、已经调用过很多外部 API 的流程,如果因为一次网络抖动就要全盘重跑,成本是不可接受的。
另外还有一点,Graph 的边应该被设计成显式条件,而不是藏在代码逻辑里的隐式流转。也就是说,你要能清楚地说出"A 节点完成后,什么条件下走 B、什么条件下走 C"。这样整张图的执行路径是可枚举的、可测试的。我在实践中甚至会给关键边写单元测试,用模拟数据验证条件分支是不是按预期走。
5. 三层架构的协同:一次完整的生产实践复盘
5.1 三层如何配合:一个典型的协作模型
把 Harness、Loop、Graph 三层放在一张图里看,它们的协作关系是这样:Graph 是总导演,把一个大任务拆成一场多幕剧;每一幕戏里,可能是一个 Loop 在驱动某个 Agent 演戏;而 Agent 所有的动作,都必须发生在 Harness 搭建的舞台和道具间里。
从一个具体例子来看更直观。假设我们要做一个"智能日报生成系统":每天自动汇总多个数据源,生成一份业务日报,并推送到团队群。
Graph 层的设计:主图分为"数据采集 -> 指标计算 -> 日报撰写 -> 审核推送"四个阶段。数据采集阶段拆成三个并行子任务(订单数据、流量数据、客户反馈数据),全部完成后汇合进入指标计算。指标计算是一个工具节点,调用数据分析服务。日报撰写阶段是一个 Agent 节点,这个节点内部有一个完整的 Loop。审核推送阶段是一个人工审核节点,审核通过后调用推送工具。
Harness 层的设计:为日报撰写这个 Agent 准备一个独立的 Harness,注册了"查询指标数据"和"查询历史日报"两个只读工具,同时注入了一个专门的日报写作 Prompt 模板作为它自己的系统指令。为了防止它乱写,把所有可写工具全部从 Harness 里移除,并且所有工具调用都走统一的审计日志。
Loop 层的设计:日报撰写 Agent 的循环逻辑是:先读取指标数据 -> 撰写初稿 -> 自我检查(对照写作规范检查是否遗漏关键指标) -> 修改 -> 输出最终稿 -> 判断是否达到质量标准 -> 达到则结束循环,未达到则最多迭代 3 次,3 次后直接以最好的版本输出。
这样一个三层协作的设计,每一层都职责清晰,任何一层出问题都可以单独排查,不会互相污染。Graph 出了问题,检查节点状态流转;Loop 出了问题,去看迭代日志;Harness 出了问题,去查工具注册和权限配置。
5.2 扛住并发:Agent 系统生产化的试金石
热搜词里有"ai agent 怎么扛并发",这确实是一个很多人都会撞上的实际瓶颈。Agent 系统的并发和普通 API 服务的并发完全不同——普通 API 一个请求大概几毫秒到几百毫秒,Agent 一个请求可能持续几十秒甚至几分钟,而且期间要调用多次模型接口、多次工具接口。这意味着你不能用传统的"一个请求对应一个线程"的思路来设计。
我的经验是有几个关键决策点:
把 Agent 执行与请求响应解耦。用一个任务队列(比如 Redis Stream 或者消息队列)接收请求,由 Worker 池消费队列执行 Agent 逻辑,执行完把结果写入结果存储,再由前端轮询或长连接把结果返回给调用方。这样请求方不会被一个漫长的 Agent 任务阻塞住,整个系统也能更好地横向扩容。
模型调用的并发控制。Agent 任务的绝大多数耗时都在模型调用上。要做好限流和排队,避免突发流量把模型服务的负载打爆。我的做法是在 Harness 层统一做模型调用代理,代理层负责令牌桶限流、超时控制、失败重试、以及不同模型之间的路由切换。
无状态化与外部状态管理。Agent 执行过程中产生的上下文和状态,绝对不能只存在进程内存里,否则实例一重启或者流量被调度到别的 Worker,任务就断了。把 Loop 状态、Graph 状态都持久化到 Redis 或数据库,Worker 实例本身就是无状态的,这样扩容缩容都毫无压力。
我遇到过的一个真实血泪案例:早期系统把 Agent 上下文存在内存里,某一天业务量突增,我们高高兴兴加了几台机器做负载均衡,结果大量运行到一半的 Agent 任务直接断掉,用户体验瞬间崩盘。后来把所有状态全部外置,这个问题才算根治。
5.3 三层架构的性能与调优思路
三层架构每层的性能瓶颈和解法方向都不一样。Harness 层瓶颈通常在工具执行的 IO 延迟上,解法方向是工具结果缓存、恰当地并发执行多个工具、给关键工具做结果压缩。Loop 层瓶颈通常在模型调用次数和上下文长度上,解法方向是精简每轮 Prompt、用优秀的摘要压缩历史、那步能确定完成的就也别硬让模型做无用推理。Graph 层瓶颈通常在节点的串行等待上,解法方向是拆并行、做预测性预取(比如某个分支大概率要走,提前开始该分支的一些可并行准备动作)。
我给团队定的调优原则很简单:先用 Profile 找出耗时分布,再去针对 TOP 耗时环节做优化,不要凭感觉优化。Agent 系统比普通服务复杂,牵一发动全身,没有数据支撑的优化往往是在瞎忙。
6. 生产实践中常见的坑与排障方法
6.1 Harness 层的典型问题
现象一:工具描述与实现不一致。模型按照工具描述调用了工具,结果工具的实现在某些边界条件下行为异常。比如描述里说"查询商品信息",参数是商品 ID,但某类特殊 ID 会让工具直接抛异常。排障思路是先看 Harness 层记录的完整工具输入输出日志,确认是哪一步出现了偏差,然后修实现或者改描述。
现象二:API Key 或环境变量泄漏。Harness 环境隔离没做好,Agent 的日志里打印了包含密钥的请求头。这个问题必须从架构上杜绝:密钥只通过环境变量注入到执行进程,日志系统在处理请求体、响应体时统一做脱敏逻辑,尤其是 Authorization 头和包含敏感字段的 JSON 内容。
现象三:上下文窗口溢出。这个最常见,具体表现是突然某次调用报 max token 错误,或者发现 token 消耗激增。排障要看上下文里到底是什么在膨胀——是历史消息太多?是工具结果没压缩?还是 Prompt 模板里有内容爆炸的结构。解决方案在之前的记忆管理部分讲过:摘要压缩、过期清理、按需检索。
6.2 Loop 层的典型问题
现象一:Agent 陷入了重复尝试的怪圈。日志里能看到同一个工具被用相同参数调用好多次,每次结果都一样,但 Agent 还在继续。治理方法是在 Loop 的循环控制里加上"重复失败检测",达到阈值后强制中断,并且在 Prompt 里给模型明确指令做失败后的策略切换。
现象二:Agent 的输出质量和迭代次数成反比。有些任务让 Agent 反复自我反思、自我修复,结果越改越烂。我遇到过让 Agent 写代码的场景,初版通过率其实很高,但你让它"再检查检查、优化优化",它反而开始把正确的代码改出 bug。处理思路:为自我优化设置明确的验收标准,同时限制最大迭代轮数,避免无休止打磨。轮数到了就取历史上评分最高的版本,而不是取最后输出的版本。
现象三:模型开始输出与任务无关的内容。上下文漂移导致模型忘了自己到底要干什么。排障的方法是检查系统提示词是否被淹没在大量的中间结果里,需要把核心指令固定在每个循环的最前面,或者把长期目标做成一个独立的状态字段,在每轮循环中动态注入。
6.3 Graph 层的典型问题
现象一:并行节点的竞态冲突。多个并行子任务同时写同一个状态变量,后写覆盖先写。这个问题在早期阶段极难发现,因为大多数时候没问题,直到某个任务恰好同时执行了两个写入逻辑。解决思路是变量命名空间化,每个节点只能写自己专属的变量区,跨节点传递信息必须走显式的"消息管道"。
现象二:回退流程设计不当。某个节点失败后简单回退到前置节点,但前置节点的输出状态并没有正确恢复,重新执行时产生脏数据。排障思路是把回退也设计成显式的节点,回退时先执行"状态恢复"逻辑,再进入重跑。
现象三:Graph 执行链路过深导致观测困难。节点多了以后,一张图嵌套一张图,日志被切得稀碎。我推荐的做法是全链路追踪,每次 Graph 运行生成一个 trace_id,节点、工具调用、模型调用都带着这个 trace_id 上报,排障时用 trace_id 把所有关联日志拉出来组成完整的执行时间线。
6.4 几个很实用的排查工具与手段
这里分享几个我日常排查 Agent 问题的高频手段。第一,重放日志:把一个出问题的 trace 完整记录成结构化事件流,修复代码后可以把事件流重放到修复版本里验证是否还复现。第二,Prompt 快照:模型每次调用的实际 Prompt(包含系统指令、历史、工具结果)都存一份,遇到模型表现诡异时,直接看快照定位是哪部分内容干扰了判断。第三,影子模式:新版本的 Agent 逻辑先不直接替换线上版本,而是让它跟线上版本并行跑,只在内部记录它的输出和决策,对比差异后再决定是否切换。这三个手段的组合,能帮你把大多数玄学问题变成可解释、可验证的工程问题。
7. 再聊一点个人的体会
我带过的每一个 Agent 项目,最后都会在"能跑"和"能稳定跑"之间找到一条分界线。分界线这边的项目,基本都遵循了这套三层视角:Harness 管边界、Loop 管驱动、Graph 管编排。分界线那边的项目,往往都是把这三样东西揉在一团,看起来很灵活,真正出问题的时候根本无从下手。
如果你现在正要开始一个新 Agent 项目,我的建议是先把这三层在文档里画清楚——哪怕每层都非常简单,也要明确地分出来。简单但清晰的边界,比复杂但混沌的实现有价值得多。等系统成长了,你会发现这三层就像是三个单独的房间,你可以分别装修、分别杀虫、分别换家具,而不用为了改一个工具配置就把整个房子拆了重建。
最后再送一条小技巧:给 Agent 系统加日志的时候,别光想着"记录错误",要把模型的决策轨迹也一并记录下来。很多 Agent 的问题不是"执行错",而是"决策错"。只记录执行结果、不记录决策依据,你永远不知道模型为什么走了一条错路。把决策轨迹留好,你的 Agent 系统才真正具备了"可以被复盘、可以被改进"的可能。