AI Agent 核心知识点总结
一、Agentic Loop(Agent 核心循环)
Agent 的心脏,所有 Agent 框架的底层都是这个循环。
while (true) {
response = callLLM(messages, tools) // 1. 发给模型
if (没有工具调用) break // 2. 模型说停就停
results = 执行工具(response) // 3. 执行工具
messages.push(results) // 4. 结果喂回去
} // 5. 继续循环
关键点:
-
代码不做流程控制,模型自己决定要不要继续调工具
-
代码只负责:执行工具 → 把结果喂回去
-
循环终止条件:模型不再返回 tool_calls
二、ReAct 模式(Reasoning + Acting)
Agent 最主流的执行模式,边想边做。
用户提问
→ Thought(思考): 我需要什么信息?
→ Action(行动): 调用工具获取信息
→ Observation(观察): 看到了什么结果
→ Thought(思考): 还需要什么?
→ Action → Observation → ...
→ Thought: 信息够了
→ Final Answer: 输出最终回答
三种 Agent 模式对比:
| 模式 | 思路 | 优点 | 缺点 |
|------|------|------|------|
| ReAct | 边想边做 | 灵活,能应对意外 | 步数不可控 |
| Plan-and-Execute | 先规划再执行 | 有全局视野 | 计划赶不上变化 |
| Reflexion(反思)| 做完后回顾纠正 | 能从错误学习 | 耗时长,成本高 |
三、LangGraph 状态图
把 Agent 执行流程建模成一个有向图。
三个核心概念
- **State(状态) **:在节点之间传递的数据包
```
state = { messages: [...] }
```
- **Node(节点) **:每个节点做一件事
```
"llm" 节点:调用模型推理
"tools" 节点:执行工具
"审核" 节点:等待人工审核
```
- **Edge(边) **:节点之间的流转规则
```
普通边:A 做完一定去 B
条件边:A 做完后看条件决定去 B 还是 C
```
ReAct 的状态图(最简形式)
START
│
▼
┌───────────┐
│ LLM │ ←────────────┐
└─────┬─────┘ │
│ │
有工具调用? │
╱ ╲ │
是 否 │
↓ ↓ │
┌──────┐ ┌───┐ │
│Tools │ │END│ │
└──┬───┘ └───┘ │
└───────────────────────┘
状态图 vs while 循环
while 循环 = 所有逻辑写在一个函数里
状态图 = 每个步骤是独立节点,用连线组合
流程简单 → 没区别
流程复杂(加审核、并行、条件分支)→ 状态图好维护
为什么不需要多个图
一个图,多条路径,条件边控制走哪条:
"气虚是什么" → LLM → 直接回复 → END(只走1个节点)
"我头疼" → LLM → Tools → LLM → Tools → LLM → END(工具循环)
"帮我开方" → LLM → Tools → LLM → 药方 → 审核 → END(走审核)
"附子20克" → LLM → 药方 → 审核 → 主任审核 → END(两级审核)
Tool vs 状态图节点的区别
| | Tool | 状态图节点 |
|---|------|---------|
| 谁决定走不走 | LLM 自己决定 | 代码逻辑决定 |
| 能跳过吗 | 能(LLM 可以不调)| 不能(强制执行)|
| 适合场景 | 搜索、查数据库、记日记 | 审核、合规检查、人工确认 |
关键理解:安全关键流程(审核)要用状态图节点强制执行,不能做成 Tool 让 LLM 自己选。
状态冲突处理
| 冲突类型 | 解决方式 |
|---------|---------|
| 条件边匹配多个 | 代码里写优先级(if / else if) |
| 需要并行执行 | Promise.all 在一个节点里并行 |
| 多节点改同一字段 | Reducer 定义合并策略(自动合并,不覆盖) |
四、工具系统(Tool System)
工具定义结构
const tool = {
name: 'searchCode', // 工具名
description: '搜索代码库', // 给模型看的描述(决定模型会不会调它)
parameters: { query: string }, // 参数定义
execute: async (params) => {}, // 执行逻辑
}
双轨输出
同一个工具执行结果,两种呈现:
给模型看:纯文本,结构化,可能截断(控制 token)
给用户看:语法高亮、diff 着色、可折叠、spinner 动画
模型不知道用户看到了什么界面,它只管输出结构化的工具调用参数。
工具渲染
每个工具注册时就预先设计好展示效果,不是动态生成的:
-
Read → 文件名 + 行号 + 语法高亮
-
Edit → diff 红绿对比
-
Bash → 命令 + 输出 + 退出码
-
没有专门渲染器的工具 → 通用 JSON 渲染(fallback)
五、记忆系统(三层架构)
Layer 1: MEMORY.md → 轻量索引,始终在上下文中
Layer 2: 主题记忆文件 → 按需加载(user/feedback/project/reference)
Layer 3: CLAUDE.md → 项目级配置,每次会话自动加载
Self-Healing 机制:
-
自动合并历史观察
-
检测并消除矛盾
-
删除过时信息
六、上下文压缩策略
上下文使用率: 0%──────────50%──────────80%────95%──100%
│ │
AutoCompact 熔断
三级压缩:
- MicroCompact:局部压缩已缓存内容
eg:你让 Claude 读了一个 500 行的文件 原始内容占了 2000 tokens 但后来你只改了其中 3 行
MicroCompact 做的事:
把那 500 行的完整内容替换成:
"文件 src/index.ts,500行,主要是一个用户认证模块"
只占 50 tokens 省了 1950 tokens
类比:你拍了 100 张照片,只留缩略图,需要时再看原图
- AutoCompact:全局压缩,保留 13K buffer,生成 ≤ 20K 摘要
eg:上下文用了 80%:
之前的 50 轮对话
├── 第1-10轮:讨论了项目架构 ← 很早的内容
├── 第11-30轮:写了一堆代码 ← 中间的内容
└── 第31-50轮:调试 bug ← 最近的内容
AutoCompact 触发:
把第1-40轮压缩成一段摘要:
"用户在做一个中医Agent项目,我们讨论了架构,写了患者工具集和聊天API,目前在调试认证bug"只保留第41-50轮的完整内容(最近的)
压缩前:180K tokens(快满了)压缩后:60K tokens(又有空间了)
类比:你的笔记本快写满了,把前面的内容总结成一页摘要,后面继续写。
触发时机:上下文用到 ~80%
保留缓冲:13K tokens(留给下一轮对话用)
摘要上限:20K tokens(摘要本身不能太长)
- 熔断器:连续 3 次失败停止重试
AutoCompact 第1次尝试 → 压缩后还是太大 → 失败
AutoCompact 第2次尝试 → 还是太大 → 失败
AutoCompact 第3次尝试 → 还是太大 → 停!不再重试
为什么要停?
因为反复压缩本身也消耗 token 和时间
压缩3次都失败说明这个对话确实太长了
这时候应该开新对话(/clear)而不是继续压
类比:你的行李箱怎么压都关不上,试了3次就别硬塞了,换个大箱子吧。
核心思路:不是截断历史,而是用摘要替换原始内容。
七、多 Agent 协作
核心思路
就像项目经理带团队:你不写代码,只负责拆任务、分配、汇总结果。
用户: "做一个复杂任务"
│
▼
┌─────────────────────────┐
│ 主 Agent (Coordinator) │ ← 上下文小,只管分配和汇总
│ 思考 → 拆任务 → 分配 │
└───┬─────────┬───────┬───┘
│ │ │
▼ ▼ ▼ ← Promise.all 并行
┌───────┐┌───────┐┌───────┐
│Worker1││Worker2││Worker3│ ← 各自独立的上下文
│ Read ││ Read ││ Read │ ← 各自独立的 Agentic Loop
│ Edit ││ Grep ││ Write │
│ Bash ││ Web ││ Bash │
│结果摘要││结果摘要││结果摘要│
└───┬───┘└───┬───┘└───┬───┘
│ │ │
▼ ▼ ▼
┌─────────────────────────┐
│ 主 Agent 汇总结果 │
│ → 回复用户 │
└─────────────────────────┘
底层实现
子 Agent 在主 Agent 眼里就是一个"工具":
// 主 Agent 的工具列表里有一个特殊工具叫 "Agent"
{
name: "Agent",
execute: async ({ prompt, tools }) => {
// 创建一个全新的 Agentic Loop
const worker = new AgenticLoop({
systemPrompt: prompt, // 主Agent写的任务描述
tools: tools, // 限定工具集
messages: [], // 空的!全新的上下文
model: "sonnet", // 可以用更便宜的模型
});
const result = await worker.run();
return result; // 只把结果摘要返回给主Agent
}
}
关键点:
-
每个 Worker 有独立的上下文窗口,互不干扰
-
Worker 看不到其他 Worker 在干什么,只知道主 Agent 给自己的任务
-
主 Agent 只看结果摘要,不看执行过程
为什么不用一个 Agent 全干
单 Agent 串行:
上下文 = 所有文件 + 所有工具结果 = 150K tokens(快满了)
耗时 = 10轮 × 5秒 = 50秒
多 Agent 并行:
主Agent上下文 = 任务 + 结果摘要 = 5K tokens(很轻)
Worker各自上下文 = 只装自己相关的 = 30K tokens
耗时 = 1轮(分配) + max(各Worker) + 1轮(汇总) = 35秒
优势:上下文不膨胀 + 并行更快 + 互不污染
Worker 之间能通信吗
默认不能,完全隔离。需要协作时两种方式:
方式1:通过主 Agent 中转
Worker1 做完 → 结果给主 Agent → 主 Agent 传给 Worker2
方式2:通过文件系统间接共享
Worker1 改了文件 → Worker2 读同一个文件
⚠️ 风险:同时改同一个文件会冲突
解决:用 Worktree 给每个 Worker 独立的文件系统副本
八、性能优化(Agentic Loop 加速)
一次循环的耗时 = API调用(1-10s) + 工具执行(0.1-5s) + 网络开销(0.1-0.5s)
最大瓶颈是 API 调用,不是工具执行。优化核心:减少循环次数 + 加速每次循环。
1. 并行工具调用(影响最大)
一次让模型返回多个工具调用,并行执行:
❌ 串行(3次循环):
循环1: LLM → 读文件A → 结果喂回
循环2: LLM → 读文件B → 结果喂回
循环3: LLM → 读文件C → 结果喂回
= 3次 API 调用 ≈ 15秒
✅ 并行(1次循环):
循环1: LLM → 同时读文件A、B、C → 结果一起喂回
= 1次 API 调用 ≈ 5秒
代码实现:
// 模型一次返回多个 tool_calls
const results = await Promise.all(
response.toolCalls.map(tc => executeTool(tc))
)
// 一次性把所有结果喂回去
messages.push(...results)
靠系统提示引导模型这么做:
"独立的工具调用要并行发出,不要一个一个调"
2. Prompt Caching(加速每次 API 调用)
第1次循环: 发送 50K tokens → API 完整处理 → 慢(5-8s)
第2次循环: 前 50K tokens 命中缓存 → 只处理新增部分 → 快(1-3s)
第3次循环: 前 55K tokens 命中缓存 → 更快(1-2s)
原理:每次循环只是在消息末尾追加了工具结果,前面的内容完全一样。
API 自动识别相同的前缀,跳过已处理的部分。
消息数组:
系统提示词 ← 缓存命中 ✓(每次都一样)
历史消息1 ← 缓存命中 ✓
历史消息2 ← 缓存命中 ✓
...
上一轮工具结果 ← 缓存命中 ✓
本轮新增的工具结果 ← 新的,需要计算
越往后缓存命中率越高,每次调用反而越快。同时成本也更低(缓存 token 比新 token 便宜)。
3. 流式输出(体感速度优化)
实际速度没变,但用户感知快了很多:
❌ 不用 streaming:
等 10 秒 → 突然出现一大段文字
用户感知:"好慢啊"
✅ 用 streaming:
0.3 秒第一个字就出来了,边生成边显示
用户感知:"好快啊"(其实总时间一样)
更重要的:工具调用参数流式解析完就立即开始执行,不用等整个响应结束:
时间线:
t=0.0s API 开始响应
t=0.3s 文字开始流出 → 立即渲染
t=0.8s 工具调用参数解析完 → 立即开始执行(不等响应结束)
t=1.0s 响应结束
t=1.1s 工具执行完成
4. 上下文控制(防止越循环越慢)
上下文越大 → API 处理越慢。必须控制上下文大小:
工具结果截断:
Bash 输出只保留前后 N 行
文件读取限制行数
搜索结果限制条数
function truncateResult(result, maxTokens) {
if (tooLong(result)) {
return 头部 + "\n...(省略了 X 行)...\n" + 尾部
}
return result
}
配合上下文压缩策略(第六节),防止对话越长越卡。
5. 子 Agent 并行(分治复杂任务)
❌ 主 Agent 串行做 10 件事 = 10 轮循环 ≈ 50秒
✅ 拆给 3 个子 Agent 并行:
主 Agent: 1轮(分配)
Worker 1: 3轮 ┐
Worker 2: 4轮 ├─ 并行执行
Worker 3: 3轮 ┘
主 Agent: 1轮(汇总)
总耗时 ≈ 2轮 + max(3,4,3)轮 = 6轮 ≈ 30秒
额外好处:每个 Worker 上下文独立,不会因为上下文膨胀而变慢。
6. 智能路由(简单任务少循环)
不是所有请求都需要多轮循环:
"什么是 tmux"
→ 0 次工具调用,直接回答,1 次 API 调用,1秒
"读一下这个文件"
→ 1 次工具调用,2 次 API 调用,3秒
"重构整个模块"
→ 10+ 次工具调用,但拆给子 Agent 并行
通过系统提示引导模型对简单问题直接回答,不要多余调用工具。
优化优先级总结
影响最大 ──────────────────────────── 影响最小
│ │
▼ ▼
并行工具调用 > Caching > 子Agent并行 > 流式输出 > 结果截断 > 智能路由
(减少循环数) (加速调用) (分治任务) (体感快) (防膨胀) (避免浪费)
最快的循环就是不循环 — 让模型一次发出多个工具调用。
九、安全架构(四层纵深)
Layer 1: 工具级权限 → auto / ask / deny
Layer 2: 路径过滤 → 白名单/黑名单
Layer 3: 注入检测 → 检测工具结果中的可疑指令
Layer 4: 破坏性操作拦截 → git push、rm -rf 需确认
十、Agent 可视化设计
核心思路:Agent 执行过程是事件流,捕获每个事件并渲染。
事件类型:
task_start → thinking → tool_call → tool_running →
tool_result → text_output → agent_spawn → task_done
架构:
Agent Engine (事件总线) → SSE 推送 → 前端时间线渲染