0%

背景

构建 Agent 过程中,都会遇到上下文窗口不够用的问题。

很多人觉得这不是事儿——现在模型上下文窗口动辄 128K、200K,塞就完了。但我们做 Agent 的人都知道,窗口大不等于能用得久。原因很直接:

  • 成本:token 是按量计费的,200K 上下文跑一轮可能就要几毛钱,一天跑几百轮,月底账单能吓死人
  • 延迟:上下文越长,首 token 延迟越高,用户体验直线下降
  • 噪声:塞进去的信息越多,模型越容易”走神”,关键指令被淹没在历史对话的海洋里

所以上下文压缩不是”要不要做”的问题,而是”怎么做好”的问题。

这篇文章从工程视角系统拆解 Agent 上下文压缩这件事——不是简单做摘要,而是一套完整的长任务状态治理体系


一、核心矛盾:信息保真 vs 窗口有限

1.1 一个形象的比喻

想象你是一个项目经理,工位上只有一张 1 米宽的桌子。桌上要放:当前任务的资料、历史沟通记录、团队通讯录、公司规章制度、客户反馈……

桌子就这么大,你不可能把所有东西都摊开。你的做法是:

  • 手边放当前正在处理的文件
  • 抽屉里放最近几天的沟通记录
  • 文件柜存历史项目档案
  • 脑子里记着关键决策和客户偏好

桌子就是你的上下文窗口,你的信息管理策略就是上下文压缩

1.2 Agent 面临同样的困境

Agent 的”桌子”就是模型的上下文窗口,需要同时容纳:

信息类型 说明 示例
系统指令 角色定义、行为约束、工具描述 “你是一个代码助手,不要执行删除操作”
用户目标 当前任务、约束条件、截止时间 “在今天 5 点前完成用户模块重构”
对话历史 多轮交互的上下文 用户之前的提问和纠正
工具结果 API 返回、文件内容、搜索结果 数据库查询结果、文件读取内容
中间状态 已完成的步骤、待办事项、中间结论 “已完成身份验证模块,还差权限校验”
外部知识 RAG 召回的文档片段、参考资料 相关技术文档、历史 case

当任务只有 2-3 轮对话时,全部塞进去毫无压力。但当任务进行到 50 轮、100 轮时,这些信息的总量会远超窗口限制。

核心矛盾就在这里:长任务需要的信息量 > 模型能装下的信息量。

压缩的本质,就是在有限的窗口里,最大化当前决策所需的信息密度


二、分层架构:不是所有信息都值得同等对待

最直觉的做法是”全塞进去”——把历史对话、工具结果、外部知识一股脑丢给模型。这种做法的结局我们在前面已经说了:成本高、延迟大、噪声多。

正确的思路是分层管理——不同信息有不同的保留策略,就像项目经理不会把所有文件都摊在桌上一样。

2.1 四层信息架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌───────────────────────────────────────────────────────┐
│ 上下文窗口(有限空间) │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ L1:热区 — 原文保留 │ │
│ │ 最近 N 轮对话、当前任务状态、未完成步骤 │ │
│ │ 特点:一字不改,完整保留 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ L2:温区 — 结构化摘要 │ │
│ │ 早期对话摘要、已完成任务的关键结论 │ │
│ │ 特点:保留决策和结论,丢弃过程 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ L3:冷区 — 按需召回 │ │
│ │ 历史工具结果、旧文件内容、早期搜索结果 │ │
│ │ 特点:只存索引和引用,需要时再拉取 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ L4:冻结区 — 外部记忆 │ │
│ │ 用户偏好、项目规范、历史经验教训 │ │
│ │ 特点:持久化存储,跨会话可用 │ │
│ └─────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────┘

每层的”温度”不同,访问频率不同,保留策略也不同。热区信息每次都完整进模型,冷区信息只带一个索引,冻结区信息需要显式召回。

2.2 层间流转

信息不是静态分层的,它会随着任务推进从热变冷

1
2
3
4
5
第 1 轮对话 → 在热区(原文保留)
第 5 轮对话 → 还在热区
第 20 轮对话 → 移入温区(摘要化)
第 50 轮对话 → 移入冷区(只存引用)
用户偏好 → 写入冻结区(持久化记忆)

这个流转过程是自动的、渐进的,不是一刀切地截断。


三、压缩的五种武器

有了分层架构,接下来要解决具体问题:怎么压?针对不同类型的信息,有不同的压缩策略。

3.1 对话摘要:把过程变成结论

最基础的压缩手段——对早期对话生成摘要。

但这里有个常见误区:摘要不是”缩短版的对话”,而是提取决策链

差的摘要:

1
2
用户问了怎么实现登录功能,助手建议使用 JWT,
然后讨论了 token 过期时间,最后决定设为 24 小时。

好的摘要:

1
2
3
4
5
6
7
## 已完成决策
- 认证方案:JWT(原因:无状态,适合微服务架构)
- Token 过期时间:24 小时(原因:平衡安全性和用户体验)

## 待完成
- 邮箱验证功能
- 刷新 token 机制

区别在哪?差的摘要在讲故事,好的摘要在保存状态。Agent 拿着好的摘要能直接继续工作,拿着差的摘要还得重新理解一遍来龙去脉。

摘要的原则:记结论,不记过程;记决策,不记讨论。

3.2 工具结果裁剪:只留有用的部分

Agent 调用工具后,返回结果往往很长。一个数据库查询可能返回几千行,一个文件读取可能上万字。全部塞进上下文,一两轮就把窗口吃光了。

工具结果压缩的核心思路是结构化裁剪

1
2
3
4
5
6
7
原始返回(5000 行)

├── 提取字段名和类型 ──── 保留
├── 提取关键值/异常值 ─── 保留
├── 列表类结果 Top-K ──── 只保留前 10 条 + 聚合统计
├── 错误码和错误信息 ──── 保留
└── 其余数据 ──────────── 丢弃(存对象存储,带引用 ID)

举个例子,Agent 查了一下用户表:

1
2
SELECT * FROM users WHERE create_time > '2026-01-01';
-- 返回 3000 条记录

压缩后不是把 3000 条记录全塞进去,而是:

1
2
3
4
5
查询结果:共 3000 条用户记录
字段:id, name, email, status, create_time
状态分布:active=2400, inactive=500, banned=100
最近 10 条:[前 10 条记录的摘要]
引用 ID:result_20260701_001(需要完整数据时可通过此 ID 回溯)

从 5000 行压缩到不到 20 行,关键信息没丢。

3.3 重复检测:同样的内容不出现两次

Agent 在处理文件时,经常多次读取同一个文件。如果不做处理,同一个文件的内容会在上下文里出现好几次。

解决方式很直觉:去重

1
2
3
第 3 轮:读取了 src/auth.ts 的内容(完整展示)
第 8 轮:再次读取 src/auth.ts 的内容
→ 压缩为:[文件 src/auth.ts 内容已在第 3 轮展示,此处省略]

这看起来简单,但在实际工程中非常有效。据 Claude Code 的实现(参考本博客之前的文章 Context 管理:四级压缩与无限对话的秘密),仅去重这一步就能节省大量 token。

3.4 状态提取:把”隐性知识”变成”显性清单”

这是最容易被忽略、但最关键的压缩手段。

在多轮任务中,有很多关键约束是散落在对话各处的:

  • 第 2 轮用户提到”数据库用的是 PostgreSQL”
  • 第 5 轮确认了”接口格式用 REST,不用 GraphQL”
  • 第 8 轮说了”部署环境是 K8s,注意内存限制”

这些信息如果不显式提取出来,一旦被摘要化就可能丢失。而它们是硬约束,丢了就会导致后续工作做错方向。

所以压缩前要做一件事:状态提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────┐
│ 当前任务状态(每轮自动更新) │
├─────────────────────────────────────────┤
│ 目标:重构用户模块 │
│ 约束: │
│ - 数据库:PostgreSQL 15 │
│ - 接口风格:RESTful │
│ - 部署:K8s,内存限制 512Mi │
│ - 截止时间:今天 17:00 │
│ 已完成: │
│ - ✅ 身份验证模块 (auth.service.ts) │
│ - ✅ 密码重置功能 │
│ 进行中: │
│ - 🔄 权限校验逻辑 │
│ 待办: │
│ - ⏳ 邮箱验证 │
│ - ⏳ 接口限流 │
│ 风险和阻塞: │
│ - ⚠️ Redis 集群今天维护,session 方案 │
│ 需要临时调整为 DB session │
└─────────────────────────────────────────┘

摘要负责背景,状态面板负责保存关键约束。 两者分离,才能保证压缩后不丢关键信息。

3.5 渐进式折叠:不要一刀切

最后要强调的是,压缩不应该是一次性完成的,而是渐进式的。

1
2
3
4
上下文用量 60%  → 不做压缩,原文保留
上下文用量 75% → 去重 + 工具结果裁剪(无损压缩)
上下文用量 85% → 早期对话摘要化(有损压缩,但保留状态面板)
上下文用量 95% → 全面压缩,只保留最近 3 轮 + 状态面板 + 摘要

这就像手机存储空间管理:空间充足时什么照片都留着;空间紧张时先删视频(占空间大但信息密度低);快满时才删不重要的截图。

越紧急,压缩越激进,但状态面板始终保留。


四、最难的问题:压缩后怎么知道没丢关键信息?

前面的策略都是”怎么压”,但真正的工程难题是”怎么保证压完之后信息没丢”。

4.1 状态验证机制

每次压缩完成后,不应该直接把压缩结果丢给模型,而是要做一次状态验证

1
2
3
4
5
6
7
压缩前:提取关键约束清单

执行压缩:生成摘要

验证:摘要中是否包含了所有关键约束?
├── 是 → 继续使用
└── 否 → 补拉原文,修正摘要

验证什么呢?主要是检查:

  • 用户目标是否还在
  • 硬约束(数据库类型、部署环境等)有没有丢
  • 未完成的步骤清单是否完整
  • 有没有跟原始记录矛盾的地方

4.2 冲突检测

比丢信息更隐蔽的问题是摘要跟原始记录矛盾

举个真实场景:

1
2
3
第 3 轮:用户说"用 MySQL"
第 15 轮:用户改主意了"算了,还是用 PostgreSQL"
压缩摘要:写成了"使用 MySQL"(只摘了第 3 轮,漏了第 15 轮的纠正)

这时候 Agent 拿着错误摘要继续干活,后面所有关于数据库的操作都会做错。

更糟糕的是,错误会累积。第 20 轮的摘要基于第 15 轮的摘要,如果第 15 轮就错了,第 20 轮会继续错下去,形成错误链

解决方案:

  1. 原始记录优先:当摘要和原始记录冲突时,以原始记录为准
  2. 用户确认优先:用户明确确认过的信息,优先级最高
  3. 标记冲突来源:发现矛盾时,标记是哪一轮的摘要出了问题
  4. 触发重压缩:发现错误摘要后,不是简单修补,而是重新压缩

核心原则:宁可让系统慢一点重新压缩,也不能让错误摘要继续进入后续链路。


五、可回放性:压缩不能只存结果

很多系统只做”向前看”的压缩——保留当前状态就够了。但实际工程中,向后看同样重要。

5.1 为什么需要回放?

考虑这些场景:

  • 任务执行到一半出了错,需要回溯是哪一步出了问题
  • 用户说”你刚才那个方案不对,退回到之前的版本”
  • Agent 做了一个决策,但结果不好,需要复盘决策过程

如果压缩后只留了一个摘要,这些场景全玩不转。

5.2 Checkpoint 机制

正确的做法是每一步都保存一个轻量级的 checkpoint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"step": 5,
"input": "用户要求添加邮箱验证功能",
"tool_calls": [
{
"tool": "read_file",
"params": { "path": "src/auth/service.ts" },
"result_ref": "result_005_read_file"
}
],
"output_summary": "在 auth service 中添加了邮箱验证逻辑",
"state_delta": {
"completed": ["邮箱验证逻辑"],
"files_modified": ["src/auth/service.ts"]
}
}

注意这里的关键设计:

  • 工具调用的原始结果不在 checkpoint 里,而是存了一个引用 ID(result_ref
  • output_summary 是这步的摘要,不是完整输出
  • state_delta 记录了这一步对全局状态的改变

这样做的好处是:需要回放时,可以通过引用 ID 拉回原始数据;正常执行时,只占很少的 token。


六、与 RAG 的配合

上下文压缩和 RAG 经常被放在一起讨论,但它们解决的问题不同:

上下文压缩 RAG
解决什么 控制进入模型的信息量 提供模型不具备的外部知识
核心动作 裁剪、摘要、分层 检索、重排、注入
信息来源 对话本身的历史 外部知识库

但在实际系统中,两者必须配合工作。

6.1 配合模式

1
2
3
4
5
6
7
8
9
用户提问

RAG 检索 → 召回 20 个文档片段

重排序 → 按相关性排序

裁剪 → 只保留 Top-5 片段(上下文压缩介入)

注入上下文 → 连同对话摘要一起进入模型

关键点在于:RAG 召回的结果也要经过压缩处理。如果召回了 20 个片段,每个 2000 token,那就是 40K token,可能直接把窗口吃满。

6.2 保留可追踪性

压缩后还有一个容易丢的东西:引用来源

RAG 注入的信息如果没有标注来源,模型可能会出现幻觉——把检索到的知识说成是自己”知道”的,用户也无法验证信息的可靠性。

正确做法是在压缩后的片段上保留元数据:

1
2
[来源:技术文档 v2.3,第 5 章,2026-03-15 更新,权限:内部公开]
PostgreSQL 的连接池默认最大连接数为 100,建议在高并发场景下调整至 200...

压缩的是内容,不是来源。 每条注入的信息都应该能追溯到原始出处。


七、评估体系:怎么衡量压缩效果?

做了这么多优化,怎么知道效果好不好?

7.1 不能只看 Token 压缩率

最常见的错误指标是”Token 减少了多少”。压缩 90% 但任务失败了,那不叫优化,叫丢信息。

7.2 正确的评估维度

维度 说明 怎么测
任务成功率 压缩后 Agent 能不能正确完成任务 端到端测试
约束保留率 用户提出的约束条件在压缩后还剩多少 标注测试集 + 人工检查
答案一致性 同一个问题,压缩前后的回答是否一致 对比测试
延迟变化 压缩前后的首 token 延迟对比 性能监控
成本变化 压缩前后的 token 消耗对比 成本统计

7.3 重点测试的边界场景

常规对话的压缩效果通常不错,真正拉开差距的是边界场景:

  • 长对话(50 轮以上):压缩系统能不能保持状态连贯
  • 工具调用失败:失败信息是否被正确保留(而不是被压缩掉)
  • 用户纠正:用户说”不对,应该是 XXX”,这个纠正有没有被保留
  • 约束变更:用户中途改了需求,压缩后是否反映了最新需求
  • 冲突场景:摘要和原始记录矛盾时,系统怎么处理

边界场景才是压缩系统的试金石。


八、工程实践:一个完整的压缩流水线

把前面的内容串起来,一个完整的 Agent 上下文压缩系统长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
每轮对话开始时:

├── 1. 计算当前上下文用量
│ └── token 计数 → 确定当前处于哪个压缩阶段

├── 2. 状态面板更新
│ └── 从最近对话中提取关键约束、更新待办清单

├── 3. 按需执行压缩
│ ├── 去重(移除重复文件内容、工具结果)
│ ├── 工具结果裁剪(只保留关键字段 + 引用 ID)
│ ├── 对话摘要化(渐进式,先摘要最旧的)
│ └── 全面压缩(仅在极端情况下触发)

├── 4. 状态验证
│ └── 对比压缩前后的约束清单,检查是否有遗漏或矛盾

├── 5. Checkpoint 保存
│ └── 记录本轮的输入、工具调用、输出摘要、状态变化

└── 6. 组装最终上下文
└── 状态面板 + 最近 N 轮原文 + 历史摘要 + RAG 注入 → 送入模型

这套流水线在每轮对话时都会执行,但大部分步骤是轻量级的——只有上下文用量超过阈值时才会触发重度压缩。


九、总结

上下文压缩,不是简单的”做摘要”,而是一套完整的长任务状态治理体系。

核心要点:

  1. 分层管理:热区原文保留、温区摘要化、冷区按需召回、冻结区持久化
  2. 多种武器:对话摘要、工具裁剪、重复检测、状态提取、渐进折叠
  3. 状态优先:摘要负责背景,状态面板负责保存关键约束,两者分离
  4. 安全网:状态验证 + 冲突检测 + 原始记录优先
  5. 可回放:每步保存 checkpoint,能回溯、能还原
  6. 配合 RAG:压缩控制信息量,RAG 提供外部证据,引用来源不能丢
  7. 全面评估:不看压缩率,看任务成功率、约束保留率、答案一致性

能压的是过程、是冗余、是重复。不能压的是决策、是约束、是用户意图。把这条线划清楚,压缩系统就不会出大问题。


系列文章导航:

背景

做 Agent 应用,前后端之间的实时通信协议选择是一个绕不开的工程设计问题。目前主流的技术方案有两种:SSE(Server-Sent Events)和 WebSocket。

实际项目中,不少团队一开始选了 WebSocket,运行一段时间后发现连接管理复杂、运维成本高,最终又切回了 SSE。这不是说 WebSocket 不好,而是在 Agent 这个特定场景下,两者的适配度有明显差异。

本文从协议原理、Agent 通信模式、工程实践三个层面做分析,把选型的判断依据讲清楚。


一、Agent 的通信模式

讨论协议选型之前,需要先理解 Agent 应用的通信模式。协议为场景服务,脱离场景谈选型没有意义。

1.1 典型的 Agent 交互流程

一次典型的 Agent 对话流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
用户发消息 ──→ 服务端接收请求


LLM 开始推理(可能几十秒)


逐 token 生成内容(流式输出)


一边生成一边推给前端(打字机效果)


生成完毕,关闭连接


等待用户发下一条消息

这个流程有几个关键特征:

特征 说明
一问一答 用户发一条消息,Agent 回一段话,天然轮次制
服务端慢、客户端快 LLM 推理可能 10-60 秒,用户只发几个字
流式输出 内容逐 token 生成,需要实时推送给前端
单向推送为主 推理期间客户端只需接收数据,不需要中途发送

这四个特征直接决定了协议选型的方向。

1.2 通信的不对称性

Agent 交互中,客户端和服务端的通信量是不对称的。客户端发送的内容很短(一条用户消息),服务端返回的内容很长(可能几千个 token),而且是一个一个 token 持续推送过来的。

在推理进行的过程中,客户端除了等待和渲染,不需要向服务端发送任何数据。这种“发一次请求,持续接收响应”的模式,是理解后续选型的关键。


二、SSE 协议原理

2.1 基本概念

SSE(Server-Sent Events)是一种基于 HTTP 的服务器推送技术。它允许服务端在建立 HTTP 响应后,不立即关闭连接,而是持续向客户端推送数据。

2.2 协议格式

请求是普通的 HTTP 请求,响应头中通过 Content-Type: text/event-stream 标识这是一个 SSE 流:

1
2
3
4
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

响应体是一个持续的数据流,每条消息以 data: 开头,以两个换行符 \n\n 结尾:

1
2
3
4
5
6
7
8
9
10
11
12
13
data: {"content": "你"}

data: {"content": "好"}

data: {"content": ","}

data: {"content": "我是"}

data: {"content": "AI"}

data: {"content": "助手"}

data: [DONE]

前端逐条接收这些消息,拼接渲染到界面上,形成”打字机”效果。

2.3 前端使用方式

浏览器提供了 EventSource API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const eventSource = new EventSource('/api/chat/stream?message=你好');

eventSource.onmessage = (event) => {
if (event.data === '[DONE]') {
eventSource.close();
return;
}
const data = JSON.parse(event.data);
appendToChat(data.content);
};

eventSource.onerror = () => {
// 浏览器会自动尝试重连
};

没有握手过程,没有帧解析,没有心跳维护。断线重连和事件 ID 追踪由浏览器内置处理。

2.4 核心特性

特性 说明
单向通信 只能服务端向客户端推送
基于 HTTP 跑在标准 HTTP 上,兼容现有基础设施
自动重连 浏览器原生支持
轻量 不需要协议升级和握手
文本格式 只支持 UTF-8 文本,不支持二进制

三、WebSocket 协议原理

3.1 基本概念

WebSocket 通过 HTTP 握手”升级”出一条全双工通信通道,客户端和服务端可以随时互发消息。

3.2 协议格式

WebSocket 的连接建立分两步。

第一步:HTTP 升级握手

1
2
3
4
5
6
7
8
9
10
11
// 客户端发起
GET /chat HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

// 服务端确认
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

握手完成后,HTTP 连接升级为 WebSocket 连接,后续数据通过 WebSocket 帧传输。

第二步:全双工通信

1
2
3
4
5
6
7
// 客户端可以随时发
→ {"type": "message", "content": "你好"}

// 服务端也可以随时发
← {"type": "message", "content": "你好,有什么可以帮你的?"}

// 双方同时发送,互不阻塞

3.3 前端使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const ws = new WebSocket('wss://example.com/chat');

ws.onopen = () => {
ws.send(JSON.stringify({ message: '你好' }));
};

ws.onmessage = (event) => {
const data = JSON.parse(event.data);
appendToChat(data.content);
};

ws.onclose = (event) => {
// 需要自行实现重连逻辑
reconnect();
};

ws.onerror = (err) => {
console.error('WebSocket error:', err);
};

相比 SSE,需要自行处理连接生命周期管理、断线重连、心跳保活等逻辑。

3.4 核心特性

特性 说明
全双工通信 双方可以同时互发消息
独立协议 握手后脱离 HTTP,使用自己的帧格式(RFC 6455)
低开销 帧头 2-14 字节,比 HTTP 头部小
无自动重连 断线后需要自行实现
支持二进制 可传输文本和二进制数据

四、SSE 与 WebSocket 对比

4.1 协议特性对比

对比维度 SSE WebSocket
通信方向 单向(服务端→客户端) 全双工
协议基础 标准 HTTP HTTP 升级后的独立协议
连接方式 普通 HTTP 长连接 需要协议升级握手
自动重连 浏览器原生支持 需要自行实现
数据格式 纯文本(UTF-8) 文本 + 二进制
防火墙穿透 极好(走 HTTP 通道) 一般(可能被代理拦截)
负载均衡 普通 HTTP LB 即可 需要 sticky session

4.2 开发复杂度对比

同一个”流式聊天”功能,两种方案的实现差异比较大。

SSE 后端(Spring Boot):

1
2
3
4
5
6
7
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> stream(@RequestParam String message) {
return chatService.streamResponse(message)
.map(content -> ServerSentEvent.<String>builder()
.data(content)
.build());
}

SSE 前端:

1
2
const es = new EventSource(`/api/chat/stream?message=${msg}`);
es.onmessage = (e) => appendToChat(e.data);

WebSocket 后端(Spring Boot):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@ServerEndpoint("/chat")
@Component
public class ChatWebSocket {
private static final Map<String, Session> sessions = new ConcurrentHashMap<>();

@OnOpen
public void onOpen(Session session) {
sessions.put(session.getId(), session);
}

@OnMessage
public void onMessage(String message, Session session) {
chatService.streamResponse(message).subscribe(content -> {
try {
session.getBasicRemote().sendText(content);
} catch (IOException e) {
// 需要自行处理异常
}
});
}

@OnClose
public void onClose(Session session) {
sessions.remove(session.getId());
}

@OnError
public void onError(Session session, Throwable error) {
sessions.remove(session.getId());
}
}

WebSocket 前端:

1
2
3
4
5
6
const ws = new WebSocket('wss://example.com/chat');
ws.onopen = () => ws.send(JSON.stringify({ message: msg }));
ws.onmessage = (e) => appendToChat(e.data);
ws.onclose = () => {
setTimeout(() => reconnect(), 3000);
};

SSE 方案中,后端是一个标准的 HTTP 接口,前端使用 EventSource。WebSocket 方案需要管理 Session 生命周期、处理连接状态、实现重连逻辑。代码量差异明显。

4.3 运维复杂度对比

运维维度 SSE WebSocket
Nginx 配置 几乎不用改 需要配置 Upgrade
负载均衡 普通 HTTP 均衡即可 需要 sticky session
CDN 支持 原生支持 不支持
监控 与 HTTP 接口一致 需要专门的监控方案
水平扩展 无状态,直接扩展 有状态连接,需要 session 同步
调试 DevTools Network 面板直接查看 需要专用调试工具

SSE 的运维成本基本等同于一个普通 HTTP 接口。WebSocket 需要维护一套长连接基础设施。


五、LLM 场景下 SSE 成为主流的原因

OpenAI、Anthropic、Google、阿里、字节等厂商的大模型 API,在流式输出上全部选择了 SSE。这个选择背后有清晰的技术逻辑。

5.1 LLM 推理是单向数据流

LLM 生成文本是一个自回归(auto-regressive)过程:

1
输入 prompt ──→ 推理 ──→ 生成 token₁ ──→ 拼上 token₁ 再推理 ──→ 生成 token₂ ──→ ...

每个 token 的生成依赖前面所有 token。这个过程是单向的、顺序的。从通信角度看,这就是一个单向数据流——服务端持续产生数据,客户端持续接收。SSE 的设计目标恰好是服务端向客户端的单向推送。

5.2 双向通道在推理期间是闲置的

WebSocket 的核心优势是全双工通信。在 LLM 推理过程中:

1
2
3
4
5
用户发消息 ──→ 推理开始 ──→ token₁ token₂ token₃ ... ──→ 推理结束 ──→ 用户发下一条
↑ ↑
│ │
推理期间客户端 推理结束后才能
不需要发送任何数据 发送下一条消息

推理可能持续数十秒,这段时间内客户端不需要向服务端发送任何数据。WebSocket 的双向通道在大部分时间里处于半闲置状态。

5.3 SSE 继承了 HTTP 生态的全部基础设施

优势 来源
自动重连 EventSource 内置
穿透防火墙 走 HTTP 通道,不需要额外端口
水平扩展 本质是 HTTP 请求,无状态
CDN 加速 标准 HTTP 响应
监控运维 复用现有 HTTP 监控体系

这些不是 SSE 本身的设计优势,而是因为它就是 HTTP,天然继承了 HTTP 生态 30 年积累的基础设施。

5.4 主流厂商的协议选择

服务商 流式输出协议
OpenAI (GPT-4o) SSE
Anthropic (Claude) SSE
Google (Gemini) SSE
阿里 (通义千问) SSE
字节 (豆包) SSE
Meta (Llama) SSE

主流厂商的一致性选择,从工程实践层面验证了 SSE 在 LLM 流式输出场景下的适用性。


六、分层选型建议

SSE 在 LLM 流式输出场景下是最优选择,但 WebSocket 在其他场景中仍然有不可替代的价值。选型的依据是通信模式,不是协议的”先进程度”。

6.1 场景分层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────────────┐
│ 场景分层选型模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 第三层:点对点音视频 ──────→ WebRTC │
│ (视频通话、屏幕共享) │
│ │
│ ──────────────────────────────────────────────────────── │
│ │
│ 第二层:双向实时互动 ──────→ WebSocket │
│ (多人协作、实时游戏、语音助手) │
│ │
│ ──────────────────────────────────────────────────────── │
│ │
│ 第一层:流式文本输出 ──────→ SSE │
│ (AI 对话、通知推送、数据流) │
│ │
└─────────────────────────────────────────────────────────────┘

6.2 SSE 适用场景

AI 对话流式输出、实时通知、日志流、数据推送。

共同特征:客户端发请求,服务端持续推送数据,推理/处理期间客户端不需要中途发送数据。单向数据流即可满足需求。

代表产品:ChatGPT、Claude、通义千问、Copilot。

6.3 WebSocket 适用场景

多人协作编辑、在线游戏、语音助手、实时仪表盘。

共同特征:客户端和服务端需要同时、随时互发消息,多方状态需要实时同步。

以多人协作文档为例:A 在打字的同时 B 也在打字,双方都需要实时看到对方的操作。这种场景下双方都在持续发送数据,必须使用 WebSocket。

6.4 WebRTC 适用场景

视频通话、屏幕共享、P2P 文件传输。

共同特征:需要极低延迟的点对点传输,涉及音视频媒体流。这一层与 LLM 应用关系不大。

6.5 混合方案

在复杂的 Agent 应用中,可以同时使用两种协议:

1
2
3
4
5
┌──────────────┐         SSE(流式输出)          ┌──────────────┐
│ │ ◄────────────────────────────── │ │
│ 前端 UI │ │ Agent 后端 │
│ │ ◄──────────────────────────────► │ │
└──────────────┘ WebSocket(控制指令) └──────────────┘

SSE 负责 LLM 推理结果的流式推送(占大部分数据量),WebSocket 负责用户中途取消推理、切换模型、调整参数等控制指令。各取所长,但架构复杂度会上升。


七、实战:SSE 流式对话接口实现

7.1 后端:Spring Boot + WebFlux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController
@RequestMapping("/api/chat")
public class ChatController {

@Autowired
private ChatService chatService;

@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<ChatStreamResponse>> stream(
@RequestParam String message,
@RequestParam(required = false) String sessionId) {

return chatService.streamChat(message, sessionId)
.map(content -> ServerSentEvent.<ChatStreamResponse>builder()
.event("message")
.data(new ChatStreamResponse(content))
.build())
.concatWith(Flux.just(
ServerSentEvent.<ChatStreamResponse>builder()
.event("done")
.data(new ChatStreamResponse("[DONE]"))
.build()
));
}
}

Nginx 配置,确保不会缓冲 SSE 响应:

1
2
3
4
5
6
7
8
location /api/chat/stream {
proxy_pass http://backend;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_buffering off;
proxy_cache off;
}

7.2 前端:fetch + ReadableStream

实际业务中用 fetch + ReadableStreamEventSource 更灵活,因为 EventSource 只支持 GET 请求,而用户消息可能很长,POST 更合适。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class ChatStream {
constructor() {
this.abortController = null;
}

async sendMessage(message, onChunk, onDone) {
this.abortController = new AbortController();

const response = await fetch(`/api/chat/stream?message=${encodeURIComponent(message)}`, {
signal: this.abortController.signal,
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
const { done, value } = await reader.read();
if (done) break;

buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();

for (const line of lines) {
if (line.startsWith('data:')) {
const data = line.slice(5).trim();
if (data === '[DONE]') {
onDone?.();
return;
}
const parsed = JSON.parse(data);
onChunk?.(parsed.content);
}
}
}
}

cancel() {
this.abortController?.abort();
}
}

// 使用示例
const chat = new ChatStream();
chat.sendMessage('介绍一下你自己',
(chunk) => {
document.getElementById('chat-box').innerText += chunk;
},
() => {
console.log('生成完毕');
}
);

八、常见问题

SSE 有连接数限制

浏览器对同一域名的 HTTP 连接数有限制,Chrome 是 6 个。如果同时开多个 SSE 连接,可能占满配额。

解决方案:使用 HTTP/2(多路复用,无连接数限制),或合并多个 SSE 流为一个(通过事件类型区分)。

SSE 只能 GET 请求

EventSource API 只支持 GET。使用 fetch + ReadableStream 解析 SSE 格式数据可以支持 POST。大部分 Agent 应用实际采用这种方式。

SSE 断线处理

EventSource 会自动重连。fetch 方案需要自行实现重连逻辑,使用指数退避策略即可。

WebSocket 传输性能更优

WebSocket 帧开销确实更小(2-14 字节 vs HTTP chunked 编码),但在 LLM 流式场景下,瓶颈在推理速度(几十毫秒到几秒一个 token),网络传输的开销差异可以忽略。


总结

Agent 应用中 SSE 和 WebSocket 的选型,核心判断依据是通信模式:

场景 推荐协议 理由
AI 对话流式输出 SSE 单向推送,简单高效
实时通知推送 SSE 天然适合服务端推送
数据流/日志流 SSE 持续单向数据流
多人协作编辑 WebSocket 需要双向实时同步
在线游戏 WebSocket 需要低延迟双向通信
语音/视频通话 WebRTC 需要 P2P 媒体流

LLM 推理是单向数据流,客户端在推理期间不需要发送数据。SSE 的单向推送模型与这个通信模式天然匹配,同时继承了 HTTP 生态的全部基础设施,开发和运维成本都远低于 WebSocket。

在不需要双向实时通信的场景下,SSE 是更合适的选择。

背景

2023 年 ChatGPT 火了,2024 年 Agent 热了,2025 年 AI 应用全面开花。Java 开发者的 AI 工具箱已经非常成熟——Spring AI 1.0 GA 在 2025 年 5 月正式发布,经过近一年的迭代,已经成为企业级 Java AI 应用的事实标准。

这篇文章站在当下时间点,从产生背景架构演进,从与 LangChain4J 的深度对比实战代码,让你看完就能上手做 AI 应用。不讲虚的,全是干货。


一、Spring AI 是什么,为什么需要它

1. Java 开发者的 AI 困境(2023-2024)

AI 大模型爆发初期,Java 开发者面临两难选择:

方案 问题
直接调用 API 每个服务商 API 不同,切换成本高;缺乏高级抽象(RAG、对话记忆、Agent 等)
用 Python 框架 LangChain、LlamaIndex 都是 Python 原生,Java 项目得跨语言集成,架构不统一

更关键的是:企业级 Java 应用 80% 以上跑在 Spring 上,需要一个原生融入 Spring 生态的 AI 框架。

2. Spring AI 的诞生与演进

Spring AI 的版本演进时间线:

时间节点 版本 里程碑意义
2023 年 11 月 0.8.0 项目启动,首个 Milestone
2024 年全年 0.8.x - 0.9.x 快速迭代,积累社区反馈
2025 年 5 月 1.0.0 GA Spring I/O 2025 正式发布,生产就绪
2025 年下半年 1.0.x - 1.1.x 稳定迭代,修复问题,增加 MCP 支持
2026 年初 1.1.x / 1.2 Agent 能力增强,国产模型支持完善

1.0 GA 版本的核心里程碑

  • API 稳定:ChatClient、Advisor、VectorStore 等核心接口不再变化
  • 生产就绪:Observability、错误处理、配置管理达到企业级标准
  • 生态完善:官方支持 20+ 模型服务商,10+ 向量数据库
  • 文档成熟:官方文档完善,示例丰富,社区活跃

3. 当前定位

站在今天,Spring AI 已经是:

Java 企业级 AI 应用的标准框架,Spring 生态的 AI 基础设施。

一句话概括:Spring AI 是 Java 界的 LangChain,但它比 LangChain 更懂企业级应用。

4. 核心价值矩阵

价值点 说明 企业收益
统一 API 抽象 OpenAI、Azure、Anthropic、Ollama、国产模型……一个接口搞定 模型切换零代码改动,供应商议价能力提升
Spring 原生集成 自动配置、依赖注入、Observability、配置中心 Spring 开发者零学习成本,复用现有基础设施
企业级特性 Token 消耗监控、调用链追踪、熔断降级、安全审计 生产环境直接部署,运维体系一体化
高级 AI 抽象 RAG、Agent、MCP、Function Calling、对话记忆 不用自己造轮子,专注业务逻辑
国产模型支持 阿里通义、百度文心、讯飞星火、腾讯混元 数据合规、成本可控、中文效果优化

二、架构设计:三层抽象 + MCP 协议层

Spring AI 1.0 的架构已经稳定,可以概括为四层:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (Application) │
│ ChatClient / PromptTemplate / Advisor / Agent │
├─────────────────────────────────────────────────────────────┤
│ MCP 协议层 (Protocol) │
│ MCP Client / MCP Server / Tool Registry │
├─────────────────────────────────────────────────────────────┤
│ 核心抽象层 (Core Abstractions) │
│ ChatModel / EmbeddingModel / ImageModel / AudioModel │
├─────────────────────────────────────────────────────────────┤
│ 集成层 (Integrations) │
│ OpenAI / Azure / Anthropic / Ollama / Alibaba / VectorDB │
└─────────────────────────────────────────────────────────────┘

1. 核心抽象层:统一接口设计

Spring AI 定义了一套稳定的模型接口,不管底层用哪个服务商,API 都一样。

ChatModel——对话模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 核心接口定义(1.0 GA 后已稳定)
public interface ChatModel extends Model<Prompt, ChatResponse> {
ChatResponse call(Prompt prompt);
}

// 使用示例:服务商切换只需改配置,代码不动
@Autowired
private ChatModel chatModel; // 配置决定具体实现(OpenAI、Azure、Ollama...)

public String chat(String userMessage) {
Prompt prompt = new Prompt(userMessage);
ChatResponse response = chatModel.call(prompt);
return response.getResult().getOutput().getContent();
}

EmbeddingModel——向量嵌入模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {
EmbeddingResponse embed(EmbeddingRequest request);

// 1.0 新增:批量嵌入
List<float[]> embed(List<String> texts);
}

// 使用示例
@Autowired
private EmbeddingModel embeddingModel;

public float[] embed(String text) {
return embeddingModel.embed(text);
}

ImageModel——图像生成模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface ImageModel extends Model<ImagePrompt, ImageResponse> {
ImageResponse call(ImagePrompt prompt);
}

// 支持 DALL-E 3、Stability AI、Midjourney API
@Autowired
private ImageModel imageModel;

public String generateImage(String promptText) {
ImagePrompt prompt = new ImagePrompt(promptText,
ImageOptions.builder()
.width(1024)
.height(1024)
.style("vivid")
.build());
return imageModel.call(prompt).getResult().getOutput().getUrl();
}

2. 应用层:高级功能封装

ChatClient——对话客户端(1.0 推荐使用)

ChatClient 是 Spring AI 1.0 推荐的高级 API,采用流式 Builder 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Autowired
private ChatClient.Builder chatClientBuilder;

// 构建带默认配置的 ChatClient
ChatClient chatClient = chatClientBuilder
.defaultSystem("你是一个专业的 Java 开发顾问")
.defaultOptions(ChatOptions.builder()
.model("gpt-4o")
.temperature(0.7)
.build())
.build();

// 简单调用
String response = chatClient.prompt()
.user("解释一下 Spring 的依赖注入")
.call()
.content();

// 流式响应(SSE 或 WebSocket)
chatClient.prompt()
.user("写一个 Spring Boot 启动流程分析")
.stream()
.content()
.subscribe(chunk -> System.out.print(chunk));

// 结构化输出(1.0 新增)
Person person = chatClient.prompt()
.user("从文本中提取人物信息:张三,35岁,北京人")
.call()
.entity(Person.class); // 自动转换为 POJO

PromptTemplate——提示词模板

支持 Mustache 语法,动态生成提示词:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PromptTemplate template = new PromptTemplate("""
请根据以下信息回答问题:

用户姓名:{{name}}
用户角色:{{role}}
问题:{{question}}

请以{{style}}的风格回答。
""");

Map<String, Object> params = Map.of(
"name", "张三",
"role", "架构师",
"question", "如何设计微服务架构",
"style", "专业简洁"
);

Prompt prompt = template.create(params);

Advisor——拦截器模式(1.0 正式 API)

Advisor 是 Spring AI 1.0 的核心拦截器机制,有两种类型:

  • CallAroundAdvisor:同步调用拦截
  • StreamAroundAdvisor:流式调用拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 自定义 Advisor(1.0 正式 API)
public class LoggingAdvisor implements CallAroundAdvisor {

@Override
public String getName() {
return "LoggingAdvisor";
}

@Override
public int getOrder() {
return 0; // 执行顺序
}

@Override
public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) {
// 前置处理:记录请求
log.info("AI Request: {}", request.userText());

// 执行调用
AdvisedResponse response = chain.nextAroundCall(request);

// 后置处理:记录响应
log.info("AI Response: {}", response.response().getResult().getOutput().getContent());

return response;
}
}

// 内置 Advisor:QuestionAnswerAdvisor(RAG 自动检索)
QuestionAnswerAdvisor qaAdvisor = new QuestionAnswerAdvisor(
vectorStore,
SearchRequest.defaults()
);

// 内置 Advisor:对话记忆
PromptChatMemoryAdvisor memoryAdvisor = new PromptChatMemoryAdvisor(
chatMemory,
"conversation-123"
);

// 使用多个 Advisor(链式执行)
String response = chatClient.prompt()
.user("什么是 Spring AI?")
.advisors(memoryAdvisor, qaAdvisor, loggingAdvisor)
.call()
.content();

Agent——自主任务执行(1.1+ 新增)

Spring AI 1.1 开始引入 Agent 抽象,支持自主任务分解和执行:

1
2
3
4
5
6
7
8
9
10
// Agent 基础示例
@Autowired
private Agent agent;

public String executeTask(String task) {
return agent.execute(task)
.plan() // 自主规划
.execute() // 执行步骤
.result(); // 返回结果
}

3. MCP 协议层:AI 的 USB-C 接口

MCP(Model Context Protocol) 是 Anthropic 在 2024 年推出的开放协议,2025 年成为 AI 工具连接的事实标准。Spring AI 1.0 正式支持 MCP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MCP 架构:
┌─────────────────────────────────────────────────────────────┐
│ Spring AI MCP Layer │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ MCP Client │──────► MCP Servers │ │
│ │ (Spring AI) │ │ (Tool/DB/API)│ │
│ └─────────────┘ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ ChatClient │ │ GitHub MCP │ │
│ │ Agent │ │ File MCP │ │
│ │ │ │ Postgres MCP │ │
│ └─────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

使用 MCP Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 配置 MCP Server
spring.ai.mcp.servers.github.type=stdio
spring.ai.mcp.servers.github.command=/usr/local/bin/github-mcp-server

// 使用 MCP 工具
ChatClient client = ChatClient.builder(chatModel)
.defaultMcpServers("github", "filesystem", "postgres")
.build();

String response = client.prompt()
.user("查看我最近的 GitHub PR,并总结变更内容")
.call()
.content();
// Agent 会自动调用 GitHub MCP Server 获取 PR 数据

4. 集成层:服务商适配现状(2026)

类型 支持的服务商 状态
对话模型 OpenAI、Azure OpenAI、Anthropic Claude、Google Gemini、Amazon Bedrock、阿里通义、百度文心、讯飞星火、腾讯混元、Ollama ✅ 生产就绪
Embedding OpenAI、Azure、阿里通义、本地模型 ✅ 生产就绪
图像生成 OpenAI DALL-E 3、Stability AI、Azure ✅ 生产就绪
向量数据库 Pinecone、Chroma、Weaviate、Milvus、Redis、PostgreSQL/pgvector、MongoDB、Elasticsearch ✅ 生产就绪
MCP Server GitHub、Filesystem、PostgreSQL、Slack、Google Drive(官方),第三方生态丰富 ✅ 1.0+ 支持

三、Spring 生态兼容方案

Spring AI 最大的优势是原生融入 Spring 生态,这是 LangChain4J 无法比拟的。

1. 自动配置(Spring Boot Starter)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- OpenAI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>

<!-- 阿里云通义千问 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0</version>
</dependency>

<!-- 向量数据库 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId>
</dependency>

2. Observability(可观测性)

Spring AI 自动集成 Spring Boot Actuator,AI 调用的可观测性零配置开启

1
2
3
4
5
6
7
8
9
10
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
tracing:
enabled: true
observations:
ai:
enabled: true # AI 调用自动追踪

自动上报的指标

指标 说明
ai.chat.tokens.used Token 消耗量(Prompt + Completion)
ai.chat.latency 调用耗时(P50、P95、P99)
ai.chat.success.rate 成功率
ai.embedding.latency Embedding 耗时
ai.vectorstore.search.latency 向量检索耗时

自动追踪的 Span

1
2
3
4
5
6
AI Chat Call
├── ai-chat-request (Prompt 构建)
├── ai-chat-model-call (LLM 调用)
│ ├── token-count (Token 计算)
│ └── response-parse (响应解析)
└── ai-chat-response (结果处理)

3. Spring Cloud 集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 配置中心:模型参数动态刷新
@RefreshScope
@Service
public class AiService {

@Value("${spring.ai.openai.chat.options.model}")
private String model;

@Value("${spring.ai.openai.chat.options.temperature}")
private Double temperature;
}

// 熔断降级:AI 调用失败保护
@Service
public class ResilientAiService {

@Autowired
private ChatClient chatClient;

@CircuitBreaker(name = "aiService", fallbackMethod = "fallback")
@Retry(name = "aiService")
@RateLimiter(name = "aiService")
public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}

public String fallback(String message, Exception e) {
return "AI 服务暂时不可用,请稍后重试";
}
}

4. Spring Security 集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// AI API Key 安全管理
@Configuration
public class AiSecurityConfig {

@Bean
public ChatModel chatModel(KeyProvider keyProvider) {
// Key 从 Vault 或密钥管理服务获取
String apiKey = keyProvider.getApiKey("openai");

return OpenAiChatModel.builder()
.apiKey(apiKey)
.build();
}
}

// 用户级 AI 权限控制
@Service
public class SecuredAiService {

@PreAuthorize("hasRole('AI_USER')")
public String chat(String message) {
// 只有 AI_USER 角色才能调用
return chatClient.prompt().user(message).call().content();
}
}

四、Spring AI vs LangChain4J:2026 年深度对比

LangChain4J 是另一个流行的 Java AI 框架,两者经常被拿来对比。站在 2026 年的视角,对比结果已经清晰。

1. 版本成熟度对比

框架 最新版本 发布时间 成熟度
Spring AI 1.1.x / 1.2 2025.05 GA 生产就绪,API 稳定
LangChain4J 1.0.x 2025.06 GA 生产就绪,社区活跃

两者都在 2025 年上半年发布了 1.0 GA 版本,生产就绪度相当。

2. 设计哲学对比

维度 Spring AI LangChain4J
设计哲学 Spring 原生,企业级优先 轻量独立,框架中立
依赖要求 必须有 Spring Boot 3.x 无强制依赖,支持 Quarkus/Micronaut/Spring
API 风格 Builder 模式 + Advisor 链 流式 API + AI Services 注解
配置方式 YAML 自动配置 + Actuator 代码 Builder 配置
生态集成 Spring 全生态无缝集成 需手动集成各框架

3. 核心能力对比

能力 Spring AI LangChain4J 说明
Chat ✅ ChatClient ✅ ChatLanguageModel 都成熟
Embedding ✅ EmbeddingModel ✅ EmbeddingModel 都成熟
RAG ✅ QuestionAnswerAdvisor ✅ ContentRetriever + Augmentor Spring AI 更简洁
Function Calling ✅ @Bean + @Description ✅ @Tool 注解 LangChain4J 更直观
对话记忆 ✅ PromptChatMemoryAdvisor ✅ ChatMemoryProvider 都成熟
结构化输出 ✅ .entity(Class) ✅ 返回 POJO 都成熟
MCP 协议 ✅ 1.0+ 原生支持 ✅ 1.0+ 支持 Spring AI 集成更深
Agent ⚠️ 1.1+ 基础支持 ✅ 成熟的 Agent 框架 LangChain4J 更成熟
Observability ✅ Actuator 自动集成 ⚠️ 需手动配置 Spring AI 完胜
国产模型 ✅ 官方支持阿里/百度/讯飞 ⚠️ 需自行适配 Spring AI 优势

4. 代码风格对比

对话模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Spring AI:流式 Builder
@Autowired
private ChatClient chatClient;

String response = chatClient.prompt()
.system("你是一个专业的顾问")
.user("你好")
.advisors(memoryAdvisor)
.call()
.content();

// LangChain4J:直接调用
ChatLanguageModel model = OpenAiChatModel.builder()
.apiKey("key")
.modelName("gpt-4o")
.build();

String response = model.generate("你好");

AI Services 注解(LangChain4J 特色)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// LangChain4J 的声明式 AI Services
interface Assistant {
@SystemMessage("你是一个专业的 Java 顾问,回答要简洁")
String chat(@UserMessage String question);

@UserMessage("讲一个关于 {{topic}} 的笑话")
String tellJoke(@V("topic") String topic);
}

// 一行代码构建
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(10))
.tools(new MyTools())
.build();

// Spring AI 需要用 ChatClient + PromptTemplate 实现
// 目前没有直接的 AI Services 对应

RAG 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Spring AI:Advisor 自动处理
@Autowired
private VectorStore vectorStore;

QuestionAnswerAdvisor qaAdvisor = new QuestionAnswerAdvisor(
vectorStore,
SearchRequest.defaults().withTopK(5)
);

String response = chatClient.prompt()
.user("什么是 Spring AI?")
.advisors(qaAdvisor)
.call()
.content();

// LangChain4J:ContentRetriever + Augmentor
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
.build();

Augmentor augmentor = DefaultAugmentor.builder()
.contentRetriever(retriever)
.build();

Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(model)
.augmentor(augmentor)
.build();

5. Agent 能力对比(关键差异)

LangChain4J 的 Agent 更成熟

1
2
3
4
5
6
7
8
9
10
// LangChain4J:完整的 Agent 框架
Agent agent = Agent.builder()
.chatLanguageModel(model)
.tools(new FileTools(), new WebSearchTools(), new DatabaseTools())
.chatMemoryProvider(id -> MessageWindowChatMemory.withMaxMessages(20))
.systemMessage("你是一个自主解决问题的助手...")
.build();

// Agent 自主执行复杂任务
String result = agent.run("分析这份文档,找出关键问题,并生成改进建议");

Spring AI 的 Agent(1.1+)还在发展

1
2
3
4
5
6
7
// Spring AI:基础 Agent 能力
AgentExecutor executor = AgentExecutor.builder()
.chatModel(chatModel)
.tools(toolRegistry)
.build();

AgentResult result = executor.execute("任务描述");

结论:复杂 Agent 应用,LangChain4J 目前更成熟;企业级可观测 Agent,Spring AI 更适合。

6. 选型决策矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
选型决策树:

├─ 项目技术栈?
│ ├─ Spring Boot → Spring AI(生态融合度最高,必选)
│ ├─ Quarkus → LangChain4J(官方支持更好)
│ ├─ Micronaut → LangChain4J
│ └─ 纯 Java → LangChain4J(轻量)

├─ 是否需要企业级特性?
│ ├─ Observability 重要 → Spring AI(零配置集成)
│ ├─ Spring Cloud 配置中心 → Spring AI
│ ├─ Spring Security 权限 → Spring AI
│ └─ 不需要 → LangChain4J 或 Spring AI 都行

├─ Agent 复杂度?
│ ├─ 复杂自主 Agent → LangChain4J(Agent 框架更成熟)
│ ├─ 简单对话 + RAG → Spring AI(QuestionAnswerAdvisor 更简洁)
│ └─ 可观测 Agent → Spring AI

├─ 国产模型需求?
│ ├─ 阿里通义/百度文心 → Spring AI(官方支持)
│ └─ 国际模型为主 → 两者都支持

├─ 团队熟悉度?
│ ├─ Spring 开发者 → Spring AI(零学习成本)
│ ├─ 非 Spring 开发者 → LangChain4J

└─ 快速原型?
├─ 声明式接口 → LangChain4J AI Services(接口定义即实现)
└─ Spring 体系 → Spring AI ChatClient

一句话总结

场景 推荐
Spring Boot 企业项目 Spring AI(必选)
Quarkus/Micronaut 项目 LangChain4J
复杂自主 Agent LangChain4J(Agent 更成熟)
RAG + 可观测性 Spring AI(Advisor + Actuator)
国产模型合规 Spring AI(官方支持)
快速原型 LangChain4J AI Services

五、实战:Spring AI 经典实践

1. 基础对话应用

Maven 依赖(Spring Boot 3.x)

1
2
3
4
5
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>

配置

1
2
3
4
5
6
7
8
9
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com # 可切换代理地址
chat:
options:
model: gpt-4o-mini
temperature: 0.7

Controller(推荐写法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@RestController
@RequestMapping("/api/chat")
public class ChatController {

private final ChatClient chatClient;

// 推荐:构造时注入,一次性构建
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个友好的助手,用简洁的语言回答")
.defaultOptions(ChatOptions.builder()
.temperature(0.7)
.build())
.build();
}

@PostMapping
public String chat(@RequestBody String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}

@GetMapping("/stream")
public Flux<String> streamChat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}

// 结构化输出
@PostMapping("/extract")
public Summary extract(@RequestBody String text) {
return chatClient.prompt()
.user("总结以下文本:\n" + text)
.call()
.entity(Summary.class);
}
}

public record Summary(String title, List<String> keyPoints, String conclusion) {}

2. Function Calling(工具调用)

Spring AI 1.0 的 Function Calling 已经非常成熟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Configuration
public class AiFunctions {

// 方式一:Spring Bean 函数
@Bean
@Description("查询指定城市的实时天气信息")
public Function<WeatherRequest, WeatherResponse> weatherFunction(WeatherService weatherService) {
return request -> weatherService.getCurrentWeather(request.city());
}

// 方式二:复杂函数
@Bean
@Description("在数据库中搜索相关文档")
public Function<SearchRequest, List<Document>> searchDocuments(DocumentRepository repo) {
return request -> repo.searchByKeyword(request.keyword(), request.limit());
}
}

public record WeatherRequest(String city) {}
public record WeatherResponse(String city, double temperature, String condition, int humidity) {}
public record SearchRequest(String keyword, int limit) {}

// Controller 使用
@RestController
public class WeatherController {

private final ChatClient chatClient;

public WeatherController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultFunctions("weatherFunction", "searchDocuments")
.build();
}

@GetMapping("/weather")
public String askWeather(@RequestParam String question) {
// LLM 自动识别需要调用 weatherFunction
// 如 "北京今天天气怎么样?适合户外运动吗?"
return chatClient.prompt()
.user(question)
.call()
.content();
}
}

3. RAG 应用实战(QuestionAnswerAdvisor)

Spring AI 1.0 的 RAG 实现已经非常简洁:

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store-spring-boot-starter</artifactId>
</dependency>

文档导入服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Service
public class DocumentService {

private final EmbeddingModel embeddingModel;
private final VectorStore vectorStore;

public DocumentService(EmbeddingModel embeddingModel, VectorStore vectorStore) {
this.embeddingModel = embeddingModel;
this.vectorStore = vectorStore;
}

public void importPdf(String filePath) {
// 1. 读取 PDF
Resource resource = new FileSystemResource(filePath);
TikaDocumentReader reader = new TikaDocumentReader(resource);
List<Document> documents = reader.get();

// 2. 分块
TokenTextSplitter splitter = new TokenTextSplitter(
500, // 默认块大小
100, // 重叠大小
5, // 最小块大小
10000, // 最大块大小
true // 保持段落完整
);
List<Document> chunks = splitter.split(documents);

// 3. 向量化并存入 VectorStore(自动 Embedding)
vectorStore.add(chunks);
}
}

RAG 查询服务(使用 QuestionAnswerAdvisor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Service
public class RagService {

private final ChatClient chatClient;
private final VectorStore vectorStore;

public RagService(ChatClient.Builder builder, VectorStore vectorStore) {
this.vectorStore = vectorStore;

// 核心:QuestionAnswerAdvisor 自动处理检索和上下文注入
QuestionAnswerAdvisor qaAdvisor = new QuestionAnswerAdvisor(
vectorStore,
SearchRequest.defaults()
.withTopK(5)
.withSimilarityThreshold(0.7)
);

this.chatClient = builder
.defaultSystem("""
你是一个专业的问答助手。
请严格基于提供的文档内容回答问题。
如果文档中没有相关信息,请明确告知"文档中未找到相关内容"。
回答时请标注信息来源。
""")
.defaultAdvisors(qaAdvisor)
.build();
}

public String query(String question) {
// Advisor 自动检索相关文档并注入上下文
return chatClient.prompt()
.user(question)
.call()
.content();
}
}

对比:Spring AI 的 RAG 只需要 3 行核心代码,LangChain4J 需要更多配置。

4. 使用阿里云通义千问(国产模型)

国内项目首选阿里云,性价比高,中文效果好:

1
2
3
4
5
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter</artifactId>
<version>1.0.0</version>
</dependency>
1
2
3
4
5
6
7
spring:
ai:
alibaba:
api-key: ${DASHSCOPE_API_KEY}
chat:
options:
model: qwen-plus # qwen-turbo / qwen-plus / qwen-max
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service
public class AlibabaAiService {

private final ChatClient chatClient;

public AlibabaAiService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个专业的中文顾问")
.build();
}

public String chat(String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}

支持的通义模型:

  • qwen-turbo:速度快,成本低,适合简单对话
  • qwen-plus:能力均衡,推荐日常使用
  • qwen-max:最强推理,适合复杂任务
  • qwen-vl:多模态,支持图像理解

5. MCP 工具集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 配置 MCP Server
spring:
ai:
mcp:
servers:
github:
type: stdio
command: /usr/local/bin/github-mcp-server
args:
- --token=${GITHUB_TOKEN}
filesystem:
type: stdio
command: /usr/local/bin/fs-mcp-server
args:
- --root=/data/documents
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class McpAiService {

private final ChatClient chatClient;

public McpAiService(ChatClient.Builder builder) {
this.chatClient = builder
.defaultMcpServers("github", "filesystem")
.build();
}

public String analyzeGithubPr(String repo, int prNumber) {
return chatClient.prompt()
.user("分析 " + repo + " 的 PR #" + prNumber + " 的变更内容")
.call()
.content();
// 自动调用 GitHub MCP Server 获取 PR 数据
}
}

六、Spring AI 在 AI 潮流中的作用与影响

1. 降低 Java 企业接入 AI 的门槛

经过近一年的生产验证,Spring AI 已经证明了自己的价值:

2024 年痛点 2026 年现状
Java 开发者被迫学 Python Spring 开发者零学习成本接入 AI
跨语言架构复杂 单一技术栈,架构统一
缺乏企业级特性 Observability、安全、配置管理一体化

数据佐证:Spring AI GitHub Stars 从 2024 年的几千增长到 2026 年的数万,企业采用率快速增长。

2. 推动 AI 应用标准化

Spring AI 带来的标准化价值:

标准化 影响
API 统一 模型切换零成本,供应商议价能力提升
Prompt 模板化 版本管理、复用、测试规范化
RAG 模式 QuestionAnswerAdvisor 成为参考模式
Observability AI 调用监控追踪有 Spring 标准
MCP 协议 工具连接有开放标准,生态繁荣

3. 企业级 AI 应用的基础设施

Spring AI 不是玩具框架,是生产级基础设施:

能力 Spring AI 实现
可观测性 Actuator 自动集成,Token 消耗、耗时、成功率自动上报
可配置 YAML 配置 + 配置中心动态刷新
可扩展 新服务商扩展简单,遵循 Spring 模式
可集成 Spring Cloud、Security、Data 一体化
可运维 熔断、限流、重试、降级开箱即用

4. 与 LangChain 生态的分工协作

生态 定位 2026 状态
LangChain(Python) AI 创新的前沿试验场 继续引领新模式
LangChain4J Java 轻量级选择,Agent 优先 Agent 能力领先
Spring AI Java 企业级标准,可观测优先 企业采用率领先

三者不是竞争,是分工协作:LangChain 探路 → LangChain4J 快速跟进 → Spring AI 企业级封装。


七、避坑指南(2026 版)

1. 常见问题

问题 解决方案
API Key 管理混乱 用 Spring Cloud Vault 或配置中心,禁止硬编码
Token 消耗失控 启用 Actuator,配置预算告警,用 maxTokens 限制
RAG 效果不好 调整 Top-K、相似度阈值、分块策略,换 Embedding 模型
响应慢 流式响应,用小模型处理简单请求,预热连接
幻觉问题 RAG + 严格系统提示,设置”无法回答”的 fallback
国产模型不稳定 多模型备份,配置 fallback chain

2. 最佳实践

  1. 用 ChatClient,别直接用 ChatModel:ChatClient 封装更好,Advisor 链式处理
  2. RAG 用 QuestionAnswerAdvisor:3 行代码搞定,别手动拼接上下文
  3. Function 定义清晰@Description 写清楚用途和参数,帮助 LLM 正确调用
  4. VectorStore 选型:原型 Chroma,生产 Milvus/Qdrant,小数据 Redis/pgvector
  5. Observability 必开:上线前先配好,AI 调用必须有追踪
  6. 国产模型合规:数据不出境,用阿里/百度/讯飞,成本可控

3. 生产部署 Checklist

  • [ ] API Key 从配置中心/Vault 获取
  • [ ] Observability 开启,Token 消耗监控
  • [ ] 熔断降级配置(AI 调用失败 fallback)
  • [ ] 流式响应(降低首字延迟)
  • [ ] 日志脱敏(Prompt 可能含敏感信息)
  • [ ] VectorStore 持久化配置
  • [ ] 多模型备份(OpenAI 备通义,反之亦然)

总结

Spring AI 已经是 Java 企业级 AI 应用的事实标准:

维度 Spring AI 优势
成熟度 1.0 GA 近一年,API 稳定,生产验证
生态融合 Spring Boot/Cloud/Security/Actuator 一体化
企业级特性 Observability、配置管理、安全审计开箱即用
国产支持 阿里/百度/讯飞官方支持,合规可控
RAG 简洁 QuestionAnswerAdvisor 3 行代码搞定
MCP 支持 1.0+ 原生支持,工具生态繁荣

一句话:Spring 项目做 AI 应用,Spring AI 是唯一合理的选择。LangChain4J 在 Agent 方面领先,但企业级可观测性,Spring AI 完胜。

如果你是 Spring 开发者,想做 AI 应用,现在就是最好的时机——Spring AI 已经成熟,文档完善,社区活跃,生产就绪。


参考资料

一、为什么 SDD 突然火了?

2025 年下半年,GitHub 上突然冒出两个”爆款”项目:

项目 Stars 出品方 创建时间
spec-kit 87,549 GitHub 官方 2025-08
OpenSpec 39,543 Fission AI 2025-08

两个项目都主打同一个概念——SDD(Specification-Driven Development,规格驱动开发)

有意思的是,SDD 这个概念早在 2010 年左右就有人提过,但一直不温不火。为什么到了 2025 年突然”爆了”?

答案很简单:AI 编码助手成熟了

以前写规格是为了”人理解需求”,现在写规格是为了”AI 理解需求并生成代码”。这个转变,让 SDD 从”锦上添花”变成了”必备技能”。


二、SDD 的起源与发展

2.1 方法论的演进脉络

软件开发方法论一直在”左移”——把质量保障的关口往前推:

1
2
3
4
5
6
7
8
9
10
11
传统开发:需求 → 编码 → 测试 → 部署
↑______________| 问题发现太晚

TDD(2003):测试 → 编码 → 重构
↑_____| 先写测试,确保可测试

BDD(2006):行为描述 → 测试 → 编码
↑__________| 用自然语言描述行为

SDD(2025):规格 → AI生成代码 → 审查验证
↑______________| 先写清楚"要什么",AI 再生成"怎么做"

用一个生活类比:

方法论 类比
传统开发 厨师边做菜边尝,最后才发现盐放多了
TDD 先准备好品尝标准,做菜过程中不断对照
BDD 先写好”菜谱描述”(这道菜应该有什么味道),再让厨师做
SDD 先写好”完整菜单规格”(食材、步骤、口味),让 AI 厨师按规格来做

2.2 为什么以前 SDD 不火?

SDD 的核心思想是”先写规格再写代码”,听起来很合理。但以前有三大障碍:

  1. 人写代码比写规格快:资深程序员脑子里有模板,写代码比写文档快多了
  2. 规格容易过时:代码改了,规格忘了同步,最后变成”摆设”
  3. 没有工具支撑:规格只是 Markdown 文档,没法”执行”,也没法验证代码是否符合规格

2.3 为什么现在突然火了?

2024-2025 年,三个关键条件同时成熟:

条件 变化
AI 编码工具成熟 Claude Code、Cursor、Copilot 能真正写出可用代码
开发者角色转变 从”代码编写者”变成”规格设计者 + 代码审查者”
工具链完善 spec-kit、OpenSpec 提供了规格的”执行框架”

一个数据:预计到 2026 年,80% 的开发者将常态化使用 AI 编码工具。开发者花在规格设计和架构上的时间将超过写代码的时间。


三、SDD 的核心能力

3.1 一句话定义

SDD = 先用结构化规格精确描述”要做什么”,再让 AI 照着规格生成代码,最后验证代码是否符合规格。

3.2 核心能力拆解

能力 说明
协作澄清 开发者可能不清楚怎么做,AI 协助完成问题定义和方案探索——这是 SDD 最被低估的能力
规格即契约 规格不是”参考文档”,而是人机之间的”契约”——AI 必须按规格实现
迭代友好 规格可以随时更新,AI 会同步更新代码(不是”写完就扔”)
验证闭环 有工具验证代码是否符合规格,偏离时自动提醒
追溯性强 每一行代码都能追溯到对应的规格条款

3.3 协作澄清:SDD 最被低估的能力

很多人以为 SDD 是”开发者先写清楚规格,再让 AI 照着做”。但现实是:开发者往往也不清楚自己要什么

这时候,SDD 的”澄清模式”就派上用场了:

1
2
传统模式:开发者想清楚 → 写规格 → AI 实现
澄清模式:开发者有个模糊想法 → AI 协助澄清 → 共同定义问题 → 探索方案 → 写规格 → AI 实现

用一个类比:

场景 类比
传统 SDD 你拿着完整菜单去餐厅,厨师照着做
澄清模式 你说”我想吃点清淡的”,厨师和你讨论:是汤?是蒸菜?有什么忌口?最后一起定菜单

这就是 /speckit.clarify/opsx:explore 模式的价值——AI 不是被动执行者,而是主动的”需求澄清伙伴”

3.4 规格包含什么?

一个完整的 SDD 规格通常包含四个部分:

1
2
3
4
5
6
7
openspec/changes/add-dark-mode/
├── proposal.md # 为什么做这个改动?影响范围?
├── specs/ # 详细需求规格
│ ├── requirements.md # 功能需求
│ └── scenarios.md # 使用场景
├── design.md # 技术方案:怎么实现?
└── tasks.md # 任务清单:分几步完成?

3.5 与 TDD/BDD 的关系

SDD 不是替代 TDD/BDD,而是它们的”前置层”:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────┐
│ SDD │
│ 规格 → 定义"要做什么" │
├─────────────────────────────────────────────────────┤
│ BDD │
│ 行为描述 → 定义"应该有什么行为" │
├─────────────────────────────────────────────────────┤
│ TDD │
│ 测试 → 定义"怎么验证正确性" │
├─────────────────────────────────────────────────────┤
│ 实现 │
│ 代码 → AI 按上述三层约束生成 │
└─────────────────────────────────────────────────────┘

四、如何与 Code Agent 配合使用

4.1 工作流程对比

没有 SDD 时的工作流

1
2
3
4
5
6
7
8
9
10
你:帮我加个深色模式
AI:(直接开始写代码)好的,我创建 ThemeContext...
你:等等,我想要的是 localStorage 持久化
AI:那我改一下...
你:还要支持系统主题自动切换
AI:再改一下...
你:切换按钮放在导航栏右侧
AI:...

问题:需求在"聊天中不断补充",AI 来回改,效率低

有 SDD 时的工作流

1
2
3
4
5
6
7
8
你:/opsx:propose "添加深色模式"
AI:生成完整规格 → proposal + specs + design + tasks
你:(审查规格)嗯,规格里漏了 localStorage 持久化,补一下
AI:(更新规格)
你:(确认无误)/opsx:apply
AI:按规格一次性实现,不用来回改

优势:先对齐"要做什么",再一次性实现

4.2 支持的 Code Agent

Code Agent spec-kit 支持 OpenSpec 支持
Claude Code --ai claude /opsx:* 命令
Cursor ✅ slash commands ✅ slash commands
GitHub Copilot CLI ✅ 原生支持 ✅ skills 模式
Codex CLI ✅ skills 模式 ✅ skills 模式
Windsurf

五、问题澄清模式:当你不知道怎么做时

前面讲的流程都有一个前提:开发者知道”要做什么”。但现实往往是:

“我想给博客加个评论功能,但不知道怎么设计…”
“用户反馈搜索太慢,但我不清楚具体慢在哪里…”
“产品说要提升用户体验,但这个需求太模糊了…”

这时候,澄清模式(Clarify/Explore) 就派上用场了。

5.1 澄清模式的价值

澄清前 澄清后
“我想加评论功能”(模糊) “评论功能需要:登录才能评论、支持楼中楼、可编辑删除、有审核机制”(清晰)
“搜索太慢”(笼统) “问题定位:首页搜索接口响应 3s+,原因是全文检索没走索引,需要优化 SQL 并加缓存”(具体)
“提升用户体验”(抽象) “具体方案:页面加载加骨架屏、关键操作加确认弹窗、表单加实时校验提示”(可落地)

澄清过程本身就是在”定义问题”——很多 Bug 的根因不是代码写错了,而是需求本身就没想清楚。

5.2 spec-kit 的澄清流程

使用 /speckit.clarify 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
你:/speckit.clarify "我想给博客加个评论功能"

AI:让我帮你澄清一下这个需求...
Q1: 评论需要登录才能发吗?
你:是的,必须登录

Q2: 评论支持回复别人的评论吗?(楼中楼)
你:支持,最多嵌套 3 层

Q3: 评论发出去能编辑或删除吗?
你:发出去 5 分钟内可以编辑,用户自己发的可以删除

Q4: 评论需要审核吗?还是直接显示?
你:需要审核,管理员后台审核后才显示

AI:好的,我来整理一下澄清后的需求...

[生成 clarification.md]
✓ 问题边界已明确
✓ 关键决策已记录
✓ 可以进入规格编写阶段

你:/speckit.spec blog-comment-system

5.3 OpenSpec 的探索流程

使用 /opsx:explore 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
你:/opsx:explore "用户反馈搜索太慢,想优化一下"

AI:让我先帮你分析问题...

1. 定位问题范围:
- 是首页搜索慢?还是后台管理搜索?
- 是所有搜索都慢?还是特定关键词?
- "慢"的标准是什么?2s?5s?

你:首页的全站搜索,大概 3-4 秒才出结果,用户说体验很差

2. 排查可能原因:
- 检查当前实现:全文检索 LIKE '%keyword%'
- 数据量:posts 表 10万+,没走索引
- 缓存策略:无缓存,每次查库

3. 方案探索:
方案A:加全文索引(MySQL FULLTEXT)
方案B:引入 Elasticsearch
方案C:加 Redis 缓存热点关键词

各方案对比:
| 方案 | 成本 | 效果 | 维护难度 |
|------|------|------|----------|
| A | 低 | 中(大数据量仍慢) | 低 |
| B | 高 | 高 | 高(新增组件) |
| C | 低 | 中(命中率依赖) | 低 |

建议组合方案:A + C,先加索引打底,再加缓存加速热点

你:就用 A + C 组合方案

AI:好的,我来生成探索记录...

[生成 openspec/explorations/search-optimization/]
✓ exploration.md — 问题分析 + 方案对比
✓ decision.md — 最终选择的方案 + 原因
✓ 可以进入 /opsx:propose 阶段

5.4 澄清模式的核心价值

用一个公式总结:

1
需求质量 = 澄清深度 × 规格精度 × 验证闭环

澄清是起点。如果一开始需求就模糊,后面写再详细的规格、做再严格的验证,都是”在错误的方向上努力”。

很多项目的返工、延期,根因都在这里:

返工原因 根因
“这不是我想要的” 澄清不够,理解偏差
“需求又变了” 边界没定清楚,后面不断加范围
“实现方案不对” 没探索充分,选错了技术路线

SDD 的澄清模式,就是帮你把这些问题”前置解决”——在写代码之前,先把问题聊清楚。


六、与 Claude Code 配合的安装步骤

6.1 spec-kit 安装

1
2
3
4
5
6
7
8
# 方式一:推荐安装稳定版本(v0.6.1)
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git@v0.6.1

# 方式二:安装最新版本
uv tool install specify-cli --from git+https://github.com/github/spec-kit.git

# 验证安装
specify check

初始化项目:

1
2
3
4
5
# 创建新项目
specify init my-project --ai claude

# 或在现有项目中初始化
specify init . --ai claude

6.2 OpenSpec 安装

1
2
3
4
5
6
7
8
9
# 需要 Node.js 20.19.0+
npm install -g @fission-ai/openspec@latest

# 验证安装
openspec --version

# 初始化项目
cd your-project
openspec init

七、实战示例:同一个需求,两种工具

需求:为博客网站添加深色模式

7.1 spec-kit 使用流程

步骤 1:定义项目原则

1
2
cd my-project
claude

在 Claude Code 中执行:

1
2
3
4
> /speckit.constitution 创建项目核心原则:
- 代码风格遵循 ESLint 规范
- 所有 UI 组件需要单元测试
- 新功能需要配套文档

AI 会生成 .speckit/constitution.md 文件。

步骤 2:创建功能规格

1
2
3
4
5
6
7
> /speckit.spec add-dark-mode 创建深色模式功能规格

需求要点:
- 支持亮色/深色/跟随系统三种模式
- 用户偏好存储在 localStorage
- 导航栏右侧放置切换按钮
- 切换时平滑过渡动画

AI 会生成 .speckit/features/add-dark-mode/spec.md

步骤 3:生成实施计划

1
> /speckit.plan add-dark-mode

AI 生成 .speckit/features/add-dark-mode/plan.md,包含:

  • 技术方案(用 React Context 还是 CSS 变量?)
  • 文件改动清单
  • 实施步骤

步骤 4:分解任务

1
> /speckit.tasks add-dark-mode

生成 .speckit/features/add-dark-mode/tasks.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## Tasks

### 1. 基础设施
- [ ] 1.1 创建 ThemeContext
- [ ] 1.2 定义 CSS 变量体系
- [ ] 1.3 创建 useTheme hook

### 2. UI 组件
- [ ] 2.1 创建 ThemeToggle 组件
- [ ] 2.2 添加过渡动画样式
- [ ] 2.3 集成到导航栏

### 3. 持久化
- [ ] 3.1 localStorage 存取逻辑
- [ ] 3.2 系统主题监听

### 4. 测试
- [ ] 4.1 ThemeContext 单元测试
- [ ] 4.2 ThemeToggle 组件测试

步骤 5:执行实施

1
> /speckit.implement add-dark-mode

AI 按任务清单逐项实现代码。

步骤 6:验证审查

1
> /speckit.verify add-dark-mode

AI 检查:

  • 每个任务是否完成
  • 代码是否符合规格
  • 测试是否通过

7.2 OpenSpec 使用流程

步骤 1:一键生成完整规格

1
2
cd my-project
claude

在 Claude Code 中执行:

1
2
> /opsx:propose "添加深色模式,支持亮色/深色/跟随系统三种模式,
用户偏好存储在 localStorage,导航栏右侧放置切换按钮"

AI 一次性生成完整规格目录:

1
2
3
4
5
6
7
openspec/changes/add-dark-mode/
├── proposal.md # ✅ 为什么做?影响哪些页面?
├── specs/
│ ├── requirements.md # ✅ 功能需求详情
│ └── scenarios.md # ✅ 用户使用场景
├── design.md # ✅ 技术方案:React Context + CSS 变量
└── tasks.md # ✅ 实施任务清单

步骤 2:审查并调整规格

1
2
> 打开 specs/requirements.md,补充一个需求:
切换按钮需要显示当前模式图标(太阳/月亮/电脑)

AI 更新规格文件。

步骤 3:执行实施

1
> /opsx:apply

输出:

1
2
3
4
5
6
7
8
9
10
11
12
AI: Implementing tasks...
✓ 1.1 Create ThemeContext provider
✓ 1.2 Define CSS variable system
✓ 1.3 Create useTheme hook
✓ 2.1 Create ThemeToggle component
✓ 2.2 Add transition animation
✓ 2.3 Integrate to navbar
✓ 3.1 localStorage logic
✓ 3.2 System theme listener
✓ 4.1 Unit tests
✓ 4.2 Component tests
All tasks complete!

步骤 4:归档

1
> /opsx:archive

AI 将完成的变更归档:

1
2
Archived to openspec/changes/archive/2026-04-13-add-dark-mode/
Specs updated. Ready for the next feature.

八、spec-kit vs OpenSpec:如何选择?

维度 spec-kit OpenSpec
出品方 GitHub 官方 Fission AI(创业公司)
风格 工程化、严谨 灵活、快速
安装 Python + uv Node.js + npm
流程 分步执行(6 个命令) 一键生成 + 一键应用
扩展性 40+ 官方扩展 配置化定制
适合场景 大型项目、团队协作 个人项目、快速迭代
学习曲线 较陡(概念多) 较平(上手快)

选择建议

1
2
3
4
5
6
如果你是...
├─ 大型团队,需要严格控制 → 选 spec-kit
├─ 个人开发者,追求效率 → 选 OpenSpec
├─ Python 项目 → spec-kit(工具链一致)
├─ TypeScript/JS 项目 → OpenSpec(工具链一致)
└─ 想快速体验 SDD → 先试 OpenSpec,再学 spec-kit

九、总结:SDD 是 AI 编码时代的”必修课”

回顾一下:

  1. SDD 不是新概念,但 AI 编码工具让它变得至关重要
  2. 核心能力:协作澄清、规格即契约、迭代友好、验证闭环、追溯性强
  3. 澄清模式:当你不知道怎么做时,AI 协助你把需求聊清楚——这是最被低估的能力
  4. 与 Code Agent 配合:先对齐”要做什么”,再让 AI 一次性实现
  5. 两个主流工具:spec-kit(GitHub 官方)、OpenSpec(灵活高效)

一句话总结:

以前写代码靠”手艺”,现在写代码靠”规格”。写得越清楚,AI 生成得越精准。

如果你自己都不清楚要什么,先用澄清模式把需求聊明白——这才是 SDD 的起点。


参考资料

如果你是一个人做项目,你不需要成为产品经理、架构师、营销专家——只需要知道何时调用哪个「虚拟专家」。Agency-Agents 这个开源项目,就是把 200 多个专业角色打包成了可以直接导入 Claude Code 的 Agent 文件。本文会介绍这个工具是什么、怎么用,以及如何用它协作开发一个完整项目。

阅读全文 »

现在一提到意图识别,很多人第一反应就是丢给大模型。用户说句话,让模型判断他想干嘛,返回个意图标签,完事。

这么做当然能跑通,现在的模型理解个意图绰绰有余。但真到了有流量的系统上,这条路会很快让你难受。意图识别真正难的地方,不是能不能识别出来,而是怎么在准确率、延迟和成本之间找平衡。

这篇就聊聊怎么把这件事做好。为什么不能全靠大模型,三层结构各自怎么落地,还有一些教程里不太提、但上线之后八成会碰到的问题。

阅读全文 »

用 Claude Code 写代码的时候,你有没有过这种焦虑:不知道当前用的什么模型、context 还剩多少、这一轮对话花了多少钱?这三个指标直接影响代码质量和你的钱包,但 Claude Code 默认不显示。好在有个 statusLine 功能,可以自定义一个「仪表盘」实时监控这些数据。

阅读全文 »

画架构图、流程图、时序图,传统方式是打开 draw.io 手动拖拽。Next AI Draw.io 提供了另一种方式:你用自然语言描述,AI 帮你画。更棒的是,它支持 MCP 协议,可以在 Claude Code 中直接调用——写代码时让 AI 顺手把图也画了。

阅读全文 »

写在前面

如果你刚读完《AI 概念指南》,对 LLM、Agent 这些基础概念有了初步了解,但在实际使用 AI 时,可能又遇到了新的困惑:

  • “Claude Code 和 Claude 是什么关系?用 Claude Code 必须用 Claude 吗?”
  • “Claude Code 里可以切换模型,那它和 Kimi、GLM 是什么关系?”
  • “我想用国产模型,该选工具还是选模型?”

这些问题非常普遍。AI 领域有两层东西叠在一起:一层是”大脑”(模型),一层是”应用”(工具)。很多小白搞混,就是因为分不清哪层是哪层。

这篇文章的目标:让你彻底搞清楚模型和工具的关系,看完之后不再迷糊


一、核心概念:两层架构

把 AI 世界想象成一个餐厅:

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────────────────────────────────┐
│ 餐厅(AI 生态) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 厨师(模型) │◄──────│ 服务员(工具) │ │
│ │ │ │ │ │
│ │ 负责做菜 │ │ 负责端菜、接待 │ │
│ │ 提供"智能能力" │ │ 提供"交互界面" │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

第一层:AI 模型(厨师)—— 提供”智能”

模型是 AI 的核心,负责思考、理解、生成内容。它就像餐厅里的厨师,决定菜品的”味道”(智能水平)。

模型的核心特点:

  • 不直接面向用户,而是通过 API 被”工具”调用
  • 是智能的”发动机”,所有的推理、理解、生成都在这里完成
  • 一次训练成本极高(数亿到数十亿美元),但边际使用成本低

第二层:AI 工具(服务员)—— 提供”交互”

工具是用户实际接触的产品,负责界面交互、功能整合。它就像餐厅里的服务员,把厨师做的菜端到顾客面前。

工具的核心特点:

  • 直接面向用户,提供聊天界面、编程环境、文档处理等功能
  • 调用一个或多个模型的 API,把模型能力”包装”成用户友好的产品
  • 本身不产生智能,只是”智能的搬运工”

二、主流模型一览(第一层)

先认识一下市面上的”厨师们”:

国内模型

模型 出品公司 特点 适用场景
Kimi 月之暗面 超长上下文(200万字),中文优化 长文档阅读、论文分析
GLM 智谱AI 开源+闭源双轨,学术背景强 技术研究、企业部署
Qwen(通义千问) 阿里巴巴 中文能力强,开源生态好 中文场景、企业应用
DeepSeek 深度求索 开源、性价比极高、数学推理强 代码、数学、技术研究
Doubao(豆包) 字节跳动 中文理解好,性价比高,多场景适配 日常对话、办公助手
MiniMax MiniMax 多模态、语音交互强 语音场景、多模态应用

国外模型

模型 出品公司 特点 适用场景
Claude Anthropic 推理严谨、代码能力强、上下文长 编程、长文档分析、安全场景
GPT(ChatGPT 底层) OpenAI 生态最完善、插件最多 通用场景、创意写作
Gemini Google 多模态原生、谷歌生态集成 多媒体处理、谷歌用户
Llama Meta 完全开源、可本地部署 私有部署、技术研究

一个关键认知

模型本身不是产品。你无法”直接使用”模型,必须通过某个工具(如 ChatGPT、Claude 网页版、API 接口等)来访问模型的能力。

1
2
3
4
5
6
7
8
┌─────────────────────────────────────────────────────────┐
│ 模型不能直接访问 │
│ │
│ ❌ "我要用 GPT-4" —— 这是无法直接做到的 │
│ ✅ "我要用 ChatGPT" —— 这才是正确说法 │
│ │
│ 因为 ChatGPT 是工具,GPT-4 是它背后的模型 │
└─────────────────────────────────────────────────────────┘

三、主流工具一览(第二层)

再认识一下”服务员们”:

AI 对话工具(网页版/APP)

这些工具提供对话界面,让你能和模型”聊天”:

工具 主要模型 是否支持切换模型
ChatGPT GPT 系列 ❌ 仅 OpenAI 模型
Claude 网页版 Claude 系列 ❌ 仅 Anthropic 模型
Kimi 网页版/APP Kimi ❌ 仅月之暗面模型
豆包 Doubao ❌ 仅字节跳动模型
Poe 多模型 ✅ 支持 Claude、GPT、Llama 等
Cherry Studio 多模型 ✅ 支持配置多种模型 API
Chatbox 多模型 ✅ 支持配置多种模型 API

AI 编程工具(IDE/编辑器插件)

这些工具专注于编程场景,能读写代码、理解项目:

工具 类型 支持的模型 特点
Claude Code CLI/IDE插件 Claude、Kimi、GLM、MiniMax、Doubao 等多模型 官方出品,代码理解最强,支持多模型切换
Cursor AI IDE Claude、GPT、DeepSeek、Gemini 等 可切换模型,IDE 集成体验好
GitHub Copilot IDE 插件 GPT 系列 微软出品,与 GitHub 深度集成
Trae AI IDE Kimi、GLM、Doubao 等(国内模型为主) 字节出品,中文优化
Qoder AI 编程工具 多模型 支持多种国产模型
OpenCode 开源 AI 编程工具 多模型 开源免费,可自定义配置

重要说明:Claude Code 是 Anthropic 官方出品的编程工具,但它支持多种模型,包括国内的 Kimi、GLM、MiniMax、Doubao 等。对于没有海外信用卡的中国用户,可以配置国产模型 API 来使用 Claude Code。

工具的本质

工具做的事情就是把你的输入(问题、代码、文档)发送给模型,然后把模型的回答展示给你:

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────────────────────────────────┐
│ 工具的工作流程 │
│ │
│ 用户输入 ──► 工具处理 ──► 调用模型 API ──► 获取模型回答 ──► 展示给用户
│ │ │ │
│ "帮我写个函数" 发送请求 返回代码
│ 到模型服务器 给用户
│ │
└─────────────────────────────────────────────────────────────────┘

四、模型与工具的关系图

关系示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
                              工具层(用户直接接触)
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Claude Code │ │ Cursor │ │ Trae │ │
│ │ (多模型) │ │ (多模型) │ │ (国内模型) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 多模型 API │ │ 多模型 API │ │ Kimi/GLM API │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────┬───────────────────────────────┘


模型层(智能来源)
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Claude │ │ GPT │ │ Gemini │ │ Kimi │ │ DeepSeek│ │
│ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ GLM │ │ Doubao │ │MiniMax │ │ Qwen │ │ ... │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

两种关系模式

模式一:官方绑定型

1
2
3
4
5
6
7
8
9
10
11
┌─────────────┐           ┌─────────────┐
│ ChatGPT │ ────────► │ GPT │
│ (工具) │ 官方绑定 │ (模型) │
└─────────────┘ └─────────────┘

┌─────────────┐ ┌─────────────┐
│ Claude 网页版│ ────────► │ Claude │
│ (工具) │ 官方绑定 │ (模型) │
└─────────────┘ └─────────────┘

特点:一个工具只能用自家模型的 API,不能切换其他模型

模式二:多模型支持型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────┐           ┌─────────────┐
│ Claude Code │ ────────► │ Claude │
│ (工具) │ │ (模型) │
│ │ ────────► ├─────────────┤
│ │ │ Kimi │
│ │ ────────► ├─────────────┤
│ │ │ GLM │
│ │ ────────► ├─────────────┤
│ │ │ MiniMax │
│ │ ────────► ├─────────────┤
│ │ │ Doubao │
└─────────────┘ └─────────────┘

特点:一个工具可以切换多个模型,用户自己选择

注意:Claude Code 虽然是 Anthropic 官方出品,但它支持配置多种模型的 API。对于中国用户来说,这意味着你可以用 Kimi、GLM、MiniMax、Doubao 等国产模型来驱动 Claude Code,而不必依赖海外支付渠道。


五、常见困惑解答

Q1:Claude Code 就是 Claude 吗?

不是。

  • Claude 是模型,是 Anthropic 开发的 AI “大脑”
  • Claude Code 是工具,是 Anthropic 官方出品的编程助手

Claude Code 调用模型来帮你写代码,但它不只是调用 Claude,还支持 Kimi、GLM、MiniMax、Doubao 等多种模型。你可以理解为:

1
Claude Code = 编程专用界面 + 代码操作能力 + 多模型 API 支持

Q2:为什么 Claude Code 可以选择 Claude、Kimi、GLM、MiniMax?

因为 Claude Code 是”多模型工具”,它支持配置多个模型厂商的 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────────────────────────┐
│ Claude Code 架构 │
│ │
│ 用户界面 ◄──► Claude Code 核心 ◄──► 模型配置 │
│ │ │
│ ┌──────────────────┼────────────┐ │
│ ▼ ▼ ▼ │
│ Claude API Kimi API GLM API │
│ (Anthropic) (月之暗面) (智谱AI) │
│ │
│ ▼ ▼ ▼ │
│ MiniMax API Doubao API 其他 API │
│ (MiniMax) (字节跳动) (可扩展) │
└─────────────────────────────────────────────────────────┘

Claude Code 做的是”整合商”的工作,让你在一个工具里能用多个模型。

对于中国用户特别友好:如果你没有海外信用卡,无法购买 Claude API,完全可以配置国产模型(如 Kimi、GLM、MiniMax、Doubao)的 API 来使用 Claude Code。这些国产模型通常支持国内支付方式,获取 API 更方便。

Q3:我想用国产模型,该怎么选?

方案一:直接用模型官方工具

  • Kimi → Kimi 网页版/APP
  • GLM → 智谱清言
  • Doubao → 豆包 APP
  • Qwen → 通义千问
  • DeepSeek → DeepSeek 网页版

方案二:用支持国产模型的编程工具

这是很多程序员的选择,既能用国产模型,又能享受专业编程工具的体验:

  • Claude Code(推荐)—— 支持 Kimi、GLM、MiniMax、Doubao 等多种国产模型,代码能力最强
  • Trae(字节出品)—— 默认 Kimi/GLM/Doubao,中文优化
  • Qoder —— 支持多种国产模型
  • Cursor —— 支持 DeepSeek

Q4:模型和工具哪个更重要?

都重要,但影响点不同:

维度 模型决定 工具决定
智能水平 ✓ 回答质量、推理能力
使用体验 ✓ 界面设计、操作流畅度
功能丰富度 ✓ 代码高亮、文件管理、历史记录
模型选择 ✓ 是否支持切换模型
价格 ✓ API 调用成本 ✓ 订阅费用
隐私合规 ✓ 数据处理方式 ✓ 本地存储、日志记录

一句话总结:模型决定”上限”,工具决定”体验”。


六、选型指南

按需求选

你的需求 推荐模型 推荐工具
日常聊天问答 GPT-4o、Claude、Doubao ChatGPT、Claude 网页版、豆包
读长文档/论文 Kimi、Claude Kimi 网页版、Claude
写代码/重构 Claude、DeepSeek、GLM Claude Code、Cursor
数学/逻辑推理 DeepSeek-R1、Claude DeepSeek 网页版、Claude Code
国产模型+编程 Kimi、GLM、Doubao、MiniMax Claude Code(配置国产模型 API)
企业私有部署 GLM、Qwen、DeepSeek OpenCode、自建平台

按用户类型选

用户类型 推荐组合 理由
完全小白 ChatGPT 或 Kimi/豆包 网页版 开箱即用,无需配置
办公族 Kimi/豆包 + Claude 网页版 长文档 + 通用问答
程序员(有海外支付) Claude Code + Claude API 官方模型,体验最佳
程序员(国内用户) Claude Code + Kimi/GLM/Doubao API 国产模型,支付方便
技术极客 Cherry Studio + 多模型 API 灵活配置,自由切换
企业用户 私有部署 + 定制工具 数据安全,合规可控

七、避坑指南

常见误区

误区 正确理解
“我买了 Cursor,是不是就能用 Claude?” Cursor 是工具,Claude API 需要单独付费或配置
“Claude Code 只能用 Claude 吗?” 错误! Claude Code 支持多种模型,包括 Kimi、GLM、MiniMax、Doubao 等
“Kimi 和 DeepSeek 哪个好?” 没有绝对好坏,看场景:Kimi 长文档强,DeepSeek 代码数学强
“Claude Code 是官方出品,肯定只支持自家模型” 官方出品不代表只支持自家模型,Claude Code 是开放的多模型工具
“国产模型不如国外模型” 各有优势,国产模型中文理解更好,国外模型生态更完善

省钱技巧

  1. 简单任务用小模型:日常对话用更便宜的模型(如 GPT-4o-mini、Claude Haiku)
  2. 复杂任务用大模型:代码、推理用更强的模型(如 Claude Sonnet、DeepSeek-V3)
  3. 对比后再订阅:先用免费额度测试,再决定是否付费
  4. 注意 API 调用成本:工具可能免费,但模型 API 调用要收费
  5. 国产模型性价比高:Kimi、GLM、Doubao 等国产模型通常价格更友好,支付更方便

八、总结

用一个比喻来总结模型和工具的关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 模型 = 发动机(提供动力) │
│ 工具 = 汽车(提供驾驶体验) │
│ │
│ Claude Code = 一辆可以换不同发动机的改装车 │
│ 可以装 Claude、Kimi、GLM、MiniMax、Doubao 等 │
│ │
│ ChatGPT = 装了 GPT 发动机的官方轿车(不能换发动机) │
│ Claude 网页版 = 装了 Claude 发动机的官方轿车(不能换发动机) │
│ │
│ 你可以: │
│ - 买不能换发动机的官方车(ChatGPT、Claude 网页版) │
│ - 买可换发动机的车(Claude Code、Cursor、Poe) │
│ - 自己组装(Cherry Studio + API) │
│ │
│ 核心原则:先想好你要什么体验,再选车和发动机 │
│ │
└─────────────────────────────────────────────────────────────────┘

记住三个要点

  1. 模型是智能来源,工具是交互入口 —— 搞清楚你在用哪层
  2. 一个工具可能支持多个模型,一个模型可以被多个工具调用 —— 它们是多对多的关系
  3. Claude Code 支持多模型 —— 对于中国用户,可以配置国产模型 API 来使用

附录:术语对照表

模型术语 所属公司 常见混淆
Claude Anthropic Claude Code 是工具,Claude 是模型;但 Claude Code 支持多模型
GPT-4、GPT-4o OpenAI ChatGPT 是工具,GPT 是模型
Kimi 月之暗面 同名,但要注意区分工具和模型
DeepSeek 深度求索 同名,网页版是工具,API 是模型
GLM 智谱AI 智谱清言是工具,GLM 是模型
Doubao(豆包) 字节跳动 豆包 APP 是工具,Doubao 是模型
Qwen 阿里巴巴 通义千问是工具,Qwen 是模型
Gemini Google Gemini 网页版是工具,Gemini 是模型

参考资料

背景

很多刚开始接触大模型的朋友,经常会看到”消耗了 XXX tokens”、”超出 token 限制”这样的提示。但到底什么是 Token?为什么大模型要用 Token 而不是直接理解整段话?本文将深入浅出地解答这些问题。


一、Token 是什么?

Token 是大模型处理文本的最小单位。可以把它理解为文本世界的”乐高积木块”——模型不是逐字逐句读取,而是先把文本拆成一块块 Token,再逐块处理。

1.1 通俗理解

想象你在读一本书:

阅读方式 特点
字符级别 一个字母一个字母读 → 太慢,无法理解语义
单词级别 一个单词一个单词读 → 更合理,但词汇量太大
Token 级别 介于两者之间,灵活切分 → 效率与能力的平衡点

1.2 具体例子

英文句子 I love artificial intelligence

1
2
3
按字符:20 个字符(含空格)
按单词:4 个单词
按 Token:约 5-6 个 Token(取决于分词器)

中文句子 我爱人工智能

1
2
按字符:6 个汉字
按 Token:约 6-10 个 Token(每个汉字可能单独成 Token)

二、为什么使用 Token 而不是”理解整段话”?

这个问题触及了大模型的核心原理。答案很简单:计算机只能处理数字,不能直接理解文字

2.1 大模型的工作流程

1
文字 → Token → 数字(向量)→ 计算 → 预测 → Token → 文字

每一步都必须是有限、可枚举的。

2.2 如果是”整段话处理”

假设模型要直接处理”整段话”,会遇到以下问题:

问题 说明
无限组合 一段话可能有任意长度、任意组合,无法穷举
无法对齐 “我爱AI”和”我 爱 AI”算同一段话吗?
无法计算 神经网络本质是矩阵乘法,需要固定维度的输入
存储爆炸 要存储所有可能的句子组合,内存直接撑爆

2.3 Token 方案的优势

优势 说明
有限词汇表 常用 Token 约 5-10 万个,可以穷举并编号
可数学化 每个 Token 对应一个向量(如 4096 维),可以计算
灵活组合 像乐高一样,有限积木块能拼出无限造型
长短皆可 Token 序列可以处理任意长度的文本

2.4 语言的组合性原理

语言有一个核心特点:有限符号,无限组合

1
2
3
英语:26 个字母 → 无数句子
中文:几千常用字 → 无数句子
Token:几万个 Token → 无数句子

大模型的设计正是利用了这个特性:

如果 Token 只有 5 万个,但能组合出无限句子,那么模型只需要学会这 5 万个 Token 的”表示”和”关系”,就能理解所有可能的句子。

这比试图记忆所有可能的句子要高效得多。


三、Token 的产生背景与历史

3.1 为什么不按”单词”处理?

早期自然语言处理确实尝试过按单词处理,但遇到两个问题:

问题一:词汇量爆炸

英语有超过 100 万个单词,而且每天都在增加新词。要让模型记住所有单词,词汇表会无限膨胀。

问题二:罕见词无法处理

遇到没见过的单词(如 unprecedented),模型直接”不认识”。

3.2 子词分词的诞生

研究者提出子词分词的概念:

1
2
常见词 → 整体作为一个 Token
罕见词 → 拆成多个子词 Token

例如:

单词类型 处理方式 例子
常见词 整体作为一个 Token the, is, hello, love
罕见词 拆成多个 Token unprecedentedun + pre + ced + ented
新词 用已知 Token 组合表示 chatgptchat + g + pt

3.3 主流分词算法

目前主流的分词算法有三类:

算法 全称 特点 代表模型
BPE Byte Pair Encoding 合并频率最高的相邻符号 GPT 系列
WordPiece WordPiece 基于概率的分词,用 ## 标记子词 BERT 系列
Unigram Unigram Language Model 基于统计概率,选择最优分词 T5、XLNet

3.4 BPE 分词示例

BPE 的核心思路是迭代合并

1
2
3
4
5
6
7
8
9
10
11
初始状态:每个字符都是一个 Token
["u", "n", "p", "r", "e", "c", "e", "d", "e", "n", "t", "e", "d"]

迭代 1:合并频率最高的字符组合 "e" + "d" → "ed"
["u", "n", "p", "r", "e", "c", "ed", "e", "n", "t", "ed"]

迭代 2:合并 "un"
["un", "p", "r", "e", "c", "ed", "e", "n", "t", "ed"]

迭代 N:最终得到
["un", "pre", "ced", "ented"]

通过这种方式,模型既能高效处理常见词,又能”认识”从未见过的罕见词。


四、Token 计量规则

4.1 不同语言的 Token 效率

这是很多中文用户最容易困惑的地方:

语言 Token 效率 估算公式
英文 1 Token ≈ 0.75 个单词 ≈ 4 个字符 Token 数 ≈ 单词数 × 1.3
中文 1 Token ≈ 0.6-0.8 个汉字 Token 数 ≈ 字数 × 1.5-2
代码 1 Token ≈ 4 个字符 Token 数 ≈ 字符数 ÷ 4

4.2 为什么中文 Token 数更多?

原因在于分词器的训练数据。主流分词器(如 GPT 系列)主要基于英文语料训练:

  • 英文artificial 可能是 1 个 Token(常见词)
  • 中文:每个汉字 可能各自成为 1 个 Token

这导致同样内容,中文 Token 数通常是英文的 1.5-2 倍

4.3 实例对比

同样的内容用不同语言表达:

内容 语言 Token 数估算
The future of artificial intelligence is bright 英文 约 7-8 个
人工智能的未来很光明 中文 约 10-12 个

这就是为什么用中文对话可能更快消耗 Token 配额。

4.4 不同模型的 Token 效率对比

模型系列 中文 Token 效率 特点
GPT-4 较低(约 2 字/Token) 英文优化,中文拆字多
Claude 中等(约 1.5 字/Token) 多语言平衡
DeepSeek 较高(约 1 字/Token) 中文专项优化
Qwen 较高(约 1 字/Token) 中文原生模型

五、Token 在大模型中的作用方式

5.1 完整处理流程

以句子 "人工智能很强大" 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
步骤 1: 分词
"人工智能很强大" → ["人工", "智能", "很", "强大"]

步骤 2: 编号(在词汇表中查找)
["人工", "智能", "很", "强大"] → [3821, 567, 89, 4521]

步骤 3: 向量化(每个编号对应一个向量)
3821 → [0.12, -0.34, 0.56, 0.78, ...](4096 维向量)
567 → [0.23, 0.45, -0.11, 0.89, ...]
89 → [0.67, -0.23, 0.34, 0.12, ...]
4521 → [0.45, 0.67, 0.23, -0.45, ...]

步骤 4: 神经网络计算
向量输入 Transformer 模型,计算"下一个 Token 是什么"
- 注意力机制:计算 Token 之间的关联关系
- 前馈网络:提取和变换语义信息

步骤 5: 预测输出
模型输出概率分布 → "的"(概率最高)

步骤 6: Token 转文字
["的"] → "的"

完整输出:"人工智能很强大" + "的" → "人工智能很强大的..."

5.2 向量化是什么?

每个 Token 都有一个向量表示——可以理解为这个 Token 的”数学身份证”。

1
2
3
4
5
Token "人工智能" 的向量(简化示例):
[0.12, -0.34, 0.56, 0.78, 0.23, 0.45, -0.11, 0.89, ...]

这个向量有 4096 个数值(或更多)
每个数值代表这个 Token 在某个语义维度上的"坐标"

向量的神奇之处:语义相似的 Token,向量也相似。

1
2
3
4
5
6
7
Token "狗" 的向量:[0.8, 0.2, 0.1, ...]
Token "猫" 的向量:[0.7, 0.3, 0.2, ...]
Token "汽车" 的向量:[0.1, 0.9, 0.5, ...]

计算相似度:
"狗" vs "猫" → 高相似度(都是动物)
"狗" vs "汽车" → 低相似度(语义差异大)

5.3 为什么是 5 万个 Token 而不是 5 万个单词?

关键区别在于覆盖能力

1
2
3
4
5
6
7
8
单词方案:
- 词汇表:5 万个单词
- 遇到新词:直接"不认识"

Token 方案:
- 词汇表:5 万个 Token(子词)
- 遇到新词:用已知 Token 组合表示
- 例:新词 "deepseek" → ["deep", "seek"](都是已知 Token)

这就是 Token 方案的核心优势:有限词汇表 + 无限组合能力


六、Token 量化方法

6.1 方法一:在线可视化工具

最直观的方式是使用官方提供的 Tokenizer 工具:

工具 链接 特点
OpenAI Tokenizer https://platform.openai.com/tokenizer GPT 模型分词可视化
Anthropic Console https://console.anthropic.com Claude 模型分词查看

这些工具可以让你直观看到文本是怎么被拆成 Token 的。

6.2 方法二:代码计算

使用 Python 进行精确计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 方案 1:使用 tiktoken(OpenAI 官方库)
import tiktoken

enc = tiktoken.encoding_for_model("gpt-4")
text = "我爱人工智能"
tokens = enc.encode(text)
print(f"Token 数量: {len(tokens)}")
print(f"Token 列表: {tokens}")
print(f"Token 文本: {[enc.decode([t]) for t in tokens]}")

# 方案 2:使用 transformers(通用方案)
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("gpt2")
text = "我爱人工智能"
tokens = tokenizer.encode(text)
print(f"Token 数量: {len(tokens)}")

6.3 方法三:API 返回值

调用大模型 API 时,返回结果通常包含 Token 使用量:

1
2
3
4
5
6
7
8
9
10
11
12
13
from openai import OpenAI

client = OpenAI(api_key="your-key")

response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "介绍一下人工智能"}]
)

# Token 使用量
print(f"输入 Token: {response.usage.prompt_tokens}")
print(f"输出 Token: {response.usage.completion_tokens}")
print(f"总 Token: {response.usage.total_tokens}")

6.4 方法四:经验估算(快速估算)

当你没有工具时,可以用经验公式:

语言 估算公式 示例
英文 Token ≈ 单词数 × 1.3 100 单词 ≈ 130 Token
中文 Token ≈ 字数 × 1.5-2 100 字 ≈ 150-200 Token
代码 Token ≈ 字符数 ÷ 4 400 字符 ≈ 100 Token
混合 各部分分别估算再相加 英文 + 中文 + 代码

七、Token 限制与成本

7.1 各模型的 Token 限制

模型 上下文窗口 最大输出
GPT-4o 128K Token 16K Token
GPT-4 Turbo 128K Token 4K Token
Claude 3.5 Sonnet 200K Token 8K Token
Claude Opus 4.6 200K Token 32K Token
DeepSeek V3 200K Token 8K Token

7.2 Token 与 API 成本

API 费用通常按 Token 计算:

1
2
3
4
5
6
7
8
成本 = 输入 Token × 输入单价 + 输出 Token × 输出单价

例:GPT-4o
- 输入:$2.5 / 1M Token
- 输出:$10 / 1M Token

对话消耗 1000 输入 Token + 500 输出 Token
成本 = 1000 × $2.5/1M + 500 × $10/1M = $0.0075

7.3 超出 Token 限制会怎样?

1
2
3
4
5
6
7
情况 1:输入超出上下文窗口
→ API 报错:context_length_exceeded
→ 解决:截断历史消息或分段处理

情况 2:输出超出最大输出限制
→ 输出被截断,不完整
→ 解决:设置合理的 max_tokens 或分段生成

八、实用技巧:如何节省 Token

8.1 语言选择

同样的内容,用英文表达 Token 数更少:

内容 中文 Token 英文 Token
请帮我写一篇关于人工智能的文章 约 15-20 约 8-10

技巧:如果不需要输出中文,可以用英文提问和回答。

8.2 Prompt 精简

1
2
3
4
5
❌ 冗长版本(约 50 Token):
"请帮我写一篇非常详细的、包含多个方面的、内容丰富的关于人工智能发展历史的文章,要涵盖从早期研究到现在的发展历程"

✅ 精简版本(约 15 Token):
"写一篇 AI 发展史,涵盖早期到现在的关键节点"

8.3 避免重复上下文

每次对话都会带上历史消息,Token 消耗会累积:

1
2
3
4
第 1 轮:100 Token 输入
第 2 轮:100 + 50(历史)= 150 Token 输入
第 3 轮:100 + 50 + 50 = 200 Token 输入
第 N 轮:线性增长...

技巧

  • 定期开启新对话,清除历史
  • 只保留必要的历史消息
  • 使用 System Prompt 存储固定指令

8.4 System Prompt 的妙用

System Prompt 只计算一次,不会重复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 每条消息都重复指令(浪费 Token)
messages = [
{"role": "user", "content": "请用简洁的语言回答:什么是 AI?"},
{"role": "assistant", "content": "AI 是..."},
{"role": "user", "content": "请用简洁的语言回答:什么是机器学习?"}, # 重复了指令
]

# 使用 System Prompt(高效)
messages = [
{"role": "system", "content": "请用简洁的语言回答所有问题"}, # 只算一次
{"role": "user", "content": "什么是 AI?"},
{"role": "assistant", "content": "AI 是..."},
{"role": "user", "content": "什么是机器学习?"},
]

8.5 图片的 Token 消耗

图片也会消耗 Token,计算方式较复杂:

1
2
3
4
图片 Token ≈ (图片宽度 ÷ 512) × (图片高度 ÷ 512) × 基础 Token 数

例:1024×1024 图片
Token ≈ 2 × 2 × 85 = 340 Token(大致估算)

技巧

  • 压缩图片尺寸
  • 只传必要的图片
  • 使用低分辨率版本

九、Token 与上下文窗口的关系

9.1 上下文窗口是什么?

上下文窗口是模型能”记住”的最大 Token 数:

1
2
3
4
5
模型上下文窗口 = 128K Token

意味着:
- 输入 + 历史对话 + 输出 的总 Token 数不能超过 128K
- 超过部分会被截断或报错

9.2 为什么需要长上下文?

场景 Token 需求
简单问答 100-500 Token
代码分析 5K-20K Token
长文档总结 50K-100K Token
复杂 Agent 任务 100K-1M Token

9.3 长上下文的代价

1
2
3
4
上下文越长:
- 计算成本越高(Transformer 是 O(n²) 复杂度)
- 响应速度越慢
- API 费用越高

十、总结

10.1 Token 的核心概念

概念 说明
Token 大模型处理文本的最小单位,类似”积木块”
分词器 把文本拆成 Token 的工具,不同模型分词方式不同
词汇表 所有已知 Token 的集合,通常 5-10 万个
向量 每个 Token 的”数学身份证”,用于计算

10.2 为什么选择 Token

原因 说明
计算机只认识数字 文字必须转换成数字才能计算
有限词汇表 5 万个 Token 可以穷举和编号
无限组合能力 有限积木块拼出无限造型
处理新词 用已知 Token 组合表示未见过的词

10.3 Token 计量要点

语言 估算公式
英文 Token ≈ 单词数 × 1.3
中文 Token ≈ 字数 × 1.5-2
代码 Token ≈ 字符数 ÷ 4

10.4 实用建议

  • 用英文对话可以节省 Token
  • 精简 Prompt,避免冗长描述
  • 使用 System Prompt 存储固定指令
  • 定期清理历史对话,避免累积
  • 压缩图片尺寸减少 Token 消耗

附录:Token 可视化体验

想直观感受 Token 是怎么”切”文本的?试试这些工具:

  1. OpenAI Tokenizerhttps://platform.openai.com/tokenizer

    • 输入中英文混排的内容
    • 看看模型是怎么”切”的
    • 每个 Token 用不同颜色高亮显示
  2. Anthropic Consolehttps://console.anthropic.com

    • Claude 模型的 Token 计数
    • 支持实时估算
  3. 本地体验

    1
    2
    3
    4
    5
    import tiktoken
    enc = tiktoken.encoding_for_model("gpt-4")
    text = "你的测试文本"
    print(enc.encode(text))
    print([enc.decode([t]) for t in enc.encode(text)])

参考资源