先说个可能不太中听的结论:一个能在本地跑通的 AI Agent 原型,和一套能在企业生产环境里稳定运行 7x24 小时的 Agent 系统,中间隔着的不是一两个 bug,而是一整套工程方法。
过去两年我参与了好几轮 Agent 从 demo 到落地的过程,最深的感触是:Agent 的工程化难点,根本不在“怎么让模型调用工具”,而在“模型不可控时,系统如何依然可控”。模型输出是概率性的,企业系统是确定性的,把概率接到确定性的流程里,中间缺的每一条规则,都会以线上事故的形式还给团队。
这篇是系列的上篇,我按自己踩坑的顺序,把从原型到生产链路里最关键的五块内容整理出来:架构分层、工具契约、上下文工程、稳定性兜底、安全治理。每块都有真实背景和可以直接带走的配置思路。
1. 架构分层:先解决“代码长在对话里”的致命伤
1.1 绝大多数原型都跑在一个巨大的 for 循环里
很多团队的第一版 Agent 长这样:一个while True,把用户消息丢给模型,模型返回消息或工具调用,代码解析工具调用,执行,再把结果塞回对话历史,循环直到模型说“结束”。
原型阶段这没问题,跑通一个“帮我查一下订单状态”的 demo 绰绰有余。可一旦想接入多个业务系统、多个工具,问题立刻冒出来:业务逻辑散落在各段 prompt 和 if 分支里,工具一多,prompt 就膨胀,改一个工具的行为可能影响所有对话;状态全留在对话历史里,模型记不住、调试看不明白;出了问题也没有分层可查,只有一段混沌的日志。
我见过某团队的线上事故:一个 Agent 在30分钟里对着同一个 API 重试了上百次,因为“是否重试”的逻辑写在模型输出解析里,而模型又在这种循环里被之前的失败样例带偏了。架构上缺的“护栏”,靠提示词补不回来的。
1.2 三层拆分:推理层、执行层、控制层
进入生产前,我建议把代码结构改造成这样三层:
- 推理层:只负责把用户请求转成结构化的“任务计划”。它不直接接触任何 API,也不执行动作。它的输入是用户意图加系统能力列表,输出是一个 JSON 计划对象。
- 执行层:一个工具注册表加统一的执行器。所有外部调用都走同一套接口:入参校验、限流、重试、超时、出站过滤,全在这里完成。执行层不关心“为什么调用”,只关心“能不能调用、怎么调用、结果是否符合预期”。
- 控制层:真正的主角。它维护运行状态、检查执行结果、判断任务是否完成、决定要不要把结果返回给推理层继续规划,或者直接结束。
代码结构大致是:
class AgentRuntime: def __init__(self, planner, executor, policy): self.planner = planner # 推理层 self.executor = executor # 执行层 self.policy = policy # 控制层:所有护栏逻辑 def run(self, user_request: str, session: Session): plan = self.planner.create_plan(user_request, session.capabilities) if not self.policy.approve(plan): # 计划审批 return self.policy.on_denied(plan) while not self.policy.is_finished(plan): step = self.policy.next_step(plan) result = self.executor.execute(step) # 统一执行入口 if not self.policy.validate(step, result): self.policy.record_failure(step, result) plan = self.planner.replan(plan, result) # 失败重规划 return self.policy.compose_response(plan)关键变化在于:模型不再直接决定动作序列,它只提方案,由控制层去批准、执行、验收。模型每多一个自由度,线上就多一类不可控风险,这里的取舍不是“更智能”,而是“可控的智能”。
1.3 把“重试策略”从 prompt 里挪到代码里
典型的重试配置,直接在控制层用策略对象完成,不经过模型:
# 控制层里的重试策略:指数退避 + 最大次数 retry_policy = { "max_attempts": 3, "base_delay_s": 1.0, "exponential_factor": 2.0, "retryable_errors": ["timeout", "rate_limit", "5xx"], }不要指望模型“自己判断”该不该重试——模型对时延根本不敏感,它不知道上一次调用已经花了3秒,也不知道目标服务当前压力如何。这类确定性判断必须下沉到代码层。
架构分层这件事,本质上是把之前靠“灵性”运转的东西,变成靠“纪律”运转的东西。纪律听起来不性感,但在生产环境里,它就是命。
2. 工具契约:模型调工具,也得“先约法三章”
2.1 为什么模型就是遵循不了 JSON Schema
模型在生成工具调用时,入参错乱是个长期问题。字段名对不上、枚举值写错、日期格式五花八门、数组多加一层、必填项漏填,这些问题在 GPT、Claude 和各种开源模型上都发生过。
不是模型变笨了,而是模型生成参数本质上是在“做翻译”——把自然语言里的东西翻译成结构化数据。自然语言是模糊的,结构化数据是严格的,翻译必然会出错。另一个隐蔽因素是 prompt 膨胀,工具数量增多时,模型对每个工具的入参约束的注意力会被稀释。
生产系统的数据库不会因为模型“尽力了”就接受一个错误的外键。所以必须加一道机械闸门:模型的输出必须经过严格校验,才能进入执行阶段。
2.2 校验 + 纠偏 + 重试的三级兜底
我用的是“三级兜底”方案,实际效果远好于单纯提示模型“请遵循 Schema”:
- 第一级:结构化输出约束。能强制 JSON 输出就走强制,把语法错误消灭在源头。
- 第二级:Schema 校验。用类似 Pydantic 或 JSON Schema 的机制做严格校验,字段类型、枚举、范围全部校验。
- 第三级:纠偏重试。校验失败时,把校验报错反馈给模型,让模型修正调用。注意要带上“调用名 + 原始参数 + 校验错误 + 修正要求”四要素,模型才能高效纠偏。
伪代码示例:
def validate_and_repair(func_name, raw_args, attempts=2): for _ in range(attempts): try: validated = schema_registry.validate(func_name, raw_args) return validated except ValidationError as e: raw_args = model.correct_call(func_name, raw_args, str(e)) raise AgentToolError(f"tool call failed validation: {func_name}")实际数据中,三级兜底能把工具调用的最终成功率拉到 99% 以上。剩下的部分则直接转入人工确认流程。
2.3 工具返回体也要有 Schema
入参校验只是半边天。另一个常被忽略的地方是工具返回值的结构。很多团队只定义入参 schema,返回来什么全靠执行层自由发挥。返回体一旦结构混乱,控制层验收就困难,模型读取结果后也容易产生幻觉。
我的习惯是把每个工具的返回也定义成带版本号的 schema,执行器统一做返回清洗。返回字段统一用序列化后的 JSON,所有日期统一成 ISO 8601 格式,货币统一成最小单位整数,枚举项必须出现在注册表里。
工具本质上也是 API,只要有多个消费者,就需要契约。趁规模小的时候把契约立好,后面才不用天天救火。
3. 上下文工程:句句都重要,句句都别留
3.1 上下文越长,Agent 不是越聪明,而是越“健忘”
一个很反直觉的现象:上下文越长,模型对早期信息的利用反而越差,而且这种劣化不是线性的。语义上,距离当前问题越远的内容,对最终决策的干扰越大;工程上,长上下文还直接推高推理成本和延迟,每次调用都在重新处理全部历史。
有一次线上排查,一个 Agent 任务从早上九点跑到中午,对话历史里堆了六十多轮工具调用结果,最后模型居然在总结里把第一单的金额当成最新金额,给用户报错了。人看完整的聊天记录不会犯这种错,但模型的注意力是“按比例分配”的,历史越长,关键信息越容易被淹没。
所以上下文工程的第一条纪律是:不要把所有历史都丢给模型,没用的尽快删,有用的结构化保留,能总结的坚决不原文保留。
3.2 给 Agent 建“工作记忆”和“长期记忆”
我把 Agent 的记忆拆成两层:
- 工作记忆:当前任务有效范围内需要的信息。比如“用户在查2025年3月的账单”,那 2025年3月的订单明细就是工作记忆,只保留最近的几轮关键工具结果,历史对话逐字内容不保留。
- 长期记忆:所有任务结束后沉淀下来的结论性信息。比如“客户 A 偏好夜间联系”“上次报价客户嫌贵,已下调服务档位”。长期记忆必须经过结构化抽取,存成键值对或向量,使用时再按需检索放回上下文。
这里有一个可以立刻落地的技巧:对话历史滚动摘要。每轮任务结束后,由模型生成一段小于 200 字的结构化摘要,包含关键实体、决策依据、未完成事项。下一轮开始前,把上一轮的摘要放进上下文,而不是把上一轮的原始输出塞进去。实测中,这种做法的准确率显著高于“全量历史保留”。
3.3 按时效给上下文分桶
不是所有信息都要活到任务结束。我把上下文按生命周期分了几个桶:
| 数据类别 | 生命周期 | 存储位置 |
|---|---|---|
| 用户原始请求 | 本轮任务 | 工作记忆 |
| 工具运行结果 | 仅当轮步骤内 | 工作记忆(过期即弃) |
| 阶段结论、业务决策 | 整个任务周期 | 工作记忆 + 长期记忆 |
| 用户偏好、历史总结 | 跨任务长期 | 长期记忆(向量库/键值对) |
这样分桶之后,模型每轮的输入规模能压掉很大一部分,延迟和成本一起降下来。更关键的是,重要信息不会被淹没,任务失败率也随之下滑。
3.4 长任务的 Checkpoint:让 Agent 学会“断点续传”
生产环境的 Agent 经常要跑长流程:跨系统核对、分批次处理、等待人工审批。这类任务如果中途崩溃,最怕的是从零开始。没有检查点的 Agent,会让用户体验极度糟糕——他提交资料填了半小时,系统崩了,又要重来。
解决方案是给任务编排加 Checkpoint:每个任务步骤完成后,把当前的状态序列化存起来,包括已完成步骤、未完成步骤、当前上下文摘要、已收集到的结果。恢复时不需要重新执行已完成的部分,只需要加载 Checkpoint 后从断点继续规划。
Checkpoint 还能带来两个隐藏收益:一是方便审计,出了纠纷可以精准回放某一步发生了什么;二是方便调试,开发期可以省掉重复跑通前置步骤的浪费。
4. 稳定性兜底:让 Agent 学会“停下来,别硬编”
4.1 设定硬边界:迭代次数、Token 预算、时间预算
我能理解很多人对 Agent 的想象是“给自己设定目标的自主智能体”,但生产环境里,无限循环的自主性就是事故源头。最稳妥的策略不是禁止自主,而是给自主加上明确的边界:
- 最大迭代次数:单次任务建议 8~15 步。超过就强制转入人工。
- Token 预算:单次任务的累计推理 token 设上限。超出意味着“这个任务对模型来说太难了”,直接升级。
- 时间预算:每步执行有单独超时,整个任务也有整体超时。时间到了直接终止。
一次 Agent 在循环里不断调用“生成报表”接口,每次调用都产生新的任务 ID,然后拿着新 ID 继续查状态。理论上这种循环可以被设计成数学上的死循环——在控制层加上最大迭代次数和总时长预算之后,这类事故直接变不可能。
4.2 引入置信度评估:该问人的时候别硬编
很多 Agent 失败的根源不是能力不够,而是“没意识到自己不确定还继续编”。让 Agent 学会说“我不确定,需要问一下”,是生产环境最重要的能力之一。
具体做法:在执行层做完关键步骤后,让控制层对“当前结果是否足以答复用户”做一次置信度评估。这个评估可以基于规则(比如关键字段是否为空、工具返回是否有异常指标),也可以让模型对自己输出的自评打分,但更推荐规则优先、模型自评辅助。
当置信度低于阈值时,控制层应该生成一个“澄清问题”返回给用户,而不是继续推进。注意这个“澄清问题”必须是选项式的,比如“请问您指的是账单 A 还是账单 B?”而不是开放式提问——开放式的澄清,通常会开启另一场拉锯。
4.3 Chain-of-Thought 日志:线上问题的“黑匣子”
Agent 系统的可观测性和普通后端不一样,只看调用链看不到“模型为什么这么决策”。所以从第一天起就要把每个决策步骤的推理摘要落盘,包括模型生成了什么计划、选了哪个工具、执行结果是什么、为什么判定需要重规划、最后自信度分数是多少。
我建议按这个最小结构记录日志:
{ "session_id": "s_20250303_001", "step": 3, "timestamp": "2025-03-03T10:15:22Z", "input_summary": "用户查询上月订单金额", "plan": {"action": "query_orders", "params": {"date": "2025-02"}}, "tool_result": {"status": "success", "total": 888}, "confidence": 0.94, "control_decision": "continue", "reasoning": "用户问题已得到明确答复,无需进一步调用" }这一步投入不大,但价值极高。线上出了任何问题,靠这份日志能直接回放出逻辑链路,而不是对着 prompt 和代码空猜。很多“模型这次怎么不听话”的问题,最后都能追溯到上下文被污染、工具返回异常、或者重规划策略过于激进这些具体的工程原因。
5. 安全与权限治理:企业落地的分水岭
5.1 最小权限:工具级权限 + 身份映射
企业里最常被轻视的问题是权限。很多原型是给系统配一个全局 API Key,所有 Agent 调用共享一个身份。这在 demo 阶段无所谓,但在生产环境是绝对红线。
实现上至少做到这样两点:
- 身份映射:每个用户会话绑定一个代表用户身份的 token。Agent 调用业务系统时,应模拟用户身份,而不是服务身份。
- 工具级权限:每个工具在注册表里就声明最低权限要求和资源范围。给某个角色分配工具时,按业务需求最小授权,而不是“能调就配”。
5.2 出站数据过滤:Agent 不能“什么都往外传”
Agent 每调用一次工具,就会把上下文中的相关数据一起发出去。这就带来一个风险:上下文里若存在不该进入调用流程的敏感字段,就会发生数据泄露。
解决方式是在执行层加统一的数据策略过滤器:所有出站请求在离开系统前,先经过字段级脱敏过滤器。这个过滤器不需要理解逻辑,它只认规则——比如“本环境禁止传输身份证号”“联系方式字段在测试环境一律打码”“上传到第三方分析工具的数据不能包含客户姓名”。
常见过滤级别:
- 阻断:含敏感字段的调用直接拒绝
- 脱敏:替换成掩码后再放行
- 放行:仅限已获得显式授权的字段
5.3 审计:每个 Agent 决策都要能回放
不知道你们有没有被这样的场景逼疯过:业务方跑来问,“这个客户为什么收到这样一封信?是谁触发的?”如果 Agent 系统没有完整的审计记录,这个问题的答案就是“模型决定的”——这不能作为企业级答复。
审计至少要包含:谁发起的任务、经过哪些工具调用、每步输入输出是什么、调用了哪些外部系统、数据流向如何、最后执行了什么动作。不能再拆了,直接落到日志文件长期保留。Cheakpoint 加审计日志的组合,能让整个 Agent 的行为做到完全可追溯,这也是企业合规审查的基础。
从原型到生产的路上还有很多硬仗:评估体系、灰度发布、成本控制、部署形态。这篇先覆盖我认为最容易被忽视、又最能决定生死的五块。如果你正在把 Agent 推向生产,希望这些内容能让你少踩几个我踩过的坑。下篇准备整理评估与回归、发布策略和成本治理,到时候接着聊。