一个能对话、能查天气、能调两三个API的AI Demo,我大概一天就能写出来。但你把它拿给团队或者客户用,马上就会撞上一堵墙:它只能在我电脑上跑,换个场景就得改代码;大模型输出稍微偏一点,整条链路就跟着乱套;而且你根本说不清一个多轮对话到底是在哪一步开始走歪的。这篇文章不聊怎么写一个Agent Demo——写Demo这件事本身没什么门槛。我想聊的是更麻烦的那一段:怎么把一个看起来不错的AI对话Demo,一步一步长成一个能被业务持续使用、能迭代、能出问题又能被快速定位的Agent平台。不管你是刚接触Agent开发的初学者,还是已经做出Demo但不知道怎么往下走的团队,这篇都值得你花十分钟读完,不会有太多虚的,全是实操里磨出来的东西。
1. 从一个Demo讲起:它到底证明了多少东西
1.1 Demo和平台之间那条"看不见的线"
很多人觉得,Demo做得越炫,距离产品就越近。其实不是。Demo和平台之间隔着的不是代码量,而是对"不确定性"的容忍度。
AI对话Demo本质上是一个"单机验证程序":你写一段Prompt,把用户的提问塞进去,调用模型接口,拿到回复再显示出来。这个流程跑通了,你验证的是"大模型能不能理解这个场景、能不能给出大概可用的结果",仅此而已。
但Agent平台解决的是另一类问题:大模型能不能在真实环境里稳定地、可重复地、可追溯地完成任务。真实环境意味着有多用户、有权限、有并发、有失败重试、有成本上限、有版本迭代。这些东西,Demo阶段完全可以不管,但一旦平台要上线,哪一个漏掉都是事故。
我自己见过太多次这样的场景:项目组兴奋地演示完一个Agent Demo,老板直接问"这个能接到我们的系统上吗?" 然后就没有然后了。不是产品没有价值,而是Demo的架构压根没有为"被接入"做准备。
所以我习惯把Demo和平台的差异整理成一张表,每次立项先过一遍,看自己到底缺在哪:
| 维度 | AI对话Demo | 可演进的Agent平台 |
|---|---|---|
| 运行方式 | 本地脚本或单机进程 | 服务化,支持多用户并发 |
| Prompt管理 | 写死在代码里 | 独立配置,可灰度可回滚 |
| 上下文处理 | 简单拼接历史消息 | 有预算控制、压缩、检索、持久化 |
| 工具调用 | 固定写死几个函数 | 动态注册、可热插拔、带权限校验 |
| 可观测性 | print语句、截图 | 结构化日志、会话Trace、成本统计 |
| 稳定性 | 靠运气和提示词 | 有超时、重试、兜底、护栏规则 |
| 迭代方式 | 改代码重启进程 | Agent版本、Skill版本、模型版本可独立演进 |
| 效果验证 | 人工看几条对话 | 回归评测集自动跑,量化对比 |
把这张表填完,你基本就知道自己手里的Demo离"平台"还差多少个模块了。
1.2 三句让你从"Demo兴奋"回到"现实"的话
在我的经验里,一个Demo做得再漂亮,也扛不住三个来自业务方和团队的追问。
第一个问题:"这个能上到我们的系统里吗?" 这问的是工程化能力。你的代码里有没有鉴权?能不能做横向扩容?模型密钥是不是写死在配置里?工具函数能不能脱离当前进程单独部署?
第二个问题:"如果大模型回答错了怎么办?" 这问的是容错能力。Agent拿着错误信息继续往下走,会不会把业务流程带偏?工具调用失败了,是重试还是放弃?有没有人能干预和打断?
第三个问题:"你怎么知道它刚才为什么这么做?" 这问的是可观测能力。Agent执行了一个五步任务,每一步为什么选这个工具、传了什么参数、模型消耗了多少Token,有没有完整记录?没有这些,线上出问题你根本无从下手。
这三个问题基本决定了你接下来的工作方向。我见过很多团队在Demo阶段花了大把精力调Prompt、做花哨的交互,结果一上真实业务,全部精力都耗在"为什么又报错"和"这个结果是怎么来的"上面。所以标题里说的"可演进的Agent平台",关键不在Agent多聪明,而在它能不能被稳定地接入、控制、追踪、迭代。
2. Agent平台的核心能力:到底要把什么做出来
2.1 先分清"对话"和"Agent"
如果把"AI对话"和"Agent"混为一谈,后面的架构大概率会跑偏。
对话模型的目标是生成"更像人话的回复",它的输出是文本。写一个对话Demo,你只需要负责把上下文拼好、调用模型、把回复展示出来就行。
Agent的目标是"完成一个需要多步骤的真实任务",它的输出是一系列决策和动作。比如"帮我查一下合同里有没有关于违约金的条款,有的话顺手整理成摘要"——这件事需要大模型理解任务、决定调用哪个检索工具、拿到结果后再判断是否需要追问还是直接生成答案。
目前最主流的Agent执行范式就是ReAct循环:Thought(思考下一步该做什么)→ Action(调用工具)→ Observation(观察工具返回结果)→ 再思考 → 直到输出Final Answer。本质上,Agent就是一个让模型反复做"决策-执行-观察"的循环结构。
生活里类比一下:对话模型像是一个很能聊的朋友,你跟他说什么他都能接,但他不会真帮你办事。Agent更像一个能帮你跑腿的助理,他会先确认你要什么,然后自己决定先去银行还是先去邮局,事情没办完还会自己调整方案。Demo到平台的跨越,就是从"能聊"到"能办事"的跨越。
2.2 我理解的Agent平台五层结构
做Agent平台之前,脑子里一定要有一张分层的地图,不然写着写着就会变成一个大杂烩。我习惯把平台拆成五层:
第一层是接入层。统一封装模型调用接口,不管底层用的是开源模型还是商业API,上层只面对一个标准接口。这一层还负责模型路由,比如简单的查询走便宜的小模型,复杂推理才调用大参数模型。
第二层是能力层。能力层是Tool和Skill的集合。Tool是原子能力,比如"查询工单""发送邮件""调用某个内部API",通常就是一个函数加一份JSON Schema描述。Skill是能力的组合,把多个工具、一段专用提示词、参数校验逻辑打包成一个可复用的业务技能。
第三层是决策层。决策层跑Agent循环,负责决定"下一步调用哪个能力、参数是什么、要不要停下来问人"。单Agent负责简单任务,复杂任务可以拆给多个子Agent协作,这种场景我们叫Multi-Agent编排。
第四层是记忆层。短期记忆是上下文窗口里的对话记录,长期记忆是跨会话存下来的用户画像、历史结论、业务知识,一般用向量数据库做检索。没有记忆层的Agent就像一个每次都失忆的实习生,永远记不住五分钟前自己干过什么。
第五层是治理层。治理层是"平台"真正的底气:评测集、日志追踪、成本统计、权限控制、版本管理、灰度发布,全都归这一层管。很多团队把Agent接上线后又退回Demo,就是因为治理层没跟上,线上跑得心里没底。
这五层不要求一次性做全,但设计时脑子里要有这张图,每个模块知道自己属于哪一层,后期才不会变成一锅粥。
2.3 新手必看:Skill和Agent的分工边界
在Agent平台的讨论里,Skill和Agent的概念被混用得最厉害。热搜里一堆人问"skill和agent区别",其实这两个东西边界很清楚。
Skill是"能力包",它是静态的,描述的是"我能做什么、需要什么输入、什么场景下适合用"。比如一个"合同审查Skill",它绑定了文档解析工具、条款检索工具、一份审查提示词,还有一套输出格式校验。它自己不会主动跑起来,它只是一套打包好的能力。
Agent是"决策体",它是动态的,负责决定"现在要不要用某个Skill、用完之后下一步干什么、如果失败是重试还是换方案"。同一个Agent,可以按任务需求在多个Skill之间切换。
我常用一个比喻:Skill是工具箱里的电钻,Agent是装修师傅。电钻本身很能干,但它不知道自己该在哪面墙上打孔。装修师傅看了现场,决定用几号钻头、先打哪个位置、打完孔之后下一步做什么。师傅会犯错,但师傅能根据现场反馈调整方案——这就是Agent的决策价值。
工程上怎么切分?Skill尽量做成配置化:一份YAML描述清楚名字、用途、绑定的工具、提示词模板、运行参数。Agent的代码则保持相对通用,不要把所有业务规则都写死在Agent的循环里,否则换个业务场景又得改一遍Agent。很多项目死在"把Skill写得像Agent、把Agent写得像Skill",模块边界一旦模糊,后续任何改动都牵一发动全身。
3. 从Demo演进到平台的关键路径
3.1 第一步:把"对话链路"和"业务逻辑"解耦
Demo时代的典型代码是:接收用户消息拼上下文、调用模型、把结果直接返回。问题在于,模型的决策过程和业务动作完全搅在一起,你没法单独升级任何一边。
我说过很多次,平台化的第一步不是引入什么高级框架,而是把接口语义从"对话"改成"任务"。让上层调用方提交一个明确的任务描述,平台负责拆解、执行和回传结果。
一个比较推荐的接口设计是这样:
{ "task": "查一下工单WO-2024-001的状态,如果还在处理中,提醒负责人明天中午前更新进展", "session_id": "session_001", "user_id": "user_123" }平台执行完,返回的不应该只是一段话,而是"最终结果+动作轨迹":
{ "status": "success", "result": "工单当前为处理中状态,已向负责人发送提醒", "actions": [ { "tool": "query_work_order", "input": {"work_order_id": "WO-2024-001"} }, { "tool": "send_reminder", "input": {"owner": "zhangsan", "deadline": "明天12:00"} } ] }把"动作记录"和"自然语言结果"分开返回,上层的业务系统可以拿动作记录做审计、做UI展示,甚至直接触发其他业务流程。这个改动成本不大,但会把架构从"一个会聊天的接口"推向"一个能办事的平台"。
3.2 第二步:把能力沉淀成Skill
当你能跑通"任务→规划→工具调用→结果"之后,下一步就是沉淀Skill。这一步的核心目的,是把业务专家经验从代码里剥离出来,让运营和产品同学也能参与配置和优化。
一个标准的Skill配置文件,我一般包含这些部分:
- name和description:这个Skill叫什么、什么场景下该用、什么场景下不该用
- 绑定的工具列表:这个Skill需要依赖哪些原子能力
- prompt模板:引导模型在这个场景下的行为规则和输出格式
- 输入输出校验:入参的JSON Schema、出参的格式约束
- 运行参数:使用哪个模型、温度是多少、最大步数是多少
用一个工单场景举例子,配置大概长这样:
name: work_order_agent description: 处理工单查询、状态更新与责任人指派的场景 model: gpt-4o-mini temperature: 0.2 max_steps: 8 prompt: | 你是工单处理助理。你可以查询工单详情、更新工单状态、指派责任人。 如果用户提供的信息不完整,先追问,不要猜测。 如果工具调用连续失败两次,直接告诉用户当前无法办理,并说明原因。 tools: - query_work_order - update_work_order_status - assign_owner schema: input: type: object required: ["task"] output: type: object required: ["result", "actions"]这段配置里的重点在prompt里那句"如果工具调用连续失败两次,直接告诉用户当前无法办理"——这是很多人会漏掉的东西。没有兜底话术的Agent,会在失败时反复重试,把Token烧光还不给用户一个交代。
Skill写好后,把它注册进平台的Skill仓库。模型升级、提示词调整,都只需要发一个新版本,不需要发布代码。这就是"可演进"的第一步。
3.3 第三步:给Agent装上决策、记忆和人工介入
Skill解决了"会做什么",Agent和记忆层解决"怎么决定做什么"。
决策机制就是ReAct循环,这部分在第四章我会贴一段可以跑的伪代码。这里我想重点谈谈"停下来"的能力。
一个负责任的Agent,不能只知道闷头执行。涉及以下情况时必须暂停下来,把决定权交还给用户:
- 要执行的操作不可逆,比如删除数据、发正式邮件、提交报销
- 要花的成本超过预设阈值,比如某个工具调用会触发高额的第三方服务
- 上下文信息不够,但Agent无法自行补齐
- 连续多次尝试都失败,需要用户重新给方向
Human-in-the-loop(人在回路)不是偶尔用一下的功能,它是Agent平台里必须内置的机制。常见做法是让Agent输出一个waiting_for_user_confirmation的状态,平台收到这个状态后挂起任务,等用户确认或补充信息再继续执行。
记忆层同样重要。平台至少要有两级记忆:短期记忆跟着会话走,长期记忆放进向量库。比如一个用户上周说过"我司用的ERP是金蝶",如果系统记得这件事,下次他再问"帮我看看库存接口怎么对接",Agent就能结合前面提到的ERP信息给出更精准的答复。没有长期记忆的Agent,每次都从零开始,体验非常断层。
3.4 第四步:让平台能"放心演进"
平台和Demo最根本的区别,是"你敢不敢改它"。
一个能让你放心演进的平台,至少要具备四件事:评测、版本、灰度、观测。
评测是安全网。把你关心的典型场景写成回归集,比如"工单查询准确率""退款流程完成率""敏感话题拒答率",每次改完提示词或者换模型,先自动跑一遍回归。没有评测集,你根本不知道改动是变好了还是变坏了。
版本管理要覆盖三个对象:模型版本、Prompt版本、Skill版本。模型升级不能直接全量上,必须先跑评测,再灰度。
灰度发布要做到按比例或按用户维度切流量。比如先让5%的真实请求走新模型,跑两天看错误率和耗时,没问题再逐步放大。
观测是整张逃生图。每次Agent执行都要把过程记录下来:模型返回了哪些思考、调用了哪个工具、传入什么参数、工具返回什么结果、每一步耗时和Token消耗。这样线上出了问题,你能从头到尾重放一遍执行过程,而不是对着黑盒猜。
我在实操中发现,社区里现在有大量个人Agent项目和开源框架,它们很多都是很好的起点Demo,研究它们的编排和工具定义方式能省不少时间。但注意,别指望直接把别人的东西拿过来当平台用,框架能解决通用能力,解决不了你那套业务规则和评测体系。平台永远是自己长出来的。
4. 实操:一个最小但完整的Agent运行时
4.1 极简Agent循环:核心逻辑就一个while
第四节上干货。虽然大厂框架很多,但我建议每个想深入Agent开发的人都手写过一个最小循环,这样你才知道框架里那些参数到底在控制什么。
这个循环的逻辑非常朴素:每次调用模型时带上工具定义,让模型要么输出工具调用指令,要么输出最终答案。如果输出了工具调用指令,就本地执行工具,把结果作为Observation塞回上下文,再让模型继续决策。
import json def run_agent(task, tools, llm, max_steps=10): messages = [ {"role": "system", "content": "你是一个任务执行Agent。"}, {"role": "user", "content": task} ] step = 0 while step < max_steps: step += 1 # 让模型决定:调用工具 or 输出最终答案 response = llm.chat(messages=messages, tools=tools) msg = response.message # 没有 tool_calls,说明模型给出了最终答案 if not getattr(msg, "tool_calls", None): return msg.content, step # 执行工具调用 messages.append(msg) for call in msg.tool_calls: result = execute_tool(call.function.name, call.function.arguments) messages.append({ "role": "tool", "tool_call_id": call.id, "content": json.dumps(result, ensure_ascii=False) }) # 这里可以根据业务需要,判断是否要停下来等人工确认 if should_ask_human(messages): return "WAITING_FOR_CONFIRMATION", step return "MAX_STEPS_EXCEEDED", step这段代码的每个细节都有用意。max_steps是护栏,防止Agent死循环把Token烧光;tool_call_id用来把工具返回和对应的调用请求对上,这是OpenAI兼容接口的硬性要求;execute_tool是本地函数分发,生产环境会换成服务发现机制;should_ask_human是人工介入的检查点。
整个循环对应平台五层结构里的决策层和能力层,是最核心的"跑腿中枢"。
4.2 工具与Skill的接入规则:描述即真相
工具能否被正确调用,一半功劳在代码,一半功劳在描述。模型是完全按照工具的name和description来决策的,描述写得模糊,再好的模型也会选错工具。
一个标准的工具定义长这样:
{ "type": "function", "function": { "name": "query_work_order", "description": "根据工单ID查询工单的处理状态、责任人、当前备注。当用户提到工单、单号、报修编号时使用该工具。", "parameters": { "type": "object", "properties": { "work_order_id": { "type": "string", "description": "工单ID,格式如WO-2024-001" } }, "required": ["work_order_id"] } } }注意description里说明了"什么时候用"和"输入格式",比一干巴巴的"查询工单"要好用得多。工具返回的数据尽量返回结构化JSON,不要让工具返回一大段给人看的文案。模型解析结构化数据远比解析大段文本稳定,而且能省Token。我在项目里给团队的硬性要求是:工具只返回关键字段,描述性长文本一律截断。
Skill的接入方式是配置化注册,在3.2节的YAML基础上,平台启动时扫描Skill目录,解析配置并注册到工具列表里。这样新增一个业务能力,产品同事写个配置就能上线,开发不用改一行代码。
4.3 可观测性:每一次思考都要有"接缝"
Agent的核心问题是不可预测性。不可预测的东西如果还没有过程记录,那线上出了事故就只能干瞪眼。所以可观测性不是后期加分项,是从第一行Demo代码就该埋的基建。
我的做法是给每一步执行输出一个结构化日志,包含下列信息:
| 字段 | 示例 |
|---|---|
| session_id | session_001 |
| step_index | 3 |
| thought(模型思考) | 用户要求查工单,需要先调用查询工具 |
| action(动作) | {"tool": "query_work_order", "input": {"id": "WO-2024-001"}} |
| action_result(动作结果) | {"status": "processing", "owner": "张三"} |
| model | gpt-4o-mini |
| prompt_tokens | 2350 |
| completion_tokens | 168 |
| latency_ms | 324 |
| error | null |
这些字段攒起来,每一个Agent会话天然就是一个可以完整重放的过程记录。排查问题的时候,直接定位到具体step,看模型当时想了什么、做了什么、工具返回了什么,问题基本一眼就能看出来。
想把这个做好的一个建议:提前把日志规范定好,Demo阶段就按这个标准打印,后面接平台时你的历史数据也能用。我见过太多项目Demo阶段只管print,等做平台时再补日志,那叫一个痛苦,以前的错误case全丢了,没法回溯。
4.4 从Demo到MVP,我建议按这个节奏走
实战中,从Demo到最小可用平台我一般按四周推进,节奏大概是这样:
| 周期 | 目标 | 关键产出 |
|---|---|---|
| 第1周 | 服务化改造 | 把本地脚本改为HTTP服务,接入统一模型层,支持多模型切换 |
| 第2-3周 | 能力沉淀 | 建立工具注册表,沉淀2-3个业务Skill,接入基础记忆功能 |
| 第4周 | 治理基座 | 接入评测集、结构化日志、基础灰度开关和成本统计 |
| 第5周起 | 真实业务打磨 | 跑真实流量,收集失败case,反哺Prompt和Skill迭代 |
这个计划的核心逻辑是:前两周解决"能用",后两周解决"敢用",第五周以后解决"好用"。别一上来就做多Agent编排、复杂记忆网络,先把最小闭环跑稳,再一步步加能力。
5. 常见问题与排查技巧实录
5.1 "agent execution terminated due to error." 到底为什么
这个是很多Agent开发者的噩梦,用过LangChain的人基本都遇到过。这个报错看起来很笼统,根因其实就那么几种。
第一种是Agent达到了max_iterations或max_steps上限,循环被强制中止。这种情况通常是你设置的步数太小,或者模型在反复调用同一个工具没有进展。
第二种是工具调用过程中抛出了未捕获的异常,框架直接终止了整个执行链。比如工具函数里写了一个KeyError,模型根本不知道发生了什么。
第三种是模型连续输出的内容格式不对,比如工具调用参数不是合法的JSON,或者解析出了没有注册的工具名。
我的排查思路固定三步:先翻日志看最后一步模型输出的是什么、有没有tool_calls;再看中间步骤有没有重复调用同一工具的迹象;最后检查工具函数自身的异常有没有被try-except兜住。这三个方向基本能覆盖90%的情况。
解决问题时,除了修工具代码,别忘了在系统提示词里加一句:如果同一个操作连续失败两次,停止尝试,向用户说明情况并询问是否换一种方式。这一句能在很多场景下把死循环变成优雅收场。
5.2 上下文窗口不够用,三个方案按序选
Agent跑久了,上下文一定会膨胀。对话历史在涨,工具返回结果也在涨,哪天突然报token超限很正常。
我的优先级排序是这样的:先上摘要压缩。把早期的对话历史定时压缩成一两句话的摘要,替换掉完整历史,能立刻解决大部分问题。具体做法是单独调用一次模型,把已有对话做总结,然后作为一条system message放回去。
如果摘要压缩还扛不住,上向量检索。把历史对话拆成片段、embedding后存入向量库,每次任务只检索与当前问题最相关的三五段历史塞回上下文。这个方案更适合需要跨长时间跨度记忆的场景,比如用户一周前说过的偏好。
最后,限制工具返回长度。很多token是被工具返回的大段JSON吃掉的。修改工具,让它只返回摘要字段或状态字段,原始详情放到另一个查询接口里。一个查询工单的工具,返回"处理中、负责人张三",没必要把整个工单时间线全倒出来。
预算上也留一个参考:整个上下文里,系统提示词和建议示例控制在20%以内,用户任务和对话历史占40%,工具定义和工具返回占40%。如果工具定义太长,可以考虑哪些工具不是每个任务都需要,动态加载。
5.3 工具调用反反复复、输出不稳定怎么治
还有一类高频问题:Agent老是在工具调用上打转而给不出最终答案,或者同一个问题跑三次三个结果。
工具调用反复打转,先看工具描述是不是有歧义。比如两个工具都能"查看工单",模型分不清该用哪个,就会随机抽。我建议把工具描述里的使用场景写明确,甚至加上"当用户提到……时使用,如果没有……请勿使用"这种排除性描述。
输出不稳定,第一件事把temperature调低,通常降到0.2以内会有明显改善。第二件事检查提示词里是否给出了输出格式的强约束,比如让模型必须按JSON格式输出,并给出一个few-shot样例。第三件事检查模型选型,同一个场景,小参数模型和大参数模型的稳定性差距非常大,如果业务容错低,别为了省钱牺牲效果。
另外,给工具调用加一个观测字段,比如在action_result里带上调用时间、耗时、第几次尝试。这样能在数据层面看到"是不是同一个工具反复失败",比靠肉眼和感觉判断高效得多。
5.4 从Demo到平台的避坑速查表
最后把我在多个项目里踩过的坑整理成一张速查表,每一条都是拿真实代价换来的:
| 阶段 | 最容易踩的坑 | 应对方式 |
|---|---|---|
| Demo转平台 | 模型密钥写死在代码里 | 立即改用环境变量或密钥管理服务 |
| Tool接入 | 工具返回大段文本导致Token爆炸 | 工具只返回结构化关键字段 |
| Skill管理 | Skill之间职责重叠,提示词互相覆盖 | 先画边界再写配置,每个Skill功能唯一 |
| Agent循环 | 忘记设置最大步数和兜底话术 | 必须配置max_steps + 失败放弃逻辑 |
| 评测阶段 | 只测"回答像不像人" | 改成"任务完成率"和"关键动作正确率" |
| 灰度切换 | 一次性全量切换新模型 | 按用户或百分百灰度,盯错误率和耗时 |
| 记忆管理 | 长期记忆写入脏数据 | 写向量库前加一层清洗和去重 |
| 人工介入 | 把所有操作都交给Agent自动执行 | 涉及不可逆操作,强制等待用户确认 |
这张表我每次带队做Agent平台都会发给成员当checklist。项目一旦过了Demo阶段,这些坑几乎必踩,提前写进规范里能省几个通宵。
从我自己的实践来看,把一个Demo做成平台,最重要的不是选什么框架,而是愿意在"不确定性和失败"上下多少功夫。Agent再好用,也一定会出错,平台的意义不是让错误消失,而是让错误可以被控制、被追溯、被修复。这个系列先写到这里,下一篇我准备重点聊多Agent编排和成本治理,这两块也是我自己最近在折腾的方向,到时候接着把这些实操细节掰开揉碎了讲。