大模型无法直接干活?自研Agent执行调度框架的六层架构与实践

发布时间:2026/10/6 10:38:18
大模型无法直接干活?自研Agent执行调度框架的六层架构与实践 1. 大模型很聪明却干不了具体活Agent落地卡在哪一环先说个我做技术架构这些年最深的感觉模型能力早就不是瓶颈了真正卡住人的是那一层“让大模型的判断变成真实系统动作”的胶水。你调一个接口拿到一段漂亮的中文回复系统却没动手做任何事用户的工单还在那儿躺着告警还在那儿响着数据还是乱的。你总不能靠人眼把模型的输出再复制粘贴到运维后台吧。我当时接了一个内部智能化改造的活需求听起来也不复杂让一个大模型充当“值班助手”用户跟它说“把华北区这台机器重启一下”“拉一下最近三个小时nginx的5xx错误”它得真的去执行而不是给出一段“建议您登录后台执行xxx”的废话。项目拖了两周团队越做越难受。问题不在提示词也不在模型选型而在一个特别不上台面的点大模型产出的意图和参数没有一条可靠的通道到达执行端。模型说“restart_server(timeout30)”但代码怎么知道该调用哪台机器、哪个Agent进程、用什么凭证、超时了怎么回滚这些活儿的复杂度比调API高一个数量级。这就是“Agent-Reach”这个项目最初的来源。我给它起的名字也很直白Reach让Agent的能力真正“够得着”外部世界。简单说它是一套面向大模型应用的自研Agent执行与调度框架把模型判定的意图翻译成真实系统的操作并且把执行结果、上下文、任务状态完整地送回模型做下一步判断。如果你也在做大模型自动运维、工单自动处理、测试环境自动巡检这类偏“动手”的场景这篇内容应该能帮你少走不少弯路。2. Agent-Reach的六层核心设计从意图解析到任务闭环项目做了三个月架构改了四版最终稳定下来的结构我习惯叫它“六层管道”。这六层分别负责一件事彼此之间通过事件消息解耦任何一层挂了其他层都能兜住。2.1 第一层入口层Reach Gateway入口层是所有外部请求统一进出的地方。它干的不只是转发还要做身份校验、租户隔离、请求去重、限流熔断。因为接入方既可能是用户对话框也可能是定时任务也可能是别的Agent发来的子请求所以我把Gateway做成了多协议兼容WebSocket走实时对话、HTTP走同步调用、MQ走异步任务。意外的是这个层最消耗开发时间的不是协议解析而是超时策略设计。大模型思考要时间工具执行要时间前后两项加起来经常超过常规接口的30秒上限。最后我采用的是“两段式超时”第一段留给模型首字响应5秒第二段留给工具整体执行按工具类型区分比如命令执行类120秒数据查询类60秒文件分发类180秒。这样用户不会觉得“卡死”后端也不会傻等。2.2 第二层语义层Intent Resolver这一层负责把模型输出转成结构化指令。业界常用做法是“function calling JSON Schema”但实测下来有几个坑模型偶尔会返回残缺JSON偶尔会把参数名改掉偶尔会在should_return里塞额外文本。我当时没有追求“模型输出必须永远正确”而是加了一个修正器Normalizer用规则加小型校验模型做二次兜底。比如说模型输出{tool: restart_service, params: {host: 10.2.3.4, service: nginx, timeout: null}}这个timeout的 null 在真实系统里意味着“无限等待”我的修正器会把它替换成默认值30并且标记这一改动方便审计留痕。这类小细节初看没什么生产环境里能少一堆半夜告警。语义层还会做一件重要的事置信度分级。模型给出的每个动作修正器都会算一个分字段完整性、参数范围合法性、历史模式匹配度。低于0.6的指令不会直接执行而是转成“需人工确认”状态。分级规则参考下表置信度处理方式典型场景0.9-1.0自动执行参数完整、与历史执行模式一致0.7-0.89半自动执行参数合法但涉及非核心机器0.5-0.69人工确认涉及删除操作、生产环境、批次变更0.5拒绝执行参数缺失、工具名不存在、疑似恶意指令2.3 第三层调度层Orchestrator这一层是整个框架的发动机。它的核心职责是把一条复杂任务拆成多个可执行单元并且维护单元之间的依赖关系。比如“帮我看一下所有网关机的磁盘使用率如果哪台超过80%顺便查一下是哪个目录在涨”这个需求拆出来是两组动作批量查询 条件触发分析。Orchestrator会把这两组动作编排成依赖图前一组全部返回之后再对结果做条件筛选筛选命中才启动第二组。我在这里用了一个很轻量的实现每个动作节点是一个异步任务节点之间通过有限状态机流转。状态只有五种PENDING、RUNNING、SUCCEEDED、FAILED、BLOCKED。没必要上复杂的工作流引擎状态太多反而难排查。编排过程中上下文窗口的管理比想象中麻烦。每个节点的输入输出都会塞进一个Context Store我用的RedisTTL设24小时但模型上下文有长度上限所以需要对历史做裁剪只保留最近两轮的完整工具结果更早的做成摘要。这里用大模型给大模型做摘要成本可控效果远好于“截断式裁剪”。2.4 第四层执行层Reach WorkersWorker是真正动手干活的进程。我的设计是每种“工具族”一个Worker类型比如CommandWorker负责跑shell命令APICallWorker负责调内部系统接口FlowWorker负责编排子流程。Worker与调度层之间走的是gRPC双向流而不是HTTP轮询。这个选择有两个理由一是双向流能实时上报进度日志一行行往回传用户在界面上能看到滚动输出二是可以省去大量重复握手开销一个长连接不断复用。执行层还做了生死探针Worker每5秒上报心跳调度层记录上次心跳时间。如果超过15秒没有心跳任务会被重新分配到备用Worker并且标记原节点“疑似失联”。这层机制在测试环境用处不大但到了生产环境尤其是容器频繁迁移的时候可靠性全靠它撑着。2.5 第五层记忆层Trace Store做Agent的人最容易忽视的是“这个Agent上一次是怎么干成这件事的”。第五层就是解决方案每一次任务的执行轨迹包括意图、参数、调用链、中间日志、最终结果、耗时全部结构化存储。底层选了Elasticsearch主要是为了全文检索和耗时分析。这层最大的产出是几类东西同类型任务的历史平均耗时、历史成功率、历史常见失败原因。这些数据回流到语义层作为置信度计算和历史模式匹配的参考。这不只是一个“日志系统”它是Agent的行为记忆跑得越久越聪明。2.6 第六层反馈层Feedback Loop最后一层是闭环任务结束后模型会对执行结果做一次自评输出类似“成功/部分成功/失败”的判定同时附上理由。这个自评分会写回Trace Store参与后续任务的历史模式匹配。我遇到过最有意思的案例某次模型自评“部分成功”但人工复核发现其实全部成功只是用户提问含糊导致实际执行范围比预期小。这类数据多了以后我们就训练了一个单独的“结果判定小模型”不再完全依赖大模型自评准确率提升了不少。3. 让“睡着的Agent”真正醒来路由、注册与实例接管这一节说点偏底层的设计也是很多Agent框架最容易糊弄过去的部分Worker实例是怎么被找到、怎么被分配、又怎么被接管任务的。3.1 注册中心与实例指纹每个Worker启动后会向注册中心我用的是etcd写入自己的实例信息。实例信息包括Worker类型、IP、端口、当前负载、已执行任务数、资源水位CPU/内存。Key的设计是reach/workers/{type}/{instanceId}租约TTL设10秒每5秒续约。服务发现侧调度层订阅前缀任何实例变化都会实时推送到所有调度节点。这里有个细节值得单独说实例指纹。同一个Worker在一台机器上反复崩溃重启注册中心如果不加指纹会出现一堆僵尸实例。每次启动时生成一个随机ID但加上了“启动时间进程PID主机名”的复合标签。这样排查问题的时候一眼能看出来哪几个实例是同一台机器上的历史残留。我在生产环境里靠这个字段解决过至少三次“任务被分配到已死节点”的疑难杂症。3.2 任务分配策略任务分配我试过三种策略随机分配、轮询、一致性哈希。随机最简单但会导致某类任务全堆在一台机器上轮询平滑但没考虑负载一致性哈希能让相同特征的任务固定打到同一批节点利用局部缓存。最终我选的是“最小负载优先 一致性哈希兜底”。具体逻辑是新任务先查所有空闲Worker的实时负载选择负载最低的如果负载都差不多则按任务ID做一致性哈希保证相似任务尽量落在同一批节点上。分配策略对比策略优点缺点适用场景随机分配实现简单代码量少负载可能倾斜演示Demo、任务量小的内部工具轮询分配均衡无局部性缓存命中率低任务特征差异不大一致性哈希相同任务命中同节点缓存友好节点增减可能引发热点高吞吐、可重复执行的任务最小负载优先实时负载感知需要维护实时指标生产主方案3.3 实例失联与任务接管实例失联是我在压测和运维中反复遇到的场景处理逻辑必须想清楚。我的方案分三步调度器发现Worker心跳超时先把该Worker上所有RUNNING任务标记为“调度中”SUSPECTED。启动一个独立的接管队列逐个读取任务快照任务输入、已执行步骤、中间产物路径。重新分配任务到健康Worker并且带上“接管标记”新Worker会先执行一个补偿动作检查上一次操作是否已生效如果已生效则直接拉取结果不再重复执行。这个补偿动作特别重要不然会出现一个经典事故任务杀了一个进程实际杀成功了但因为Worker挂了没来得及上报结果接管Worker又杀了一遍返回“目标不存在”。这类重复执行在运维场景里风险极大。4. 我踩过的最深的坑长任务断了没人知道Agent-Reach开发过程中让我印象最深的不是架构设计而是一个特别具体的问题一个跑了好几分钟的长任务执行到一半连接断了整个链路没有任何一个人知道任务失败了。第一次复现这个问题的场景是这样的我们让Agent去批量迁移一批日志文件大约3.4GB跨机传输。命令执行Worker在上传进度条走到76%时因为网络抖动导致gRPC流中断。前端看到的是“等待中”用户以为还在跑Worker那边连接断了任务状态还是RUNNING调度器没收到完成消息也没收到失败消息。排查链路花了将近四个小时。最后的根因不是网络网络只是诱因是任务状态只存在内存里没有落盘也没有状态上报的超时机制。我们当时做了一张图把任务生命周期所有节点列出来才发现“连接断开”这个中间状态压根子没有定义。修这个问题的方案现在看也很常规但当时是真的踩疼了任务状态增加一个“UNKNOWN”阶段Worker端检测到流断开后主动上报一次“可能中断”事件。调度器对超过任务预估耗时20%还没结束的任务发起主动探活探活三次没响应强制判定失败并触发接管。任务快照每30秒写入本地磁盘一次至少保证断点恢复时不会丢失“从哪一步开始”的信息。之后我记得专门搞了一次故障注入演练人为杀掉Worker进程、人为关掉防火墙端口、人为把etcd租约时间改短每次都把“Agent-Reach能否自动恢复”作为验收标准。这一步的收益极高很多之前只在理论上讨论的边界情况在演练里全都暴露了出来。5. 从“能跑”到“扛住”超时、限流与服务分层项目到了第二阶段我把重心从“功能能用”转向“生产扛得住”。这个阶段调了很多参数改了无数版配置最有价值的经验集中在三块超时管理、限流策略、服务分层。5.1 超时不是越大越好很多人倾向于把各种超时都设得很长觉得“大模型慢是正常的”。但超时越长资源占用越久一个卡住的任务会拖垮整个执行集群。我最后的设计是分层超时每层独立且下层超时略小于上层。实际配置参考层级超时时间说明Gateway等待模型首字5秒超出直接返回“系统思考中请稍后”Gateway等待工具执行120秒可配置超时后先查任务状态再决定调度器等待Worker上报180秒超时后触发探活和接管Worker命令执行240秒对应内部命令自带超时保护外部API调用60秒对接第三方系统时的通用兜底这个设计既能保证用户感知流畅又不会让某个异常任务无限占用资源。每层超时触发后的行为都必须是幂等的因为超时和实际执行完成之间常常存在竞态。5.2 限流别只看QPSAgent场景的限流和普通API限流不太一样普通API看QPS就够了Agent要看的是并发执行中的总需求数。一个Agent任务可能同时拉起20个子任务如果只按入口QPS限流内部并发还是有可能把资源打爆。我的做法是在Gateway、Orchestrator、Worker三个层面分别设置额度Gateway每秒最多200个新任务进入。Orchestrator全局暂停队列最多300个待调度任务。Worker单机最多同时执行8个任务多余排队。超过额度的请求不会直接拒绝而是返回“排队中预估等待时间”由前端自动做轮询或webhook回调。这样做用户体验比“直接报错重试”好太多。5.3 服务分层核心链路和辅助链路拆开我犯过一个比较初级的错误把Agent的实时对话服务和内部的日志上报、trace写入、摘要生成全部混在同一组机器上。结果一度出现日志写入量大时实时对话卡顿的诡异问题。后续把服务按链路拆成三层核心链路Gateway Orchestrator Worker高性能要求独立部署独立扩缩容。辅助链路Trace Store写入、事件订阅、审计日志异步写入允许一定延迟。AI辅助链路结果自评、上下文摘要、置信度修正走独立模型池避免抢占核心链路调用额度。拆分之后整个系统的坐席化运维变得清爽很多。核心链路出了问题辅助链路不会受影响也不会出现“日志把业务拖垮”这种反直觉故障。6. 二次开发记录自定义Agent的三种接入法Agent-Reach做了一段时间后团队里陆续有人问我“我不需要完整的框架我只想把我的一个Python脚本挂进来让大模型能调用它怎么做”这里我整理了三种接入方式由浅入深。6.1 方式一注册为“哑工具”最简单如果你只有一个独立脚本不需要上下文不需要中间状态最简单的方式是把它注册为一个命令工具。在Worker的配置里加一条tools: - name: parse_nginx_log command: python3 /opt/reach/scripts/parse_nginx_log.py args_schema: - name: log_path type: string required: true - name: lines type: integer required: false default: 100之后Agent在语义层解析出parse_nginx_log(log_path/var/log/nginx/access.log, lines200)就会调用脚本。对脚本的要求只有一个输出必须是JSON不然结果没法回填给模型。这一条要求我在接入文档里用加粗字标了三遍。6.2 方式二封装为长生命周期服务如果你的脚本需要加载模型、需要预热缓存、需要保持数据库连接那不适合每次调用都新起进程。这种情况我推荐写一个带HTTP接口的常驻服务然后用API工具接入。接入配置tools: - name: forecast_demand type: http endpoint: http://127.0.0.1:8765/v1/forecast method: POST auth: bearer-token timeout: 45服务内部可以随意实现Agent-Reach只关心“收到请求、返回JSON”。这个方式绕开了命令执行的进程开销也更好做依赖管理和日志收集。实测下来模型加载类工具用这种方式单次调用耗时能从8秒降到1秒多性能差距非常明显。6.3 方式三实现Reach Protocol最灵活如果你需要让Agent代理执行多步交互、需要接收实时进度流、需要助手端主动推送消息那就得实现Reach Protocol。这个协议本质就是一组gRPC接口核心是三个方法service ReachWorker { rpc Execute(ExecuteRequest) returns (stream ExecuteResponse); rpc Heartbeat(stream HeartbeatRequest) returns (stream HeartbeatAck); rpc Cancel(CancelRequest) returns (CancelResponse); }实现这套接口的Worker才能完整享受到调度层的编排能力、接管机制和状态追踪。我这边的建议是先想清楚你的工具到底是一次性调用还是多轮交互。前者用方式一或二就足够后者才值得上方式三。不要一上来就搞最重的维护成本会拖垮你的热情。这三种方式也在文档里画过一张选型逻辑有没有常驻状态要不要流式反馈会不会被并发调用三个问题回答完基本能定位到正确的接入姿势。7. 连Python都用不熟练的人怎么用上这套框架文章快收尾的时候我得照顾一下另一类朋友需求特别具体、但编程基础一般的人。比如你想让大模型帮你每天自动巡检某个服务的健康状态但你又不想深入理解gRPC、etcd、有限状态机这些词汇。Agent-Reach对这部分用户是友好的因为它留了一条纯配置路线。7.1 你能用到的配置文件模板一个“每日巡检”类任务的配置大体长这样agent: name: daily_health_check trigger: type: cron expression: 0 */4 * * * tools: - name: http_get_status endpoint: http://192.168.1.15:8080/healthz expect: OK fallback: - notify: type: webhook url: https://your-alert-system/webhook/reach这份配置的含义很直白每四小时访问一次指定地址如果返回内容不是OK就触发webhook告警。不需要写一行业务代码。框架内部自动完成意图注册、定时触发、结果判定、异常通知。7.2 我见过的最普通的成功案例我们内部有一位非技术背景的运营同事自己照着这份模板改参数做出了“发布会页面存活巡检”“注册服务返回码巡检”两个自动化任务。她全程只修改了endpoint地址和期望值其他什么都没碰。上线两周准确抓到过一次某页面部署后流量异常省了一宿手工盯。这类纯配置玩法能覆盖的需求比想象中广得多。任何“隔一段时间检查一个东西、不对就通知我”的场景都能套进这个模板。7.3 如果你连配置文件都不想写怎么办那就用内置的小助手交互模式。在Agent-Reach的调试面板里你可以直接用自然语言描述你的需求模型帮你生成整套配置你只需要点击“预览并确认”。这套能力本身也是Agent-Reach的eat your own dog food我们用自己搭的框架去生成配置再让框架执行。我在后面越来越觉得Agent类技术真正普适的突破口不是让每个人都变成“提示词工程大师”而是让底层复杂度消化在框架里普通用户只需要描述场景。Agent-Reach走到这一步的时候才算是它真正“Reach”到大多数人的时候。最后分享一个小经验不要一开始就去设计“万能的Agent”那是无尽的需求黑洞。先圈定一类高频的、模式化的动作用框架跑通再做横向拓展。Agent-Reach帮我趟过了从模型到执行这条最难的沟希望这篇文字也能让你省掉几个月的试错时间。