如何设计一个 Agent 系统:5 个决策,一套能跑的实现

📅 2026/7/22 17:03:46
如何设计一个 Agent 系统:5 个决策,一套能跑的实现
Claude Code、Cursor、Codex 这一年把“Agent”从概念变成了日常工具。但当一个团队开始认真考虑“我们自己搭一个 Agent 系统”——不管是 Coding Agent、客服 Agent 还是数据分析 Agent——很快会发现市面上的资料两极分化不是“接个 API 调个循环”的玩具教程就是某个具体框架的使用文档。当中缺失的那块拼图是设计决策层一个生产级的 Agent 系统到底由什么组成每一层有哪些实现路径各自的权衡是什么做出选择后又该如何落地。这篇文章补的就是中间这块五个设计决策每个决策先讲通用原则再给落到代码和参数的实现。贯穿全文的实例是 Coding Agent——它是所有 Agent 类型里工具链最重、反馈最直接、风险也最典型的一种把它想清楚之后再理解其他领域的 Agent许多设计问题都会变得更清晰。本文实现部分来自“牛码”。牛码不是七牛云的产品是小编为本次实践搭建的个人项目。借助“牛码”的案例来验证这五个决策到底是能落地的工程判断还是纸上谈兵。模型 API、存储和 CDN 用的全是七牛云对外开放的能力也就是说这套东西你在业余时间也能搭起来。为什么越来越多团队选择自建使用现成工具当然更便捷但企业级用户很快会遇到三个跨领域的现实问题成本结构**不可控**。通用工具按 token 计费且模型不可选团队规模一大账单里有大量“用大炮打蚊子”的浪费——简单任务和复杂任务打到同一个最贵的模型上。数据出不去。代码、客户对话、经营数据要发到第三方往往是海外的服务器涉及保密的团队天然会犹豫。闭环断在最后一步。通用工具只做到“生成结果”任务就结束了代码写完了要你部署回复草稿拟好了后要你发送报表生成了要你来归档。Agent 和你的基础设施是两套体系。而这三个问题的共性不是工具不好而是它不为你的场景定制。自建 Agent 的价值就在这三点——成本可路由、数据留在自己的基础设施里、闭环接到自己的系统上。一个 Agent 系统的四层架构抛开具体产品任何 Agent 系统本质上是四层东西叠起来的大脑模型层负责理解任务、生成内容、做判断循环编排层让模型不再“问一句答一句”而是能规划、调用工具、看结果、再决定下一步手脚工具层是操作外部世界的能力——Coding Agent 是读写文件、执行代码客服 Agent 是查订单、开工单现在一般以 MCP 工具的形式接入闭环落地层决定结果生成之后怎么真正生效这一层是绝大多数通用工具的空白。牛码的架构就是这四层的直译四层各对应一个核心设计决策加上贯穿全部四层的安全设计一共五个决策。决策一模型——单一供应商还是多模型路由原则上不同任务对模型能力的要求差别极大把所有请求打到同一个模型上不是在简单任务上浪费钱就是在复杂任务上牺牲质量。生产级系统应该按任务特征路由到不同模型并且接入层要协议兼容保证供应商可随时更换——模型市场无时无刻不在洗牌锁死在单一供应商上的系统会跟着供应商一起过时。牛码的模型层走七牛云 AI 大模型推理服务一个 API 同时兼容 Anthropic 和 OpenAI 协议认证用标准的ANTHROPIC_AUTH_TOKENANTHROPIC_BASE_URL配置密钥集中托管、不下发到开发者本地是生产化目标MVP 阶段 Key 仍存在本地配置文件里。换模型供应商不用改一行接入代码——这是“不锁死”原则的基础设施前提。多模型路由的实现有三条路让模型给任务复杂度打分再路由、写死规则路由、完全交给用户手动选。这边权衡下来结论是规则先行、手动覆盖兜底自动打分出局。不采用打分机制的理由是如果让模型先给任务打分再决定用哪个模型本身多了一次模型调用延迟和成本都会涨而且打分结果不稳定——同一个任务两次打分可能落在不同档位调试时完全无法复现。规则路由虽然“笨”但是可预测、可调试、零开销。具体的路由规则有三条系统按顺序判断命中后即停止用户指定优先。命令带--model thinking或会话里说“用推理模型”直接切档。这是逃生舱——任何自动规则都可能判断错必须给人留手动覆盖的口子。任务特征触发推理档。任务描述命中“架构 / 重构 / 设计方案 / 性能优化 / 排查”等关键词且涉及文件数 5 或首轮计划步骤数 8就路由到z-ai/glm-5.2。注意这里是“且”不是“或”——只有关键词没有规模日常档足够。其余全部走日常档deepseek/deepseek-v4-flash。具体实现可以参考编排层入口处的代码REASONING_KEYWORDS [架构, 重构, 设计方案, 性能优化, 排查] def route_model(task: str, file_count: int, plan_steps: int, override: str | None) - str: if override: # 规则 1显式指定 return MODEL_MAP[override] hit any(k in task for k in REASONING_KEYWORDS) if hit and (file_count 5 or plan_steps 8): # 规则 2关键词 规模 return z-ai/glm-5.2 return deepseek/deepseek-v4-flash # 规则 3默认日常档这里有一个细节首轮计划生成之前拿不到plan_steps的值所以实际顺序应该是——首轮统一用日常档生成计划计划出来后步骤数超阈值从第二轮起升档。这就避开了“为了路由先问一次模型”的鸡生蛋问题。其实设计方案原本还有第三档简单快速任务用小模型碍于实现时间的问题最后遗憾砍掉了。补全、格式化这类任务在 Agent 循环里占比极低主要是被 IDE 处理了单独维护一档路由的复杂度和收益不成正比。三档简化为两档这是设计阶段自我否决的第一处第二处就是上面的打分出局——两个方案看起来都更智能但推演到调试和成本层面就站不住了。真正被实践打脸的地方在后面。决策二循环——回答一次还是迭代到验证通过这也是 Agent 系统和聊天机器人最本质的区别是五个决策里最影响“能不能真正干活”的一个。Agent 的核心是一个循环接收任务 → 拆解计划 → 执行一步 → 观察结果 → 判断是否完成没完成就继续推进。循环设计的第一性原理是可验证即模型的每一步判断必须建立在真实的执行反馈上而不是它自己的想象上。代码到底报没报错、订单到底查没查到这些信息都要原样回传给模型。模型一旦凭空判断“我觉得应该对了”就会是 Agent 翻车的第一大原因。循环要回答三个问题什么时候算完成、失败了怎么办、长任务怎么不失忆。牛码的答案如下。双保险终止成功终止考察验证命令是否通过。每个任务在计划阶段必须声明机器可判定的验证命令pytest tests/、npm run build、curl 健康检查接口只要返回 0 即任务结束。不允许没有验证命令的任务进入执行阶段这是个硬性条件。这会逼着 Agent 把“完成了”定义成机器可判定的东西而不是看起来改好了。强制终止设置三道熔断闸门最大迭代 30 轮。将上限设置为 30 轮是为了在任务完成率和成本之间取得平衡上限太低复杂任务可能无法完成上限太高又容易导致成本失控。连续 3 轮无进展熔断。如果连续 3 轮工具调用结果与上一轮高度相似即同一文件反复改同一处、同一报错反复出现。判定为原地打转立即熔断并把当前状态汇报给用户。这条设计针对的是 Agent 失控时的一种常见情况很少有任务真的会“走了 30 步还没走完”更多时候是“从第 8 步开始原地打转”。但实践下来这个设定一次都没被触发过。因为这里的判定逻辑是“工具调用与结果签名完全相同”而模型每轮都会换个小花样加-v、加echo、换个目录签名轮轮不同检测器就眼睁睁看着它转圈最后是最大轮数兜的底见下图。这也提醒我们如果相似度判定过严等同于形同虚设。单任务 token 预算。Agent 系统可以配置限额超了直接停。无论什么时候都要有成本熔断。重试三类失败三种策略牛码将失败分成三类环境瞬时错误、代码逻辑错误和计划性错误。三类失败分别采用直接重试、带完整报错重试和重新规划三种策略。其中第二类失败类型的关键点是要原样、完整回传报错信息不做摘要。把长报错截成“测试失败共 3 个用例未通过”看似省 token但实际上剥夺了模型定位问题的全部原料它只能瞎猜。完整的 traceback 才是让它一轮定位的东西。第三类错误类型是同一步骤改代码重试 2 次仍失败就升级为计划性错误触发重新规划。上下文三层压缩防失忆循环跑到十几轮上下文会超过模型窗口。这个系统将压缩分三层工具结果分级保留。最近 3 轮全量保留而更早轮次中成功的调用压成一行失败的保留报错摘要。这里遵循两个原则越近的信息保留得越完整失败信息的保留优先级高于成功信息。计划置顶。和计划相关的信息包括任务原始描述、当前计划、每步状态永远要固定在上下文最前面不要做压缩。Agent 最致命的失忆不是忘了细节是忘了自己在干嘛。文件内容不进历史。读过的文件只留类似“读过 auth.py)412 行”这种记录需要时再让 Agent 重新读。文件是随时可再取的外部状态没必要在上下文里存副本——三层压缩机制中这条省的 token 最多。决策三工具——能力边界和权限边界要一起画模型的能力边界很大程度上取决于你给它接了哪些工具。但工具接得越多项目风险越大。所以接入工具的铁律是每接入一个工具同步回答它的权限问题。我们要理清哪些操作会自动执行哪些操作必须人确认还有哪些操作要直接禁止。如果先接工具再补权限系统那么补权限的那天一般是出事的那天。另一条反复被验证的原则是工具保持愚蠢智能留给模型。工具层替模型摘要、替模型判断的每一处小聪明后面都会变成模型判断失准的盲区。牛码的工具层分两步走本次实测的 MVP 阶段出于够快、够验证循环设计的考虑四个工具以本地函数直连实现。而生产化的目标路径是封装成 MCP 工具、通过七牛云自定义 MCP 托管服务接入——控制台 MCP 商店的预置工具已覆盖企业微信、联网搜索等通用能力代码沙箱这类刚需需要自建接入。工具层目前包含四类能力文件系统读写限定项目根目录内.env、密钥文件、系统路径默认拒绝代码执行沙箱见下Git 操作MVP 未做独立工具git 命令经沙箱执行强制推送会被判为高危命令进行拦截部署见决策四沙箱用 Docker 落地一个任务一个容器任务结束即销毁。完整规格docker run --rm \ --network none \ --memory 2g \ --cpus 2 \ --pids-limit 256 \ --read-only \ --tmpfs /tmp:size512m \ -v /workspace/task-{id}:/workspace:rw \ --user 1000:1000 \ --security-opt no-new-privileges \ niuma-sandbox:latest \ timeout 300 bash -c {command}上面的设计逻辑为默认断网是最大的杀手锏。绝大多数“跑代码、跑测试”的任务不需要网络需要装依赖的场景有内部 npm/pip 镜像源的白名单网络。如果 Agent 生成的代码中混入外传的数据逻辑断网会是最后一道保险。超时设置宁短勿长这里的单命令上限为 5 分钟。超时的痛苦是显性的跑飞的成本是隐性的。胖镜像要一次把常用运行时Python、Node、Go、JDK装够代价是体积膨胀到 2GB 左右。反面教材是按语言拆小镜像但因为任务经常要跨语言切来切去的复杂度大于省下的磁盘。本次实测用的是最小镜像恰好撞出了这个反面教材。在某次运行中镜像内缺少 pytest模型又要执行pip install pytest。由于是在--read-only--network none的容器运行时装依赖这条路行不通最后只能改用 unittest 重写测试才完成任务下图。沙箱越严镜像越要一次装够。工具侧封装很轻量沙箱工具只暴露一个run_command(command)返回{exit_code, stdout, stderr}三元组零加工。决策四闭环——你的 Agent 的最后一公里是什么这个决策是最容易被忽略但又是自建系统真正的差异化来源。每个领域的 Agent 都有一条通用工具到不了的最后一公里。通用 Coding 工具停在“代码写完、测试通过”部署上线是人工的通用客服工具停在“回复已经拟好”发送和工单流转仍由人工完成通用数据工具停在“报表已经生成”后续进入决策流程仍要依赖人工。自建 Agent 最大的价值是把这最后一公里接进你自己的基础设施让“我描述一个需求”到“这个需求已经生效”变成一条不中断的链路。设计这一层要想清楚三件事生效动作失败了怎么回退、不同环境怎么隔离、Agent 在这一层该有多大权限。牛码的最后一公里是部署编码完成、测试通过后Agent 调用对象存储 Kodo 版本化上传构建产物、切换 current 指针、尝试刷新 CDN这样用户拿到的是一个链接而不是一堆改动过的文件。图注本次实测跑通了上传/版本化/切指针/健康检查/失败自动回滚云主机部署未实现测试域名被平台 CDN 访问控制拦截公网可访问性未验证。回滚版本化切指针不做撤销部署工具上传产物到 Kodo 时不覆盖旧文件按版本号写入独立前缀kodo://app-bucket/releases/v20260715-1432/ ← 本次 kodo://app-bucket/releases/v20260714-0910/ ← 上一版 kodo://app-bucket/current → 指向某个 release 前缀上线是把current切到新版本 CDN 刷新而回滚是切回上一版 再刷新一次不需要重新构建。部署后健康检查失败时Agent自动回滚然后停下来汇报。这里回滚可以自动但“回滚之后再试一次”必须由人来决定否则会出现“部署-失败-回滚-再部署”的死循环。多环境物理隔离不靠参数测试和生产不共用 bucket、不共用云主机、不共用密钥是完全独立的两套资源部署工具也通过不同的凭证访问。区分环境不依赖命令里传--env prod——参数会写错、会被模型幻觉这里依靠凭证本身的权限边界拿着测试环境的 key物理上碰不到生产资源。权限生产的钥匙从头到尾不在 Agent 手里部署工具的默认配置中只有测试环境凭证生产凭证根本不在 Agent 可触达的范围内。生产发布走独立流程Agent 完成测试环境部署并验证通过后生成发布请求版本号、变更摘要、测试环境验证结果。人在控制台确认后由平台侧持有生产凭证的独立服务执行同样的切换动作。Agent 全程不接触生产 key。牛码 MVP 目前做到了“只配测试 bucket”这一层——用的仍是账号级 AK/SK物理上仍能碰到其他资源。真正的凭证隔离要靠子账号 授权策略这属于生产化待办。这自然比单纯依赖“生产操作需要二次确认”更可靠。二次确认在防误操作凭证隔离防的是模型被提示注入、被诱导之后的恶意操作。Agent 系统的安全要按最坏情况设计假设模型某一天会被外部输入操纵你的架构还能不能兜住。决策五安全——不是第五个决策是前四个决策的约束条件把安全放在最后讲不代表它最后做。它贯穿前四层而且必须在设计阶段就定下来上线后再补的安全都是筛子。前面各节已经把安全设计织在了各层里这里收拢成五条不分领域的底线密钥集中托管不下发到终端——API Key 散落在每个开发者本地的系统泄露只是时间问题。敏感资源默认拒绝——黑名单保护密钥文件、生产配置、客户隐私数据。高危操作二次确认——删除、外发、资金变动类动作自动化止步于此。生产与测试物理隔离——靠凭证权限边界不靠参数。全量审计日志——每次工具调用留痕。审计日志的格式也定下来每条工具调用记一行 JSON{ts: 2026-07-15T14:32:0108:00,task_id: t-8891,step: 7,tool: run_command,args_digest: pytest tests/ -x,exit_code: 1,duration_ms: 4210,model: deepseek/deepseek-v4-flash,tokens: {in: 8412, out: 356}}两个设计点args_digest对超长参数只存摘要和哈希日志不能变成第二个代码副本每条带model和tokens审计日志同时就是成本账本用来事后追溯“这个任务为什么花了这么多钱”。这样就不用查第二个系统。设计被实践修正的地方才是最值钱的回头对照最初的设计方案有两处是设计阶段就自我否决的三档路由简化为两档、复杂度自动打分被规则路由替代。而真正被实践打脸的也有两处连续 3 轮无进展熔断一次都没触发过判定过严形同虚设以及“运行时装依赖”在断网只读的沙箱里彻底行不通胖镜像不是可选项是必选项。留下来的部分——循环吃真实反馈、工具保持愚蠢——被实测反复验证生产凭证不进 Agent 这条本次没有生产环境可验仍是设计原则。设计方案在纸上永远是完美的价值在于它被现实修正的那些地方。这也是我把这四处“推翻”原样写出来的原因如果你在搭自己的 Agent 系统这些弯路可以直接跳过。全文速查表一张表速览全文。建议收藏搭自己的 Agent 时对着抄想亲手试试文中这套“模型 循环 工具”的能力不用等你自己搭完才能感受。现在七牛云Agent 体验平台多模态交互、工具调用、智能推理都可以在对话里直接体验——让它审查一段代码、调用工具完成一个任务你就会对“循环吃真实反馈”这条原则有直观得多的体感。体验完想动手搭自己的我搭牛码用到的全部积木——协议兼容的大模型推理 API、对象存储与 CDN 的部署链路——都在七牛云 AI 控制台可以直接接入我没有用到任何内部特权。一个人业余时间能搭出来的东西你和你的团队没有理由搭不出来。这篇文章里的参考实现和参数就是给你抄的。