ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent工程化:从Demo到生产可用的五道鸿沟与四板斧

Agent工程化:从Demo到生产可用的五道鸿沟与四板斧 约四成 Agent 项目最终没有进入生产环境这个数字在不少团队复盘里并不夸张。失败原因很少是模型能力不足更多是工程化没有跟上项目能跑通一个 Demo却无法稳定支撑真实业务。Agent 项目看似只是“用模型调用工具”一旦进入业务系统就会遇到评估缺失、状态混乱、工具异常、链路不可观测、多人协作没有规范等问题。这里想讨论的不是某个框架的用法而是 Agent 工程化本身为什么项目会失败怎么拆解失败原因以及如何用系统工程化的方式让 Agent 从“演示可用”走向“生产可用”。1. 先理解Agent 工程化解决的不是“模型能做什么”而是“系统能否稳定交付”1.1 分清“Agent 能力”和“Agent 系统”两个概念单个大模型调用能生成一段文本、总结一篇文章、甚至依据少量上下文回答问题这是“模型能力”。Agent 项目则完全不同它需要一个能够持续运行的执行系统接收用户输入、调用模型、解析意图、选择工具、执行工具、读取结果、维护上下文、判断是否继续循环、最终输出答案。整个过程还涉及安全校验、异常处理、日志记录、成本控制、人工接管等环节。“Agent 能力”解决的是“模型能不能理解并处理这个任务”“Agent 系统”解决的是“这个流程能不能在生产环境里稳定重复运行”。很多失败项目本质上只做了前者靠 Prompt 堆出了一个看起来很聪明的 Demo却忽略了后者。工程化就是把后者变成一套有规范、可评估、可回滚的软件系统。在常见项目中Agent 并不是单个函数而是一个循环。这个循环通常包括读取当前任务和已有上下文。让模型决定下一步是直接回答还是调用工具。如果需要调用工具解析参数并执行。把工具结果追加到上下文。重复以上步骤直到模型认为任务完成或达到步数上限。输出最终答案。这个循环一旦缺少终止条件、异常捕获、状态保存和可观测记录就会在真实场景里出现各种不可预期的问题。1.2 失败的项目普遍卡在 demo 到交付的断层Demo 阶段通常只验证一小批精心挑选的用例输入的写法、语气、格式都相对友好。生产环境面对的是真实用户输入同一个意图可能有几十种表达方式工具可能超时、返回异常、权限不足模型升级后行为也可能发生变化。这些因素叠加会造成一个典型断层开发时“看起来不错”上线后“经常出错”。下表列出了 Demo 阶段与生产系统在几个关键维度上的差异。维度Demo 阶段生产系统要求输入空间固定几条用例全量无死角用户输入模型输出期望看到正确结果非法 JSON、乱答也要兜底工具服务假设可用必须处理超时、错误码、限流上下文单次会话或短对话多轮、长会话、跨会话记忆效果判断人工看输出需要评估集和回归指标排障方式本地打印调试需要 trace 和日志链路团队协作一个人改 Prompt需要版本管理、评审、灰度这里的关键判断是Agent 项目不能只看“能不能跑”还要看“跑错了能不能发现、能不能恢复、能不能回滚”。1.3 最容易失败的 Agent 项目画像从工程实践看最容易倒在工程化阶段的 Agent 项目有几种典型画像。第一类是强交互客服 Agent。用户输入空间大多轮意图变化快还要频繁调用订单、售后、库存等业务系统。这类项目一旦没有严格的上下文管理和工具异常处理很容易在一个普通问题上无限循环或给出不一致答复。第二类是操作型 Agent。它不只是回答问题还会创建工单、修改配置、发起退款、更新数据库。操作型 Agent 对工具权限和校验要求极高一次错误的工具参数可能造成真实业务损失。第三类是文档处理 Agent。长文档切片、检索质量、上下文拼装都依赖工程手段单纯依赖模型长窗口并不能解决知识准确性问题当检索结果错误时Agent 会基于错误内容继续推理错误被放大。第四类是数据分析 Agent。它要生成 SQL 或 Python 代码再执行并解读结果。问题在于代码生成的正确性不稳定执行结果可能污染后续判断而且执行环境如果不受控会带来安全风险。这些项目失败不是模型不聪明而是缺少一整套工程约束。接下来拆解成五道鸿沟方便对照自己的项目到底卡在哪一层。2. 五道鸿沟Agent 项目从原型到规模化的断层2.1 第一道鸿沟Demo 到生产环境的可靠性鸿沟模型输出存在概率性。同一个 Prompt 连续调用两次结果可能不同。Demo 只看成功路径生产环境必须接受失败路径。例如模型返回了一段无法解析的 JSON直接导致工具参数解析失败或者模型在连续失败后仍然继续重试而不是进入兜底回复。这块最直接的工程手段是给模型输出加约束和解析层。不要假设模型一定会输出合法 JSON更不要假设它每次都会调用正确工具。典型做法包括使用结构化输出能力要求模型返回固定 schema。在解析层对非法 JSON 做一次修复或重试。对模型返回的工具名做白名单校验。连续多次失败时停止 Agent 循环并返回固定兜底文案。示例解析函数如下import json def parse_agent_output(raw: str): 解析模型返回内容兼容 markdown 代码块和简单错误。 try: return json.loads(raw) except json.JSONDecodeError: start raw.find(json) if start ! -1: start len(json) end raw.find(, start) if end ! -1: return json.loads(raw[start:end].strip()) raise ValueError(invalid_agent_json)这个函数解决的问题很具体模型可能不按约定输出纯 JSON而是包在 markdown 代码块里。不处理这一步后面工具调用就会失败。2.2 第二道鸿沟单轮调用到多轮任务的确定性鸿沟Agent 的价值在于能完成多步任务但难点也在这里。多步循环缺少终止条件时模型可能反复调用同一个工具或者陷入“思考—调用—出错—再思考”的循环。常见框架会返回类似agent terminated due to error. you can prompt the model to try again or start a new agent的提示本质就是这个循环内部异常没有被妥善处理。跨过这道鸿沟需要为循环建立确定性约束设定最大步数例如max_steps10。设定总超时时间例如单次 Agent 任务不超过 60 秒。记录每次工具调用参数检测重复调用。对工具调用做幂等控制避免重复执行同一操作。失败重试只允许有限次数不能无限重试。一个最简单的循环控制结构如下def run_agent_with_budget(input_text, max_steps10): messages [{role: user, content: input_text}] for step in range(max_steps): response call_model(messages) if not response.tool_calls: return response.message messages.extend(to_tool_messages(response)) if is_duplicate_call(response.tool_calls): return 检测到重复操作任务中止 return 超过最大步数任务中止这里的核心不是限制模型聪明程度而是给整个执行过程一个安全边界。没有边界模型会在长尾输入上浪费 token甚至触发真实操作。2.3 第三道鸿沟上下文与记忆的连续性鸿沟真实用户不会只发一句话Agent 需要理解多轮对话中的指代关系还要能记住之前查过哪些信息。但模型上下文窗口有限把所有历史都塞进 Prompt 既浪费 token又会降低指令跟随质量。工程上通常把记忆分层处理。记忆类型存储位置使用方式风险短期记忆会话上下文 messages每次请求直接携带超过窗口限制token 成本高状态记忆Redis、数据库保存会话状态、已收集字段序列化失败、状态过期长期记忆向量库 关系库检索相关历史片段召回质量低混合无关信息业务事实业务系统数据库实时查询保证权威性权限控制复杂很多失败项目的问题在于把这些层次混在一起。比如把用户隐私信息直接塞进向量库或者把上一轮工具结果放在全局变量里换一个用户就串上下文。正确的做法是多轮 Agent 的每一步都要有明确的“状态快照”。至少应包含当前会话 ID、用户 ID、已完成步骤、待执行目标、已获取的工具结果。生产环境建议把会话状态持久化到 Redis 或数据库这样即使服务重启用户也可以继续之前任务。2.4 第四道鸿沟工具集成与安全边界的一致性鸿沟Agent 依赖工具完成任务但工具层往往是最薄弱的环节。一个常见场景是模型生成了工具调用参数却不符合接口要求或者某个工具接口原来返回{code: 0}后来换成{status: success}Agent 的解析逻辑立刻失效。更严重的是安全边界问题。如果 Agent 可以调用“删除订单”这类敏感操作但 Prompt 只是告诉它“不要随意删除”这远远不够。Prompt 约束不是权限控制工具层必须强制校验。推荐在工具层做三层防护参数校验检查类型、长度、枚举值。权限校验当前用户是否有权调用该工具。风险操作护栏删除、退款、批量修改类操作需要二次确认或人工审批。下面是一个带参数校验和权限判断的工具示例def cancel_order(order_id: str, user_id: str): if not re.fullmatch(rA\d{6}, order_id): return {error: invalid_order_id} if not has_permission(user_id, order:cancel): return {error: forbidden} return order_service.cancel(order_id, operatoruser_id)这里要注意的取舍是不要在 Prompt 里定义业务校验逻辑而要在工具函数中强制实现。因为模型输出有随机性Prompt 规则不能作为安全基线。2.5 第五道鸿沟个人脚本到团队协作的工程化鸿沟一个开发者自己调试 Agent 时可以直接改 Prompt、改工具、跑几次看结果。但项目进入团队后问题会快速放大Prompt 改了但没记录版本某个工具接口变化导致整体失效模型从旧版本升级到新版本后效果明显波动却没有评估数据支撑。这道鸿沟是前面四道鸿沟在团队协作层面的体现。要跨过它需要把 Agent 相关产物当作软件工程对象管理。需要纳入版本管理的包括Prompt 模板。工具定义和 schema。评估用例集。模型名称和参数配置。护栏规则和权限配置。没有版本管理任何一次“顺手改动”都可能成为线上事故的源头。没有评估集团队就只能在“感觉变好了”和“感觉变差了”之间争论。3. 系统工程化四板斧把 Agent 当系统来建设从结构上看四板斧分别是评估基线、可观测性、状态管理、流程护栏。四者互相支撑评估用于回答项目好不好可观测性用于回答坏在哪里状态管理用于保证执行连续性流程护栏用于防止风险和越权。3.1 第一板斧用评估基线锁定质量底线Agent 项目很难靠一两个指标衡量效果。准确性只是其中一项还要关注工具调用成功率、任务完成率、平均耗时、token 成本、失败原因分布。没有评估基线任何 Prompt 优化都无法判断是正向还是负向。落地方式并不复杂。先把历史用户输入、典型业务场景、边界异常整理成评估集规模从几十条到几百条即可。每次改动代码或 Prompt都运行同一套评估脚本对比指标变化。一个最小评估脚本结构如下def evaluate(run_case, cases): passed 0 results [] for case in cases: try: output run_case(case[input]) except Exception as exc: output ferror: {exc} ok case[check](output) results.append({input: case[input], passed: ok, output: output}) passed int(ok) return { pass_rate: passed / len(cases), passed: passed, total: len(cases), results: results, }实际项目中这个脚本可以扩展为从文件读取评估用例、记录每次运行结果、输出指标对比。关键不是评估集多大而是每次改动都跑形成回归机制。一个没有回归机制的项目即使现在效果不错也无法阻止未来的退化。3.2 第二板斧用可观测性暴露 Agent 的内部链路普通接口出问题靠堆栈和日志就能定位。Agent 出问题定位链路要复杂得多模型想了什么、调了哪个工具、工具返回了什么、为什么最终放弃。这些信息必须记录下来。推荐为每个 Agent 请求生成一个trace_id贯穿一次任务的完整生命周期。每次模型调用、工具调用、上下文变化都可以作为 trace 中的一个节点。一条最小 trace 结构如下{ trace_id: 68a3e9f2, session_id: s-1024, start_time: 2025-06-01T10:00:00Z, steps: [ { step: 1, type: llm, model: gpt-4o-mini, latency_ms: 203, tokens: 156, action: tool_call, tool: get_order_status, args: {order_id: A123} }, { step: 2, type: tool, tool: get_order_status, latency_ms: 18, result: {order_id: A123, status: shipped} } ], stop_reason: tool_completed, total_cost_usd: 0.0012 }有了这类 trace排查问题就不再依赖“凭直觉复现”而是直接回答几个问题是哪一步开始偏离预期工具结果是否异常是模型决策错误还是外部服务错误在复杂 Agent 中甚至可以接入专门的追踪平台但在此之前先把结构化日志写好这是可观测性的第一步。3.3 第三板斧用状态与记忆管理构建确定性Agent 是典型的有状态任务状态一旦丢失用户只能重新开始。生产环境必须区分短期上下文、会话状态和长期记忆。层级可选存储典型场景工程注意点会话上下文内存、Redis当前对话的 messages设置过期时间防止无限增长状态快照Redis、数据库保存当前任务进度使用 JSON 序列化版本字段业务记忆向量库 元数据过滤用户偏好、历史偏好按用户隔离设置写入来源权威数据业务数据库订单、库存、价格Agent 只读敏感操作受限在选择记忆方案时要注意不要把所有数据都塞进上下文。一个常见设计是每次请求只携带最近几轮对话和经过检索后的高相关记忆而不是把全部历史重新放入 Prompt。状态管理还要考虑恢复能力。一个 Agent 任务被用户中断后下次继续时应该读取之前的状态而不是重新开始。此时状态快照中至少需要包含“当前目标”“已收集信息”“下一步动作”。3.4 第四板斧用流程护栏和版本治理约束行为边界Agent 不是不可控的但必须主动设计控制点。控制点包括输入过滤、输出校验、工具权限、敏感操作审批、异常降级。每个控制点都应该对应一个可执行的规则而不是一句写在 Prompt 里的“请勿随意操作”。一个典型的护栏配置如下agent: name: order-helper model: gpt-4o-mini max_steps: 10 timeout_seconds: 60 tool_policy: allow_tools: - get_order_status - list_orders deny_tools: - delete_order - refund_order guardrails: require_human_approval: - refund_order - cancel_order output_validator: required_fields: - answer - confidence这段配置表达的含义是模型只允许调用两个查询类工具删除和退款类操作直接被禁掉如果未来需要退款能力必须开启人工审批最终输出必须包含answer和confidence字段。即使模型输出了非法结构校验层也会拦截。版本治理方面重要的是把“模型升级”看作一次需要评审的发布。Prompt、工具、模型参数、护栏规则任何一项变化都要走同一套评估和灰度流程。4. 最小工程化示例一个带评估和追踪的 Agent Loop4.1 场景与要求以“订单查询助手”为例目标是让用户输入订单号后Agent 判断是否需要查询工具调用工具并返回物流状态。如果用户没有提供订单号Agent 应要求补齐信息。示例会展示三件事有限步数、异常兜底、trace 落地。4.2 工程目录一个相对清晰的初始目录结构如下agent_project/ ├── agent.py ├── tools.py ├── eval_cases.json ├── run_eval.py └── trace_logs/tools.py存放业务工具agent.py存放 Agent 循环run_eval.py用于跑评估trace_logs存放每次运行的 trace 文件。4.3 核心实现tools.py提供一个带参数校验的订单查询工具import re def get_order_status(order_id: str) - dict: order_id order_id.strip().upper() if not re.fullmatch(rA\d{3}, order_id): return {error: invalid_order_id, order_id: order_id} # 简化模拟实际调用订单服务 return {order_id: order_id, status: shipped, status_text: 已发货}agent.py实现简化版 Agent 循环。这里用call_llm模拟模型输出实际项目中替换为具体模型 SDK 即可import json import uuid def call_llm(messages): 模拟模型返回用户输入包含 A123 时调用工具否则要求补充订单号。 last_user messages[-1][content] if A123 in last_user: return { content: None, tool_calls: [ { id: call_1, type: function, function: { name: get_order_status, arguments: json.dumps({order_id: A123}), }, } ], } return {content: 请提供订单号例如 A123, tool_calls: None} def run_agent(user_input, tools_map, max_steps5): trace_id uuid.uuid4().hex[:8] messages [{role: user, content: user_input}] trace {trace_id: trace_id, steps: []} final_answer None stop_reason completed for step in range(max_steps): try: response call_llm(messages) trace[steps].append({step: step 1, response: response}) tool_calls response.get(tool_calls) if not tool_calls: final_answer response.get(content, 没有获取到结果) break for call in tool_calls: func_name call[function][name] args json.loads(call[function][arguments]) if func_name not in tools_map: messages.append({ role: tool, tool_call_id: call[id], content: json.dumps({error: tool_not_found}), }) continue result tools_map[func_name](**args) messages.append({ role: tool, tool_call_id: call[id], content: json.dumps(result, ensure_asciiFalse), }) trace[last_tool_result] result except Exception as exc: trace[error] f{type(exc).__name__}: {exc} final_answer 系统暂时无法处理请稍后再试 stop_reason error break else: final_answer final_answer or 超过最大步数未完成 stop_reason max_steps_reached trace[final_answer] final_answer trace[stop_reason] stop_reason trace_path ftrace_logs/trace_{trace_id}.json with open(trace_path, w, encodingutf-8) as f: json.dump(trace, f, ensure_asciiFalse, indent2) return final_answer, trace_path这个示例的关键点有三个循环被max_steps限制异常被捕获并写入stop_reason每次运行的 trace 都保存成独立 JSON 文件。虽然简化但已经具备 Agent 循环的必要骨架。run_eval.py读取两条用例并执行from agent import run_agent from tools import get_order_status tools_map {get_order_status: get_order_status} cases [ { input: 订单 A123 到哪了, check: lambda output: 已发货 in output, }, { input: 帮我查一下物流, check: lambda output: 请提供订单号 in output, }, ] def main(): total len(cases) passed 0 for case in cases: output, trace_path run_agent(case[input], tools_map) ok case[check](output) passed int(ok) print(f{case[input]} - passed{ok} | {output}) print(f trace: {trace_path}) print(fRESULT {passed}/{total} passed) if __name__ __main__: main()4.4 运行验证在项目根目录运行python run_eval.py预期输出接近下面内容订单 A123 到哪了 - passedTrue | 已发货 trace: trace_logs/trace_xxxxxxxx.json 帮我查一下物流 - passedTrue | 请提供订单号例如 A123 trace: trace_logs/trace_xxxxxxxx.json RESULT 2/2 passed打开任意 trace 文件可以看到模型返回、工具调用、最终答案和终止原因。这就是最基础的可观测性。4.5 生产化需要补齐的能力这个示例能演示工程化思路但离生产还有明显差距。真实项目至少还需要补齐以下内容接入真实模型服务并统一处理超时、重试、限流。使用 Redis 或数据库持久化会话状态而不是每次从零构建 messages。将 trace 上报到日志平台或追踪系统而不是只写本地文件。增加输入输出内容过滤避免敏感信息进入模型和外部工具。对敏感操作增加人工审批和审计日志。将 Prompt、工具 schema、模型版本纳入配置管理。从最小示例到生产系统的过程正是“工程化”从概念落到具体实施的过程。5. 常见失败复盘先看现象再定位根因5.1 失败现象与排查路径实际项目中常见的失败现象可以整理成下面这张表现象可能原因检查方式处理建议模型输出 JSON 解析失败输出格式约束不足或模型版本变化查看 trace 里原始 response增加结构化输出约束解析失败时修复重试Agent 反复调用同一个工具缺少循环检测或终止条件统计 trace 中 tool 调用序列设置 max_steps、重复调用检测、工具幂等任务长时间无响应模型服务超时或工具阻塞检查日志中的 timeout 记录设置总超时和单工具超时失败后快速返回上下文过长导致请求报错messages 超过上下文窗口检查请求 token 数对话摘要、截断、记忆分层工具返回数据不匹配接口字段发生变化对比 trace 和接口文档工具层增加返回结构校验和字段映射用户 A 的数据出现在用户 B 的会话使用了全局变量存状态检查状态存储 key 是否包含用户 ID状态必须按用户维度隔离模型升级后效果明显下降Prompt 对模型版本敏感用同一评估集对比新旧模型固定模型版本升级前跑回归有不少运行时报错是类似agent terminated due to error. you can prompt the model to try again or start a new agent这样的提示本质都是循环内部异常没有被捕获框架直接终止了执行。看到这类信息时不要急着改 Prompt先定位异常发生在哪一次工具调用以及异常类型是什么。5.2 推荐的一条复盘顺序遇到 Agent 系统异常建议按以下顺序排查先找到该请求的 trace_id拿到完整执行链路。判断是模型决策错误还是工具执行错误。如果是模型决策错误检查 Prompt、工具描述、历史上下文是否清晰。如果是工具错误检查参数校验、接口返回、权限配置。检查是否触发了终止条件或异常捕获分支。最后把这次失败补充进评估集避免后续回归。这个顺序能有效避免无目标地反复调 Prompt。调 Prompt 是最后一步而不是第一步。5.3 常见坑至少四个重点第一个坑是裸try-except吞掉所有异常。很多 Agent 代码在循环外层包了 try但 except 里只打印一句话没有记录 trace。结果线上出了问题什么证据都没有。正确做法是至少记录异常类型、调用链和当前状态。第二个坑是依赖 Prompt 限制敏感操作。例如在系统 Prompt 里写“不要删除订单”这不能作为安全机制。工具层必须做权限校验敏感操作必须人工审批。模型输出是不可完全预测的安全边界不能建立在概率上。第三个坑是没有评估就反复调整 Prompt。一个用例调好了另一个用例又坏了最后整个团队靠“手感”维护。正确做法是先建评估集再修改再跑回归。没有评估集的项目不建议上线。第四个坑是忽略 token 成本和任务预算。Agent 循环一旦失控一次请求可能消耗上千 token。生产环境必须在模型调用层加预算控制例如每轮任务设置 token 上限和成本上限。否则一个失败任务可能悄悄消耗大量成本。6. 落地的最佳实践与扩展方向6.1 上线前检查清单Agent 项目上线前建议逐项确认以下内容检查项是否完成说明评估集覆盖正常路径和异常路径必选至少 30 条用例包含工具超时和非法输入每次 Agent 请求有唯一 trace_id必选trace 中记录模型、工具、耗时、终止原因Agent 循环有最大步数和总超时必选防止死循环和长时间占用工具调用有参数校验和权限校验必选业务规则在工具层强制不写在 Prompt 里敏感操作有人工审批或二次确认风险相关时必选删除、退款、批量操作必须加审批会话状态按用户隔离并持久化必选避免串用户和重启丢失Prompt、工具 schema、模型版本可回滚必选配置纳入版本管理输出内容有过滤和校验建议防止模型输出非法格式或敏感信息6.2 学习环境与生产环境的差异初学者在本地跑 Agent 时可以用最简单的内存变量保存状态直接在终端打印日志。生产环境必须把存储、日志、权限、成本控制都做完整。维度学习环境生产环境模型 API Key写在配置文件用于本地调试使用密钥管理服务不允许进代码库状态存储内存变量Redis、数据库带过期策略日志print 输出集中日志平台trace_id 关联评估手动看几条输出自动回归指标对比权限不校验或简单校验最小权限敏感操作审批成本不关注有监控和预算上限6.3 从单 Agent 到多 Agent 协作的工程化升级当任务复杂度继续上升单个 Agent 会被拆成多个子 Agent。此时工程化复杂度会进一步提高每个子 Agent 需要明确的输入输出协议、角色边界、共享状态和失败隔离机制。不要把多个 Agent 的上下文全部混在一起否则错误会被快速放大。这里要区分几个容易混淆的概念Agent 是执行体负责理解任务、决策和调用能力Skill 是复用能力单元可以被多个 Agent 共享Tool 是最小的外部功能调用。工程化的做法通常是把 Tool 和 Skill 独立维护Agent 通过配置选择自己可以使用的能力集合。这样既避免重复开发也方便权限管控。多 Agent 场景下建议优先做好三件事统一的任务协议、统一的追踪链路、统一的评估入口。否则会出现“单 Agent 都能跑多 Agent 协作一多就崩”的问题。真正把 Agent 项目做成核心是把它当成一个会被长期维护的软件系统评估、观测、状态、护栏四件事缺一不可。如果只能带走一条经验那就是从第一天开始记录 trace在第二周构建评估集让每一次改动都有据可查让每一个失败都能被复现和修复。
返回列表