说实话这两年“AI Agent”这个词已经被聊烂了。身边不少人兴致勃勃接了大模型接口写了几段提示词搭出一个能“自动规划、自动调用工具”的demo演示的时候全场眼前一亮。可真到生产环境里跑上一周问题就全出来了任务做到一半突然停住、工具参数传错、模型开始胡编乱造、上下文越滚越长导致行为越来越怪。最后大家得出结论Demo Agent一推就倒根本不敢放开用。我也经历过这个阶段。后来接手了一个客服工单分类与处理Agent的模拟项目被各种“不靠谱”折磨了很久才慢慢摸清了一套构建可靠Agent的方法论。这篇文章就把我实际踩坑、逐步沉淀下来的思路完整写出来。核心聚焦在三个问题上可靠性问题到底从哪来、架构上怎么设计才能兜住底、具体实现和排障时该盯住哪些环节。适合正在从demo走向真实场景的开发者也适合想系统理解Agent可靠性设计的产品和技术同学。1. 为什么Agent总在关键时刻掉链子不可靠的根源要拆开看想解决可靠性问题第一步是把“不可靠”拆成具体的现象。只笼统说“模型不行”或者“逻辑太复杂”没法下手。我个人习惯把它归因成三个源头每个源头对应不同的解决手段。1.1 模型行为本身的不确定性抽卡式输出是天然属性大模型天生是概率模型。同样一句话温度调到0也不是每次输出都一样温度越高发散越明显。这在单次问答里问题不大但Agent的任务往往是一连串决策只要其中某一步的决策发生了微小偏移后面所有步骤都会跟着跑偏。我遇到过一个很典型的例子客服Agent在判断工单优先级时模型大部分时间会遵循“高优VIP客户故障类影响面大”的逻辑但偶尔会把“客户语气着急”也当作高优条件导致一个普通咨询被转成了紧急工单整个处理链路被带偏。再往深一层说模型还有幻觉问题。在执行“查询订单状态”这类工具调用时Agent即使没有拿到数据库返回也可能会基于历史对话“脑补”出一个合理的订单状态。这种问题最阴险——表面看任务完成了实际上整个结果都是虚构的后续所有决策都建立在假数据上。所以只看“模型聪明不聪明”远远不够必须在架构层面对模型输出做约束和校验。1.2 长链路执行中的状态失控上下文越长越容易丢关键信息Agent任务和普通问答最大的区别在于“多步执行”第一步的结果要传给第二步第二步的结果又影响第三步的决策。这套流程看着顺理成章但做起来就会发现模型工作记忆是有限的。上下文窗口是变大了但“能把文字塞进去”和“能准确回忆并利用”是两回事。当对话历史累积到一定程度模型往往会遗忘早期步骤里的关键约束甚至被中间某个看起来很重要、实际无关的细节带跑。更普遍的问题比遗忘更隐蔽状态被覆盖。初期我实现过一个很朴素的Agent状态类所有中间变量塞进一个大dict每步执行完就往里追加。结果有一次工具返回了新的客户ID和原有订单ID用了同一个字段名覆盖了旧值后续查询全部基于错误ID进行。这个问题的本质是状态管理没有做结构化隔离混乱只是时间问题。1.3 工具调用的脆性外部环境根本不受你控制Agent的现实世界里工具是最大的不确定性来源。模型能力再稳定只要下游接口出了问题任务一样失败。我把这些失败归纳成几类接口结构变化比如API返回字段从order_no改成了orderIdAgent解析不到数据网络超时或限流比如批量查询时被服务端限速Agent拿不到响应参数校验失败工具要求ISO日期格式模型给了个“明天下午3点”这种自然语言权限与依赖问题工具依赖的环境变量没配好报错信息Agent又看不懂。工具调用失败的可怕之处在于它不是个小概率事件而是必然会发生的。任何一种外部依赖只要发生一次异常整条链路就得停下来等待处理。所以构建可靠Agent时工具层的容错设计不是“可选优化”而是“必做功课”。1.4 可靠的定义要落地用指标取代感觉聊可靠不能停留在“感觉还比较稳”。在做可靠性改造之前我先把“可靠”拆成了五个可量化的指标后面所有设计都围绕这些指标展开指标含义例子任务成功率完整链路跑通并得到最终结果的占比100个工单里99个正确完成分类与处理部分完成率任务没全做完但已完成部分可被利用查询完成但邮件通知失败查询结果仍保留失败可恢复性失败后能否通过重试或降级路径继续推进主API超时后自动切换备用数据源结果可验证性最终输出能否被程序或模型二次校验生成的SQL在实际库上试跑通过后再执行成本可控可靠性的提升不能靠无限烧token实现多次失败重试消耗的token控制在任务预算内这五个指标不是学术定义是我实际运维时用来衡量“这版改造有没有用”的标尺。后续的架构设计和代码实现本质上都是在往这些指标上使劲。2. 架构层先把底兜住让每一步都有校验、有兜底、有退路很多人一开始构建Agent时想着把“思考能力”全交给模型自己只写一个循环模型想一步执行一步。这种架构最大的问题是——所有环节都依赖模型一个人做对。真正可靠的Agent设计应该反过来把能做主的事全部工程化把模型当做一个强力的决策器但绝不让它独自承担全流程。2.1 计划-执行-验证把单次“神判断”拆成可追踪的三层我采用的架构核心是经典的“计划-执行-验证”三段式但做了不少工程化改造。它的核心思想很简单不让模型在一次输出里同时完成“理解目标、拆解步骤、执行每个步骤、检查结果”四件事而是把每一步拆开每层各司其职。计划层负责接收任务描述输出一份结构化行动计划。这一步只动脑、不动手。我在设计计划层时会强制模型按照固定schema输出JSON包含目标、步骤列表、每个步骤的依赖关系、预期产物。一旦输出格式不合法就重试解析不让“半格式化的脏数据”进入下一步。执行层按计划逐条执行。每执行完一步把真实结果写回状态区而不是让模型凭记忆猜测。这里的关键原则是执行层的每一步动作必须“可观测”——无论成功还是失败都要留下结构化日志后面排查时一眼能定位到是哪一步出了问题。验证层是我特别强调的一层。很多Agent任务失败不是前面的计划或执行出问题而是模型压根没发现自己做错了。我在每完成一个步骤或最终产出后都会加一次独立验证。验证的方式有两种一种是确定性规则校验比如结果里有没有关键字段、金额是不是负数这种程序能直接判断的另一种是模型自校验让LLM用“挑剔视角”检查上一步结果与原始目标是否一致。这也是整个架构里性价比最高的一环。这三层拆开以后有一个直接好处出问题时报错可以精确到层。比如“计划层给出的步骤不可执行”和“执行层调用工具失败”处理方式完全不同排查成本差了好几倍。2.2 显式状态管理把记忆从模型脑子里搬出来Agent执行过程中记忆就是个“必须强管控”的东西。所谓强管控就是要有一套外部状态存储来管理所有变量而不是指望模型通过对话历史记住它们。我把Agent的记忆拆成两个层级短期任务态记录当前任务的中间结果、当前在第几步、待执行列表。这一层用结构化状态对象管理读写都走代码逻辑不经过模型思考长期记忆态记录跨任务复用的知识比如用户偏好、历史处理模式。这一层会定期做摘要和压缩避免把无关的历史信息全部塞进上下文。这么做最直接的好处就是防止“上下文爆炸”。之前我的Agent跑到十几步时经常行为漂移后来一查发现每一轮的思考、工具返回都完整堆在对话历史里几万字的噪声让模型彻底迷失了重点。引入外部状态管理之后每步只把“与当前决策相关的精简摘要”发给模型行为稳定性提升非常明显。状态管理还有一条铁律所有被使用的变量必须初始化且要有类型约束。我曾经因为状态字段混用吃过亏后来把所有状态字段做成了dataclass或Pydantic模型字段名冲突、类型错误在运行前就被拦截掉。2.3 工具层要做防御式设计接口是会变的Agent要扛得住工具调用层的可靠性设计直接影响整个系统的下限。我这里有几条实际有效的经验工具描述要“厚”而不是“薄”。很多Agent开发者在注册工具时只写一句“查询订单信息”剩下的全靠模型悟。这是不对的。工具描述应当像给新员工写操作手册一样参数格式、字段含义、返回值示例、常见的失败情况、需要满足的前置条件。模型虽然聪明但它没有你对你系统的背景知识描述越明确误调用和传错参数的概率越低。统一错误返回格式。我把所有工具的错误返回包装成同一种结构success: false、error_code、error_message、retryable标志。Agent看到这种结构后可以快速判断这个错误是永久性的还是暂时的从而决定是放弃还是重试。如果每个工具报错格式都不一样Agent就无法统一处理只能在某一步彻底卡死。降级路径一定要备好。核心工具不可用时必须有一个“低配版”方案。比如主数据查询API超时自动切到只读副本实时预测接口挂了先用规则流程顶上。这个思想跟微服务里的降级一样但Agent场景里很少人提前设计所以一旦遇到外部故障整个Agent就是瘫痪状态。3. 实操记录从零搭一个带可靠性的Agent骨架理论说再多不如直接看能跑的代码。下面是我在一个客服工单分类与处理Agent里实际沉淀下来的骨架。它不依赖任何特定框架只依赖模型接口和基础数据结构你可以直接平移到自己的项目里。3.1 技术选型够用就好别被花活带偏我先说说选型思路。模型方面我选的是通用对话大模型的API支持JSON输出模式和系统提示词约束。没有选择非常“重”的自托管方案因为项目阶段最需要的是快速迭代。编排层没有上复杂的社区框架直接用了一个极简的自研执行循环。理由很简单社区框架抽象太多出了问题反而更难看懂。自研几十行核心代码任何一步都能自己控制。状态存储用了关系型数据库加内存对象的组合。执行过程中的短期状态放在内存跨会话需要持久化的数据写数据库。队列方面任务提交走的是简单的优先队列没有引入额外的消息中间件。这些选型不一定适合所有场景。如果任务并发高、链路长还是需要更完善的基础设施。但如果你和我一样处于“先把可靠性跑通”的阶段这个组合足够轻量又足够灵活。3.2 核心代码骨架与实现细节下面这段代码是整个Agent的骨架我把它尽量压缩成最核心的表达。它包含四个部分状态类、计划器、执行器、验证器。from dataclasses import dataclass, field from typing import Any, Callable, Optional import json # ---------- 状态管理 ---------- dataclass class AgentState: task: str # 原始任务描述 plan: list[dict] field(default_factorylist) # 结构化计划 step_index: int 0 # 当前执行到的步骤 memory: dict[str, Any] field(default_factorydict) # 跨步骤关键状态 step_results: list[dict] field(default_factorylist) # 每一步的真实结果 retries: int 0 # 当前步骤重试次数 max_retries: int 2 # 最大重试次数 final_result: Optional[dict] None # 最终输出 def update(self, key: str, value: Any) - None: # 所有状态更新必须走这里保证可追溯 self.memory[key] value self.step_results.append({step: self.step_index, key: key, value: value})状态类我觉得最重要的设计是update方法。所有写状态的操作都必须经过它这样每一步修改都进了step_results后面排查时能精确还原状态变化轨迹。这个习惯救了我很多次。# ---------- LLM 调用封装与 JSON 解析 ---------- def llm_call(prompt: str, temperature: float 0.2, schema: Optional[dict] None): # 这里实际会调模型接口 # 注意带 schema 时开启 JSON 输出模式温度调低 raw model_api.chat(promptprompt, temperaturetemperature) return parse_json_with_retry(raw, schemaschema) # ---------- 计划器 ---------- PLAN_SYSTEM_PROMPT 你是一个任务规划器。你的任务是基于用户目标生成结构化行动计划。 要求 1. 输出必须是合法JSON格式{goal: str, steps: [{id: int, action: str, tool: str, args: dict, depends_on: list}]} 2. steps不得超过5步每一步只做一件具体的事 3. 每一步必须写明需要的工具和参数不允许模糊描述 4. 如果任务本身包含明显不可执行的部分明确标记为需要用户确认 def planner(state: AgentState) - None: prompt f用户目标{state.task} plan_data llm_call(PLAN_SYSTEM_PROMPT prompt, temperature0.1) state.plan plan_data[steps] state.step_index 0计划器里有个容易被忽视的点depends_on。我要求模型显式写出步骤间的依赖关系。别看这个字段简单它让执行器可以判断哪些步骤可以并行、哪些必须串行也方便在后续步骤失败时快速回溯依赖链。# ---------- 工具注册与错误包装 ---------- class ToolRegistry: def __init__(self): self._tools {} def register(self, name: str, executor: Callable, schema: dict, description: str): self._tools[name] {executor: executor, schema: schema, description: description} def call(self, name: str, args: dict) - dict: tool self._tools.get(name) if not tool: return {success: False, error_code: TOOL_NOT_FOUND, retryable: False} try: result tool[executor](**args) return {success: True, result: result, retryable: False} except TimeoutError: return {success: False, error_code: TIMEOUT, retryable: True} except ValueError as e: return {success: False, error_code: INVALID_ARG, error_message: str(e), retryable: False} except Exception as e: return {success: False, error_code: UNKNOWN, error_message: str(e), retryable: True}ToolRegistry把工具调用和错误处理集中到了一处。所有工具返回结构都一样执行器对这一层非常信任。每条错误信息里的retryable字段是给执行器做重试判断用的。判断逻辑简单但有效超时和未知异常可以重试参数错误和工具不存在重试再多次也没用。# ---------- 执行器 ---------- EXECUTE_SYSTEM_PROMPT 你是任务执行器。根据当前计划步骤选择工具并生成严格符合工具schema的参数。 不需要解释只输出JSON{tool: str, args: dict} 如果缺少必要信息输出{tool: ask_user, args: {question: ...}} def executor(state: AgentState, registry: ToolRegistry): # 检查所有依赖步骤是否已完成 step state.plan[state.step_index] for dep_id in step.get(depends_on, []): if state.step_results[deps_ok(dep_id)] is None: return {success: False, error_code: DEP_NOT_DONE} # 让模型根据当前状态决定调用什么工具、传什么参数 decision_prompt f当前任务目标{state.task}\n当前步骤{step}\n当前状态{json.dumps(state.memory, ensure_asciiFalse)} decision llm_call(EXECUTE_SYSTEM_PROMPT decision_prompt) if decision.get(tool) ask_user: return {ask_user: decision[args][question]} # 实际调用工具 result registry.call(decision[tool], decision[args]) if result[success]: state.update(fstep_{state.step_index}_result, result[result]) state.step_index 1 state.retries 0 else: return result return {success: True, result: result}执行器的核心设计是“决策和执行分离”模型只负责决定调用哪个工具、传什么参数真正执行工具、处理异常都由代码负责。这一步隔离很重要——模型永远不直接接触外部系统外部系统的异常也就无法直接污染模型的世界观。执行器还有一个细节ask_user这个特殊工具。当Agent发现关键信息缺失时可以选择直接问用户而不是硬着头皮瞎猜。这个设计我第一次加上时觉得有点“不够智能”后来才发现这正是可靠性的重要组成部分——聪明的Agent要知道自己什么时候该停下来。# ---------- 验证器 ---------- VERIFY_SYSTEM_PROMPT 你是任务验证器。你的任务是检查任务最终结果是否真正满足了用户原始目标。 请以挑剔视角审查重点关注 1. 结果与原始目标是否一致有没有遗漏要求 2. 是否有数值、日期、关键字段错误 3. 是否存在模型编造的信息 输出JSON{passed: bool, reason: str, risks: [str]} def verifier(state: AgentState): final_state state.memory prompt f原始目标{state.task}\n最终状态{json.dumps(final_state, ensure_asciiFalse)} verdict llm_call(VERIFY_SYSTEM_PROMPT prompt, temperature0.0) return verdict # ---------- 主循环 ---------- def run_agent(task: str, registry: ToolRegistry): state AgentState(tasktask) planner(state) while state.step_index len(state.plan): step state.plan[state.step_index] result executor(state, registry) if not result.get(success): if result.get(retryable) and state.retries state.max_retries: state.retries 1 continue state.final_result {status: failed, reason: result.get(error_message), partial: state.step_results} return state.final_result if state.step_index len(state.plan): break verdict verifier(state) if not verdict[passed]: # 验证不通过的情况尝试让计划器生成修复方案 repair_plan planner_for_repair(state, verdict[reason]) if repair_plan: state.plan repair_plan # 从头执行修复计划 state.step_index 0 continue state.final_result {status: success, result: state.memory, verdict: verdict} return state.final_result主循环里最容易被忽略的是验证不通过后的“修复分支”。我最初的做法是验证不通过就直接标记失败。后来调整成验证器给出失败原因计划器根据原因生成修复计划然后重新进入执行循环。这种“验证-修复-重执行”的机制把很多本来会直接失败的任务救了回来。比如计划器一开始漏掉了工单必须附带客户等级信息执行器执行完发现结果里根本没这个字段验证器标记不通过并说明原因计划器重新补一步查询客户等级再次执行后验证通过。整个过程不需要人介入成本只是多消耗了一次规划调用和一次工具调用。3.3 关键参数配置心得几个直接影响可靠性的设置这些参数看似不起眼实际影响非常大。我把调优过程中沉淀下来的配置经验整理成了表格参数项推荐值作用与说明模型温度0.1~0.2温度越高输出越发散。规划、执行、验证都用低温牺牲一点创意换确定性JSON输出模式开启强制模型输出合法JSON避免解析失败格式问题是可靠性的隐形杀手最大重试次数2超过两次重试基本说明问题不可自愈继续重试只会烧钱。重试间隔建议指数退避单任务最大步数5~8超过这个步数链路复杂度飙升错误概率指数增加。长任务拆成子任务更靠谱验证器温度0.0验证环节温度必须拉到最低不需要任何创造性只需要严格审查上下文摘要长度尽量精简每次只注入与当前决策相关的摘要防止无关历史信息干扰决策温度是我第一个调的参数。刚做Agent时看到有人推荐“温度高一点有创造性”结果任务执行得越来越奔放各种奇怪的工具调用和参数层出不穷。后来所有决策环节全部降到0.1左右可靠性立刻上升一个台阶。步骤数上限则是另一个被低估的参数。我之前遇到过Agent规划出12步的长任务结果第9步出问题时前面8步的成果也全部作废。后来加了一条规则超过5步的任务必须拆成多个子任务分阶段执行每个子任务单独验证。这个改动起效明显——长的链路天然易碎能拆就拆。4. 从运行到排障把可靠性建立在可观测、可回归的基础上代码写好了、架构搭对了并不代表可靠性工作结束。系统上线后才是真正的考验。这节我分享运行时观测和故障排查的实战经验。4.1 高频故障速查表我梳理了运行期间遇到最多的几类问题做成速查表新的问题出现时先从这里排查故障现象根因排查思路处理方式Agent反复调用同一个工具结果都一样工具返回了可重试错误但执行器没记录重试状态检查重试计数与退避逻辑确认retryable标记加入次卡控结果看起来合理但和事实不符模型幻觉工具调用实际失败但Agent脑补了结果检查工具返回日志是否真实包含数据在验证器中引入事实性校验早期步骤信息被遗忘上下文被无关中间过程淹没检查每轮注入的context长度启用摘要机制精简上下文任务中途停下不继续执行器把ask_user结果当成了终态检查状态机是否处理用户回复后的继续逻辑补充用户反馈回填流程计划步骤依赖错误模型没有正确理解依赖关系检查计划JSON中的depends_on字段在计划器中增加依赖合法性校验这五个问题里“模型脑补结果”是我最想强调的一个。它的典型表现是Agent调用的工具返回了失败但模型的输出却是一段完整的分析结果好像数据真的查到了一样。一旦这种结果进入状态区后续所有步骤都建立在一个捏造的事实上最终任务展示出来“看起来成功”实际上是虚假成功。我在验证器中专门加了一条规则如果工具调用未成功任何涉及该工具结果的判断都必须标记为风险。4.2 用日志与追踪还原每一步排查Agent故障最大的困难在于“链路太长、状态太多、因果链复杂”。我踩坑得到的教训是没有结构和追踪的日志排查Agent问题等于大海捞针。我在实际项目里落地的日志方案分三层运行日志记录每一次模型请求、工具调用、状态更新附带时间戳和耗时决策追踪记录每一步模型输出的完整内容以及它读取到的上下文摘要方便复盘模型为什么会做这个决策结果审核记录验证器的每一次判定结果和理由用于评估整套系统的误判率。三层日志里决策追踪是最容易被忽略、又最有价值的。比如某次Agent做了个错误决策光看运行日志只能看到“调了某个工具传了某个参数”但不知道它为什么这么传。结合决策追踪里注入的上下文就能发现原来是某一步工具返回的格式里有个隐藏字段模型误读成了另一个意思。链路追踪同样重要。我给每次任务分配一个唯一的trace_id从计划生成到最终验证所有日志都带上这个ID。排查时只要拿到一个trace_id整条执行链都能串起来。4.3 回归测试没有基准集一切优化都是自我感动可靠性优化的最大陷阱是“修一个bug引出三个新问题”。模型输出的随机性决定了每次改动都可能改变整体行为。我在项目中期引入了回归测试机制才让优化变得可持续。我的做法是维护一个“基准任务集”包含几十个覆盖典型场景的测试任务每个任务都有期望结果或者至少有关键检查点。任何一次提示词调整、参数修改、工具描述变更都要先跑一遍整个基准集对比通过率变化。为了提高回归效率我把基准任务分成三个等级核心任务比如“正确查询订单状态并回复客户”必须100%通过常规任务比如“处理退货流程”要求通过率不低于95%探索任务比如“处理异常或模糊需求”通过率可放宽但需要记录失败模式。每次优化后只看三个指标核心任务通过率降没降、平均token消耗涨没涨、失败原因有没有新增模式。这三个指标比任何感觉都可靠。4.4 一批值得提前规避的典型坑除了上面提到的速查表问题我再补充几个比较容易踩、但很少人提前预警的坑一是“伪并行”的依赖陷阱。如果计划器生成多个步骤且这些步骤都修改同一个状态字段并行执行会互相覆盖。我这个项目里就遇到过两个步骤同时写customer_id结果其中一个写错了值另一个已经用它做完了查询。我的解决办法是计划生成时凡是存在写同一字段的步骤自动降级为串行。二是“验证器也吃幻觉”的问题。验证器本身也是模型也可能犯错。尤其当模型对结果“过于自信”时验证器可能跟着一起漏过问题。为了防这个我在验证器之外增加了少量确定性规则校验。规则校验优先模型校验为辅。比如客服工单的处理结果必须包含工单号这条规则直接用正则检查根本不过模型。三是“成本失控”的问题。验证器、修复流程、重试机制都会增加额外调用可靠性提升和成本提升是伴生的。我的经验是给每个任务设置一个token预算比如单个任务最多消耗多少token超过就直接终止标记为需要人工介入。这避免出现“一个简单的查询任务因为反复重试烧掉巨额成本”的极端情况。5. 写在最后可可靠性不是一次改造而是一套习惯这篇文章里写的方法是我在构建客服场景Agent时一步步踩出来的。说实话没有哪一条是惊天动地的创新大部分都是很朴素的工程手段状态显式化、错误统一化、验证结构化、观测完整化。但正是这些朴素手段组合在一起把Agent的可靠性从“看运气”提升到了“可预期”。如果你正在做自己的Agent我的建议是从最简单的环节入手——先把执行器里所有的工具错误统一格式再加验证器再加回归集。不要一上来就追求复杂的记忆系统和多Agent协作。基础打牢了后面加什么功能都稳基础不牢功能越多越容易翻车。最后再分享一个我个人的习惯每次Agent出现奇怪故障时我不急着改提示词而是先问自己三个问题——模型是不是读到了错误的状态工具返回是不是与预期不一致验证器是不是漏掉了该查的维度大多数时候答案就藏在这三个问题里。