
Agent 这个词这两年听得耳朵起茧但真到项目里把它跑起来、还要扛住线上流量的人其实没那么多。大多数人卡在两个地方一是看不懂框架里那一堆抽象到底在干什么二是本地 Demo 跑得挺顺一上生产就各种超时、死循环、成本失控。这篇就围绕国内头部厂商开源的那套 Agent 框架把我自己从读源码、搭 Demo 到推进生产的完整过程摊开讲它是什么、能解决什么问题、哪些环节是真正的难点、哪些坑可以提前绕开。不管你是刚接触 Agent 的后端同学还是已经在做 PoC 想往生产推的负责人下面这些内容应该都能直接拿去用。1. 先把 Agent 这个概念拆开看1.1 从调一次模型到跑一个闭环普通的大模型调用是这样一个动作我给一段文本模型返回一段文本结束。这个模式能解决的问题是有边界的——翻译、改写、分类、抽取都属于一次输入一次输出。但真实业务里的需求往往不是这样。用户说帮我查一下上周的订单异常把金额超过五千的列出来再按客户分组发一份汇总这一句话背后是查库、筛选、聚合、生成报告四个动作而且第二个动作依赖第一个动作的结果。单次调用做不到因为模型不知道上周的订单数据长什么样。Agent 的本质就是把这个多次调用 外部动作的编排逻辑交给模型自己决定。模型先想一步决定要做什么调用一个工具拿到结果看到结果再想下一步直到它认为任务完成。这个循环在业内通常叫 ReAct即推理 行动交替进行。它解决的不是模型聪不聪明的问题而是模型能不能接触到外部世界并改变外部世界的问题。明白了这一点后面所有框架设计你都能对上号。1.2 一个 Agent 的最小构成模型、工具、记忆、循环把这套东西拆到最简其实就四个零件。模型是大脑负责决策工具是手脚是模型能调用的外部函数比如查数据库、调接口、读文件记忆是上下文包括当前对话历史、中间结果、以及跨会话的长期信息循环是骨架负责把思考—调用—观察串起来并决定什么时候停。四个零件里最容易被人低估的是循环。因为循环意味着不确定的轮次而不确定的轮次意味着不确定的耗时和成本。我见过太多团队在设计阶段只想着能力多强上线之后被 P99 延迟和账单打醒。所以在动手之前你心里要有一根弦Agent 的每一个额外轮次都是要花钱、花时间、并且增加出错概率的。后面讲工程落地时很多手段都是围绕怎么把轮次压住展开的。1.3 开源框架到底帮你干了哪些脏活自己从零写一个 ReAct 循环代码量其实不大一两百行能跑起来。但为什么还要用框架因为能跑和能上线之间隔着一条河。框架真正有价值的部分不在那个 while 循环而在于它把这几件事标准化了工具的描述格式与参数校验、多轮消息的拼装与裁剪、流式输出的分片处理、不同模型厂商的接口差异抹平、以及调用链路的钩子hook机制——后者是你接入日志、监控、权限校验的入口。我读这套框架源码时最大的感受是它的分层做得比较克制底层是模型适配层中间是消息与工具抽象上层是 Agent 编排。这种分层的好处是你可以在不改业务代码的前提下换模型、换工具实现。判断一个 Agent 框架值不值得用我一般就看它这三层是不是清晰、钩子是不是够用、以及出问题时能不能拿到足够的信息定位。这三点比支持多少种花哨的编排模式重要得多。2. 框架选型先看业务形状再看框架2.1 三类主流开源 Agent 框架的定位差异市面上能拿来做生产的开源 Agent 框架大致可以分成三类它们的取舍完全不同选错了后面会很别扭。类型核心抽象优势适合的场景主要代价轻量工具调用型工具 单 Agent 循环代码少、链路短、好调试单任务、工具数量在十个以内复杂编排要自己写工作流编排型节点 边 状态流程可控、可视化、易审计步骤固定、需要人工介入灵活性差改流程成本高多 Agent 协作型角色 消息总线能拆解复杂任务、可并行长链路、多领域协同调试困难、成本高、易失控我的经验是能用一个 Agent 加几个工具解决的绝不上多 Agent。多 Agent 的调试成本是线性叠加的两个 Agent 互相传消息出问题时你要同时看两边的上下文定位难度直接翻倍。很多团队一开始就设计出规划者 执行者 审核者的三层结构结果上线后发现 80% 的请求根本用不到这么复杂反而被额外的消息往返拖慢了。2.2 什么场景该用 Agent什么场景老老实实写代码这是我最想强调的一条。Agent 不是万能胶它适合的是步骤不固定、需要根据中间结果决定下一步的任务。反过来如果业务流程是确定的——比如收到订单→校验库存→扣减→发通知——那就写普通代码用工作流引擎别让模型来决定流程。让模型做确定性的事只会带来不确定性没有任何收益。判断标准可以简化成一句话如果这件事你没法用 if-else 写清楚那才轮到 Agent 上场。比如帮用户从他的知识库里找到相关文档并总结检索哪几个关键词、要不要换关键词重试、总结到什么粒度这些都不好写死模型来判断更合适。而把这份 JSON 转成固定格式写个解析函数比什么都靠谱。2.3 选型时我重点看的五个硬指标抛开文档写得漂不漂亮我实际会盯这五点。第一模型适配层是否解耦能不能在不改业务代码的情况下换模型这决定了你未来被单一供应商绑死的程度。第二工具调用的参数校验是否严格有没有基于 schema 的校验和错误回传机制这直接决定线上参数错误的爆炸半径。第三是否有完整的回调钩子能挂上 trace、日志、耗时统计。第四流式输出是否支持工具调用场景很多框架在流式下遇到工具调用就退化成非流式体验会断档。第五社区活跃度和 issue 响应这不是虚荣指标而是你踩坑时能不能找到同行。这五点里我认为第二点最容易被忽略但最要命。模型生成参数出错是常态不是异常。如果框架没有把校验错误结构化地回传给模型模型就没法自我纠正你只能看到一堆莫名其妙的失败。3. 从零跑通第一个 Agent3.1 依赖与项目骨架先搭一个最小可运行的环境。我的习惯是先把目录结构定好避免后面工具越加越乱。# 建议使用独立虚拟环境避免和现有项目依赖打架 python -m venv .venv source .venv/bin/activate # 安装框架本体与模型 SDK具体包名以你选用的框架文档为准 pip install agent-framework-core pip install openai-like-sdk目录我会这样分project/ ├── agent/ │ ├── builder.py # Agent 构建入口 │ ├── tools/ # 所有工具一个文件一个工具 │ └── prompts/ # 系统提示词独立管理 ├── config/ │ └── settings.py # 模型、超时、轮次上限等配置 ├── observability/ │ └── tracer.py # 链路追踪封装 └── main.py # 对外服务入口把提示词和工具从代码里拆出来单独管理是我踩过坑之后的习惯。提示词改动频率远高于代码放在独立文件里改完不用重新走一遍发布流程也方便做 A/B 对比。工具一个文件一个是为了让每个工具的描述、参数、实现、错误处理都聚在一起读代码时不用来回跳。3.2 工具注册把函数变成模型能理解的能力工具注册看着简单其实是整个 Agent 里最需要精心设计的部分。下面是一个典型的工具定义伪代码示意具体装饰器名称以你用的框架为准from agent_framework import tool tool( namequery_order_stats, description按时间范围和金额阈值查询订单统计。仅支持查询最近90天的数据。 ) def query_order_stats( start_date: str, # 格式 YYYY-MM-DD end_date: str, # 格式 YYYY-MM-DD min_amount: float # 最小金额单位元 ) - dict: 查询订单统计信息。 if not _valid_date_range(start_date, end_date): return {error: 日期格式错误或范围超过90天请重新确认} rows db.query(...) return {count: len(rows), total: sum(r.amount for r in rows)}这里有几个细节值得说。第一描述里写清楚边界仅支持最近90天这句话必须写进 description不能只写在代码注释里因为模型看到的是 description。第二参数的类型和格式要写在注释或 schema 里日期格式不写清楚模型很可能给你传上周一这种自然语言。第三错误要以结构化形式返回而不是抛异常。抛异常会中断循环返回 error 字段则给了模型一次自我纠正的机会——这是提升成功率最廉价的手段之一。注意工具函数里绝对不要做猜测式容错。比如日期解析失败就默认取今天这种隐式兜底会让模型误以为参数是对的错误被掩盖到下游才爆出来排查成本极高。3.3 跑通第一个 ReAct 循环工具就位之后构建 Agent 并跑起来from agent_framework import Agent from agent.tools import query_order_stats, send_report agent Agent( modelyour-model-name, system_promptload_prompt(prompts/system.md), tools[query_order_stats, send_report], max_iterations8, # 硬性轮次上限必须有 temperature0.1, # 工具调用场景建议低温 ) result agent.run(帮我查一下上周金额超过5000的订单统计一下总数) print(result.final_answer)max_iterations这个参数看起来不起眼但它是我认为必须设置的第一道保险。没有它遇到模型反复调用同一个工具却不收敛的情况你的服务会一直烧钱到超时。我在测试环境故意构造过这种场景工具一直返回空结果模型就一直重试八轮下来耗时四十多秒成本是正常请求的六倍。设了上限之后超限就直接降级返回当前无法完成请补充信息用户体验反而更好。temperature设低也是实践出来的。工具调用需要的是稳定的参数生成不是创造性。温度高了同一个问题两次调用可能给你两种参数格式日志看起来会非常难受。3.4 流式输出与中断处理面向用户的场景基本都要流式否则用户盯着空白屏幕等十几秒体验很差。但流式遇上工具调用会有个尴尬期模型决定调工具的那一瞬间输出流是断的然后要等工具返回。这时候前端如果什么都不显示用户会以为卡死了。我的处理办法是把 Agent 的运行状态也做成事件流推给前端thinking、tool_call_start、tool_call_end、answer_delta。前端在工具调用期间显示正在查询数据用户就知道系统在干活。这个改动不大但对体验的提升非常明显。框架一般会提供事件回调你只要在钩子里把这些状态转发出去就行。另外要处理的是中断。用户可能中途取消这时候要确保工具调用能被取消、连接能释放。我见过因为没处理取消导致工具里的数据库查询一直挂着连接池被占满的事故。流式场景下客户端的断开不一定能可靠传到服务端所以工具本身也要设超时双保险。4. 让 Agent 从能跑到跑得对4.1 工具描述就是提示词的一部分很多人调 Agent 效果不好第一反应是换模型、调温度、加 few-shot其实大部分问题出在工具描述上。模型选择调用哪个工具完全依赖 name 和 description 这两个字段。描述写得含糊模型就只能猜。我总结了几条写描述的规矩。一是动词开头说清楚做什么而不是是什么比如查询订单统计比订单统计工具好。二是写清楚不适用的情况仅用于查询不支持修改这种否定描述能挡掉不少误调用。三是工具之间要有明显区分度如果你有search_docs和find_documents两个工具模型必然混淆要么合并要么在描述里写清楚各自的数据源。四是参数说明里给出示例值尤其是枚举和格式敏感的参数。这里有个反直觉的点工具不是越多越好。我做过对比工具数量从 5 个增加到 15 个之后工具选择准确率明显下降。原因是模型要在更多选项里做判断而且描述之间容易互相干扰。超过十个工具我会考虑按场景分组用路由先缩小范围或者干脆拆成多个专职 Agent。4.2 上下文管理与记忆分层Agent 跑多轮之后上下文会迅速膨胀。每一轮的工具返回结果都往里塞几轮下来就可能顶到模型的上限。这时候框架一般会触发截断但粗暴截断的后果是模型忘了最初的任务目标开始胡来。我的做法是把记忆分成三层。任务层放当前任务的原始诉求和关键约束永不裁剪中间层放工具返回的结果超过一定长度就做摘要只保留结构化要点历史层放更早的对话按时间衰减必要时整段丢弃。实现上不用太复杂一个简单的策略函数就够判断消息类型按优先级保留超长内容走一次小模型摘要。def trim_context(messages, budget_tokens): # 1. 永远保留系统提示和用户原始诉求 keep [m for m in messages if m.role in (system,) or m.pinned] # 2. 工具返回结果优先做摘要而非直接丢弃 rest [summarize_tool_result(m) if m.role tool else m for m in messages if m not in keep] # 3. 从旧到新填充直到预算用尽 return fill_by_budget(keep, rest, budget_tokens)提示摘要本身也要花 token 和时间。如果工具返回结果本来就不长比如一个数字直接保留比摘要更划算。别为了架构好看而引入不必要的开销。4.3 多 Agent 编排什么时候该拆前面说过尽量别拆但有些场景确实需要。判断标准是子任务之间是否有清晰的边界和独立的上下文需求。比如一个客服场景查询订单和查询物流用的是完全不同的工具集和话术硬塞给一个 Agent工具列表长、系统提示词互相干扰这时候拆成两个专职 Agent 再由一个调度者分发是合理的。拆的时候有两个关键点。第一交接必须结构化。不要让 Agent A 把一大段自然语言甩给 Agent B而是定义一个明确的交接结构任务目标、已知信息、约束条件、期望输出格式。自然语言交接会导致信息在传递中失真而且 B 无法判断哪些是事实哪些是 A 的推测。第二必须设总预算。多 Agent 的成本是各环节之和没有全局的轮次和 token 预算很容易出现两个 Agent 互相客气地推诿十几轮的情况。4.4 结构化输出与参数校验Agent 的最终产出如果要对接下游系统就必须是结构化的。我的做法是在系统提示里明确给出 JSON Schema并且在框架层开启结构化输出约束如果有的话最后在代码里再做一次 schema 校验。三重保险看起来冗余但生产环境里我确实遇到过模型输出多余字段、少字段、类型不对的情况。校验失败怎么办不要直接报错给用户。把校验错误信息作为一条新消息喂回模型让它重试一次。实测下来一次带错误提示的重试能解决大部分格式问题成本也可控。第二次还失败再降级处理。5. 生产环境落地难的是工程不是模型5.1 超时、重试、熔断与幂等上线之后你会发现Agent 请求的失败模式比普通接口复杂得多因为它包含多个外部依赖。我一般分三层设超时单次模型调用超时比如 30 秒、单次工具调用超时比如 5 秒、整个会话总超时比如 60 秒。三层缺一不可。只设总超时你无法知道是模型慢还是工具慢只设单次超时累积起来可能超出用户耐心。重试要非常克制。模型调用可以重试但要区分错误类型限流和网络抖动可以退避重试参数错误和内容违规重试没有意义。工具调用只对幂等操作重试查询可以重试扣款、发消息这类有副作用的操作绝对不能自动重试否则会出现重复执行。真要做就带上幂等键让下游去重。幂等键的设计很简单session_id tool_call_id。框架一般会给每次工具调用生成唯一 ID拿它和会话 ID 拼起来传给下游下游据此判重。这个字段不加出事之后你对账会非常痛苦。5.2 并发与成本把账算在前面Agent 的成本模型和普通接口不一样因为一次请求可能触发多次模型调用。上线前必须估算清楚。我的估算公式大致是单次请求成本 ≈ 平均轮次 × (输入token × 输入单价 输出token × 输出单价) 工具侧成本举个例子假设平均 3 轮每轮输入 2000 token、输出 300 token输入单价 0.004 元/千 token输出单价 0.012 元/千 token那么单次成本约3 × (2×0.004 0.3×0.012) ≈ 0.035元。看着不多但如果日请求十万次一天就是三千多块。所以必须给每个请求设 token 预算上限超过就强制收尾返回当前已有结果。并发方面模型服务通常有自己的限流阈值。我一般用信号量在应用层做一道限流再配合队列削峰。关键是别让请求无限排队排队时间超过总超时就直接快速失败否则用户等了一分钟拿到一个超时错误体验比立刻失败还差。5.3 可观测性没有 trace 就别上线Agent 的调试难度在于它是非确定性的同样的输入两次跑出来的轨迹可能不同。所以你需要把每次运行的完整轨迹记录下来每一轮的模型输入输出、调用了哪个工具、参数是什么、返回什么、耗时多少。这些信息汇总成一个 trace按 request_id 串起来。我关注的核心指标有这么几个首 token 延迟直接影响体感、端到端 P95 耗时、平均轮次分布突然变多说明有循环、工具调用失败率、单请求 token 消耗分布长尾往往是异常请求、任务完成率。这几个指标里轮次分布和 token 长尾是最早能发现问题的我设了告警轮次超过 6 的比例超过 5% 就去看日志。日志里还要注意脱敏。工具参数里可能有手机号、身份证、金额这些在写日志的时候必须过一遍脱敏规则。这个工作要在第一版就做进去事后再补你会发现在几千个日志点里找敏感字段是场灾难。5.4 上线前的三类测试我的验收清单里必跑三类测试。第一类是任务完成率测试用真实场景构造 100 到 200 条评测集看端到端成功率低于阈值不放行。第二类是工具选择准确率测试单独测给定问题模型选的工具对不对、参数对不对这一类能把问题定位到具体工具。第三类是异常注入测试故意让工具超时、返回错误、返回空、返回超长内容看 Agent 能不能优雅处理而不是崩掉或者死循环。第三类最容易被跳过但我认为最重要。线上出问题几乎都是异常路径正常路径你在开发阶段已经跑烂了。异常注入不需要多复杂的工具写个开关让工具按概率返回错误就行。5.5 安全边界工具就是权限Agent 能调用的工具本质上就是它的权限范围。这块必须收得很紧。工具白名单是底线只注册必要的工具参数校验在服务端做不能信任模型给的任何值权限要按最终用户身份透传而不是用服务账号跑所有请求否则一个越权查询就能把别人的数据带出来。还有几件事要防工具描述里不要写内部系统名和表结构这些信息可能被诱导输出返回给模型的数据要过滤敏感字段模型无法区分能看和能说循环上限和成本熔断是最后一道闸异常请求宁可失败也不能无限烧钱。这几条不是理论是我在代码评审里反复强调的东西因为它们出事的时候都不是小事故。6. 踩坑实录与排查速查表6.1 常见问题速查表下面这些是我和团队实际遇到过、并且整理成清单的问题遇到类似症状可以先对着查。症状最可能的原因排查动作处理办法模型不调用工具直接编答案系统提示没强调必须查数据看 prompt 和工具描述明确写涉及数据必须调用工具禁止臆测反复调用同一工具不收敛工具返回空或错误信息不明确看工具返回内容返回结构化错误加轮次上限参数格式频繁出错参数说明含糊检查 schema 和描述补格式示例校验失败回传错误重试上下文突然被截断导致跑偏工具返回内容过长统计每轮 token对长结果做摘要保留任务目标流式输出中途卡顿工具调用期间无状态推送看事件流推送 thinking 和 tool_call 状态同一问题结果差异大温度过高或未固定随机种子查 temperature工具场景降到 0.1 以下P95 延迟突然翻倍某工具变慢或轮次变多看 trace 和轮次分布工具加超时排查下游依赖成本异常上涨长尾请求轮次失控看 token 消耗分布设预算上限和成本告警6.2 几个反直觉的实践经验最后分享几条不太好写在文档里、但确实影响很大的经验。第一条提示词越短不一定越好但越长一定越难维护。我见过系统提示写到三千字的项目里面塞了大量边界情况和历史教训结果是模型抓不住重点而且每次改动都像拆炸弹。我的做法是把通用规则留在系统提示场景相关的规则放到工具描述和具体任务提示里分层管理。第二条别迷信更聪明的模型能解决一切。模型升级确实能带来提升但很多问题出在工程层工具返回格式不统一、错误信息不可读、上下文被截断。这些问题换模型解决不了。我做过对比把工具返回统一成结构化格式、错误信息改成可读的中文成功率提升比换模型还明显而且成本没变。第三条给 Agent 一个认输的出口。早期我总想着让它尽力完成任务结果它在信息不足时开始瞎猜。后来在系统提示里明确写如果缺少关键信息直接向用户提问不要假设。这一条加上之后无效轮次明显下降用户满意度反而上升。承认做不到比硬着头皮编一个答案强得多。第四条把评测集当成资产来养。每次线上出问题我都会把那个 case 补进评测集。半年下来积累了两百多条覆盖了各种边界。这套集子成了我们改提示词、换模型、升级框架时的回归基线没有它任何改动都只能靠感觉。如果你正准备把 Agent 往生产推我的建议是先在一个流程相对封闭、容错率高的场景里跑两周把 trace、评测集、成本监控这三样东西建起来再去碰核心业务。这三样东西建好之后后面所有的优化才有依据否则你只是在凭感觉调参。