拆解 AI Agent 三大核心机制:从概念到 OpenAI API 接口实现
大模型是大脑,但光有大脑成不了事。Agent 之所以是 Agent,而不是聊天机器人,关键在于三个能力的闭环:规划、工具调用、记忆。本文从概念到 OpenAI 接口参数,逐一拆解它们在工程层面到底是怎么落地的。
一、为什么聊天机器人不等于 Agent?
大多数人对 LLM 的认知停留在"你问我答"------一个输入,一个输出。但现实中,我们希望模型能:
"帮我排查一下线上服务为什么慢。"
这条指令背后隐藏着大量能力需求:拆解步骤、执行命令、观察结果、回溯修正、记住上下文。这正是 Agent 要解决的事。
Agent 的核心在于一个循环:
感知输入 → 规划下一步 → 调用工具执行 → 观察结果 → 更新记忆 → 规划下一步 ...
这个循环中的三个关键齿轮,就是规划、工具调用、记忆。下面逐一拆解。
二、规划:Agent 的大脑
2.1 核心问题
大模型本身是"单轮输入→输出",没有天然的执行循环。你问它一个问题,它给你一段文字,到此结束。但真实任务是多步的、带分支的,甚至需要边做边调整策略。
规划要解决的就是:怎么让模型"想清楚再做",并且持续地"做一步、看一步、想下一步"。
2.2 四种主流规划范式
| 范式 | 核心思路 | 适合场景 |
|---|---|---|
| ReAct | Thought→Action→Observation 循环,边推理边行动 | 多数通用 Agent |
| Plan-and-Execute | 先生成完整计划,再逐步执行,偏差时重规划 | 步骤明确、可预测的任务 |
| Tree of Thoughts (ToT) | 每步展开多条候选路径,搜索最优解 | 开放性问题、创意推理 |
| Reflexion | 执行后自我反思,基于失败经验修正再试 | 需要自我纠错的迭代任务 |
ReAct 最常见,也是多数 Agent 框架的默认策略,核心是强制模型在每一步"先想后做":
Thought: 用户说Harbor拉镜像超时,我先确认Harbor服务本身是否正常
Action: kubectl get pods -n harbor
Observation: 所有Pod Running,但发现Ingress指向的节点NotReady
Thought: 问题可能在网络层而非Harbor本身,接下来检查节点状态
Action: kubectl get nodes
Observation: 910c-2 节点 NotReady
Thought: Ingress Controller Pod在910c-2上,节点不可达导致
NodePort回环超时。建议驱逐Po朵或修复节点。
2.3 规划的关键工程问题
- 规划深度 vs 灵活性:计划太细,一点偏差就全盘崩溃;计划太粗,模型容易跑偏。
- 动态重规划:执行结果和预期不符时,需要能回溯、修正后续步骤。
- 终止条件:如何判断任务已完成,避免无限循环。
- 状态追踪:在漫长的多步执行中,始终清楚"现在做到哪了、之前做了什么"。
三、工具调用:Agent 的手脚
3.1 为什么需要工具
大模型再强,也有硬伤:
- 知识有截止日期,无法获取实时信息
- 不能执行动作,无法读写文件、跑命令、调接口
- 数学计算靠概率,复杂运算容易出错
- 无法访问私有数据,数据库、内网系统都是黑盒
工具调用让模型突破"只能输出文字"的边界,与外部世界交互。
3.2 核心流程
用户请求
→ 模型判断是否需要调用工具
→ 生成结构化调用指令(函数名 + 参数JSON)
→ 执行层解析指令、调用真实函数
→ 结果回注模型
→ 模型基于结果继续推理
→ (循环直到任务完成)
3.3 工具类型
| 类型 | 示例 | 作用 |
|---|---|---|
| 信息获取 | 搜索引擎、数据库查询、API 调用、文件读取 | 补充模型不知道的信息 |
| 动作执行 | 写文件、发邮件、部署服务、执行 Shell 命令 | 在真实世界产生副作用 |
| 专有能力 | 代码解释器、图片生成、数学计算引擎 | 弥补模型自身能力短板 |
3.4 工程设计要点
- 工具描述是灵魂 :模型完全靠
name+description判断"什么时候该用这个工具"和"参数应该怎么填"。描述写得模糊,模型就会调错或乱填参数。 - 并行 vs 串行 :多个无依赖的工具调用可以并行,有依赖关系则需串行等待。OpenAI 接口支持一次返回多个
tool_calls。 - 容错设计:工具执行失败时,Agent 应能解读错误信息、换策略重试,而非直接崩溃。
- 权限与安全:破坏性操作(删文件、发邮件、支付)需要确认机制,防止模型"好心办坏事"。
3.5 现实挑战
- 模型可能"幻觉"出不存在的工具或编造参数
- 工具数量增多后,选择准确率下降("选哪个工具"本身成了难题)
- 长工具链中错误会逐层累积传播
- 工具调用的流式输出处理比较棘手(函数名和参数是增量拼接的)
四、记忆:Agent 的经验
4.1 为什么要谈记忆
如果每次对话都从零开始,Agent 就只是个"稍微聪明点的一次性助手"。真正有用的 Agent 应该能:
- 记住你是谁、你的技术栈、你的项目背景
- 记住上次讨论到哪了、哪些问题已经解决
- 在海量历史记录中找到和当前任务相关的上下文
4.2 三层记忆结构
┌─────────────────────────────────────────┐
│ 工作记忆(Working Memory) │
│ ← 当前对话的上下文窗口 │
│ 最近几轮对话、当前任务状态 │
├─────────────────────────────────────────┤
│ 短期记忆(Short-term Memory) │
│ ← 本次会话的过程记录 │
│ 已完成的步骤、中间结果、工具输出 │
├─────────────────────────────────────────┤
│ 长期记忆(Long-term Memory) │
│ ← 跨会话持久化 │
│ 用户偏好、项目背景、历史交互摘要 │
└─────────────────────────────────────────┘
4.3 长期记忆的实现方式
| 方式 | 原理 | 适用场景 |
|---|---|---|
| 全文写入 | 完整内容写入文件(如 USER.md) | 用户明确要求记住的事实 |
| 向量检索 | 将记忆嵌入向量库,按相似度召回 | 大规模知识、模糊查询 |
| 摘要压缩 | 定期将历史对话压缩为摘要 | 控制上下文长度 |
| 每日日志 | 按日期记录关键事件,支持搜索 | 时间线回溯、任务追踪 |
4.4 记忆的核心难题
- 检索相关性:记忆量大时,如何精准召回与当前任务相关的内容,而不是注入噪声。
- 遗忘与更新:旧信息过时后如何淘汰,避免错误知识持续影响决策。
- 一致性:多条记忆间可能矛盾,需要冲突检测与合并策略。
- 上下文窗口预算:记忆不能无限注入,必须在有限的 token 预算内做取舍。
五、OpenAI 接口实现:三个机制长什么样?
前面讲了"是什么"和"为什么",这一节解决"怎么落地"。重点在于:三个机制共享同一个 Chat Completion 接口,但参数组合和消息编排方式截然不同。
5.1 一张表看懂三者对应的 API 参数
| 机制 | 主要依赖的参数 | 本质 |
|---|---|---|
| 规划 | messages[system], model, reasoning_effort |
提示词驱动推理 |
| 工具调用 | tools, tool_choice |
结构化输出 + 外部执行 |
| 记忆 | messages 的编排(历史注入) |
上下文窗口管理 |
5.2 规划:纯 Prompt 工程
规划没有专属 API 字段,完全靠 system message 注入规划策略:
json
{
"model": "gpt-4o",
"temperature": 0.1,
"messages": [
{
"role": "system",
"content": "你是一个任务规划Agent。接到用户请求后:
1. 先输出 <thought> 分析问题、拆解步骤
2. 按步骤逐步执行,每步观察结果
3. 发现偏差时回溯修正
不要跳步,不要省略思考过程。"
},
{
"role": "user",
"content": "帮我排查这个服务为什么响应慢"
}
]
}
不同规划范式在 system message 中的体现:
jsonc
// ReAct 范式
"content": "按 Thought→Action→Observation 循环执行。
每次输出:
Thought: <你的推理>
Action: <要调用的工具及参数>
等待 Observation 后再继续。"
// Plan-and-Execute 范式
"content": "先输出完整计划:
Plan:
1. 步骤一
2. 步骤二
...
然后逐步执行,遇到偏差时输出 Plan Revision 修正。"
关键参数建议:
| 参数 | 规划场景的建议 |
|---|---|
messages[0].role=system |
必须明确规划范式(ReAct / Plan-and-Execute 等) |
model |
推理密集型任务选强模型 |
reasoning_effort |
o 系列模型专用,复杂规划用 high |
temperature |
建议设为 0~0.3,减少随机跳步 |
5.3 工具调用:唯一有显式 API 支持的机制
这是三个机制中唯一有专属 API 字段的------OpenAI 为它设计了一整套请求-响应-回传协议。
定义工具
json
{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "你是一个可以查询数据库的助手"},
{"role": "user", "content": "查一下订单表中今天的订单数量"}
],
"tools": [
{
"type": "function",
"function": {
"name": "query_database",
"description": "执行SQL查询,返回结果集",
"parameters": {
"type": "object",
"properties": {
"sql": {
"type": "string",
"description": "要执行的SQL语句"
},
"database": {
"type": "string",
"description": "数据库名",
"enum": ["orders", "users", "products"]
}
},
"required": ["sql", "database"]
}
}
}
],
"tool_choice": "auto"
}
tool_choice 控制模型是否调用工具:
| 值 | 含义 |
|---|---|
"auto" |
模型自行决定(默认) |
"none" |
禁止调用,强制纯文本回复 |
"required" |
强制必须调用某个工具 |
{"type": "function", "function": {"name": "xxx"}} |
强制调用指定工具 |
模型返回调用指令
当模型决定调用工具时,finish_reason 为 tool_calls:
json
{
"choices": [{
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "query_database",
"arguments": "{\"sql\": \"SELECT COUNT(*) FROM orders WHERE date = CURRENT_DATE\", \"database\": \"orders\"}"
}
}]
}
}]
}
注意几个细节:
content为null,模型这次不输出文字,只输出调用指令arguments是字符串化的 JSON,不是 JSON 对象,需要二次解析id是本次调用的唯一标识,回传结果时必须对应
工具结果回传
执行完工具后,以 tool 角色消息回传结果,继续对话:
json
{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "你是一个可以查询数据库的助手"},
{"role": "user", "content": "查一下今天的订单数量"},
{
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "query_database",
"arguments": "{\"sql\":\"SELECT COUNT(*)...\",\"database\":\"orders\"}"
}
}]
},
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "{\"result\": [{\"count\": 142}]}"
}
]
}
关键约束:
tool消息的tool_call_id必须与tool_calls[].id一一对应- 多个工具调用可并行(一次返回多个
tool_calls),需逐一回传结果 - 工具结果以字符串 形式放在
content中(通常是 JSON 序列化)
网关开发者的特别注意 :
tool_calls响应在流式模式(stream: true)下,函数名和参数是增量拼接的,需要做 buffer 累积,不能按行解析。这是实现转发网关时最容易踩的坑之一。
5.4 记忆:消息编排的艺术
记忆同样没有专属 API 字段 ,靠 messages 数组的组织方式实现不同层级的记忆。
工作记忆------就是 messages 数组本身
json
"messages": [
{"role": "user", "content": "Go语言的GC策略是什么?"},
{"role": "assistant", "content": "Go使用并发三色标记清除..."},
{"role": "user", "content": "那和Java的G1比呢?"}
// 上面的历史消息就是工作记忆,让模型知道刚才聊了什么
]
短期记忆------用 assistant 消息携带中间状态
json
"messages": [
{"role": "system", "content": "你是一个研究Agent"},
{"role": "user", "content": "调研Go语言GC最新进展"},
{"role": "assistant", "content": "已搜索到3篇相关文章,正在分析..."},
{"role": "user", "content": "总结核心优化点"}
// 上面的assistant消息充当短期记忆,记录了任务进度
]
长期记忆------在 system 消息中注入检索到的记忆片段
json
{
"model": "gpt-4o",
"messages": [
{
"role": "system",
"content": "你是一个编程助手。\n\n## 用户背景(长期记忆)\n- Go语言后端开发者,负责模型接口转发网关项目\n- 主要技术栈:Go 1.21+、Kubernetes、VictoriaMetrics\n- 关注系统性能优化和可观测性\n\n## 近期工作记录\n- 2026-08-07: 排查了Harbor镜像拉取超时问题\n- 2026-08-10: 讨论了Go sync.RWMutex原理\n\n请基于以上背景提供针对性建议。"
},
{
"role": "user",
"content": "帮我优化网关的内存使用"
}
]
}
记忆检索本身也可以是一个工具:
json
{
"tools": [
{
"type": "function",
"function": {
"name": "search_memory",
"description": "在长期记忆库中按关键词搜索相关历史记录",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"},
"top_k": {"type": "integer", "description": "返回条数", "default": 5}
},
"required": ["query"]
}
}
}
]
}
上下文窗口管理策略
记忆不能无限注入,必须在有限的 token 预算内做取舍:
| 策略 | 实现方式 | 适用场景 |
|---|---|---|
| 截断 | 删除最早的 messages | 简单场景,信息损失大 |
| 摘要压缩 | 用一次 API 调用将历史压缩为摘要,替换为单条 system 消息 | 对话过长时 |
| 滑动窗口 | 保留最近 N 轮 + 首条 system | 成本敏感场景 |
| 检索增强 | 每轮从外部记忆库检索相关片段,注入 system | 长期记忆量大时 |
六、三者合一:一个完整的 Agent 请求长什么样
下面是一个真实的 Agent 请求,融合了三个机制。注意 messages 数组的编排方式------它同时承载了规划策略、历史记忆、工具调用链:
json
{
"model": "gpt-4o",
"temperature": 0.1,
"messages": [
{
"role": "system",
"content": "你是一个运维Agent,按 ReAct 范式工作。\n\n## 用户背景(长期记忆)\n- Go开发者,负责gateway项目\n- K8s集群有910c-1和910c-2两个节点\n\n每步先输出Thought再决定Action,观察结果后继续。"
},
{
"role": "user",
"content": "Harbor拉镜像超时了,帮我排查"
},
{
"role": "assistant",
"content": "Thought: 先检查Harbor服务的连通性和Ingress状态",
"tool_calls": [{
"id": "call_001",
"type": "function",
"function": {
"name": "execute_command",
"arguments": "{\"command\": \"kubectl get pods -n ingress-nginx -o wide\"}"
}
}]
},
{
"role": "tool",
"tool_call_id": "call_001",
"content": "NAME READY STATUS RESTARTS AGE NODE\nnginx-1 1/1 Running 0 5d 910c-1\nnginx-2 1/1 Running 0 5d 910c-2"
},
{
"role": "assistant",
"content": "Thought: 910c-2节点状态可能异常,Ingress Pod在其上会导致流量不稳定...",
"tool_calls": [{
"id": "call_002",
"type": "function",
"function": {
"name": "execute_command",
"arguments": "{\"command\": \"kubectl get nodes\"}"
}
}]
},
{
"role": "tool",
"tool_call_id": "call_002",
"content": "NAME STATUS VERSION\n910c-1 Ready v1.27.16\n910c-2 NotReady v1.27.16"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "execute_command",
"description": "在目标服务器上执行shell命令并返回输出",
"parameters": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "要执行的命令"},
"timeout": {"type": "integer", "description": "超时秒数", "default": 30}
},
"required": ["command"]
}
}
},
{
"type": "function",
"function": {
"name": "search_memory",
"description": "搜索长期记忆库中的历史记录",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索关键词"}
},
"required": ["query"]
}
}
}
],
"tool_choice": "auto"
}
在这个请求中:
- 规划 :system message 注入了 ReAct 范式,模型每步先输出
Thought - 工具调用 :
tools定义了execute_command和search_memory,模型按需调用 - 记忆:system message 中注入了用户背景(长期记忆),assistant 消息记录了执行过程(短期记忆),messages 数组本身就是工作记忆
七、对网关开发者的实践建议
如果你在做 LLM API 转发网关(比如我的 gateway 项目),以下几个点值得重点关注:
7.1 tool_calls 的流式处理
流式模式下,tool_calls 的函数名和参数是分多个 chunk 增量返回的:
chunk 1: {"delta": {"tool_calls": [{"index": 0, "id": "call_001", "function": {"name": "execute", "arguments": ""}}]}}
chunk 2: {"delta": {"tool_calls": [{"index": 0, "function": {"arguments": "{\"comm"}}]}}
chunk 3: {"delta": {"tool_calls": [{"index": 0, "function": {"arguments": "and\":\"ls\"}"}}]}}
网关层需要按 index 累积拼接 arguments,最终拼成完整 JSON 后再透传或处理。不能按行解析,也不能假设一个 chunk 就是完整的工具调用。
7.2 tool 消息回传的校验
tool 角色消息的 tool_call_id 必须与之前 assistant 消息中的 tool_calls[].id 一一对应。网关层应做合法性校验:
tool_call_id是否存在于历史消息中- 是否有
tool_calls未被回传结果(会导致模型行为异常) - 多个并行调用的结果是否都回传了
7.3 超长 messages 的处理
随着 Agent 循环推进,messages 数组会不断膨胀。网关层需要考虑:
- token 计数:在转发前计算总 token 数,判断是否超过目标模型的上下文窗口
- 截断策略:超长时按什么策略裁剪(删最早消息?做摘要压缩?)
- 成本控制:Agent 一次任务可能触发十几次 API 调用,每次都带着不断增长的 messages,成本需要监控
7.4 多模型差异抹平
不同模型提供商对 Function Calling 的实现有细微差异:
- 有的模型
arguments返回的不是合法 JSON(缺引号、多逗号) - 有的模型在
content中也输出文字(不完全为 null) tool_choice的支持程度不一致
网关层可以做一层适配,对外暴露统一的接口语义,内部处理差异。
写在最后
回到开头的问题:Agent 和聊天机器人的本质区别是什么?
聊天机器人是单轮的 ------你问它答,结束。Agent 是循环的------它有自己的目标,能拆解步骤、调用工具、记住经验,在循环中不断逼近目标。
而这个循环的质量,取决于三个齿轮的咬合精度:
- 规划决定了"做什么、做几步"
- 工具调用决定了"每一步怎么做"
- 记忆提供了"过去经验"和"当前状态",支撑前两者的决策
理解它们在 API 层面的实现,是构建 Agent 基础设施的第一步。
AI生成