ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Agent工程三层架构:Harness、Loop与Graph的生产实践

Agent工程三层架构:Harness、Loop与Graph的生产实践 三个词这几年在 Agent 工程圈里被反复提起Harness、Loop、Graph。我第一次听到 Harness 这个词的时候以为是测试领域的“测试夹具”后来发现它在 Agent 工程里的含义完全不同。再后来当我把一个接一个的 Agent 原型推向生产环境时才真正意识到这三个概念不是三种技术选型而是三层叠加的架构模式。这篇文章我从三个层面拆解Harness 是 Agent 的“外壳与边界”Loop 是 Agent 每次决策的“循环内核”Graph 是把多个循环编排成生产流程的“连接骨架”。看完你至少能回答三个问题Agent 和 Harness 到底怎么区分循环设计为什么直接决定体验以及三层架构在生产环境里最容易踩哪些坑。我会按我实际做项目的顺序来写先讲为什么需要把这三层拆开再逐层展开核心实现最后给生产实践和问题排查。不是教材口吻是我自己反复踩过坑之后留下的经验。1. 三层架构的来龙去脉为什么 Harness、Loop、Graph 必须拆开1.1 早期 Agent 项目为什么总是“一坨”很多人开始写 Agent 的时候思路非常简单一个while True循环里面让大模型生成一段文本正则抽出来执行再喂回去。简单场景确实能跑但一旦遇到工具变多、分支变复杂、并发变高的情况这种写法就变成了一堆 if else 和全局变量。我自己最早的一个内部工具就是这样。最开始只有两个工具等到第七个工具加进去的时候流程已经完全不可控了——有的分支要求顺序执行有的分支需要并行有的分支失败后要重试。如果不把“Agent 循环”和“Agent 外层控制逻辑”分离你其实是在把编排逻辑硬编码进提示词里这等于让模型替你管理状态机结果必然是不稳定。真正让我开始反思的是一次生产事故。某个工具返回了异常格式模型误判成“任务已完成”整个流程提前结束。问题不在模型而在于我的循环里没有前置条件检查、没有失败回退、也没有对外部环境的隔离。缺的正是三层架构中的两层Loop 没做好状态管理Harness 没做好边界控制。1.2 三层各自解决的是哪一类问题Harness、Loop、Graph 不是并列的三个组件而是三个抽象层级各自对应不同维度的职责Harness解决“Agent 用什么环境运行、能接触哪些东西、可以被谁调用”。它相当于 Agent 的操作系统外壳包含工具注册表、上下文构建、权限控制、模型接入、沙箱隔离、插件加载。Loop解决“在一次任务之内Agent 如何反复决策”。它负责思考、行动、观察、再思考的闭环同时管理步数上限、记忆窗口、重试机制。Graph解决“多个任务之间如何编排甚至多个 Agent 如何协作”。它把一次 Loop 当作一个节点把节点之间的跳转条件、并行分支、汇聚逻辑以有向图的方式显式表达。这个分层最大的好处是每一层可以独立升级。我想换模型只需改 Harness 的模型适配器我想调整任务超时策略只需改 Loop 的参数我想让多个 Agent 协作只需在 Graph 层新增节点和边。三层混在一起写任何变更都牵一发动全身。1.3 面向场景的三层映射拿一个实际的智能客服场景来映射一下一个用户提交工单系统先做意图识别再选择流程再调用不同工具有返回然后决定是直接答复还是转人工。在这个例子中Harness 负责把知识库检索工具、工单系统 API、模型 SDK 全部注册好并且限制每个工具能拿到的数据范围。Loop 负责单次会话内的多轮推理——查知识库、比对用户问题、生成初步回答。Graph 负责流程编排——先分类再决定走知识库流程还是转人工流程如果转人工还要通知相关组。一个模块搞不定所有问题你需要的是把循环嵌套在图的节点里而循环又跑在 Harness 提供的环境里。这个理解是所有后续工程决策的基础。2. Harness 层深入Agent 的外壳、边界与能力接入2.1 Harness 到底是什么Harness 直译过来是“背带、挽具”但在 Agent 工程里它指的是把模型、工具、上下文、安全策略、记忆模块组装成一套可运行系统的“包装层”。你可以把它理解成船舱里的驾驶台——引擎模型在底下但船长Agent能用的所有开关、仪表、通信设备都通过驾驶台统一呈现。Harness 和 Agent 的区别是我被问得最多的一个问题。简单说Agent 是行为的定义者Harness 是行为的约束者和支持者。Agent 是那套“决策逻辑”它决定下一步调用哪个工具、怎么回答用户Harness 是那套“运行环境”它决定决策逻辑能做什么、不能做什么。举一个非常具体的例子。假设你写了一个函数def agent_loop(user_input: str) - str: while True: action llm_choose(available_actions) result execute_action(action) if action final_answer: return result这段代码是一个最原始的 Agent 循环。这个循环本身不包含任何“环境”信息它用哪个模型能访问哪些工具上下文窗口如何截断工具调用的权限边界是什么这些信息全部需要外层的人给它这个外层就是 Harness。工业级的 Harness 至少包含这几大模块工具注册表与工具 Schema 管理上下文组装器把系统提示、历史消息、工具描述、检索结果拼成完整上下文模型接入层统一接口、请求重试、率次控制权限与安全门控哪些工具在高风险操作时需要人工审批插件/技能系统动态加载领域能力提示判断一个框架是 Harness 还是单一 SDK就看一个标准它能不能让同一套 Agent 逻辑在不同模型、不同工具集、不同策略配置下切换而无需改动循环代码。能它就是 Harness不能它只是模型封装。2.2 技能与插件Harness 的扩展机制在实际项目中我是一个经验主义的人不喜欢把领域逻辑写死在系统提示词里。原因很简单提示词改动频率非常高而且没法做单元测试。更可靠的做法是把领域能力封装成“技能”在 Harness 层按需加载。技能本质上是一组“工具指令校验规则”的打包。举个例子一个“查库存”技能包含查询 API、参数校验 Schema、低库存处理建议。当任务涉及库存查询时Harness 动态注入这个技能的工具描述和调用范例任务不涉及时就完全不注入既省 token 又减少模型误调用。我实际用过的一个开源 Harness 项目它的技能目录是这样一个结构skills/ inventory_query/ manifest.yaml tools.py prompts/action.txt prompts/evaluate.txt这里的manifest.yaml声明技能的触发条件、所需权限、以及工具的参数 Schema。Harness 在每次构建上下文时会扫描技能目录把触发条件匹配的技能注入当前 prompt。这就是为什么“Harness 附带 skill 部署到内网服务器”这类问题会出现——因为你不仅仅要装 Harness 本体还要把技能目录完整移植过去并且保证技能引用的内部 API 地址在内网可达。部署这类系统到内网我总结了一个最简单的三步法把 Harness 项目和技能目录打成镜像或离线包。在内网搭建模型服务的兼容端点需要支持 OpenAI 或其他统一接口格式。通过环境变量重新指定工具调用的基础 URL确保技能里的 API 地址从公网切换到内网。真正复杂的不在部署动作本身而在配置管理。内网模型的上下文策略可能和公网不同有些模型不支持某些工具调用格式。所以一定要在部署前用技能自带的“冒烟测试”脚本跑一遍。2.3 Harness 的安全边界说到生产Harness 最大的价值其实是安全。没有 HarnessAgent 就是一把没装保险的枪——模型只要生成了一个工具调用系统就会执行。如果这个工具是“删除用户”“发送邮件”“转账”后果不言而喻。一个合格的 Harness 必须提供三类安全机制工具级权限不同角色、不同任务可调用的工具白名单不同。操作级审批高风险操作要有隔离的审批通道而不是把“确认”做成 Agent 自己的工具调用。数据脱敏在 Harness 把外部数据注入上下文前对敏感字段做掩码处理。我自己在项目里就吃过亏——Agent 在调试阶段能访问生产数据库有一次模型把一条 UPDATE 语句写成了全表更新幸好 DBA 盯着吓得所有人一身冷汗。后来我们在 Harness 里做了环境标记只有带PROD_ALLOWEDtrue的技能才允许触发写操作并且所有写操作都要经过一个额外的确认工具。这就是 Harness 在生产环境里真正的职责它不只是把模型包装起来而是把“模型的自由”约束在团队可控的范围里。2.4 开源 Harness 生态与选型思路“DeepSeek harness”之类的词在各大社区里越来越热说明已经有人在尝试给国产大模型搭建标准化的 Agent 运行外壳。实际上DeepSeek 本身可能并不自带某个官方 harness 项目但社区里确实有一批基于它的 harness 插件用于把 DeepSeek 的模型能力与多工具调度结合起来。从我的角度看真正值得关注的不是某个具体插件而是你所选 Harness 是否满足四个条件是否支持把模型接入层抽象成统一接口。是否支持技能目录热加载。是否支持权限门控和上下文策略可配置。是否支持插件失败时的隔离不至于让一个插件的崩溃拖垮整个 Agent 循环。技术选型上没有“最好”只有“合适”。团队已有代码用 Python就选 Python 生态的 Harness要极致的资源占用控制可以看看 Rust 编写的 Agent 框架但要注意 Rust 生态里可用的 Agent 工具链相对少一些你得接受自己写很多底层胶水代码。3. Loop 层深入循环内核与多窗口会话3.1 主流循环模式对比Loop 层是 Agent 的“心跳”。没有循环一次交互就只是单轮问答有了循环Agent 才能在“行动失败-重试-修正”的过程中逼近目标。主流的循环模式有三种ReActReason Act模型生成思考过程再生成下一步行动观察结果后继续思考。Plan-and-Execute先制定计划然后分步执行每步可以调用工具最后汇总。Reflection生成结果后让模型自己批判自己形成多轮自我修正。这三种模式不是互斥的复杂系统通常把它们嵌套。比如 Graph 层制定计划PlanGraph 内部每个节点是 ReAct 循环节点结束后做一次 Reflection 检查输出质量。我处理过最典型的一个失败案例是把 ReAct 循环直接用在一个需要精确多步计算的场景模型反复尝试每次都差一点循环因为步数上限强制结束最后给用户返回了一个未完成的结果。后来我把这个场景改成 Plan-and-Execute先在 Graph 层让模型输出一个严格的执行计划再按计划逐节点执行每步只做一件事准确率大幅提升。3.2 循环状态与多窗口会话的矛盾“loop 多窗口”这个词在搜索结果里出现频率很高。它指的是同一套 Loop 逻辑同时服务于多个会话窗口的场景比如一个客服 Agent 同时和一百个用户在对话每个用户都有自己的上下文历史。这里核心要解决的是状态隔离。循环是一个有状态的过程每一步都会更新上下文多窗口意味着这个状态必须按会话 ID 隔离不能让会话 A 的操作污染会话 B 的上下文。我用过一个非常轻量但有效的方案把每个会话窗口对应一个独立的上下文缓冲区和记忆栈循环开始时从该窗口加载状态循环结束时写回。关键是在 Harness 层做上下文管理而不是在模型层做。dataclass class SessionState: session_id: str messages: list[dict] # 对话历史 memory: dict # 长期记忆 KV step_count: int max_steps: int 20 class LoopEngine: def __init__(self, harness): self.harness harness self.sessions {} # session_id - SessionState def run_turn(self, session_id: str, user_input: str) - str: state self.sessions.get(session_id) if state is None: state SessionState(session_idsession_id, messages[], memory{}) # 每次循环前限制步数防止无限循环 for step in range(state.max_steps): response self.harness.generate(state.messages, user_input) # 解析响应里的 action 与 observation并更新 state ...这个设计最关键的细节是步数上限和上下文长度上限。生产环境里一个 Loop 不设步数上限等于自杀因为模型在复杂任务里非常容易陷入循环尤其是当某个 API 返回错误格式时模型可能会反复重试同一个工具调用。3.3 循环里的异常处理与自恢复正常流程谁都会写真正考验的是异常处理。我再举一个真实经历某个 Agent 调用一个外部 SOAP 服务工具返回的是一个非标准 XML。解析失败后模型收到的 observation 是“解析工具报错”然后它尝试换了一种 XML 解析方式结果又失败再尝试把 XML 转成 JSON还是失败。四次来回之后用户已经等得不耐烦了。问题的根源在于我的 Loop 把“工具报错”直接当成了普通的 observation喂回模型。模型其实有解决问题的能力但在有限的步数内连续失败会导致体验急剧下降。更合理的方案是在 Loop 里面加一层策略器当连续 N 次行动都返回异常格式时不让模型继续盲目尝试而是切换到备用路径——换一种工具、请求人工介入、或者调整目标。我在工程里经常用一张表管理这些策略异常模式检测条件处理策略工具返回格式错误连续 2 次解析失败停止调用当前工具改用备用工具模型生成空 action响应中没有 tool_call重新生成一次若再失败则提前结束上下文超限token 超过阈值摘要压缩旧消息后继续任务重复执行相同 action 出现 2 次以上强制换策略不能重试同一个参数有了这张表Loop 才不是“傻循环”而是带有基本自我修复能力的引擎。3.4 给 Loop 设定终止条件的三条经验把 Loop 跑在生产环境一段时间后我总结出三条干货第一永远基于外部事实做终止判断而不是基于模型自我声明。模型说“任务已完成”不算完成工具返回的成功状态码才算。我在真实事件中就栽过这个跟头一次模型因为上下文窗口裁掉了一个工具返回的关键字段误以为已经拿到了正确答案就把一个中间结果发给了用户。第二步数上限必须动态调整。不是所有任务都需要 20 步很多简单任务 3 步就结束了但如果你把上限设得太大模型会倾向于“多走几步但更稳妥”。更好的做法是先跑一个带反馈的样本集统计不同任务类型的平均步数然后给每类任务设置不同的上限。第三循环里必须能被打断。生产系统中用户随时可能取消任务Loop 必须监听取消信号。我的做法是给 Loop 传入一个StopEvent在每步循环开始时检查一次如果用户取消了立即保存当前状态并退出循环这样至少能留下一个可恢复的中间快照。4. Graph 层深入把多个循环编织成生产流程4.1 Graph 在 Agent 工程里的定义Graph 层的核心概念是把一次任务执行路径看作一个有向图图的节点可以是“一个 Loop”“一个工具调用”“一个人工审批”或“一个条件分支”图的边代表状态流转条件。为什么会需要 Graph因为真实业务从来不是线性的。用户输入进来经过意图识别可能走向 A 流程也可能走向 B 流程A 流程内部可能并行跑两个子任务等两个都完成后才能汇聚。如果这些逻辑全部用逻辑代码硬编码每加一个新流程就要改一遍主函数。而用 Graph 表示每个流程是独立的一段配置数据或对象图主循环只需要按图走就行。4.2 三种工程建设路径在工程实践中我见过三种定义 Graph 的方式。第一种代码定义式。用熟悉的编程语言直接构建有向图结构。对于复杂系统可视化界面反而会成为瓶颈代码定义更灵活也能做版控。比如class AgentGraph: def __init__(self): self.nodes {} self.edges [] def add_node(self, node_id, pipeline, conditionNone): self.nodes[node_id] {pipeline: pipeline, condition: condition} def add_edge(self, from_node, to_node, conditionNone): self.edges.append((from_node, to_node, condition)) def run(self, context): current start while current in self.nodes: node self.nodes[current] result node[pipeline].run(context) context[current] result next_node None for (from_n, to_n, cond) in self.edges: if from_n current and (cond is None or cond(context)): next_node to_n break if next_node is None: break current next_node这套模型非常轻量但也暴露了一个核心问题——while条件怎么写才能防死循环。我的做法是在 Graph 执行上下文里加一个深度计数器当深度超过节点总数加一个阈值时就自动终止这能防止条件边形成环形引用导致死循环。第二种声明式配置。把 Graph 的定义从代码里抽离成 JSON/YAML。搜索结果里出现的 “snap graph builder”从字面上理解就是一种快速构建图的工具这类工具通常会把图定义做成可视化画布然后导出配置。声明式的好处是业务同学也能参与编排坏处是复杂表达式写起来很别扭。我的建议是简单流程用配置复杂逻辑用代码。第三种状态机式。使用状态机的思路来定义节点的跃迁。状态机的核心是“当前只能处于一个状态”相比于通用图状态机天然避免并发冲突也更好测试。4.3 一个混合编排的案例请允许我放开一个具体案例。假设你要做一个“智能资料分析”的 Agent目标是读一批技术文档然后产出一份分析报告。直接让一个 Agent 从头做到尾效果通常不稳定。我们把它拆成 Graph 后root - 任务解析节点Loop把用户的含糊需求整理成明确的分析目标 - 资料召回节点并行执行 3 个 Loop分别检索本地库、外部API、知识图谱 - 汇聚节点等待 3 个召回子任务完成后合并去重 - 分析报告节点Loop逐段生成报告初稿 - 质量校验节点Reflection Loop模型自查 规则检查不合格则回到报告节点 - 结束节点这个图里资料召回节点的 3 个子 Loop 是并行跑的汇聚节点必须等它们全部完成。这在单 Agent 架构里很难实现但在 Graph 层就是一个“并行分支”加“汇聚点”的标准模式。并发的问题就冒出来了3 个子 Loop 同时调用模型和外部 API很容易把并发数打满。生产中必须对每个节点的并发数做限制同时用消息队列做削峰。4.4 图的可见性与可观测性Graph 层还有一个隐藏的优势可控可观测。可观测性的核心是在每条边上记录流转条件和耗时。一次任务的执行路径本质上就是一张图上走过的路径。把这个路径还原出来你就能回答“为什么这个任务走到了人工审批”这种问题。我在项目里会为每个 Graph 节点分配一个唯一 ID执行时把每次状态转移都打点记录下来node: task_parser, status: ok, latency: 1.2s node: doc_retriever, status: running, parallel: [d1, d2, d3] node: doc_retriever, status: complete, fetched: 17 docs node: report_writer, status: retry_1, reason: quality_check_failed这些日志不仅能用于问题排查还能用来统计节点级别的成功率和平均耗时反哺节点内部的 Loop 参数调优。生产环境里没有 Graph 层的可观测性你的 Agent 系统就是一个黑盒。5. 生产实践里的三层协同与并发控制5.1 并发到底是谁的职责关于“AI Agent 怎么扛并发”这是我被问过无数次的问题。很多人以为这是模型能力的问题其实模型的调用瓶颈只是一个侧面完整的并发问题其实应该拆成三层Harness 层负责 API 连接的并发控制和限流。Loop 层负责单任务内的请求串行/并行策略。Graph 层负责多任务之间的调度和资源分配。这三层必须各自独立支持并发。如果你的 Graph 层开了一百个并行分支但 Harness 层的 API 客户端只能同时建立 5 个连接那瓶颈还是在下面。我实际做过的方案是这样的Harness 层的每个模型接口调用走一个带信号量的限流器同时维护一个连接池。信号量控制在 10 以内时可以保证大多数本地模型服务的稳定。Loop 层不处理高并发它只负责把自己的请求排好队。Graph 层的调度器根据当前节点类型分配并发配额例如资料召回节点可以并行 5 个而报告生成节点只能并行 2 个因为后者更消耗模型上下文。5.2 三层的弹性伸缩策略说到生产实践弹性伸缩是避不开的。但训练过的经验是不要整体伸缩要分层伸缩。纯粹运行 Loop 的节点是无状态的可以随便扩容但绑定会话状态的 Loop 节点不能随便扩因为会话上下文只存在于某个进程里。这跟传统的 HTTP 服务有明显区别——传统服务可以在任意节点接受请求而 Agent 系统的一个会话往往绑定到一个具体节点。我的解决办法是Harness 层无状态化把会话的上下文存储挪到 Redis这样任何节点都可以接管任意会话。Loop 引擎做成可暂停的状态机暂停时可以序列化当前状态恢复时从快照继续执行。Graph 调度器做成独立服务不跟 Loop 运行节点混部署这样调度器的可用性独立于执行节点。这套方案带来的成本是多了一层 Redis 序列化但好处是 Agent 执行节点能像普通微服务一样扩缩容。实际部署中我见过太大一锅端的 Agent 服务QPS 稍微上来就死原因就是它把所有状态都压在单机内存里。5.3 部署 Harness 与技能到内网部署这个词我在前文提到了内网场景。不管是用 Docker 还是裸进程部署 Harness 本质上分四步构建基线镜像包含 Harness 核心运行框架和基础技能。准备独立的数据目录挂载技能、配置、插件、日志。启动时通过环境变量注入目标环境标识生产/测试/内网。运行健康检查确认模型连接和工具调用链路可用。部署最容易翻车的点是插件加载。搜索关键词里有一条很典型“harness failed to load plugins”。这个报错我见得太多基本都是这几个原因插件版本与 Harness 内核版本不匹配。插件依赖的本地库路径在容器里不存在。技能目录的读写权限不对。插件之间互相依赖但启动顺序错了。排查这类问题我的固定套路是先看一眼日志中插件加载的堆栈是在解析阶段还是执行阶段解析阶段的问题基本是 YAML Schema 错误执行阶段的问题先看依赖库和权限。5.4 三层架构里的 LLM 状态管理一个容易忽略的细节是loop 里的“状态”不仅仅是 user_input 和 assistant_output还包含工具调用的状态。一个工具调用可能分三步发起调用、等待结果、结果解析。这三步在 loop 里不能割裂。如果 Harness 层的工具调用方式是不支持异步的那么 Graph 层同时跑多个任务时就会出现一个任务阻塞整个线程池的情况。我的工程实践方案是把工具调用抽象成三种通道同步请求、异步轮询、订阅等待。同步请求适合快速 API异步轮询适合不需要回调的任务订阅等待适合长任务。这个抽象让 Loop 层不再阻塞Graph 层才能做到真正的并行。6. 常见问题与排查技巧实录生产 Agent 系统的调试和普通 Web 系统有本质区别——你无法用断点器去打日志因为大部分行为来自模型的不确定性。下面几个问题是在这半年里被问得最多、也是我在多个项目里反复遇到的。6.1 Harness 插件加载失败现象启动时抛出harness failed to load plugins伴随一堆堆栈。排查三步走先按插件路径逐条检查确认每个插件都在技能目录里。再检查插件版本和 Harness 内核版本的兼容矩阵很多插件在新版本里改过 API。最后检查插件里引用的本地依赖尤其是用 C 扩展的库在容器镜像里经常缺系统级.so文件。如果插件之间还有依赖关系比如 A 插件要为 B 插件提供基础数据结构那么启动顺序很重要。Harness 应提供“延迟加载”选项先把基础插件加载完再加载依赖插件而不是谁先读到谁先加载。6.2 任务循环卡死、无限重试现象任务长时间不结束日志显示同一个 action 不断被调用。这不是模型智商问题。核心原因是循环里缺少两个机制一个是相同 action 的重复检测另一个是退避控制。我的做法是维护一个“动作指纹栈”每次要执行动作前计算动作参数的哈希如果哈希与前 N 次相同就拒绝执行并返回一条“这个动作刚刚执行过”的硬反馈迫使模型换策略。另一个必要设置是把“连续失败限制”和“步数上限”做成强制参数不依赖模型的自觉。6.3 上下文混乱、串号现象会话 A 的回答引用了会话 B 的输入。排查方向首先看向状态隔离是否生效——检查你在 Harness 层构建上下文时是否真的按 session_id 做了隔离而不是把所有消息都放到一个公共列表里。曾经见过一个项目为了省事把多个会话的上下文拼在一个数组里通过加系统提示词“以下是你最近的所有对话”来区分结果模型在长上下文中完全混乱把 A 用户的名字写到了 B 用户的邮件里。正确做法是每个 session 对应一个独立的上下文构建器上下文构建器只读取该 session 的消息记录。工具调用的临时结果也按 session 存储不能让全局缓存污染会话。6.4 模型报错“self referencing loop detected”这类错误通常出现在把自定义对象直接序列化成 JSON 的时候。Python 对象之间互相引用比如 session 的 history 里包含 session 对象的引用序列化时就检测到了循环引用。解决方案有两种一是用 repr 序列化只存消息的字符串形式二是用支持“引用归并”的序列化器比如把对象转成字典之前先统一转换成不带回边的中间结构。我在循环状态持久化时只存纯数据从来不直接存对象引用这样既避免了循环引用也让状态能在不同进程间安全迁移。6.5 Codex 沙盒更新后无法发送消息“codex 无法发送消息显示更新 agent 沙盒”这类问题的一句话解释是沙盒的 API 协议和客户端版本不同步。排查方法也不复杂先确认沙盒版本升级后模型服务的工具调用格式是否兼容再确认消息队列的 topic 是否变化最后检查沙盒的健康检查路径是否被改动。这类问题大多是升级流程缺了“配置同步”这一步而不是代码逻辑坏了。6.6 生产环境的三条核心检查清单最后分享一份我个人每次上线 Agent 都会过一遍的检查清单所有工具调用都有权限门控高风险操作走人工审批。循环有步数上限、重复动作检测、连续失败退避策略。会话状态全部持久化到外部存储执行节点可以随时替换。每个 Graph 节点都有唯一 ID执行日志包含节点流转轨迹。模型接入层支持一键回退到备用模型。插件和技能做了离线打包内网可完整部署不需要公网资源。这条清单大概已经挡住过四次生产事故价值远超一套复杂的框架设计。结尾一套值得保留的实践思路做 Agent 工程到现在我最深的体会有两点。一点是这三层架构本质上是在跟“不确定性”共处——模型输出是不确定的工具返回是不确定的所以你需要 Harness 提供确定的环境边界需要 Loop 提供确定的状态流转需要 Graph 提供确定的编排路径。另一点是不要为了架构而架构。如果你只是做一个内部小工具一个简单的 ReAct 循环加几层包装就够了但一旦你要走向生产要同时服务几十上百个用户要接十几个工具三层拆分就是唯一能把复杂度控制在人类可理解范围内的办法。最后再分享一个小技巧给每个 Graph 节点都配一个“输入快照”功能在节点开始时把进入该节点的上下文 JSON 存一份。排查任何诡异问题时先对比相邻两个节点之间的快照差异往往一眼就能看出是上一层 Loop 传递了错误状态还是本节点自己的工具调用出了问题。这个习惯帮我在一次周末事故里十分钟定位了问题如果只用日志估计要折腾两个小时。希望这三个层面的解析能在你自己的 Agent 工程路上省掉一些我当年踩过的坑。
返回列表