ARTICLE DETAIL

资讯详情

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

三层架构拆解Agent工程:Harness、Loop与Graph的实践指南

三层架构拆解Agent工程:Harness、Loop与Graph的实践指南 最近聊 Agent 架构的人越来越多从 LangChain 到 Claude Code 再到各家自研的 harness 框架名字一堆但真正落到生产环境里我发现绝大多数团队踩的坑都一样模型跑起来了但不知道它下一步要干嘛工具接上了但不知道谁在管权限多步骤任务一长直接变成失控的乱循环。网上那些 demo 看着挺唬人可真要扛住线上流量、要能排查、要能灰度缺的从来不是模型本事而是工程骨架。这半年我陆续做了几套不同规模的 Agent 系统从最简单的单机脚本到要走完整个发布流程的服务最后总结下来所有花里胡哨的框架本质都是在解决同一件事把模型的自由发挥装进一套受控的管道里。这套管道拆开看就是 Harness、Loop、Graph 三层。不夸张地说这三层概念一旦理清楚市面上任何 Agent 框架你拿过来都能在三分钟内定位它在哪个层级做文章自己设计系统的时候也知道该在哪儿使劲。这篇文章我打算把这套三层架构掰开揉碎从原理讲到参数从选型聊到踩坑给正在做或者准备做生产级 Agent 的同学一份可以直接抄作业的参考。1. 为什么 Agent 工程要拆成三层1.1 一次事故暴露的三个问题先说个我亲眼见过的案例。有个团队做了一个多步骤调研 Agent输入一个商业问题让模型自己搜索资料、总结观点、生成报告。最开始 demo 阶段一切完美因为测试问题就那么三五个模型路径也固定。上线第一天就出了事模型在一个网页搜索任务里连续调了二十多次搜索工具每次返回结果都“感觉信息不够”然后换关键词继续搜把单月搜索 API 预算烧掉了百分之六十。用户看到的不是报告而是一张无限转圈的加载页。这个事故表面看是“模型太笨”但往深了挖是三个工程问题叠加在一起。第一没有边界控制——模型想调多少次工具就能调多少次没有任何外层机制对它说“停下”。第二没有循环约束——搜索、思考、再搜索这个循环没有最大轮数也没有质量标准来判定“什么时候信息够了”。第三没有路径编排——这个任务本来应该是“调研分支、评估分支、写作分支”三个子流程结果全被塞进一个线性循环里模型在调研分支里钻牛角尖时其他两个分支完全没有介入的机会。这三个问题恰好就对应了 Harness、Loop、Graph 三层各自的职责。1.2 三层架构各管一段用一句话概括Harness 管环境、Loop 管思考、Graph 管流程。更准确一点说Harness 解决的是“Agent 能碰什么、不能碰什么”的问题它负责任务启动、工具注册、权限校验、状态读写、日志审计这些围绕模型的外围能力。Loop 解决的是“模型如何一步步逼近答案”的问题它把“思考、行动、观察”这个循环体变成可重复执行的机制并控制循环什么时候开始、什么时候结束。Graph 解决的是“多个循环和多个模型之间怎么配合”的问题它用节点和边把复杂的任务组织成有向图让分支、合并、并行、人工介入都变成显式设计。这三个词在英文里其实都来自日常词汇但在这个语境下有明确的工程含义。Harness 原意是“马具、挽具”就是套在马身上的那一整套皮带用来控制方向、传递力量。Graph 最直观就是图点线相连。Loop 就是循环一个 while 语句。连起来看你给模型套上马具Harness让它在一个循环里干活Loop再把多个循环用图组织起来应对复杂任务Graph。这个比喻虽然简单但非常贴近实际分工。1.3 这套架构不是发明是逼出来的很多同学看到三层架构第一反应是“是不是又一套理论框架”。我可以负责任地说这是从无数的坑里倒推出来的产物。早期 Agent 框架只有一层一个 system prompt 加一个 while 循环模型自己决定一切。这种“一层流”在小任务上很好用但一旦工具超过五个、任务步骤超过十步、并发用户超过几十个立刻崩盘。模型会在工具之间来回跳会把同样的错误重复犯三次会因为在上下文里找不到关键信息而开始编造结果。于是大家开始给这个循环“加护栏”这些护栏长成了 Harness 层开始给循环加“刹车和油门”变成了我们说的 Loop 工程最后发现一个循环解决不了一个需要多角色协作、多分支决策的任务于是把多个循环连接成图Graph 层自然就出现了。这套演化路线和软件工程里的分层思想完全一致不是谁发明了一个漂亮的理论而是复杂度到了那个程度不分层根本活不下去。理解了这一点再看任何框架都会有一种“原来你在这个层做文章”的洞察力。2. Harness 层给 Agent 套上能干活的“操作间”2.1 Harness 管的不是模型是边界先搞清楚一个概念问题Harness 和 Agent 是什么关系。很多人以为 Harness 就是 Agent这是最常见的误解。Agent 指的是那个有自主决策能力的智能体它核心的表现是“想”和“做”。Harness 不是思考的主体而是承载 Agent 的外壳它管的是思考之外的一切事情这个 Agent 能调用哪些工具、调用工具需要什么授权、每一步的输入输出怎么记录、中途出现异常怎么恢复、用户怎么打断它。说得更直白一点Agent 是灵魂Harness 是身体身体不决定灵魂想什么但决定了灵魂能做什么。实际操作中Harness 最关键的是四个子模块。第一个是上下文管理模块负责把系统提示、历史记录、工具返回结果组装成模型需要的输入并且控制上下文的长度别让 token 无限膨胀。第二个是工具调度模块它维护一份工具清单把模型输出的结构化指令比如 JSON 参数转成真实的函数调用然后把调用结果再塞回上下文。第三个是权限与审计模块它决定某个工具在当前状态下能不能被调用同时把每次调用的请求、响应、耗时全部落到日志里。第四个是会话与状态模块它管理多轮对话的状态用户切换对话、任务中断恢复都要靠它。这四个模块听起来平平无奇但缺任何一个都会出大事。没有上下文管理长任务跑一半就报“超出上下文限制”没有工具调度模型输出的参数和函数签名对不上报错率居高不下没有权限审计模型误删数据你都不知道是谁删的没有状态管理用户刷新一下页面整个任务就得从头来过。2.2 工具注册、权限和审计Harness 设计得好不好一半看工具注册的规范性。我见过的失败案例里最常见的就是工具接入方式太随意有人直接给模型塞了一堆 Python 函数文档让模型“自己看着办生成参数”。结果模型生成 json 参数经常类型不对、字段拼错。后来我把所有工具都改成严格定义的 JSON Schema每个参数都有类型、取值范围、示例值模型调用成功率立刻从七成涨到九成五以上。工具 Schema 是 Harness 层最重要的“接口契约”它就像是给模型的一份工具使用说明书。定义 Schema 的时候有几条原则值得记住参数名要跟真实业务名词保持一致别搞“a1、b2”这种缩写每个参数都要写清楚默认值和边界条件比如“日期范围默认最近 30 天最大不超过 365 天”有依赖关系的参数要明确说明比如“调用转账接口之前必须先调用身份验证接口”。这些话模型都会认真读你写得不清楚它就会犯错。权限控制我建议走“默认拒绝按需开放”的策略。比如一个 Agent 同时有搜索工具、数据库查询工具和文件删除工具那在初始状态下应该只开放只读类工具写操作和删除操作必须在用户明确授权之后才动态加入工具列表。这个策略听起来简单但是很多团队图省事把所有工具一股脑全塞给模型然后出问题再怪模型不听话。实际上模型就是“给什么工具就会用什么工具”你不给它删除工具的把手它想删也没辙。审计日志是另一个容易被忽略的模块。我个人的习惯是Agent 的每一次工具调用都记录这样一条结构化日志任务 ID、当前动作、工具名称、入参摘要、出参摘要、耗时、Token 消耗、是否成功。这些日志不用多花哨但要能支撑两件事一是出问题时能快速回放整个任务轨迹二是在调参时能统计工具调用成功率、单步耗时和平均循环轮数。没有审计日志的 Agent 就像是没装行车记录仪的车出事全凭嘴说。2.3 从裸调 API 到 Harness 框架很多早期开发者做 Agent 就是直接调用大模型 API写个循环丢一个 system prompt 就完事了。这种裸调方式在实验阶段完全没问题但生产环境里你会发现大量工作其实是在重复造轮子你要写 token 计数和截断、要写参数解析和异常重试、要写并发控制和队列、要写慢日志和告警。这些代码跟业务逻辑一点关系都没有但每一项都影响系统能不能稳定跑下去。于是出现了各种 Harness 框架把我上面说的四个子模块打包成可复用的产品。最近很火的 DeepSeek Harness、Claude Code 里的 Agent Skills、社区里的各类 skill 插件本质上都是在 Harness 层做文章。它们把工具注册、技能加载、权限配置、会话恢复这些能力做成了一套标准化流程让开发者不用从零开始写外围设施。我自己的体会是做原型可以裸调 API但决定长期维护的话尽早迁到一个 Harness 框架上省下的不是开发时间而是后期跟基础设施搏斗的精力。但框架不是上了就万事大吉。很多 Harness 框架自带默认配置非常适合 demo但到了生产环境你需要改的地方非常多内置的 prompt 模板可能和你的业务场景冲突、默认超时时间可能太短、日志格式可能不满足你的监控系统要求。所以我建议把这层看作“可替换的底座”在上面做一层薄薄的封装把你们公司的工具接入规范、权限模型、审计标准确定下来这样就算底层框架换了上面的业务代码不用动。2.4 自己写一个极简 Harness 的要点如果你不想一开始就依赖重量级框架直接手写一个最小 Harness 也能跑大概需要四五十行代码。核心结构就三部分工具字典、循环器、观察者。工具字典存放工具名到实际函数的映射循环器负责跑模型和调度工具观察者负责记录日志和校验权限。我建议新手可以先写一个这样的最小实现把“模型调用、工具执行、结果回填”这个链路亲手跑通再考虑引入框架。这样你对后面遇到的每一个抽象概念都会有一个实体感知。这里有一个技术选型问题用 Python 还是用 Rust。如果是团队已有 Python 技术栈、工具生态依赖 Pandas 这些库那闭眼选 Python开发效率优先。但如果你要做一个高并发、低延迟的 Agent 网关Rust 的优势就体现出来了内存安全、无 GC 停顿、异步并发能力极强。我见过有人用 Rust 把 Agent 的单次循环开销压到了几毫秒级别而同样的逻辑在 Python 里光解释器开销就几十毫秒。但 Rust 的代价是开发效率低尤其是和 AI 相关的生态还有不少坑。我的建议是你如果是做业务集成用 Python如果是做中间件和网关用 Rust两者之间用消息队列或者 gRPC 通信各取所长。3. Loop 层循环才是 Agent 的思考引擎3.1 ReAct 循环到底在循环什么Agent 的核心思考模式其实就是 ReActThought思考、Action行动、Observation观察三者不断循环。模型先看当前上下文产生一个思考说明“我现在需要获取什么信息”然后把这个思考转成一个动作——通常是调用某个工具工具返回结果作为观察模型再基于观察产生下一步思考。这个循环不断重复直到模型认为任务完成。这个循环的本质可以理解成“模型在外部世界做试验”它不确定某个假设对不对就调一次工具验证一下拿到结果再调整下一步。它跟人类解决问题的模式很像我们查资料写报告也是“先想、再搜、再看结果、再写”。但有个关键区别人的记忆容量几乎是无限的而模型的上下文窗口是有限的钱包每多一次循环就多吃一笔 token循环越长成本越高出错的概率也越大。所以 Loop 层真正的工程量其实就三个字控得住。控得住长度、控得住次数、控得住方向。长度用上下文管理来控制超过阈值就做摘要压缩次数用最大迭代上限来控制超过就强制终止方向用路由判断来控制一旦识别出当前循环陷入重复动作就触发放弃、换路径或者求助人工。这三个控制点就是 Loop Engineering 的核心内容。3.2 终止条件防止循环变成死循环循环最怕的是退不出来。你给模型一个开放式任务比如“改进这段文案”它会觉得永远有改进空间一直在原地打转。所以我设计 Loop 的时候第一个要定死的参数就是最大迭代次数max_iterations。大部分工具调研性任务10 到 15 轮足够完成如果是复杂报告生成类任务30 轮左右也到头了。我一般会设置成 15 作为默认值线上任务再根据历史统计调整到覆盖 99% 的完成区间剩下 1% 的特例就直接终止并转入人工处理通道。除了硬性次数还要有“软性终止条件”。我常用的做法是让模型在思考里输出一个“任务完成置信度”字段0 到 1 之间。当置信度超过阈值比如 0.9而且最近两轮都没有调用新工具就判定可以结束。这个“两轮无新工具调用”的规则很实用可以防止模型嘴上说着“快完成了”手里却还在一遍遍调用同一类工具。本质上这是一个加权判断完成置信度高、动作频率低就收尾置信度低但动作还在变就继续置信度低且动作开始重复就赶紧中断。终止之后还有一个容易被忽略的操作写结论。让模型在循环结束前把当前所有观察结果压缩成一段结论性摘要而不是直接把原始工具结果一股脑塞给下游。这样既方便人工审查也能让下游模块拿到高质量输入。我管这一步叫“循环收口”它直接决定了后续 Graph 层各节点之间的信息传递质量。3.3 Loop Engineering把单循环升级为多窗口并行理解了单循环之后继续看 Loop Engineering 这个词。它不只是“调教一个循环”而是“工程化地管理多个循环”。典型场景是一个 Agent 在主循环里拆出了三个子问题跑了一轮搜索发现三个方向彼此独立于是它可以并行开三个子循环让三个模型实例同时去调研最后把结果汇总回主循环。这种多窗口并行能大幅缩短响应时间但也引入了新问题三个子循环分别消耗多少 token如果其中一个子循环死掉了其他两个是要等它还是要取消汇总冲突的结论以谁为准我的实践方案是这样的用一张共享任务表管理所有子循环。每个子循环在表里注册自己的状态pending、running、done、failed主循环定期轮询这张表一旦发现某个子循环失败就决定是重试还是带部分结果继续。并行数量一开始不要贪建议控制在 3 到 5 路太多会让总体 token 消耗呈线性爆炸也会给下游工具 API 造成压力。先串行跑通再并行优化这是一个稳妥的路子。还有一个小技巧叫“观察共享”如果两个子循环要查询的数据是同一类就让 Harness 层做一层缓存第二个子循环直接读缓存结果不再重复调用工具。很多团队把这个职责放在 Graph 层做全局缓存我试下来放在 Loop 层做更轻量因为它不需要感知整个图的结构只需要在同一个循环家族内做结果复用实现成本低收益却很直观。4. Graph 层把循环升级成可编排的有向图4.1 循环的局限与图的出现单循环和并行循环能覆盖不少场景但有一个硬伤它们用代码写死了流程。比如我要做一个客服 Agent流程是“用户提问 - 判断意图 - 查询订单 - 查询物流 - 生成回复”。用循环写所有分支都藏在 if-else 里改一次流程就要改代码、发版本而且流程内部的每一步都难以监控和单独测试。更麻烦的是当流程里需要出现“人工介入”节点时纯循环很难表达“在这里停下等人工答复后继续”。Graph 层的核心思路就是把流程从代码里抽出来变成一张显式的有向图。每个节点是一个独立执行单元——可能是模型推理、工具调用、条件判断或者人工任务每条边则定义了从哪个节点到哪个节点的流转条件。这样做的好处是流程可读、可分析、可单独编排你可以把整张图画出来给非技术同事看他们也能看懂任务走到哪一步了。这就是 Graph 在 Agent 工程里的价值它不是为了炫技而是为了让复杂流程变得可管理。从我实际使用的经验看Graph 层带来的最大收益不是执行效率而是可观测性和可维护性。节点之间不互相依赖全局变量每个节点只消费上游传入的数据结构输出也统一为同一个数据结构。这样每个节点都能单独写单测、单独灰度、单独回滚。哪怕某一个节点换了模型只要输入输出协议不变整条链路都不用动。4.2 节点、边与条件路由Graph 的基本组成是节点和边。节点按职能可以分成五类Start 节点负责接收输入并初始化上下文模型节点负责某一段推理或生成任务工具节点负责调用外部 API 或数据库路由节点负责做条件判断根据输入数据决定下一步走哪条边人工节点负责暂停流程等待人工审核确认。一套完整的 Agent 图至少会组合使用这五类中的多数很多场景甚至五类都要用。边不是简单的“从 A 到 B”它要携带转移条件。我刚用 Graph 时犯过一个错误把所有边都设成无条件转移结果流程像铁轨一样直来直去图和数据毫无差别。后来我意识到每条边都应该有一个判断函数输入是当前节点产出的结果对象输出是布尔值或路由标签。比如订单查询节点返回“订单存在”则走向查询物流节点返回“订单不存在”则走向客服回复节点。路由节点本质上是把多个条件边集中到一个节点上执行让图看起来更整洁。给一个非常小的图示例。用字典结构描述节点和边的关系这是我最常用的表达方式简洁直观graph { start: {next: intent_classify}, intent_classify: { use_model: gpt-4o-mini, routes: [ {condition: 意向为查订单, next: query_order}, {condition: 意向为找人工, next: human_handoff}, {default: True, next: chat_fallback} ] }, query_order: {tool: order_api}, human_handoff: {type: human_review}, chat_fallback: {tool: general_reply}, }这个示例看着简单但它说明了一个关键点Graph 节点需要声明自己要什么资源模型、工具、人工边的条件决定流程走向整张图以数据驱动的方式运行。生产级的 Graph 还会给每个节点配 retry 策略、超时时间和独立的日志 tag比如query_order节点配 3 次重试、30 秒超时日志 tag 是order_service。这样每次出问题你直接从日志里按 tag 过滤定位非常快。4.3 图构建工具与商图的落地思路Graph 这么有用自然有配套工具。最典型的是 LangGraph 这类编程式的编排框架它把图构建的过程变成“添加节点、添加边、编译执行”。也有一些可视化平台走拖拽建图路线类似 Snap 的 Graph Builder 那类产品——虽然它本身是内容创作工具但它把“节点-连线-条件”的交互做得极其顺畅我一度从它的交互设计里借鉴了不少灵感。可视化建图的好处是业务人员也能参与流程图设计但难度在于生产环境里的图不可能只靠拖拽完成复杂的条件逻辑还是得回到代码里写。再聊一个 Graph 层面比较进阶的概念商图quotient graph图论里它指把图中的节点按某种等价关系聚合后形成的简化图。这个概念在 Agent 编排里非常实用。举个例子一张客服流程图有二十个节点其中三个节点都在做“校验用户身份”这件事只是针对不同入口。你可以把这三个节点聚合成一个“身份校验”抽象节点整张图瞬间从二十个节点缩到十五个结构清晰很多。商图思维的本质是“找重复、提公共”它让大图不至于失控。实际操作商图时我会先从日志里统计节点调用频率和相邻关系把那些“同进同出”的节点挑出来判断它们是不是同一类职责如果是就直接合并成一个公共节点然后在公共节点上做统一的缓存和监控。这样既减少了图的复杂度也把重复的 token 成本砍掉了。4.4 什么场景必须上 GraphGraph 不是银弹有些任务用单循环就能干得很好上了 Graph 反而增加复杂度。我总结了一下以下三个信号出现时你就该考虑从 Loop 升级到 Graph。第一个信号是流程分支超过三个。诸如“根据用户身份走不同流程”“根据故障类型走不同修复路径”这种多分支逻辑用 if-else 写会非常难维护而 Graph 能把每个分支都画成显式的路径一眼看穿。第二个信号是需要人工介入闭环。客服审核、内容合规、高危操作确认这些场景要求流程能在某个节点停下来等人工没有 Graph 节点抽象你只能用一个全局 interrupt 标志手动控制特别容易出错。第三个信号是同一个 Agent 需要复用能力。比如你在做一个资料助理搜索能力同时被“查新闻”“查竞品”“查内部 wiki”三个流程复用Graph 能把“搜索”做成一个公共节点三个流程各自引用它改搜索逻辑时只需改一处。顺便提一个容易混淆的概念QT Quick 的 Scene Graph。它也是“图”但它管理的是渲染场景里的绘制元素节点之间是空间关系、层级关系而在 Agent 工程里 Graph 管理的是执行流转关系、控制流关系。两者形态相似、目的完全不同。如果面试时被问到能一句话说清这个区别基本就能证明你对 Graph 的理解不是停留在名字层面。5. 三层架构的生产实践选型、参数与代码骨架5.1 用几层才合适三层架构是一个完整参考模型但不代表每个项目一开始就要上满三层。我个人的选型决策是这样如果任务是“单次问答 最多一次工具调用”比如天气查询、计算器那就只上 Harness 层甚至 Harness 都可以简化到只做工具注册如果任务是“多步推理 多次工具调用”比如查资料写摘要、做数据分析报告就上 Harness Loop 两层如果任务是“多角色协作 多分支流程 人工介入”比如客服工单全流程处理那就三层全上。这里要特别说一句上 Graph 的前提是你的流程已经稳定绝不是开发初期就画一张大图。我见过不少团队一开始就搭了十几节点的图结果需求一变图天天改开发全耗在连线上了。更务实的做法是先用 Loop 层把单个 Agent 的循环调通、调出稳定的执行效果再从日志里发现哪些步骤出现了分支、出现了重复把这些真实出现的结构性问题抽象成 Graph 节点。换句话说Graph 是从 Loop 里长出来的不是拍脑袋画出来的。5.2 关键参数设计与成本估算Agent 生产化的核心参数其实就那几个max_iterations最大循环轮数、timeout单轮超时、max_tokens单次生成上限、temperature温度、retry_limit工具重试次数。我给一套默认值供参考参数默认值说明max_iterations15调研型任务默认 15 轮复杂任务 30 轮封顶timeout30s单轮模型调用或工具调用的超时时间max_tokens2048单次模型生成上限推荐任务可设 4096temperature0.2生产环境偏低工具调用类任务甚至可设 0retry_limit3工具调用失败重试次数指数退避间隔context_window32k上下文窗口上限超过则触发摘要压缩这些参数不能拍脑袋要从真实数据统计里来。一个简单的估算方法假设你观察了 100 个成功案例平均每个任务跑 8 轮循环每轮消耗输入 token 约 12000上下文逐步累积、输出 token 约 500那么单个任务平均消耗约8 * (12000 500) 100000 token。再乘以用户访问量和模型单价你就能算出单日成本上限。这套计算我建议每个团队都做一遍很多人在 Agent 上花了冤枉钱就是因为从没算过“一次完整任务到底烧多少 token”。还有一个细节上下文增长策略。如果不做控制第 8 轮的输入 token 会是第 1 轮的数倍。我常用的做法是“前 6 轮保留完整历史之后的旧轮次做摘要压缩”这样长任务的 token 消耗不会线性爆炸。压缩摘要的动作本身也要消耗一次生成 token但通常比保留完整原文便宜得多回退风险也更小。5.3 一个最小三层架构的代码骨架给一个能跑通的最小骨架用 Python 写重点看结构不用纠结细节。Harness 管工具和权限Loop 管循环Graph 管流程# harness.py - 外壳层 class Harness: def __init__(self, tools: dict, max_iterations: int 15): self.tools tools self.max_iterations max_iterations self.audit_log [] def execute_tool(self, name: str, args: dict): if name not in self.tools: raise ValueError(f工具 {name} 未注册) self.audit_log.append({tool: name, args: args}) return self.tools[name](**args) # loop.py - 循环层 class LoopEngine: def __init__(self, model, harness: Harness): self.model model self.harness harness def run(self, task: str, memory: list, max_stepsNone): max_steps max_steps or self.harness.max_iterations for step in range(max_steps): response self.model.plan(task, memory) if response[action] finish: return {status: done, answer: response[output]} try: tool_result self.harness.execute_tool( response[action], response[args]) except Exception as e: tool_result {error: str(e)} memory.append({step: step, **response, result: tool_result}) return {status: timeout, partial: memory} # graph.py - 图编排层 class GraphRunner: def __init__(self, nodes: dict, edges: list): self.nodes nodes self.edges edges def run(self, start_input: dict): current start payload start_input while current ! end: node self.nodes[current] payload node.execute(payload) for edge in self.edges: if edge[from] current and edge[condition](payload): current edge[to] break return payload这个骨架非常简略但它体现的职责边界是准确的Harness 不关心任务是什么只负责工具调用的安全与记录Loop 不关心流程图长什么样只负责循环的启动和终止Graph 不关心模型用什么 prompt只负责节点间数据怎么流转。三层各自干好一件事组合起来就能承载复杂的业务逻辑。生产环境在这个骨架上加缓存、加观测、加失败重试就好核心边界不用动。6. 生产踩坑实录与排查技巧6.1 循环引用报错与状态序列化先说一个离 Graph 最近的坑。你做一个带状态的 Agent希望把整个执行状态缓存起来比如把图节点的 payload 写进 Redis 或存成 JSON 文件结果一序列化就报错self referencing loop detected for property mem_memberinfo with type system...。这类报错我在 .NET 环境遇到过最典型的版本本质是对象存在循环引用序列化器无法打破这个环。这个问题的根源通常是你把模型的 response、工具函数的引用或者某个 manager 对象直接塞进了 payload而这些东西又反向持有 GraphRunner 的引用。解决方案有两个。第一个方案是在定义节点数据结构时只保留纯数据字段绝不塞函数引用和模型句柄工具和模型都通过 Harness 层按名称索引不直接放进 payload。第二个方案是序列化时配置循环引用处理JSON.NET 里设ReferenceLoopHandling.IgnorePython 里用自定义编码器跳过不可序列化字段。但我要提醒一句处理掉报错只是治标治本还是设计数据结构时保持“payload 即纯数据”。6.2 死循环与 token 爆炸死循环是 Loop 层最常见的线上事故。表现是任务一直不结束、工具调用次数持续增长、用户侧看到无限转圈。排查的第一步不是看代码而是看审计日志里的“重复动作”统计如果模型连续三轮以上调用同一工具且参数几乎一样基本可以判定它陷入了“原地打转”。这通常是因为上下文里缺少一个“你已经试过这个方案结果无效”的信号模型把前一步的结果忘掉了或者没理解。我的两个保险做法一是在每轮工具结果前面加一个状态提示字段格式是“第 N 次尝试此前结果摘要xxx如果本次结果与之前相同请不要再重复此动作”给模型一个显式的记忆锚点。二是在代码层面实现“动作去重”——检测到同一工具同参数已调用过一次就在本轮直接拦截并在返回结果里注明“该调用已执行过结果同上”。后者更硬核不依赖模型自觉。Token 爆炸通常和上下文压缩策略失配有直接关系。明明设置了摘要压缩为什么还是爆大概率是你只在代码里写了压缩函数但没有在模型 prompt 里告诉它“旧记忆已被压缩细节可能丢失”。模型不理解压缩语义一旦需要细节又会去重新调用工具查询反而多烧一轮。解决办法是压缩时保留结构化摘要包含关键结论、数字、引用来源而不是压缩成自然语言散文这样模型再需要细节时可以直接用摘要里的数据不必重新查询。6.3 框架安装、内网部署与权限失控Harness 框架本身也会坑人。很多社区框架倚靠在线仓库安装依赖到了内网环境就装不上。DeepSeek Harness 这类项目我见过最多的求助就是“安装失败”“依赖冲突”“没法离线部署”。我的经验是生产环境部署这类框架之前一定要做三件事第一在隔离环境里生成完整的依赖锁文件第二把所有依赖包预先下载到本地离线仓库内网安装时直接用本地源不要现场拉取第三锁定框架版本不要追新因为你不知道上游哪次提交会改掉默认行为。权限失控是 Harness 层最恐怖的事故。模型拿到了数据库写权限一次 prompt 注入就可能导致数据被清空。虽然听起来像安全竞赛题但我见过真实案例一个 Agent 被用户输入诱导调用了管理接口关闭了某租户的服务。从那之后我定了一条铁律凡是写操作、删除操作、管理操作一律要走人工节点确认Graph 层必须在这些节点的输出上挂“人工审批”边。模型可以高度自主地做只读分析但要动“真金白银”的状态必须有人按下确认键。6.4 测试与灰度策略Agent 的测试和传统接口测试完全是两回事同一个输入模型可能给出不同的输出路径。所以我不追求“断言输出一致”而是断言“路径合理、结果结构正确、关键词覆盖”。具体做法是录制回放把线上真实的工具调用序列录下来回放时用 Mock 替代真实工具让模型跑出它的动作序列然后用规则判断动作序列是否落在预期区间。比如“用户咨询订单过期”这个场景预期动作序列应该包含“查询订单”“判断状态”不能直接跳到“生成道歉话术”。灰度发布时我用的是“影子模式”。新模型或者新流程上线前把真实的线上流量复制一份到影子环境跑同一批请求但影子环境的结果只记账不执行真实动作比如不真发短信、不真改配置。对比影子结果和线上结果看新逻辑有没有引入错误动作。这个模式尤其适合 Graph 层改动因为你改的可能只是某一个节点影子跑一周就能看出整条链路的稳定性变化比直接全量发布稳妥得多。另外一个很实用的小技巧给每个 Agent 任务加一个“trace_id”贯穿 Harness、Loop、Graph 三层日志。这样任何一层出了问题都能从 trace_id 拉出全链路的动作轨迹。没有这个 ID排查问题就是大海捞针有了它问题定位基本按分钟计。7. 做了三个生产级 Agent 后的三点体悟第一点体会是功能的边界比模型的能力重要得多。模型再聪明如果没有 Harness 管住工具和权限它在线上就是在裸奔。先把“不能让模型做的事”写清楚产出比调 prompt 高得多。第二点体会是先让 Loop 稳定再谈 Graph。每次看到有人一上来就搭一张二十个节点的流程图我都替他捏把汗。Graph 应该是从真实的循环日志里长出来的是结构提炼的结果不是设计稿的抄写。没有稳定的单循环再漂亮的图也跑不起来。第三点体会是不管哪一层都要给“人工介入”留一个口子。自动化的尽头永远需要人来看一眼哪怕只是在高风险节点上点一个确认按钮。这个口子不是流程的退步它是系统真正敢上线的前提。我做的任何一个 Agent上线前都会先检查一件事如果这个 Agent 彻底失控需要几步能把它停下来如果答案超过两步我就知道这系统还不到发布的时候。
返回列表