一文讲透 AI Agent 核心架构:从 Agentic Loop 到 LangGraph 状态图

10 阅读11分钟

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 执行流程建模成一个有向图。

三个核心概念

  1. **State(状态) **:在节点之间传递的数据包

   ```

   state = { messages: [...] }

   ```

  1. **Node(节点) **:每个节点做一件事

   ```

   "llm" 节点:调用模型推理

   "tools" 节点:执行工具

   "审核" 节点:等待人工审核

   ```

  1. **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  熔断

三级压缩:

  1. MicroCompact:局部压缩已缓存内容

eg:你让 Claude 读了一个 500 行的文件 原始内容占了 2000 tokens 但后来你只改了其中 3 行

    MicroCompact 做的事:

    把那 500 行的完整内容替换成:

    "文件 src/index.ts,500行,主要是一个用户认证模块"

    只占 50 tokens 省了 1950 tokens

类比:你拍了 100 张照片,只留缩略图,需要时再看原图

  1. 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(摘要本身不能太长)

  

  1. 熔断器:连续 3 次失败停止重试

  AutoCompact 第1次尝试 → 压缩后还是太大 → 失败

  AutoCompact 第2次尝试 → 还是太大 → 失败

  AutoCompact 第3次尝试 → 还是太大 → 停!不再重试

  为什么要停?

    因为反复压缩本身也消耗 token 和时间

    压缩3次都失败说明这个对话确实太长了

    这时候应该开新对话(/clear)而不是继续压

  类比:你的行李箱怎么压都关不上,试了3次就别硬塞了,换个大箱子吧。

  

核心思路:不是截断历史,而是用摘要替换原始内容。


七、多 Agent 协作

核心思路

就像项目经理带团队:你不写代码,只负责拆任务、分配、汇总结果。


用户: "做一个复杂任务"

  │

  ▼

┌─────────────────────────┐

│  主 Agent (Coordinator)  │  ← 上下文小,只管分配和汇总

│  思考 → 拆任务 → 分配    │

└───┬─────────┬───────┬───┘

    │         │       │

    ▼         ▼       ▼      ← Promise.all 并行

┌───────┐┌───────┐┌───────┐

│Worker1││Worker2││Worker3│  ← 各自独立的上下文

│ Read  ││ Read  ││ Read  │  ← 各自独立的 Agentic LoopEdit  ││ 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 → 结果喂回

  = 3API 调用 ≈ 15秒

  


✅ 并行(1次循环):

  循环1: LLM → 同时读文件ABC → 结果一起喂回

  = 1API 调用 ≈ 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 推送 → 前端时间线渲染