1. 从 Prompt 到 Agent:一次范式转移的底层逻辑
1.1 为什么单靠 Prompt 撑不起一个真正的 Agent
2023 年那会儿,大家做 AI 应用基本就是一条路:写一个精心雕琢的 Prompt,把用户输入塞进去,调一次模型 API,拿到结果返回。这套东西在问答、摘要、翻译这类单轮任务上确实够用,但只要任务稍微复杂一点——比如“帮我查一下这周北京的天气,如果下雨就订一张周五去上海的高铁票,并把行程加到日历里”——单次 Prompt 调用就彻底歇菜了。
原因很直接:大模型本质上是一个无状态的函数。你给它一段文本,它给你一段文本,调用结束之后它什么都不记得。它没有手去查天气,没有脚去订票,也没有脑子去判断“先查天气还是先订票”。Prompt 能做的只是“在给定上下文里生成合理的续写”,它无法主动获取新信息,也无法根据中间结果调整下一步动作。
这就是 AI Agent 要解决的核心问题。Agent 这个词在 2024 到 2025 年被炒得很热,但剥开营销外壳,它的技术内核其实就四件事:Prompt 负责决策,Tool 负责执行,Context 负责信息供给,Memory 负责状态延续。这四者缺一不可,组合起来才构成一个能“自己干活”的系统。
我见过太多团队一上来就说要做 Agent,结果做出来的东西本质上还是一个套了循环的 Prompt 调用,跑两轮就崩。问题往往不在模型能力,而在于他们没有想清楚这四层各自的职责边界。下面我按演进顺序,把这四层一层层拆开讲。
1.2 四层架构的职责划分与协作关系
先把整体图景说清楚。一个成熟的 Agent 系统,运行时大致是这样一条链路:
用户给一个目标 → Agent 把目标 + 历史记忆 + 可用工具列表一起塞进 Prompt → 模型输出一个决策(是直接回答,还是调用某个工具)→ 如果是调用工具,执行层去跑这个工具,拿到结果 → 结果作为新的 Context 回灌给模型 → 模型基于新信息继续决策 → 循环直到任务完成或达到终止条件。
这里面,Prompt 是大脑的思考方式,Tool 是手脚,Context 是眼睛和耳朵,Memory 是记忆。四者协作的关键在于“循环”和“状态管理”。单次调用没有循环,所以不是 Agent;循环了但不管理状态,跑几轮上下文就爆炸,也不是一个能用的 Agent。
我个人的经验是,判断一个系统是不是真 Agent,就看它有没有这三个特征:自主决策(自己决定下一步做什么)、工具使用(能对外部世界产生动作)、状态保持(跨轮次记住发生了什么)。三者缺一,就只能叫“增强版 Prompt”。
2. Prompt 层:Agent 的决策中枢怎么设计
2.1 从“写一句话”到“设计一套决策协议”
普通 Prompt 工程和 Agent Prompt 工程完全是两个量级的东西。普通场景下你写的是“你是一个翻译助手,请把下面的中文翻译成英文”,而 Agent 场景下你要写的是一套决策协议:告诉模型它有哪些工具可用、每个工具的参数是什么、什么情况下该调用哪个工具、调用失败怎么办、什么时候该停止。
这就引出了 Agent Prompt 的几个核心组成:
- 角色与目标定义:明确 Agent 的身份和它要完成的终极目标。
- 工具清单(Tool Schema):以结构化格式列出所有可用工具及其参数说明。
- 决策规则:什么条件下直接回答,什么条件下调用工具,调用后如何判断结果是否可用。
- 输出格式约束:强制模型以特定格式输出(比如 JSON),方便程序解析。
- 终止条件:明确什么情况下任务算完成,避免无限循环。
我踩过最大的一个坑就是:早期写 Agent Prompt 时没做输出格式约束,模型有时候返回自然语言,有时候返回 JSON,解析层直接崩。后来强制用结构化输出(比如让模型输出{"action": "tool_name", "args": {...}}这种格式),稳定性立刻上了一个台阶。
2.2 工具描述写得好不好,直接决定 Agent 聪不聪明
这一点我要重点强调,因为它是被最多人忽视的环节。很多人以为 Agent 调错工具是模型笨,其实十有八九是工具描述写得烂。
模型选择工具的唯一依据就是你给它的工具描述。如果你写的是“查询天气”,模型不知道这个工具支持哪些城市、返回什么格式、需不需要日期参数。但如果你写的是“根据城市名和日期查询天气,输入参数为 city(字符串,如'北京')和 date(字符串,格式 YYYY-MM-DD),返回该日期的天气状况和温度”,模型的选择准确率会高出一大截。
我总结的工具描述三要素:
| 要素 | 说明 | 反例 | 正例 |
|---|---|---|---|
| 功能边界 | 这个工具具体做什么、不做什么 | “处理数据” | “读取 CSV 文件并返回前 N 行” |
| 参数规格 | 每个参数的类型、格式、是否必填 | “传入参数” | “file_path(字符串,必填,绝对路径)” |
| 返回说明 | 返回什么结构、异常时返回什么 | “返回结果” | “成功返回 JSON 数组,失败返回 error 字段” |
提示:工具数量超过 10 个之后,模型的选择准确率会明显下降。这时候要么做工具分组,要么引入一个“路由层”先判断该用哪一类工具,再在类内做细选。
2.3 Prompt 层的常见故障与排查思路
实际跑起来,Prompt 层最常出的问题有这么几类,我整理成速查表:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 模型不调用工具,直接瞎编答案 | 工具描述不够明确,或决策规则没写清 | 检查 Prompt 里是否明确“不知道时必须调用工具” |
| 反复调用同一个工具 | 没有把工具结果正确回灌,或终止条件缺失 | 检查循环逻辑和结果注入位置 |
| 输出格式解析失败 | 没有强制结构化输出 | 加入格式约束和 few-shot 示例 |
| 上下文超长报错 | 历史轮次无限累积 | 引入 Context 裁剪或摘要机制 |
| 调用参数格式错误 | 参数说明不清晰 | 在工具描述里给出参数示例 |
这里特别说一下“上下文超长”这个问题。热词里出现的maximum context length is 1048576 tokens这类报错,在 Agent 场景下极其常见,因为每一轮循环都会往上下文里追加内容。解决办法后面在 Context 层会详细讲。
3. Tool 层:让 Agent 真正“下地干活”
3.1 工具的本质:把自然语言决策翻译成确定性执行
模型再聪明,它也只是一个文本生成器。它说“我要查天气”,这句话本身不会让天气发生变化。真正去查天气的,是 Tool 层的一段代码。所以 Tool 层的本质是一座桥:把模型输出的自然语言决策,翻译成确定性的、可执行的程序调用。
这个翻译过程有两个关键点。第一是参数映射:模型输出的参数是文本,工具需要的是具体类型(整数、日期、枚举),中间要做校验和转换。第二是结果封装:工具执行完的结果(可能是 JSON、可能是纯文本、可能是二进制)要转成模型能理解的文本格式再回灌。
我一般会把工具分成三类来管理:
- 信息获取类:搜索、查数据库、读文件、调 API 拿数据。特点是只读,相对安全。
- 动作执行类:发消息、写文件、下单、调外部服务。特点是有副作用,必须加权限控制。
- 计算处理类:跑代码、做数学运算、格式转换。特点是确定性高,适合把模型不擅长的精确计算外包出去。
3.2 工具选型与封装的实际考量
选什么工具、怎么封装,直接决定 Agent 的能力上限。我的经验是遵循“能用代码解决的绝不交给模型”这个原则。比如日期计算、字符串处理、数值运算,这些模型经常算错的东西,全部封装成工具让模型调用,准确率立刻从 70% 拉到接近 100%。
封装工具时有几个实操要点:
- 单一职责:一个工具只做一件事。不要搞一个“万能工具”接收各种参数做不同的事,那样模型根本学不会怎么用。
- 幂等设计:尤其是动作执行类工具,要考虑重复调用的情况。比如“发消息”这个工具,如果 Agent 因为重试调了两次,用户就收到两条消息,体验很差。
- 超时与重试:外部 API 调用必须设超时,失败要有明确的错误返回,让模型知道“这次失败了,可以换个方式再试”。
- 错误信息友好化:工具报错时返回给模型的不能是堆栈信息,而应该是“查询失败,原因:城市名不存在,请检查输入”这种模型能理解并据此调整的文本。
注意:动作执行类工具一定要加“确认机制”或“权限白名单”。我见过 Agent 因为理解偏差,把测试环境的删除操作跑到了生产库上,这种事故一旦发生就是灾难性的。
3.3 工具调用失败的典型场景与处理
工具调用失败是常态,不是异常。网络抖动、API 限流、参数错误、权限不足,各种情况都会遇到。关键是 Agent 要能优雅地处理失败,而不是一失败就整个流程崩掉。
我处理失败的标准套路是三层:
- 第一层:工具内部重试。对于网络类瞬时故障,工具内部自动重试 2-3 次,对模型透明。
- 第二层:错误信息回灌。重试仍失败,把清晰的错误信息返回给模型,让模型决定是换个工具、换个参数,还是直接告诉用户“这个操作暂时做不了”。
- 第三层:熔断降级。某个工具连续失败多次,暂时把它从可用列表里摘掉,避免模型反复撞墙。
这套机制跑下来,Agent 的鲁棒性会有质的提升。很多看起来“模型不行”的问题,其实是失败处理没做好。
4. Context 层:Agent 的“工作台”怎么管
4.1 Context 不是越长越好,而是要“刚刚好”
Context 是 Agent 运行时最稀缺的资源。模型的上下文窗口再大(现在动辄 100 万 token),也架不住 Agent 循环几十轮往里塞东西。而且上下文越长,模型的注意力越分散,关键信息越容易被淹没,响应也越慢、越贵。
所以 Context 管理的核心目标不是“塞更多”,而是“在正确的时刻,把正确的信息,以正确的形式,放到模型面前”。这跟人干活是一个道理:你桌上堆满文件反而找不到东西,只放当前要处理的那几份,效率最高。
Context 里通常包含这几类信息:
- 系统 Prompt:角色、规则、工具清单,相对固定。
- 对话历史:用户说了什么、Agent 回了什么。
- 工具调用记录:调了哪些工具、参数是什么、返回了什么。
- 当前任务状态:任务进行到哪一步了、还差什么。
4.2 上下文膨胀的三种治理手段
当上下文开始膨胀,我一般按这个顺序处理:
第一种:滑动窗口裁剪。最简单粗暴,只保留最近 N 轮对话。缺点是可能丢掉早期的重要信息。适合任务步骤之间相对独立的场景。
第二种:摘要压缩。把早期的对话历史用模型压缩成一段摘要,保留关键结论,丢掉过程细节。这个方式效果好,但要多花一次模型调用。我的经验是每积累 5-8 轮就压缩一次,压缩后的摘要控制在 200 字以内。
第三种:结构化状态外置。这是最优雅的方式:不把历史全塞进上下文,而是维护一个结构化的“任务状态对象”,只把当前状态注入 Prompt。比如一个订票 Agent,状态对象就是{出发地, 目的地, 日期, 是否已查票, 是否已下单},每轮只把这个对象序列化后放进去,上下文永远很短。
实际项目里我通常是三种混用:状态外置为主,摘要压缩为辅,滑动窗口兜底。
4.3 上下文注入顺序对模型表现的影响
这是一个很微妙但很实用的点:同样的信息,放在上下文的不同位置,模型的使用效果不一样。
我的实测经验是,模型对上下文开头和结尾的信息最敏感,中间部分容易被忽略(这就是所谓的“lost in the middle”现象)。所以注入顺序应该是:
- 系统 Prompt 和核心规则放最前面。
- 当前任务状态和最近一轮的工具结果放最后面(紧挨着模型要生成的位置)。
- 历史摘要和次要信息放中间。
另外,工具返回的结果如果很长(比如一个网页的全文),一定要先做截断或摘要再注入,否则一次工具调用就能把上下文撑爆。我一般会把单个工具结果限制在 2000 字以内,超长的做摘要。
5. Memory 层:让 Agent 拥有“记忆”
5.1 Memory 和 Context 的区别,很多人搞混
这两个概念经常被混为一谈,但它们的职责完全不同。Context 是“当前工作台”,Memory 是“长期档案柜”。
Context 是临时的,任务结束就清空;Memory 是持久的,跨会话、跨任务保留。Context 容量有限,必须精打细算;Memory 容量可以很大,但需要检索机制来决定“什么时候把哪段记忆调出来放进 Context”。
举个例子:一个客服 Agent,用户这次来问“我上次买的那个东西到哪了”。Context 里只有这次对话,但 Memory 里存着这个用户的历史订单和上次的沟通记录。Agent 需要从 Memory 里检索出相关信息,注入到当前 Context,才能正确回答。
5.2 记忆的三种类型与存储策略
我把 Agent 的记忆分成三类,各自有不同的存储和检索策略:
| 记忆类型 | 内容 | 存储方式 | 检索方式 |
|---|---|---|---|
| 工作记忆 | 当前任务的中间状态 | 内存对象 | 直接读取 |
| 情景记忆 | 历史对话、过往任务记录 | 向量数据库 | 语义相似度检索 |
| 语义记忆 | 领域知识、事实、规则 | 知识库/向量库 | 关键词+语义混合检索 |
工作记忆就是前面说的“结构化状态对象”,生命周期最短。情景记忆是“发生过什么”,用向量库存,按语义相似度召回。语义记忆是“知道什么”,通常是预置的领域知识。
热词里提到的agent 存储 working memory和tencentdb agent memory说的就是这块。实际选型上,小规模用内存 + 本地向量库就够,规模上来了再考虑专门的存储服务。
5.3 记忆检索的精度问题与优化
Memory 层最容易出问题的地方是检索精度。存进去容易,取出来准很难。用户问“上次那个事”,你得准确找到“上次”指的是哪次、哪个事。
我的优化经验有这么几条:
- 写入时做结构化:不要存原始对话,存的时候就提取出关键信息(时间、人物、事件、结论),检索时按结构字段过滤再语义匹配,精度高很多。
- 混合检索:纯向量检索对精确匹配(比如订单号、人名)不敏感,要结合关键词检索。我一般用“向量召回 Top 20 + 关键词过滤”的组合。
- 加时间衰减:越近的记忆权重越高,符合人的记忆规律。
- 定期清理:过期的、无关的记忆要清理,否则检索池被污染,精度下降。
提示:记忆污染是个隐蔽的坑。如果 Agent 把错误的信息写进了长期记忆,后续所有基于这段记忆的回答都会错,而且很难排查。所以写入记忆前最好加一道校验,或者对关键记忆做人工审核。
6. 四层协同:一个完整 Agent 的运行实录
6.1 从用户输入到任务完成的完整链路
把四层串起来看,一个完整的 Agent 运行过程是这样的(以一个“帮我整理本周工作并生成周报”的 Agent 为例):
- 接收目标:用户输入“帮我整理本周工作并生成周报”。
- 加载记忆:从 Memory 检索该用户的历史工作记录和偏好格式。
- 组装 Context:系统 Prompt + 工具清单 + 检索到的记忆 + 用户目标。
- 模型决策:模型判断需要先获取本周的工作数据,选择调用“查询工作日志”工具。
- 工具执行:Tool 层调用日志 API,返回本周的原始记录。
- 结果回灌:把日志数据注入 Context,模型基于数据生成周报草稿。
- 模型决策:判断草稿是否完整,是否需要调用“格式化”工具。
- 循环:直到模型输出最终周报,任务终止。
- 写入记忆:把本次生成的周报和用户反馈写入 Memory,供下次参考。
这个链路里,每一层都在各司其职。Prompt 决定“做什么”,Tool 负责“怎么做”,Context 保证“信息够用”,Memory 保证“记得住”。
6.2 并发场景下 Agent 的状态隔离
热词里有个问题问得很好:ai agent 怎么扛并发。这是从 Demo 走向生产必须跨过的坎。
单用户单任务的 Agent 好做,但一旦要同时服务成百上千个用户,问题就来了:状态怎么隔离?资源怎么分配?上下文怎么不串?
我的实践方案是每个会话一个独立的 Agent 实例,实例之间完全隔离。具体做法:
- 会话级隔离:每个用户会话对应一个独立的 Context 和 Memory 命名空间,绝不共享。
- 无状态服务 + 外部状态存储:Agent 服务本身做成无状态的,所有状态(Context、Memory)都存在外部存储(Redis、向量库)里,这样服务可以水平扩展。
- 工具调用的并发控制:对外部 API 的调用要做限流和排队,避免把下游打挂。
- 超时与资源回收:每个 Agent 实例设最大运行时长和最大循环次数,防止死循环占资源。
这套方案跑下来,单机撑几百个并发会话问题不大,再往上就靠加机器。
6.3 一个真实项目的踩坑记录
说个我实际做过的项目。当时做一个自动处理工单的 Agent,四层都搭好了,测试环境跑得很顺,一上生产就出问题。
第一个坑是上下文爆炸。工单处理过程中要读大量历史记录,几轮下来上下文就超了。后来改成“只注入当前工单相关记录 + 历史摘要”,问题解决。
第二个坑是工具重试导致重复操作。有个“关闭工单”的工具,因为网络超时触发了重试,结果工单被关了两次,触发了下游告警。后来给所有动作类工具加了幂等键,同一个操作只执行一次。
第三个坑是记忆污染。Agent 把一次错误的判断写进了长期记忆,导致后续同类工单全部处理错误。后来加了记忆写入的校验规则,关键记忆必须经过规则引擎确认才能落库。
这三个坑,每一个都不是模型能力问题,全是工程问题。所以我一直说,做 Agent,模型只是其中一环,工程能力才是决定成败的关键。
7. 演进方向与个人实践建议
7.1 从单 Agent 到多 Agent 协作
单 Agent 能处理的任务复杂度是有上限的。当任务需要多种专业能力时,多 Agent 协作就成了自然选择。比如一个“市场分析”任务,可以拆成“数据采集 Agent”“数据分析 Agent”“报告撰写 Agent”,各司其职,通过消息传递协作。
但多 Agent 不是银弹。Agent 之间的通信开销、任务拆分的合理性、结果汇总的一致性,都是新的挑战。我的建议是:能用单 Agent 加多工具解决的,就不要上多 Agent。多 Agent 适合那种职责边界非常清晰、单 Agent 上下文确实装不下的场景。
7.2 给不同阶段实践者的建议
如果你是刚入门,我的建议是从单工具单任务的 Agent 做起。先跑通“Prompt 决策 + 一个工具调用 + 结果回灌”这个最小闭环,理解循环和状态管理,再逐步加工具、加记忆。
如果你已经在做生产级 Agent,重点应该放在可观测性和稳定性上。每一次模型决策、每一次工具调用、每一次记忆读写,都要有日志。出了问题能快速定位是哪一层的问题,这比模型能力重要得多。
如果你在考虑架构选型,记住一个原则:Context 管理决定性能上限,Memory 设计决定能力上限,Tool 生态决定应用边界。三者要平衡投入,不要只盯着模型调优。
我个人在实际操作中的体会是,Agent 这个领域变化很快,但底层的那套逻辑——决策、执行、信息、状态——是稳定的。把这几层的原理吃透,不管上层框架怎么换,你都能快速上手。最后分享一个小技巧:调试 Agent 时,把每一轮的完整 Context 和模型输出都打印出来,你会发现很多问题一眼就能看出来,比盯着最终结果瞎猜高效得多。