1. 先把「智能体」这三个字拆开看它到底解决什么问题凌晨两点还在盯日志的人应该都有过这种体验循环跑了十七轮模型还在兴高采烈地调用同一个工具参数一字没改返回值一字没变账单倒是实打实地在跳。我头一回把智能体推到真实业务里就栽在这个坑上。所以这篇东西不打算从概念讲起而是从一个写过、崩过、修过的人的视角把智能体Agent架构的设计取舍拆开讲把能直接抄的骨架和踩过的坑一并交出来。如果你正准备把一个大模型调用从「一问一答」升级成「自己动手干活」或者已经在写循环但总感觉哪里不对下面这些内容应该能省你不少时间。先给一个不带废话的定义智能体是一段带状态的控制循环它把大模型的输出从「最终答案」降级成「下一步动作」再由外部执行器去执行把结果喂回去直到任务收敛。这句话里每个词都不是装饰。「带状态」意味着你得自己管上下文、管中间产物「控制循环」意味着退出条件、预算上限、异常兜底全是你写模型不会帮你兜「降级成动作」是最反直觉的一点——模型不再是那个直接给答案的角色它变成了一个会写指令的调度员真正干活的是你注册进去的工具。理解了这个定义很多争论就自然消解了。有人问「我的智能体为什么不好用」绝大多数情况下不是模型不够聪明而是循环里根本没有让它「感知到自己做错了」的通道工具返回一个 500代码直接把异常字符串原样丢回去模型看到一坨 traceback 只能瞎猜第二轮继续错。也有人问「为什么成本这么高」答案通常更简单——他每轮都把全部历史重发一遍还带上了十二个工具的全量定义。这些都不是模型的问题是架构没设计。1.1 从「一问一答」到「多步交付」中间差的是反馈通道单次调用的大模型本质上是个无状态纯函数同样的输入给出分布上相近的输出它不知道上一秒发生了什么也无法验证自己说的话对不对。这在写文案、做摘要、翻译这类「一次成型」的任务上够用一旦任务需要跟外部世界交互纯函数就失效了。比如你要它排查一条线上告警它必须先去查监控、再去看最近一次发布记录、然后比对配置差异每一步的结果都会改变下一步查什么这个「根据中间结果改变后续行为」的能力单次调用给不了。所以智能体的第一个必要条件是可观测的反馈。工具返回的东西必须是模型能读懂的、结构化的、能指导下一步的信息而不是一堆原始日志。我见过最典型的失败案例是把整个 JSON 响应体原样丢回去——三千多字符的嵌套结构里真正有用的只有一个 status 字段模型的注意力被稀释得干干净净。正确做法是在工具内部就把响应做一次裁剪和归纳返回「查到 3 条相关记录最新一条状态为失败错误码 E1024其余 2 条为历史成功记录」长度控制在几百字符信息密度拉满。第二个必要条件是可分解的目标。如果任务本身没法拆成子步骤智能体退化成单次调用反而多花了好几倍的钱。第三个是可执行的动作也就是你得有工具而且工具得能被安全地调用。这三条缺任何一条你都不该上智能体用固定工作流Chain会更稳、更快、更便宜。1.2 四种常见形态的选型对照以及我为什么来回横跳刚接触这块的时候很容易陷入「框架崇拜」觉得上了循环就高级。实际做过几个项目之后我的判断是架构形态应该由任务的确定性程度决定而不是由技术新鲜度决定。形态控制方式单次任务成本延迟可调试性适用场景单轮调用无状态一次成型极低秒级高摘要、改写、分类、抽取固定工作流代码硬编码步骤低秒级到十几秒很高报表生成、格式转换、固定流程审批规划-执行Plan-and-Execute先出计划再逐步执行中十几秒到分钟中步骤较多但结构相对稳定的复杂任务推理-行动循环ReAct 类边想边做动态决定下一步高分钟级中偏低排障、调研、开放式问题定位多智能体协作多个角色分工互相评审很高分钟级以上低长文档撰写、复杂方案评审、需要对抗性检查的任务我自己来回横跳过两次。第一次是从固定工作流切到推理-行动循环因为业务方总在提「能不能让它自己判断」硬编码的分支越写越多最后成了一个八百行的 if-else 沼泽。切过去之后灵活度确实上来了但随之而来的是成本不可预测和偶发的死循环于是又开始往回退——把已经跑稳的高频路径固化成工作流只在兜底分支上保留循环。这个「混合架构」是我目前最推荐的形态确定的部分用代码锁死不确定的部分交给模型判断。举个具体的例子。做日志排障时「先取告警时间点、再拉该时间窗前后十分钟的指标、然后对比上次发布记录」这三步是百分之百确定的写死在代码里一步都不让模型决定只有当三步都跑完还没定位到原因时才进入循环让它自由选择查配置、查依赖、查调用链。这样百分之八十的请求走的是确定性路径成本可控、延迟可控、结果可复现剩下百分之二十才烧钱。1.3 三个问题判断你的场景该不该上智能体在动手之前我一般会问三个问题任何一个答「否」就建议先别上。第一问任务步骤是否事先未知如果步骤可以提前枚举清楚用工作流。需要智能体的场景一定是「查到 A 才知道下一步该查 B」这种链式依赖。第二问错误成本是否可承受智能体的输出天然有波动同一任务跑三次可能给出三条不同路径。如果业务要求百分之百一致比如财务对账、合规校验那不适合让模型做决策最多让它做信息提取决策留给代码。第三问是否有足够的评估手段这一条最容易被忽略。上智能体之前你必须有一个至少二十条样本的评估集能衡量「任务成功率」「平均步数」「平均成本」三个指标。没有评估集就上线等于蒙着眼睛调参改了提示词感觉变好了实际上可能只是这一批样本恰好简单。提示如果三个问题的答案都是「是」但仍然拿不准可以先做一个「影子模式」——让智能体在后台跑输出结果只记录不执行人工比对两周确认稳定率达标再切到前台。这个做法救过我两次。2. 四大核心模块的职责边界与实现要点把智能体拆开无非是规划、记忆、工具、执行器四块。听起来简单但每一块都有清晰的职责边界越界之后就会互相污染最后变成一坨谁也改不动的代码。这一章我按模块逐个说重点是「边界在哪」和「什么时候该收手」。2.1 规划模块分解粒度、反思触发与止损设计规划这件事最常见的误区是「分得越细越好」。我试过一次把一个任务分解成十一步结果模型在第三步就开始偏离因为上下文里堆了太多还没执行的计划它开始把「计划」当成「已完成」。后来我把粒度收到二到五步效果明显好起来。经验值是单层计划的步骤数不超过五超过五步就分成两层——先出三到四个阶段每个阶段再展开具体动作执行时只把当前阶段的细节注入上下文。反思Reflection这块我更谨慎。理论上让模型每轮都自我检查一遍很棒实际上成本直接翻倍而且很容易陷入「过度反思」——模型反复说自己「需要更谨慎」然后什么也没做。我现在只在三种情况下触发反思连续两次工具调用失败、观测结果与预期明显冲突比如预期返回一条记录实际返回零条、步数过半但任务进度明显落后。这三个条件都由代码判断不交给模型自己决定要不要反思。止损设计是规划模块里最容易被跳过、但最要命的部分。我现在固定加三道闸显式的finish工具模型必须主动调用它才算完成不允许靠「不再调用工具」隐式结束、最大步数上限一般取预估步数的两倍、无进展检测连续两轮工具名和参数哈希完全相同就强制中断。第三道闸尤其重要它拦下的死循环比你想象的多得多。2.2 记忆模块三层结构别混着用记忆这块我踩过最大的坑是「把所有东西都塞进上下文」以为模型看到的信息越多判断越准。实际相反上下文里超过六成是无关历史时模型的指令遵循能力会明显下降开始忽略系统提示里的约束。现在我固定分三层。短期记忆就是原始消息序列用滑动窗口加摘要压缩保留最近三条完整消息更早的压成一段两百字以内的摘要摘要里只保留「已经确认的事实」和「已经排除的方向」过程性的思考全部丢弃。工作记忆是从上下文里剥离出去的结构化状态一个 JSON 对象存着任务目标、已完成的子步骤、关键中间产物比如文件路径、ID、错误码需要的时候按需注入不常驻上下文。这一层是省 token 的关键把中间产物放在外部状态里每轮只注入当前需要的那一小块能省掉一半以上的输入成本。长期记忆是跨会话的部分一般用向量检索加关键词检索的混合方案。这里有个容易被忽略的细节写入前要做一次「是否值得记」的过滤。我早期的做法是任务结束后把整段轨迹都存进去结果检索出来的全是过程性废话。后来改成只存三类内容——用户的稳定偏好、已经验证过的解决方案、明确的失败教训检索命中率立刻上来了。层级存放位置生命周期注入策略常见错误短期记忆消息队列单次任务最近 3 条完整 更早的摘要全量重发导致上下文爆炸工作记忆结构化 JSON单次任务按当前步骤需要注入塞进 prompt 导致注意力稀释长期记忆向量库 关键词索引跨会话检索 top-k 后二次筛选无差别写入检索噪声大2.3 工具层签名设计比工具数量重要十倍工具不是越多越好。我见过一个注册了三十多个工具的智能体模型选错工具的概率高到没法用。工具数量控制在十二个以内每个工具的职责边界清晰比什么都重要。第一个要点工具描述要写「什么时候用」而不是「这是什么」。写「查询订单状态」是没用的写「当用户询问某笔订单是否已发货、预计何时送达时使用参数为订单号不支持按用户查询」才有用。后半句的「不支持」尤其重要它提前堵死了模型的错误尝试。第二个要点参数用枚举收窄必填项明确默认值写清楚。一个status参数如果只允许五种取值就在 schema 里写成 enum别指望模型自己猜。必填和可选的边界写模糊了模型就会漏参数然后你的代码抛异常然后又是一轮无意义的循环。第三个要点返回体必须控制长度。我的默认规则是单次返回不超过四千字符超了就截断并附上一句「结果已截断可用 offset 参数继续获取后续内容」。有了这句提示模型知道该分页而不是拿到半截数据就开始瞎推理。第四个要点错误信息要可行动。返回「Error 500」毫无价值返回「查询超时可能因为时间范围过大建议把时间窗缩小到一小时以内重试」才叫有用。错误信息本质上是给模型看的提示词写得好的话一次失败就能引导它自己修好。# 工具定义的推荐写法描述里带上何时用和不支持什么 QUERY_TOOL_SCHEMA { name: query_records, description: ( 查询指定时间窗内的业务记录。仅用于按时间范围检索 不支持按用户 ID 或关键字检索那类需求请用 search_by_key。 时间窗跨度不要超过 24 小时否则会超时。 ), parameters: { type: object, properties: { start: {type: string, description: 起始时间ISO8601 格式}, end: {type: string, description: 结束时间ISO8601 格式}, offset: {type: integer, default: 0, description: 分页偏移返回被截断时用它继续取}, }, required: [start, end], }, }第五个要点也是最容易被忽略的幂等和权限分级。写操作类的工具发消息、改配置、提交工单必须带幂等键防止模型重复调用造成重复执行同时按危险程度分级只读工具直接放行写工具要求二次确认或走人工审批。我现在的默认策略是「读自动、写必审」宁可牺牲一点自动化率也不让智能体在无人监督的情况下改生产配置。2.4 执行器与状态机让循环跑不飞的那几行代码执行器是整个智能体的骨架它决定了循环怎么转、什么时候停、出错怎么办。我现在的实现都是一个显式状态机状态包括规划、行动、观测、反思、完成、失败六种每次状态转移都写日志。用状态机而不是while True的好处是出问题时你能一眼看出它卡在哪一步而不是对着一坨日志猜。重复动作检测我用的方案很朴素对「工具名 排序后的参数 JSON」做哈希存进一个集合命中就计数第二次命中先提示模型「你刚刚执行过完全相同的动作结果是 X请换一个思路」第三次命中直接中断。加了这个之后我遇到的死循环百分之九十都能自动收敛。并发工具调用也要小心。现在的模型有能力一次返回多个工具调用请求并发执行确实能省时间但结果顺序必须严格对齐请求顺序而且任何一个失败都要能单独标记不能因为一个超时就把整批结果都判失败。我见过因为结果错位导致模型把 A 工具的输出当成 B 工具的结果后面整条推理链全歪掉的案例。3. 动手搭一个能跑的智能体从骨架到可观测理论讲完下面直接上能跑的东西。这一章给的是最小可用骨架不依赖任何特定编排框架纯粹用几十行代码把循环写清楚。框架能省事但把骨架自己写一遍你对「它在干什么」的理解会完全不一样出了问题也知道往哪看。3.1 环境与依赖的最小集合依赖这块我坚持最小化一个大模型客户端、一个 HTTP 库、一个日志库够了。向量库、编排框架这些在验证阶段全都不需要等骨架跑通、评估集建好再按需往上叠。过早引入重框架的代价是出问题时你分不清是自己的逻辑错还是框架的行为排查成本翻倍。配置全部走环境变量或配置文件别硬编码在代码里。我一般会准备三套配置调试用的最大步数 5、日志全开、工具只挂只读的、评估用的最大步数按真实场景、日志全开、工具全挂但写操作走 mock、生产用的预算守卫全开、日志采样、写操作走审批。三套配置切换靠一个环境变量搞定避免「调试时能跑上线就崩」这种低级事故。3.2 主循环把 ReAct 写成可控的六态机下面这段是整个系统的心脏。它做的事情很简单把历史发给模型模型返回要么是工具调用、要么是最终答案工具调用就去执行、把结果追加进历史然后继续下一轮。但每一行都是有用的尤其是那些看起来像「保险丝」的判断。import hashlib, json, time MAX_STEPS 12 # 预估步数的两倍 MAX_TOKENS 60000 # 单任务输入 token 预算 MAX_SECONDS 180 # 单任务墙钟时间上限 def run_agent(goal, tools, llm, state): history [system_prompt(goal, tools), user_msg(goal)] seen_actions {} used_tokens 0 t0 time.time() for step in range(MAX_STEPS): # 三重预算守卫任一触发立即退出 if used_tokens MAX_TOKENS or time.time() - t0 MAX_SECONDS: return finish(state, reasonbudget_exceeded) resp llm.chat(history, toolstool_schemas(tools)) used_tokens resp.usage.input_tokens resp.usage.output_tokens if resp.final_answer: # 模型主动收尾 return finish(state, answerresp.final_answer) for call in resp.tool_calls: key action_hash(call.name, call.args) # 工具名 规范化参数 seen_actions[key] seen_actions.get(key, 0) 1 if seen_actions[key] 3: return finish(state, reasonstuck_repeat_action) if seen_actions[key] 2: history.append(hint_msg(你已经执行过完全相同的动作 结果为上一条观测请改用其他思路。)) continue result execute_with_timeout(tools[call.name], call.args, timeout15) state.record(call.name, call.args, result) # 写入工作记忆 history.append(tool_result_msg(call.id, compress(result))) return finish(state, reasonmax_steps_reached)这段代码里有三个刻意为之的设计。第一个是预算守卫放在循环开头而不是结尾这样即使模型这一轮又烧了一大笔也不会让总成本超标太多。第二个是重复动作的二级处理——第二次重复只提示不中断给模型一个自我纠正的机会第三次才强制退出。第三个是compress(result)工具返回在进入历史之前先做一次裁剪这一步能省掉的 token 比你想象的多得多。3.3 上下文预算算清楚再动手很多人的智能体之所以又慢又贵是因为从来没算过账。我现在的习惯是动手前先把预算算一遍这个习惯是从一次账单事故之后养起来的。假设上下文窗口是三万二千 token预留两千给输出再留百分之十五的安全余量那么可用输入大约是三万二乘以零点八五再减两千约等于两万五千二百。扣掉系统提示八百、十二个工具的定义约一千一百剩下的两万三千三百就是历史消息的预算。假设每轮平均消耗九百七十 token模型思考约二百五、工具参数约一百二、压缩后的工具返回约六百那么大约能容纳二十四轮。这个数字直接决定了MAX_STEPS该设多少——超过它压缩就必须介入。再算成本。因为每轮都要重发全部历史总输入 token 是累加的二十四轮大约消耗二十九万输入 token输出约九千。按一个中等价位的单价假设输入每百万三块、输出每百万十五块算下来一次完整任务差不多一块钱。如果评估集显示平均成功率只有百分之六十那单次成功任务的实际成本就是一块七。这个数字才有决策价值——它告诉你该优化的是成功率还是单价。注意这套估算里的每一项都应该用真实日志校准别拍脑袋。我一般会在评估跑完之后把实际 token 消耗和估算值对一遍偏差超过百分之二十就说明某一环的假设错了通常是工具返回体比预期大得多。压缩策略我按「先便宜后贵」的顺序做第一步工具返回超长的直接截断加提示第二步历史超过阈值时把最早的三条压成摘要第三步实在压不下去才做全量摘要。全量摘要最贵也最容易丢信息能不用就不用。3.4 可观测性没有轨迹回放调优全靠玄学这一块我放在最后讲但它的优先级应该排在前三。智能体的行为是非确定性的同一个输入跑三次可能走出三条路径如果没有完整的轨迹记录你根本不知道某次失败是提示词的问题、工具的问题还是模型随机性的问题。我记录的字段包括每一步的时间戳、状态、模型输入输出的完整内容、工具调用的名称参数和返回、token 消耗、累计耗时。这些写进结构化日志按任务 ID 聚合出问题时能一条命令拉出整条轨迹。生产环境做采样比如百分之十全量记录剩下的只记摘要避免日志本身成为成本大头。评估集是另一半。我现在固定维护二十到三十条样本覆盖典型场景、边界场景和已知的历史故障复现。每次改提示词、改工具描述、换模型版本之前先跑一遍评估集看任务成功率、平均步数、平均成本三个指标。这套流程听起来笨但它把「感觉变好了」变成了可比较的数字避免了很多无效改动。我印象最深的一次是换了更长的系统提示主观感觉回答质量提升明显评估集一跑平均步数从 6.2 涨到 8.7成本涨了百分之四十成功率只涨了三个点果断回退。4. 踩坑实录常见故障、排查路径与上线自检下面这些坑基本都来自真实项目每一条我都付过代价。按故障现象整理成表遇到问题可以直接对照查。4.1 故障速查表现象最可能的根因排查动作处置方案循环不收敛步数打满缺少无进展检测工具返回信息量不足看轨迹里最后三轮的工具名和参数加重复动作哈希检测把工具返回改得更具指导性工具参数总是缺字段schema 里必填/可选边界模糊检查参数定义和模型实际输出用 enum 收窄取值必填项写进描述正文上下文迅速爆掉工具返回体过大历史全量重发统计每轮 token 构成返回体截断加分页提示加滑动窗口加摘要提前收尾任务没做完没有显式 finish 工具提示词未约束看最后一轮模型输出强制走 finish 工具并校验必填产出字段结果时好时坏无评估集改动无法量化跑二十条样本看方差建评估集改一次跑一次成本失控每次失败都重试全流程反思过于频繁按任务 ID 聚合 token加预算守卫反思改为条件触发写操作重复执行工具无幂等键循环重试查调用记录时间戳幂等键加去重表写操作走人工审批并发结果错位结果未按请求顺序对齐比对 call_id 与返回用 call_id 严格配对失败单独标记表里最值得展开的是「提前收尾」这一条。早期我没强制要求模型调用 finish 工具它经常在拿到部分信息后就给出一段看起来很像答案的话实际上关键字段全是空的。后来我改成必须调用finish并传入结构化产出代码侧做 schema 校验缺字段就打回去重来。这一改任务完整率直接从七成出头提到了九成以上。4.2 成本与延迟的四个杠杆第一个杠杆是压缩工具返回。这一项通常能省掉百分之三十到五十的输入 token改动成本最低收益最直接。具体做法是在工具内部就把响应做归纳只保留决策相关的字段长列表只给前几条加总数。第二个杠杆是分级模型。规划用强模型执行用轻模型这个组合我用下来效果不错。规划只跑一次贵一点无所谓执行要跑十几轮用便宜的能省一大截。前提是你得先验证轻模型在单步执行上的准确率够用通常单步任务的难度远低于整体规划替换成功率比较高。第三个杠杆是缓存。工具调用结果按「工具名加参数」做短期缓存同一个任务内重复调用直接命中系统提示和工具定义这类固定前缀用服务商提供的前缀缓存能力能省下不少重复计费。第四个杠杆是并行。互相独立的工具调用并发执行延迟能砍掉一半。前提是你得先确认这些调用之间没有依赖而且要处理好失败隔离——一个超时不能让整批都失败返回里要明确标出哪几个成功哪几个失败让模型自己决定要不要重试失败的那部分。延迟这块还有个隐性成本容易被忽略模型的思考长度。有些模型在复杂任务上会输出很长的推理过程这部分既算输出 token 又拉长响应时间。如果任务本身不复杂可以在提示词里明确要求简洁推理或者换一个推理链更短的模型效果往往比优化工具调用明显。4.3 上线前的自检清单我现在的习惯是上线前逐条过一遍这份清单每一条都对应过一次真实事故。是否有硬性的步数、token、时间三重上限且都在循环开头检查是否有显式的完成信号且完成时校验必填产出字段重复动作是否有检测和分级处理所有写操作是否带幂等键是否走了审批或二次确认工具返回是否都做了长度控制和可行动的错误提示历史消息是否有压缩策略压缩后是否保留「已确认事实」和「已排除方向」是否记录了完整轨迹能否按任务 ID 一键回放是否有至少二十条样本的评估集三个核心指标是否可复现失败时的用户侧表现是什么是返回半成品、返回明确错误还是静默降级到人工是否有影子模式或灰度开关出问题能不能一键关掉最后一条尤其重要。我第一次上线时没有灰度开关出问题只能紧急回滚代码中间那十几分钟里所有请求都在跑一个已知有缺陷的循环。后来所有智能体相关的入口都加了开关和降级路径出问题直接切回固定工作流用户侧基本无感。5. 我个人的几条经验以及可以继续往下扩展的方向真正让智能体从「能演示」变成「能交付」的往往不是模型能力而是那些看起来最不性感的工程细节预算守卫、幂等键、轨迹日志、评估集。我做过对比同一套提示词和工具加上这三样之后任务成功率能从六成多提到接近九成而换一个更强的模型提升通常只有五到八个点。所以如果你现在正准备动手我的建议是先把骨架和护栏写扎实再去调提示词。另外一个体会是别追求「全自动」。人在环上human-in-the-loop不是妥协而是当前阶段最划算的设计。我的做法是把任务分成三段信息收集全自动、方案生成全自动、方案执行必须人工确认。这样既拿到了效率又把风险控制在可接受范围内。等评估集上某个场景连续几百次零事故再把那一段的确认环节去掉一段一段放权比一次性全放开稳得多。这个骨架后续还有几个自然的扩展方向。一是把工作记忆换成真正的持久化存储让长任务能跨会话续跑二是加一个「计划复用」机制把走过的高频路径固化成模板下次遇到相似任务直接复用省掉规划的开销三是把评估集做成回归测试接进日常发布流程任何改动先跑回归再上线。这三件事我都在逐步做做完之后智能体才算真正从「一个能跑的实验」变成「一个能维护的系统」。