1. 从三个词说起为什么Agent工程需要分层思考第一次接触Agent开发的人往往会被各种概念砸晕。Harness、Loop、Graph这三个词单独看都不难理解但放在一起就让人犯迷糊——它们到底是什么关系是三个独立的工具还是同一套体系的不同层面我在过去一年多的Agent项目实践中踩过不少坑也逐步摸索出一套相对清晰的分层认知框架。这篇文章就把我对这三层架构的理解和实际落地经验完整地分享出来。先说结论Harness是执行外壳Loop是运转机制Graph是编排逻辑。三者不是并列关系而是从外到内、从底层到上层的递进关系。你可以把它们想象成一家餐厅的运营Harness是厨房的硬件设备和工位布局Loop是厨师接单、做菜、出餐的循环流程Graph则是整个后厨的协作网络——谁负责切菜、谁负责炒菜、谁负责摆盘以及这些角色之间怎么传递信息。这套分层思路解决的核心问题是当Agent从Demo走向生产环境时复杂度会急剧上升。如果不在架构层面做好切分代码会迅速变成一团乱麻——工具调用逻辑和流程控制混在一起状态管理和任务编排纠缠不清最后连自己都改不动。我见过太多项目在原型阶段跑得挺好一到生产环境就各种超时、死循环、状态丢失根子往往不在模型能力上而在架构分层没做好。这篇文章适合三类人看一是正在做Agent开发但感觉代码越写越乱的工程师二是准备把Agent项目从原型推向生产的技术负责人三是对Agent架构感兴趣、想了解工程实践细节的开发者。不管你是用Python还是其他语言不管你是基于哪个模型做开发这套分层思路都是通用的。接下来我会逐层拆解每一层都会讲清楚它解决什么问题、核心设计要点是什么、实际写代码时怎么落地以及我在实践中踩过的那些坑。2. Harness层Agent的执行外壳与工具基座2.1 Harness到底解决什么问题Harness这个词在工程领域出现得很早原本指的是测试脚手架或者执行框架。放到Agent语境下它的角色是为Agent提供一个标准化的执行环境让模型能够安全、可控地调用外部工具和资源。你可以把Harness理解成Agent的“手和脚”。模型本身只是一个大脑它能思考、能推理但没法直接读文件、发请求、查数据库。Harness就是把这些能力封装成标准接口让模型可以通过统一的协议来调用。没有HarnessAgent就是一个只会说话的摆设。我刚开始做Agent的时候图省事直接在业务代码里写工具调用逻辑。结果就是每加一个工具就要改一遍主流程工具之间的错误处理各写各的日志格式五花八门。后来把这些逻辑抽出来做成统一的Harness层代码量少了三分之一可维护性提升了好几个档次。Harness层的核心职责包括工具注册与发现、参数校验与转换、执行隔离与超时控制、结果格式化与错误封装。这五件事看起来简单但每一件都有不少细节要处理。2.2 工具注册与发现的设计要点工具注册看起来就是维护一个字典但实际上要考虑的问题不少。首先是工具的元信息描述——模型需要知道每个工具叫什么、干什么用、需要什么参数。这个描述的质量直接影响模型选择工具的准确率。我的经验是工具描述要写得像给新人看的API文档一句话说清楚功能参数说明要具体到格式和取值范围最好附上一两个调用示例。我试过同一个工具用不同详细程度的描述模型选择正确率能差出20%以上。# 工具注册的典型结构 tool_registry { search_database: { description: 根据关键词搜索产品数据库返回匹配的产品列表, parameters: { keyword: {type: string, required: True, description: 搜索关键词}, limit: {type: integer, required: False, default: 10, description: 返回结果数量上限} }, handler: search_database_impl } }工具发现机制则决定了模型在运行时怎么找到合适的工具。最简单的做法是全量注入——把所有工具描述都塞进提示词里。但工具一多提示词就会爆炸不仅浪费token还会降低模型的选择准确率。更好的做法是分层发现先根据任务类型筛选出候选工具集再让模型从中选择。我在一个项目里把工具按领域分成五组每组不超过八个工具模型的选择准确率从67%提升到了89%。2.3 执行隔离与超时控制这是Harness层最容易被忽视但最不能省的部分。Agent调用的工具可能涉及网络请求、文件读写、数据库查询任何一个环节出问题都可能拖垮整个流程。执行隔离的核心思路是每个工具调用都在独立的上下文中执行一个工具挂了不能影响其他工具。具体实现上可以用进程池、线程池或者异步任务来实现隔离。我一般用异步方式配合超时控制效果比较稳。超时控制要分层设置单个工具调用有超时整个任务有总超时重试次数也要有上限。我踩过的一个坑是只设了单次超时没设总超时结果一个任务里调了二十次工具每次都卡在超时边缘整个任务跑了十几分钟才结束。后来加了总超时控制超过阈值直接中断并返回部分结果用户体验好了很多。注意超时时间不要设得太短。我见过有人把工具超时设成500毫秒结果稍微慢一点的数据库查询全部失败。建议根据实际工具的P99耗时来定一般留2到3倍余量。2.4 结果格式化与错误封装工具返回的结果需要经过标准化处理才能交给模型。原始结果可能是JSON、XML、纯文本甚至二进制数据模型需要的是结构清晰、语义明确的文本描述。我的做法是定义一个统一的结果格式包含状态码、数据体和错误信息三个部分。成功时返回数据体失败时返回错误码和可读的错误描述。这样模型能清楚地知道工具调用是成功了还是失败了失败的原因是什么从而决定下一步怎么做。错误封装要特别注意不要暴露敏感信息。数据库连接字符串、内部IP地址、堆栈跟踪这些内容绝对不能直接返回给模型。我一般会做一层错误映射把技术错误转换成业务语义的错误描述。比如“Connection refused”转换成“数据库暂时不可用请稍后重试”。3. Loop层Agent的运转机制与循环控制3.1 Loop的本质让Agent动起来如果说Harness给了Agent手脚那Loop就是让这些手脚动起来的神经系统。Loop的核心是一个循环模型思考、选择工具、执行工具、观察结果、继续思考直到任务完成或者达到终止条件。这个循环看起来简单但实际写起来要考虑的事情很多。什么时候该继续循环什么时候该停下来循环次数上限设多少中间结果怎么管理这些问题不处理好Agent要么陷入死循环烧钱要么过早停止任务没完成。我刚开始写Loop的时候最朴素的做法就是设一个最大轮次比如10轮跑完就停。结果发现有些简单任务3轮就完成了白白浪费了7轮有些复杂任务10轮根本不够跑到一半就被迫中断。后来改成动态判断模型输出中包含明确的完成信号就停止否则继续同时设一个硬性上限兜底。3.2 循环终止条件的精细设计终止条件是Loop层最核心的设计点。我总结了几种常见的终止信号显式完成信号模型明确输出“任务完成”或者返回最终答案无工具调用模型这一轮没有选择任何工具说明它认为不需要更多信息了达到最大轮次硬性上限防止无限循环总超时触发整个任务的时间预算耗尽连续错误连续多轮工具调用失败继续下去没有意义这几种条件要组合使用不能只依赖一种。我一般会同时设置显式完成信号、最大轮次和总超时三个条件任何一个满足就终止循环。这里有个细节最大轮次设多少合适我的经验值是简单问答类任务3到5轮中等复杂度的任务8到12轮需要多步推理的复杂任务15到20轮。当然这只是一个参考具体要根据实际场景调整。我建议在开发阶段把轮次上限设得宽松一些观察实际任务的轮次分布再根据P95值来设定生产环境的上限。3.3 中间结果的管理策略Loop每跑一轮都会产生中间结果——模型的思考过程、工具调用的输入输出、观察到的信息。这些结果怎么管理直接影响Agent的上下文质量和token消耗。最简单的做法是全量保留每一轮都把之前所有结果拼进提示词。这样做的问题是token消耗随轮次线性增长跑到后面提示词长得吓人不仅贵而且模型注意力容易分散。我的做法是分层管理最近两轮的完整结果保留更早的结果做摘要压缩。摘要只保留关键信息——调用了什么工具、得到了什么核心结论、有没有遇到错误。这样既保留了必要的上下文又控制了token消耗。实测下来同样的任务分层管理比全量保留能省40%左右的token而且任务完成质量没有明显下降。还有一种情况需要特别注意当工具返回的结果特别长时比如查询数据库返回了几百条记录直接塞进上下文会瞬间撑爆token限制。这时候需要在Harness层做预处理只返回最相关的部分或者在Loop层做截断和摘要。3.4 循环中的状态传递Loop的每一轮之间需要传递状态。这个状态包括当前的任务目标、已经收集到的信息、已经尝试过的操作、当前的进展阶段。状态管理做得好Agent就能在正确的轨道上推进做得不好Agent就会反复做同样的事情或者丢失关键信息。我用过几种状态管理方案比较下来最稳妥的是显式状态对象。定义一个状态结构包含任务描述、已完成步骤、待办事项、关键发现等字段每一轮循环开始时把状态注入提示词循环结束时更新状态。这样状态的变化是可控的、可追踪的出问题了也容易排查。实操心得在状态对象里加一个“已尝试操作”列表记录每一轮调用了什么工具、参数是什么、结果如何。这样当Agent想重复调用同一个工具时可以在提示词里提醒它“你已经尝试过这个操作结果是XXX请考虑其他方案”。这个小技巧能有效减少无效循环。3.5 循环中的错误恢复工具调用失败是常态网络抖动、服务限流、参数错误都可能发生。Loop层需要有一套错误恢复机制不能一遇到错误就整个任务失败。我的策略是分级处理可重试的错误如网络超时自动重试重试次数不超过3次不可重试的错误如参数格式错误把错误信息返回给模型让它调整参数后重试连续多轮出现同类错误则终止循环返回当前已获得的结果和错误说明。这里有个容易忽略的点错误信息要足够具体模型才能做出正确的调整。比如“参数错误”这种模糊的描述模型看了也不知道怎么改。要具体到“参数limit的值必须是正整数你传入的是-1”。4. Graph层Agent的编排逻辑与多角色协作4.1 从Loop到Graph为什么需要编排层单个Loop能处理的任务是有限的。当任务需要多个角色协作、需要并行处理、需要条件分支时单靠一个Loop就力不从心了。Graph层就是来解决这个问题的——它把多个Loop组织成一张有向图每个节点是一个处理单元边定义了流转规则。举个例子一个客服Agent需要先理解用户意图然后根据意图分流到不同的处理流程——查订单走订单查询流程退换货走售后流程投诉走投诉处理流程。每个流程内部可能又是一个或多个Loop。这种结构用Graph来表达就非常清晰。Graph层的核心价值在于把复杂的任务拆解成可管理的节点每个节点职责单一节点之间的流转规则明确。这样不仅代码好维护而且每个节点可以独立测试和优化。4.2 节点设计单一职责与可组合性Graph中的每个节点应该只做一件事并且把这件事做好。我见过有人把意图识别、信息提取、回复生成全塞在一个节点里结果这个节点又大又难改改一处影响一片。好的节点设计应该像乐高积木每个积木形状简单、接口标准但组合起来能搭出各种东西。具体来说每个节点要有明确的输入输出定义输入是什么格式、输出是什么格式都要约定清楚。节点内部可以是一个Loop也可以是一次简单的模型调用甚至是一个纯规则判断。节点的粒度怎么把握我的经验是如果一个节点的代码超过200行或者需要修改时经常影响到其他节点那就说明粒度太粗了应该拆分。反过来如果两个节点总是一起出现、从不单独使用那可能应该合并。4.3 边与流转规则的设计边定义了节点之间的流转关系。最简单的边是无条件流转——A节点执行完直接到B节点。但实际场景中更多的是条件流转根据A节点的输出结果决定下一步走B还是走C。条件流转的关键是条件表达式的设计。条件要写得清晰、无歧义而且要考虑所有可能的情况。我踩过的一个坑是条件覆盖不全某个边界情况没有对应的分支导致流程卡住。后来养成了习惯每写一个条件分支都要问自己“如果条件不满足且没有其他分支匹配会发生什么”。还有一种特殊的边是循环边——从后面的节点回到前面的节点。这种边要特别小心必须配合明确的退出条件否则容易形成死循环。我一般会在循环边上加一个计数器超过阈值就强制跳出。4.4 并行与汇聚的处理有些任务可以并行处理比如同时查询多个数据源然后汇总结果。Graph层需要支持并行节点和汇聚节点。并行节点的实现方式取决于你的运行时环境。如果是异步框架可以用asyncio.gather同时发起多个任务如果是多线程环境可以用线程池。关键是要处理好超时和错误——并行任务中有一个失败了怎么办是等所有任务完成还是快速失败我的做法是对于相互独立的任务等所有任务完成失败的任务返回错误信息成功的任务返回结果最后在汇聚节点统一处理。对于有依赖关系的任务则用快速失败策略一个失败就取消其他任务。汇聚节点要把多个并行结果合并成一个。合并逻辑要根据业务需求来定有时候是简单拼接有时候需要去重、排序、筛选。我一般会在汇聚节点做一层数据清洗把并行结果整理成统一的格式再往下传。4.5 状态在Graph中的传递Graph中的状态传递比Loop中更复杂因为涉及多个节点和分支。我的做法是定义一个全局状态对象所有节点都从这个对象读取输入、向这个对象写入输出。这样节点之间不需要直接通信通过状态对象解耦。全局状态对象要设计好结构按功能分区任务元信息、各节点的输出、全局变量、错误记录等。每个节点只读写自己关心的部分避免相互干扰。注意全局状态对象不要无限增长。每个节点写入的数据要有清理机制否则跑长流程时内存会持续上涨。我一般会在节点执行完成后把不再需要的中间数据标记为可回收定期清理。5. 三层架构的协作与生产环境落地5.1 三层之间的接口设计Harness、Loop、Graph三层之间的接口要清晰定义这样才能独立演进。Harness对Loop暴露的是统一的工具调用接口Loop不需要知道工具内部怎么实现Loop对Graph暴露的是任务执行接口Graph不需要知道Loop内部怎么循环。接口设计的关键是抽象要稳定。我一般会定义几个核心接口工具调用接口、任务执行接口、状态读写接口。只要这些接口不变各层的内部实现可以自由调整。实际写代码时我习惯用依赖注入的方式把各层组装起来。Graph层持有Loop层的引用Loop层持有Harness层的引用但都是通过接口引用而不是具体实现。这样测试的时候可以方便地替换mock实现。5.2 可观测性建设生产环境的Agent系统可观测性是生命线。三层架构每一层都需要有日志、指标和追踪。Harness层记录每次工具调用的耗时、成功率、错误类型Loop层记录每轮循环的轮次、token消耗、终止原因Graph层记录每个节点的执行时间、流转路径、整体任务耗时。这些数据汇总起来才能定位性能瓶颈和异常点。我用过的方案是在每层的关键位置埋点输出结构化日志然后用统一的日志收集系统聚合分析。追踪ID贯穿三层这样从一次任务执行可以追溯到每个工具调用的细节。5.3 性能优化实践三层架构的性能优化要分层进行。Harness层的优化重点是减少工具调用的开销——连接池复用、结果缓存、批量调用Loop层的优化重点是控制循环轮次和token消耗——提示词精简、中间结果压缩、提前终止Graph层的优化重点是并行化和路径优化——能并行的不串行能跳过的节点不执行。我做过一个对比测试同样的任务优化前平均耗时12秒、消耗8000 token优化后平均耗时4秒、消耗3500 token。优化手段主要是Harness层加了结果缓存Loop层做了中间结果摘要Graph层把两个串行节点改成了并行。5.4 安全与权限控制Agent系统能调用工具、访问数据安全问题不能忽视。我的做法是在Harness层做统一的权限校验每个工具调用都要检查当前上下文是否有权限敏感操作需要额外的确认步骤。权限模型我一般用基于角色的访问控制定义几种角色每种角色能调用的工具集合不同。Agent在执行任务时携带角色信息Harness层根据角色决定是否放行。还有一个容易被忽视的点是输入输出的内容安全。工具返回的结果可能包含敏感信息需要在Harness层做过滤。模型的输出也可能包含不当内容需要在返回给用户前做检查。5.5 常见问题速查问题现象可能原因排查方向解决方案Agent陷入死循环终止条件未触发检查循环计数和终止信号增加硬性轮次上限和超时工具调用总是失败参数格式不匹配查看工具调用的入参日志在Harness层加参数校验和转换任务执行到一半卡住某个节点等待超时检查各节点执行时间分布设置节点级超时和降级策略token消耗异常高中间结果未压缩分析每轮提示词长度启用中间结果摘要和截断并行任务结果丢失汇聚逻辑有bug检查汇聚节点的合并代码增加结果校验和补偿机制状态数据不一致并发写入冲突检查状态读写时序加锁或改用不可变状态6. 一些踩坑之后的个人体会这套三层架构不是一开始就设计出来的而是在实际项目中逐步演化出来的。最开始我只用了一个大Loop所有逻辑都塞在里面跑简单任务没问题稍微复杂一点就各种问题。后来把工具调用抽出来做成Harness代码清晰了不少。再后来任务复杂到需要多角色协作才引入了Graph层。我的体会是架构分层要适度不要为了分层而分层。如果你的Agent只做简单的问答一个Loop加一个轻量Harness就够了没必要上Graph。但如果你的Agent需要处理多步骤、多角色、有条件分支的复杂任务那三层架构能帮你省很多事。还有一个很实际的建议在开发阶段就把可观测性做好。我早期为了赶进度日志打得很少结果线上出问题的时候两眼一抹黑只能靠猜。后来补日志花的时间比一开始就好好打日志多得多。最后分享一个小技巧在Loop层的提示词里加一句“如果你已经连续两次尝试同一个操作都没有成功请换一种思路”。这句话看起来不起眼但能有效减少Agent在同一个坑里反复摔跤的情况。我在多个项目里验证过加上这句话之后无效循环的比例明显下降。