
做 Agent 工程最怕什么不是模型不够聪明而是你精心设计的 Agent 一上生产就变成一个“黑盒”它调了哪些工具、为什么绕了三圈才回答、上下文什么时候被撑爆、并发一起来会不会互相踩脚——这些问题光靠模型本身的参数是回答不了的。我过去半年一直在折腾基于 DeepSeek 这类开放模型 API 的 Agent 项目踩的坑多了之后慢慢把整个工程拆成了三层Harness、Loop、Graph。这三层不是某个框架的专有名词而是一套非常实用的工程抽象Harness 管好模型手上的“工具与环境”Loop 管好模型“感知-规划-行动”的循环节奏Graph 管好“多步骤协作”的全局流程。用这套三层架构去做设计和排查很多原本玄学一样的问题会变得清晰可追。这篇文章就把每一层的设计思路、实现要点和生产环境里的实战经验完整拆开讲适合正在搭 Agent 后端、准备上多 Agent 并发、或者被循环失控和上下文爆炸折磨过的开发者参考。1. 为什么 Agent 工程需要三层架构1.1 从 Demo 到生产Agent 失控的本质原因本地跑一个单轮 ReAct 示例非常简单给模型一个提示词、几个工具函数它调一下工具、生成一段回答Demo 就算成。但一旦进入生产事情立刻变味。我见过最典型的一个事故是一个客服 Agent 在用户追问“为什么刚才的方案不行”时反复调用同一个查询工具四次每次拿到的都是同一份结果最后一次终于触发工具超时然后模型开始编造一个不存在的工单编号。这种级别的失控问题不在模型本身而在工程上没有给 Agent 的行为套上边界。你放到一个更大的视角看会发现 Agent 的失控通常来自三个层面。第一工具调用没有统一封装模型不知道工具的调用协议、参数约束和失败语义于是容易“瞎调”或者“编参数”。第二循环过程没有收敛机制模型可以在行动-观察之间无限打转缺乏终止条件、回合限制和重复动作检测。第三复杂任务没有全局编排当一个任务需要多个模型、多个工具、多个分支配合时如果只靠模型临场发挥流程必然走向不可控。三层架构就是冲着这三个失控点去的。Harness 解决“模型怎么调用外部能力”Loop 解决“模型怎么在一个任务里保持推进”Graph 解决“多个任务之间怎么有序协同”。每一层解决一类独立的问题这也是为什么实际生产里必须把它们分开设计和单独测试。1.2 三层架构各自解决哪一类问题用一个比较形象的类比把 Agent 想象成一个新入职的实习生。Harness相当于给实习生配好的办公桌电脑、权限账号、内部工具、工作手册。它决定了实习生能碰什么、不能碰什么工具坏了找谁、数据格式不对怎么处理。模型本身不具备操作系统的能力但是 Harness 可以把工具、技能、上下文记忆、权限边界全部准备好。Loop相当于实习生的工作节奏接到任务→查看资料→动手尝试→检查结果→不行再调整→最终交付。这个循环必须被工程化地控制多久必须交一版反复失败几次要上报同一件事重复做三遍要不要停下来Graph相当于项目排期和流程图哪些步骤必须串行、哪些可以并行、哪一步失败了走什么分支、最终结果要不要人工审核。复杂任务靠 Graph 提前定义拓扑而不是让模型临场发挥。把它们拆开还有一个额外的好处每一层都能独立测试和演进。Harness 的工具可以单测Loop 的终止策略可以做仿真评测Graph 的拓扑可以用 DAG 校验器检查。如果三层混在一起写成一个巨大的 Agent 类后期任何改动都会牵一发而动全身。所以这篇文章后面每一章就沿着三条主线展开先讲 Harness 怎么搭再讲 Loop 怎么控最后讲 Graph 怎么编。每一层我都会带上生产环境中实际会遇到的坑和解法。2. Harness 层给模型套上“工作台”2.1 Harness 到底是什么和 Agent 有什么区别很多人第一次看到“Harness”这个词会懵因为它不是某一个具体算法的名字。Harness 在工程里的意思翻译过来更接近“测试框架”或“运行夹具”它是包裹在模型外面的一整套执行环境负责把模型的文本输出转成可执行的工具调用再把工具的返回结果格式化后塞回模型上下文。为了说清它和 Agent 的区别我更喜欢用“马车”的隐喻。Agent 是那匹马负责思考和决定方向Harness 是那套挽具负责把马的力气传导到车轮上同时兜住马的方向感。没有 Harness模型就算生成了正确的工具调用 JSON也没有人真正去执行它没有 Harness工具返回的报错信息会直接污染模型的上下文。换句话说Agent 是逻辑上的“智能体”Harness 是物理上的“运行载体”。实践中的 Harness 至少包含四块内容工具注册表与 schema 校验把所有工具函数注册成一份结构化描述包括名称、参数类型、必填字段、返回格式模型每次调工具前都拿 JSON Schema 做一次校验。执行器Executor真正调用外部 API、跑脚本、连数据库的那一层。它负责超时控制、重试策略、权限校验。技能封装Skill把一组相关工具和提示模板打包成可复用的能力包。比如一个“代码回退”技能不仅包含 git 相关命令还包含什么时候该用回退、回退前要保留什么状态的提示词。内存与记忆管理短期对话历史、任务上下文、跨会话记忆的存取接口。我最近在做个基于开源模型 API 的助手项目底层模型走的就是 DeepSeek 的开放接口第一版没写 Harness直接把工具函数列表塞进 system prompt结果发现模型经常把参数名记错比如工具定义里是file_path模型会生成fp。后来补了一层严格的 schema 校验和错误回传机制才把这个“幻觉参数”问题压下去。这层就是一个标准的 Harness 要做的事你不光要把工具给模型还要让模型在犯错时收到明确、格式化的错误反馈而不是一段晦涩的异常堆栈。2.2 工具封装与技能管理的落地方法工具封装的第一个原则所有工具输出必须结构化。模型理解 JSON 比理解自由文本稳定得多。我要求团队里所有工具返回都统一成{ status: ok | error, data: ..., message: ... }这种格式哪怕底层数据源返回的是非结构化文本Harness 也要做一次转译。第二个原则工具描述要写给模型看不是写给程序员看。同样是“查用户订单”程序员的接口注释写的可能是“根据用户ID从订单表读取记录并返回最近订单”但对大模型来说更有用的描述是“当用户询问订单状态、物流进度或购买历史并且对话中已经存在用户ID或登录信息时调用此工具。如果用户只是泛泛地问‘我的东西到哪了’应先确认订单号再调用。”这种“触发条件 调用后果 边界情况”的描述方式可以明显减少模型乱调用工具的概率。技能Skill管理是 Harness 层的进阶玩法。单个工具解决不了复杂任务把工具、提示词模板和参考案例绑在一起封装成“技能包”Agent 就能按需加载。我在生产里的做法是每个技能包是一个独立目录里面包含skill.yaml元信息包括技能名、适用场景、所需工具列表、prompt.md加载该技能后要注入的提示词、tools/技能用到的工具实现、examples/几个典型调用样例。模型启动时先通过一个“技能选择器”根据用户意图召回 1~2 个技能包而不是把所有技能全部塞进上下文。这个设计在实践中有个很大的好处上下文占用率显著下降。我们最初 20 个技能全量注入system prompt 加到八千多 token模型指令遵循率反而变差了改成按需加载后system prompt 控制在两千 token 以内工具调用的准确率明显回升。你可以简单估算一下一百个工具的描述平均每个 300 token不按需加载就是 3 万 token就算模型窗口够用注意力分散也会让关键工具被忽略。2.3 并发与安全Agent 怎么扛住生产流量“AI Agent 怎么扛并发”是我被问得最多的问题之一。模型推理本身可以通过 API 网关和服务网格做水平扩展但真正容易出问题的是 Harness 层的外部工具调用和上下文管理。并发场景下我推荐一个基础策略无状态 Harness 外部状态存储。Harness 本身不保存任何会话状态每个请求进来都是一个完全独立的执行单元对话历史、任务中间态全部放到 Redis 或数据库中用session_id关联。这样 Agent 服务可以随便横向扩容负载均衡到任何一个实例都能继续同一个会话。工具调用的并发控制要分层处理。第一层是超时与降级每个工具调用都要设置独立的超时时间比如外部搜索 API 给 8 秒内部数据库查询给 3 秒超时后不是说直接报错而是返回一个降级结果告诉模型“查询超时请稍后重试或换一种方式”。第二层是限流与排队如果同一个工具被大量 Agent 同时调用需要做 token bucket 限流。我在实践中的阈值通常这样算工具服务的容量QPS× 90% 作为 Agent 侧的软限制超过限制的请求进入排队缓冲排队超过 2 秒直接失败。第三层是互斥锁对于不可并发执行的操作比如写文件、改配置用分布式锁加锁锁的租约时间要覆盖工具最长执行时间防止锁提前释放导致重复执行。安全问题上Harness 是 Agent 安全的第一道大门。我的经验是权限模型越简单越安全。不要在工具里一层一层做细粒度授权直接在 Harness 启动时给每个会话绑定一个最小权限凭证。比如一个只读查询 Agent它的数据库凭证就只有 SELECT 权限一个代码生成 Agent它的执行目录就是一个临时沙箱。同时对所有工具调用做审计日志记录谁、在什么会话、调了什么工具、传了什么参数、返回了什么结果。出了安全事件第一件事就是查审计日志而不是猜模型为什么会那么做。2.4 Harness 落地中的版本管理与代码回退Harness 本身也是代码是代码就会改。生产环境里我们吃过一次亏更新了一个工具的参数定义之后正在进行的会话还缓存着旧版本的工具描述模型继续按旧参数调用结果大量报错。后来我们定了个规则Harness 的工具定义变更必须带版本号并为每个会话锁定版本。具体做法是工具注册表里每个工具都有一个version字段Harness 启动时从配置中心拉取一个“已发布版本号”。一个会话开始后它内嵌的 system prompt 里只描述该版本的工具。如果中途工具定义更新了已存在的会话不受影响它会继续沿用旧版本直到会话结束新会话使用新版本。这样即使上线后发现新版本有 bug只要把配置中心的版本号回退到旧版本就能快速止血不需要重新部署服务。代码回退本身也要纳入 Harness 的设计。我习惯给 Harness 做成“可回退执行体”所有工具变更通过 Git 管理发布时打 tag配置中心记录当前生效 tag。回退就是改一个配置项加重启实例整个过程控制在几分钟内。Agent 项目的发布频率远高于传统后端几乎每天都有 prompt 调整、工具新增、技能更新没有一个顺畅的回退通路生产事故的恢复时间会被拉得很长。这一块我会在第五章的可观测性部分再展开因为回退决策依赖的正是健全的监控数据。3. Loop 层可控的感知-规划-行动循环3.1 Loop Engineering 的含义与设计要点Loop 这个词在 Agent 工程里对应的是“Agent 怎么在单个任务内不断迭代”。从 ReAct 论文开始模型就被设计成“Thought → Action → Observation → Thought”的循环结构。但论文里的循环是一段代码逻辑生产里的循环必须是一套工程控制体系。Loop Engineering 要回答的就是这个循环怎么启动、怎么推进、怎么终止、怎么恢复。我把 Loop 拆成四个工程要点循环步进器每一次从“模型输出”到“工具执行”再到“结果回填”算一步。这个步进器要显式记录 step index上下文里永远能看到“这是第几步、上一步做了什么”。循环终止策略至少要有四种终止条件——正常完成模型给出 final answer、最大轮次比如 15 步、重复动作检测连续三步调用同一工具并传入相同参数判定死循环并终止、用户主动中断。循环观测每一步的状态都要写日志和 trace包括这一步的输入 token 数、工具返回耗时、模型选择的置信度如果有。循环恢复当循环被外力终止超时、报错时Agent 要有能力生成一个“阶段总结”把已完成和未完成的工作整理出来便于后续人工接手或重启任务。Loop Engineering 里最反直觉的一点是不是所有任务都需要长循环。短任务的循环反而要尽量少。我做过一次统计把一批客服工单任务分成两组实验一组允许模型最多循环 10 次另一组强制最多 3 次结果两组解决率差距不到 5%但 10 次那组的平均响应延迟是 3 次组的 4 倍。这说明很多任务里的额外探索并没有带来实质增益。正确的做法是给不同任务配置不同的最大轮次简单查询类任务 3 轮封顶复杂分析类任务允许 15 轮任务开始时先判断类型再确定循环上限。3.2 循环怎么收敛终止条件与最大轮次的工程配置终止条件是 Loop 层最容易写漏的部分。很多人只写了“模型输出 final answer 就结束”但在生产环境中模型经常不按套路出牌。我遇到过模型在工具调用失败后连续生成三四种不同的“绕过方式”每种都在调用同一个被权限拦截的工具——模型根本没有意识到自己陷入死循环。后来我给循环加了三道终止保险第一道是显式轮次计数器。系统在上下文中写入step_count模型每执行一步自增一次。轮次上限按任务类型动态配置比如我们用max_steps_map做映射。当计数器到达上限前两步系统会注入一条提醒“你还有 2 次尝试机会请优先给出一个基于当前信息的答案即使信息不完整。”第二道是重复动作检测器。Harness 会把最近五步的工具调用记录成一个签名列表签名包括工具名 参数 hash。如果队列里出现三次完全相同的签名就认为模型在兜圈子循环层直接介入切断该动作路径并给模型注入提示“你刚才已经尝试过同样的操作三次结果不变。请总结当前状况更换策略或直接输出最终答案。”第三道是超时熔断。整个循环有一个真实的墙上时钟时间上限比如 120 秒到点强制终止并生成阶段总结。这里有个细节上下文步数的计数和墙上时钟的计时经常不一致因为一次工具调用可能就要等 20 秒。所以我把两个上限都保留哪个先到按哪个算防止出现“步数没超但用户已经等了五分钟”的情况。收敛后的输出也要标准化。我要求终止后的输出必须包含三个字段final_answer给用户的最终回答、actions_taken这个任务里实际执行的动作清单、statussuccess / partial / failed。这三个字段对后面的 Graph 编排和人工审核特别有用因为它们让“一个 Agent 单元的运行结果”变成了可以被其他程序消费的结构化数据。3.3 循环引发的经典故障self-referencing loop 与多窗口混乱Loop 层在生产里有两个高频故障我自己都踩过你有八成概率也会遇到。第一个是自我引用循环。如果你用对象图序列化 Agent 的内部状态比如把session对象、tool对象、memory对象互相嵌套保存很容易碰到一个异常self referencing loop detected for property xxx。翻译过来就是对象 A 引用对象 B对象 B 又引用回对象 A序列化的时候无限递归。我第一次看到这个错是半夜两点当时还以为是模型出 bug 了后来才发现是我把一个 manager 对象塞进了另一个对象的字段里而那个对象又反向挂了一个 manager 引用。解决思路有几种。最小改动是给序列化函数加引用追踪比如 Python 的json.dumps加defaultstr或者用专门的处理函数跳过循环引用更工程化的做法是设计纯粹的 DTO数据传输对象Agent 内部状态和存储层解耦内存对象不进序列化通道需要持久化的字段单独映射到一个扁平的 dict。我强烈建议用第二种宁可多写几个转换函数也不要让序列化问题反复咬你。第二个是高并发下的多窗口上下文混乱。当同一个 Agent 服务要处理大量会话而你在上下文管理里不小心用了共享变量比如把当前对话列表放到一个类级 list 而不是请求级变量就会出现 A 会话的工具调用结果被 B 会话读到。症状就是用户明明问的是“上周的报表”Agent 回复里却出现了另一个用户的数据。排查过这种问题的都知道它是最恶心的那种 Bug因为不是必现只在并发高峰期出现。治本方案就是前面说的Harness 无状态化状态全进外部存储。每个请求进来从session_id拉取属于自己会话的上下文构造独立的上下文对象全程不碰任何全局可变状态。代码审查时重点关注有没有把可变对象挂在 class 属性或模块全局变量上标准是任何需要变更的数据都必须通过参数传递而不是共享。另外就是线程模型的确认如果用 Python 的异步框架注意协程之间的变量隔离是天然有的但如果你在 async 函数里调用了同步阻塞的共享变量读写就要格外小心。3.4 循环观测与调试把中间状态变成可回放的数据调试 Agent 比调试普通后端难因为它的行为不是确定性的。同一个 prompt 两次运行结果可能不同。所以循环层必须从第一天就建立观测能力否则出了问题你只能瞪着眼猜。我要求的观测数据包括三块步级 trace每一步的模型输入前几步的截断版本、模型输出、工具调用参数、工具返回结果、耗时、token 消耗。这些 trace 要落盘并和session_id、step_index建立索引。循环快照每隔三步或者在终止时对当前会话完整状态做一次快照包括完整对话历史、工具注册表版本、已用步数。回放功能能够把一个真实会话的 trace 按原样重放回放时可以修改某一步的模型输出观察后续偏差。这是评估和调试的利器。我分享一个具体的调试案例。有一个 Agent 老是答非所问正常思路是调 prompt但我通过步级 trace 发现了真正的原因它在第三步调用“搜全网资料”工具时传入了用户提问中的全部关键词导致返回了海量无关文章后续步数全部被噪音信息带偏。修复方式不是在 prompt 里说“请提炼关键词”而是在工具描述里明确写“一次最多接受 5 个关键词多余的去重后丢弃”。这就是观测驱动优化的标准路径问题定位在循环数据解决在 Harness 工具定义而不是盲目调提示词。4. Graph 层把复杂任务变成一张可执行的地图4.1 从线性链到有向图为什么需要 Graph当任务超过一定的复杂度单 Agent 的线性循环就不再够用。一方面有些步骤天然可以并行比如同时检索三个来源另一方面有些步骤之间有严格依赖比如先做数据清洗再做模型分析。如果用“if-else 堆逻辑”的方式串起这些步骤代码会变得像一团意大利面而且模型无法看到全局流程无法在步骤间做好衔接。Graph 层的核心思想是把任务拆成节点Node和边Edge。节点是一个具体的执行单元可以是“调用一个带 Prompt 的模型”“执行一个 Harness 工具”“发起一个子 Agent”“等待人工审批”边定义节点之间的流转条件包括顺序流、条件分支、并行聚合、循环回退。做成有向图而不是简单的线性链有一个实际收益天然支持并行和容错。比如一个“生成周报”的任务可以拆成“数据分析”“竞品动态汇总”“历史周报回顾”三个并行节点三个结果都完成后聚合到“生成正文”节点。如果其中一个并行分支失败了Graph 引擎可以只重试该分支而不是整个任务重来。Graph 还是一种对人和机器都友好的中间表示。对机器来说它是可执行的 DAG有向无环图可以用 DAG 校验器做静态检查能提前发现环依赖、孤立节点、缺少入口等结构性问题对产品经理来说它又是一份可视化的流程图可以直接在界面上调整节点顺序和分支条件不需要写代码。这也解释了为什么很多 Agent 开发框架都推出了图形化搭建工具比如 Snap Graph Builder底层做的事情就是把节点参数和边条件映射成一个可运行的图。4.2 节点、边、状态Graph Builder 的使用要点如果你第一次用图形化 Graph Builder我建议别急着画流程先把节点类型定清楚。我用过的工程实践把节点分成四类LLM 节点核心是 prompt 模板、模型参数temperature、max_tokens、输出解析规则。这里要注意 prompt 模板里引用的变量名必须和上游节点的输出字段严格对应。工具节点本质是 Harness 工具的一个影子引用配置项包括工具名、入参映射、超时时间、失败重试次数。条件节点很轻量只有一条判断逻辑比如“上一步输出中是否包含needs_approval字段”按判断结果走不同的边。聚合节点把多个并行分支的结果合并成一个结构体供后续节点使用。我通常会在这个节点做字段校验确保下游必需的字段都齐了再放行。边条件的定义是另一个容易出错的点。我给团队定的规范是边的条件必须显式写在边的定义里不能靠节点内部的逻辑做隐式流转。比如“如果用户意图是退货就进入退货流程”这个分支应该在边条件里写if intent refund而不是在节点里写死一个同意的逻辑。这样做的原因是Graph 的可维护性全部来自它的显式性——图一看就知道有哪些路径而藏在节点里的逻辑会让图失去表达能力。Graph 的状态管理尤其要注意“执行快照”的粒度。我实践下来的经验是每个节点完整执行前先记录一个 checkpoint内容包括节点入参、依赖的上游节点值、当前图中的游标位置。节点失败重试时直接从 checkpoint 恢复而不是从头开始整张图。这类设计配合上持久化存储能让一张多节点的大图跑上几个小时而不必担心中间崩溃后一切重来。4.3 商图与局部到全局复杂工作流的化简思路Graph 一旦变大有两种很自然的化简需求。第一种是“折叠”。当一个复杂的子图只在特定条件下才会执行我不需要把它所有的细节都展示在主图上而是把它打包成一个“子图节点”外部只暴露输入输出和成功失败状态。这个概念很像图论里的商图Quotient Graph把同类的节点合并成一个等价类用等价类之间的边表示原图中所有同类节点之间的关系。工程上商图帮助你在大图上只呈现主干路径把分支细节折叠到子图里让一张一百个节点的图缩减到十几条主线。第二种是“局部到全局”的信息聚合。这其实是借鉴了图神经网络中 Local-to-Global 的信息传递模式信息先在局部相邻节点之间交换再逐层传播最终形成全局视角。我做一个多 Agent 协作系统时就是这么设计的每个 Agent 只掌握自己小局部的信息比如各自的工具结果通过一层“路由节点”把局部摘要上传给上一层协调者协调者在全局层面汇总决策后再下发给各 Agent。这种分层聚合的信息流比把所有 Agent 的所有上下文都汇聚到一个超大 prompt 里更能控制 token 成本也更加符合“商图”中的逐层抽象思想。一个具体的例子假设你要让 Agent 分析一份合同的风险点。局部层每个节点负责一个章节——采购条款、交付条款、保密条款——各分析各的中间层把一个“章节风险摘要”上传给全局节点全局节点汇总后判断哪些风险跨越多个章节最后生成整份合同的综合风险报告。如果不用这种聚合结构而是把整份合同塞进单个模型里token 超限不说模型也会在长文本末尾丢失对前面章节的关注度。4.4 图的运行语义与容错设计Graph 引擎的运行语义值得仔细设计。我习惯用状态机驱动每个节点有一个状态pending、running、succeeded、failed、skipped、retrying图引擎维护一个待执行队列每完成一个节点就检查出边条件把满足条件的下游节点加入队列。这样保证每个节点只执行一次也方便失败后定点重试。容错方面我认为关键是在设计时想清楚三类失败节点级别失败工具超时、模型返回格式不可解析、下游 API 报错。策略是固定的重试次数通常 2 次指数退避重试耗尽后走到失败分支。图级别失败某一个关键节点的失败导致整张图无法继续。策略是定义一个“降级路径”比如主 Agent 失败后切到备用的规则引擎或通知人工处理。资源级别失败比如并发配额不足、模型限流。这种不能和业务错误混在一起图引擎应该暂停等待资源恢复而不是立刻把节点标记为失败。我在实践里给这种情况单独设了一个waiting状态配合一个资源监控器做唤醒。还有一个小细节Graph 里允许引入循环边即动态回退。比如一个“生成内容→内容审核”的图审核不通过时要返回“生成内容”节点重新生成。工程上把这个叫带环的图但在执行时要限制最大回退次数防止死循环。我在聚合节点里加了一个revision_count字段超过三次直接转入人工审核分支。这种带权重的循环控制本质上就是 Loop 层的终止策略延伸到了 Graph 层两层在这里衔接得非常自然。5. 三层架构的生产实践从选型到踩坑5.1 一套可行的参考架构与选型建议如果从零开始搭一个生产级 Agent 系统我不会一开始就引入大而全的 Agent 框架而是先按三层结构自己搭一个最小可运行版本。参考架构大概是这样的接入层API 网关 会话管理负责认证、限流、路由。所有 Agent 请求进来先走这一层和普通后端服务没有区别。Harness 服务无状态工作节点里面装模型客户端、工具执行器、技能加载器、上下文组装器。可以按业务维度分多套 Harness比如“客服 Harness”带客服技能“代码助手 Harness”带代码工具。Loop 控制器和 Harness 服务在同一个进程内维护 step 计数、终止策略、trace 采集。Graph 引擎独立的编排服务负责加载图定义、维护节点状态、调度节点执行。每个节点实际执行时要么调用 Harness 服务发起一次“带工具的 LLM 调用”要么直接调用内部工具服务。状态存储Redis 存短期会话状态和锁关系数据库存会话历史和审计日志对象存储放 trace 和快照文件。评测与回放服务定期用历史会话 trace 做回归评测新 prompt 或新工具上线前先跑一轮离线评测。选型上语言层面我用 Python 比较多因为模型生态最成熟但如果你的服务对性能和并发要求极高用 Rust 写 Harness 或推理网关是完全可行的Rust 的内存安全模型在并发环境下带来的稳定性收益很明显。框架层面别过度纠结尽量选支持图拓扑灵活定义的方便你把 DAG 玩出花来。模型层面我们主要接 DeepSeek 的开放 API同时也预留了 OpenAI 兼容协议的切换能力——这是 Harness 层设计成协议无关的直接结果模型供应商根本不影响上层的工具链和编排逻辑。5.2 并发、限流、降级的工程细节“AI Agent 怎么扛并发”这个问题光说“加实例”是不够的。我给你一个可以落地的处理链路第一入口限流。网关侧按用户维度和会话维度双限流单用户每秒最多 N 个请求、单会话同时最多一个运行中的任务。这是为了避免一个用户开多个请求把自己和你后端打爆。第二模型侧配额管理。如果你接的模型 API 有 RPM/TPM 限制Harness 层要做配额预估。每次请求前根据上下文 token 数估算本次调用需要消耗的 model token减去一个安全余量比如 10%再加上一个滑动窗口计数器。如果估算后配额不足请求排队而不是报错。第三工具侧熔断。给每个外部工具服务配一个熔断器Circuit Breaker连续失败 5 次进入 open 状态后续请求直接走 fallback。常见的 fallback 实现是返回一个“该服务当前不可用请尝试其他方案或提供基础信息”的结构化消息让模型自己选择变化策略。第四系统级降级。当整体负载超过容量上限我会开启“精简模式”关闭所有非核心技能限制最大循环轮次把温度调低以减少生成长度。有一个真实的经验最影响延迟的不是模型本身而是循环轮次。把最大循环从 10 降到 5整体 P95 延迟能下降 60% 左右这种降级带来的性能收益远大于堆机器。5.3 可观测性trace、回放与评估Agent 系统没有传统意义上的“堆栈日志”就能定位问题的可能所以可观测性必须做三层。第一层是全链路 trace从网关收到请求开始到 Graph 引擎选路到 Harness 跑每一步最终响应返回全程串成一条 trace ID。trace 里除了耗时还必须带 token 消耗和每步的工具调用记录。第二层是会话快照把完整对话历史、系统提示词版本、工具版本在任务结束时落盘方便事后完整重建现场。第三层是离线评测每个生产版本上线前先用标注数据集跑一轮对比预测值和期望值这个环节自动化程度越高发版越安心。回放功能我前面提过这里再补一个实际案例。我们有一次收到用户投诉“Agent 推荐了一个已下架商品”线上排查时先把那条会话的 trace 拖出来从 trace 里看到模型在第三步调用了“查询商品库存”工具返回结果是 “status: ok, data: {……” 而且和前端库存接口的结果不一致。进一步查是“查询商品库存”工具连接的是数据仓库而不是实时库存服务数据仓库里商品状态更新滞后。如果在传统后端这种问题可能被日志冲掉但在 Agent 系统里trace 回放能非常精确地把根因定位到“哪一个工具、哪一行数据、什么时间打的什么接口”。评测部分再给一个具体指标我用“工具调用准确率”作为 Harness 的核心指标计算方法很简单模型调用的工具名 参数是否完全符合预期从标注结果判断。这个指标在 90% 以下时别急着上生产先把工具描述、schema 校验和技能选择做扎实。至于模型本身生成质量的评估用 LLM-as-Judge 跑对比评测但要注意控制评测成本一天最多跑一两轮全量中间用采样集做快速回归。5.4 安全合规与模型能力的边界Agent 接入生产必然要面对安全合规问题我的经验总结成一句话对 Agent 的信任应该是零信任而不是放权。落地时具体做这几件事数据分级按数据敏感程度分 L1/L2/L3L3 级数据身份证、手机号、支付信息默认禁止进入模型上下文Harness 层做脱敏替身。比如用户真的需要查询订单Harness 把真实姓名替换成一个占位符模型处理完后再由 Harness 映射还原。工具调用权限矩阵每个技能声明自己能访问的工具每个工具声明自己的数据域和操作类型读/写/删除。Graph 引擎执行节点时校验一次Harness 执行工具时再校验一次双重保险。比如“删除订单”这种高危操作我直接在 Harness 里禁止任何模型上下文中出现只开放给人工审核通过后的确定性代码调用。审计日志强制开启所有模型输入、工具调用、输出内容都必须留存。合规审计时会要求“能解释为什么模型做了某个操作”。注意审计日志不能和业务日志混在一起建议单独存储并做访问控制。边界声明不要指望模型永远知道自己的能力边界。在 Harness 的工具描述里显式写“本工具无权访问 X 系统”比在系统提示词里说“请勿访问 X”效果好得多。模型会尊重工具的显式约束但很容易忽略全局口号式的禁令。最后我想特别提醒一点关于“模型能力边界”的思路变化现阶段不要把 Agent 设计成“全知全能”更务实的路线是让 Agent 清楚知道“我不知道什么”遇到超出自身能力范围的事应该返回一个明确的“需要人工接手”信号而不是硬着头皮生成一个看似完整的假答案。Graph 层可以为这种信号专门设计一条“转人工”边Loop 层则可以在第 N 轮失败后主动触发该信号。一个“知道自己不行”的 Agent在生产环境里远比“自信地胡说”的 Agent 更可靠、更受欢迎。6. 实战总览与经验沉淀写到这里主体内容已经完整Harness 管好工具和工作台Loop 管好循环与收敛Graph 管好全局编排与容错。最后分享一个沉淀下来的项目结构建议你可以直接作为目录模板使用agent-project/ ├── harness/ # 工具注册表、执行器、技能加载、权限校验 │ ├── tools/ │ ├── skills/ │ └── executors/ ├── loop/ # 循环控制器、终止策略、重复检测、trace采集 ├── graph/ # 图定义、Graph引擎、状态机、checkpoint ├── store/ # 会话状态、审计日志、trace存储 ├── eval/ # 离线评测、回放工具、标注数据 └── api/ # 网关、会话管理、路由这个结构的好处是每一层都有明确的边界每一层的代码可以独立测试而且新人接手项目时看到目录结构就能立刻抓住系统的运行脉络不需要读几万行代码才能理解全貌。生产环境里我还有一个“发布前检查清单”的习惯每次上线新版之前都要过一遍工具 schema 是否和代码一致、技能加载是否按需生效、循环终止条件是否覆盖新场景、Graph 是否存在潜在的环依赖、trace 日志是否完整记录模型输入输出、有无新增高危操作未加权限校验。这六项检查全部通过我才放心地把新版本推到生产。其实搭一套 Agent 系统的过程和搭传统后端没有本质区别都是把不确定的东西变得确定把看不见的状态变得可观测把失控的风险通过架构设计兜住。区别只是这次我们控制的对象从函数调用变成了模型行为。把 Harness、Loop、Graph 三层分别管住就有足够底气去应对复杂任务和突发事故。