AI 智能体开发 · Day 3 详细学习手册
主题 :Token、Embedding、Context Window------从"文本怎么进入模型"到"语义搜索初体验"
总时长 :2.5-3 小时(可分两到三次完成)
难度 :中等偏实践(概念 + 大量代码实验)
产出 :4 个实验脚本 + 1 个迷你语义搜索引擎 + 结构化笔记
前置条件 :完成 Day 1(环境搭建、API 可调用)和 Day 2(理解 Transformer/Attention)
今天最重要:Token 和 Embedding 是后面所有内容的基础------RAG、向量数据库、Agent 记忆系统全靠它们
目录
- 学习目标与知识地图
- [Part A:Token 深度理解(30 分钟)](#Part A:Token 深度理解(30 分钟))
- [Part B:Token 实验室(30 分钟)](#Part B:Token 实验室(30 分钟))
- [Part C:Embedding 深度理解(30 分钟)](#Part C:Embedding 深度理解(30 分钟))
- [Part D:Embedding 实验 + 语义搜索初体验(30 分钟)](#Part D:Embedding 实验 + 语义搜索初体验(30 分钟))
- [Part E:Context Window 与上下文管理(25 分钟)](#Part E:Context Window 与上下文管理(25 分钟))
- [Part F:综合实验------迷你语义搜索引擎(20 分钟)](#Part F:综合实验——迷你语义搜索引擎(20 分钟))
- 今日笔记模板
- 验收清单
- [常见问题 FAQ](#常见问题 FAQ)
- 扩展资源
- 核心概念速查卡
- 明日预告
1. 学习目标与知识地图
你今天要达成的目标
概念层面(能用自己话讲清楚):
- Token 是什么,为什么不是"词"也不是"字"
- BPE 分词算法的基本原理(不背公式,理解思路)
- 为什么中文比英文贵 2-3 倍 token
- Embedding 是什么,为什么能表示"语义"
- 向量维度的含义,余弦相似度怎么算
- Context Window 是什么,超出限制会发生什么
- 三种上下文管理策略的区别(截断/摘要/RAG)
操作层面(亲手做过):
- 用 tiktoken 对中英文做 token 计数和对比
- 看到每个 token 的 ID 和原始文本片段
- 调用 Embedding API 获取向量
- 计算语义相似度并可视化
- 构建一个能回答"最相关文档是哪篇"的迷你语义搜索引擎
知识地图
Part A: Token 概念
├── Token 不是词也不是字
├── BPE 分词算法(直觉理解)
├── 中英文 token 消耗差异
└── Token 对费用/速度/上下文的影响
Part B: Token 实验
├── 中英文 token 对比实验
├── Token 可视化(看每个 token 长什么样)
├── Prompt 压缩实验
└── 不同模型分词器差异
Part C: Embedding 概念
├── 为什么需要把文本变成向量
├── Embedding 向量空间直觉
├── 维度代表什么
├── 余弦相似度原理
└── Embedding 模型 vs 生成模型
Part D: Embedding 实验
├── 语义相似度计算
├── 同义词/反义词/不相关词的相似度对比
├── 语义搜索初体验
└── Embedding 可视化
Part E: Context Window
├── 各模型 Context Window 对比
├── 超出限制的后果
├── 三种上下文管理策略
└── 成本估算实战
Part F: 综合实验
└── 迷你语义搜索引擎(Token + Embedding 综合应用)
今天和前两天的关系
Day 1: 你学会了调用 LLM API,拿到了回复
→ 但你不知道 "回复" 背后经历了什么
Day 2: 你理解了 Transformer 架构
→ 但 Transformer 的输入是什么?输出又怎么变成文字?
→ 答案:输入是 Token,Token 变成 Embedding,输出也是 Token
Day 3: 今天补上这条链路的最后两块拼图
→ 文本 → Token(怎么切的)
→ Token → Embedding(怎么变成向量的)
→ 加上 Context Window(模型能"记住"多少东西)
这三件事是后面所有高级主题的基础:
→ RAG = 用 Embedding 找相关文档 + 放进 Context Window
→ Agent 记忆 = 用 Embedding 存储和检索历史对话
→ 成本优化 = 减少 Token + 管好 Context Window
2. Part A:Token 深度理解(30 分钟)
这部分纯概念。理解 Token 的本质比会算 Token 数更重要------因为它影响你写 Prompt 的方式、选模型的决策、和控制成本的能力。
2.1 Token 到底是什么(10 分钟)
一句话定义:Token 是 LLM 处理文本的最小单位,介于"字"和"词"之间。
人类看到的: "Hello, world!"
LLM 看到的: ["Hello", ",", " world", "!"]
↓ 转成数字
[15496, 11, 995, 0]
↓ 转成向量
[[0.1, -0.3, ...], ...]
这就是 Day 2 讲的 Transformer 的输入
关键认知:Token ≠ 词 ≠ 字
英文例子:
"unhappiness"
人类看: 1 个词
Token: ["un", "happiness"] → 2 个 token
或者: ["un", "h", "appiness"] → 3 个 token(取决于分词器)
中文例子:
"你好"
人类看: 2 个字 / 1 个词
Token: ["你", "好"] → 可能 2 个 token
或者: ["你好"] → 可能 1 个 token(如果分词器认识这个词)
或者: ["ä½ ", "好"] → 可能 3-4 个 token(如果被拆成 UTF-8 字节)
为什么 Token 不是"词":
原因 1: 词表大小有限
英语有数十万词,加上所有语言、代码、专有名词,不可能全部收录
→ 用"子词"(subword)来组合表示所有可能的文本
原因 2: 平衡效率和覆盖
如果用"字"做单位: 词表小(几万),但序列太长,模型要处理几万个 token
如果用"词"做单位: 词表太大(百万级),很多词没见过(OOV 问题)
→ Token(子词)是最佳折中: 常见词整体收录,罕见词拆成子词
原因 3: 处理未见过的文本
"ChatGPT" 可能不在词表中
→ 拆成 ["Chat", "G", "PT"] 或 ["Chat", "GP", "T"]
→ 模型仍能处理,不会"不认识"
2.2 BPE 分词算法直觉理解(10 分钟)
不需要背公式,理解核心思路即可。
BPE(Byte Pair Encoding)的核心思想:从字符开始,不断合并最常见的相邻对,直到达到目标词表大小。
训练过程(简化版):
第 0 步: 所有文本拆成单个字符
"low" → ["l", "o", "w"]
"lower" → ["l", "o", "w", "e", "r"]
"newest" → ["n", "e", "w", "e", "s", "t"]
"widest" → ["w", "i", "d", "e", "s", "t"]
第 1 步: 统计最常见的相邻对
"e"+"s" 出现 2 次(newest, widest)→ 合并
"l"+"o" 出现 2 次(low, lower)→ 合并
更新:
"low" → ["lo", "w"]
"lower" → ["lo", "w", "e", "r"]
"newest" → ["n", "es", "t"] ← "es" 成了新 token
"widest" → ["w", "i", "d", "es", "t"]
第 2 步: 继续找最频繁的相邻对
"lo"+"w" 出现 2 次 → 合并成 "low"
更新:
"low" → ["low"] ← 整体成为一个 token
"lower" → ["low", "e", "r"]
"newest" → ["n", "es", "t"]
"widest" → ["w", "i", "d", "es", "t"]
... 重复几万次 ...
最终结果:
常见词: 整体收录为一个 token ("the", "hello", "function")
罕见词: 拆成子词 ("tokenization" → ["token", "ization"])
极罕见: 拆成单字符甚至字节
为什么理解 BPE 对你有用:
1. 写 Prompt 时能预判 token 消耗
→ 常见英文词通常 1 token("hello", "function", "return")
→ 罕见词/专有名词会被拆分("ChatGPT" → 2-3 tokens)
→ 中文字符经常 1-2 token/字
2. 调试时能理解模型行为
→ 模型"看到"的不是你写的文字,是 token
→ "don't" 和 "do not" 对模型来说是不同的 token 序列
→ 有时换一种写法,模型表现不同,可能是 token 切分变了
3. 优化成本
→ 同一个意思,英文比中文省 token
→ 简洁表达比啰嗦省 token
→ 重复内容可以引用而非复制
2.3 中英文 Token 消耗差异(5 分钟)
这是一个直接影响成本的关键知识点。
同一段内容的中英文版本:
中文: "人工智能正在改变世界"
字符数: 10
Token: ~10-15 个(取决于分词器)
英文: "Artificial intelligence is changing the world"
字符数: 50
Token: ~8-10 个
中文: "我喜欢用 Python 写代码"
Token: ~12-15 个
英文: "I like coding in Python"
Token: ~6-7 个
为什么中文更贵:
原因 1: 中文不在 BPE 训练语料的主力位置
→ 大多数分词器(tiktoken/GPT 系列)以英文为主训练
→ 英文常见词整体收录(1 token),中文常被拆分
原因 2: UTF-8 编码
→ 一个中文字符占 3 个字节
→ 如果分词器不认识这个词,会拆成字节级 token
→ 1 个汉字可能变成 2-3 个 token
实际影响:
同样内容的文档,中文版 token 数是英文版的 1.5-3 倍
→ API 费用贵 1.5-3 倍
→ Context Window 消耗更快
→ 生成速度更慢(更多 token 要生成)
2.4 Token 对你的三重影响(5 分钟)
影响 1: 费用
DeepSeek: ~¥1/百万输入 token, ~¥2/百万输出 token
→ 一次对话用 1000 token ≈ ¥0.001-0.002
→ 一个 Agent 每天调用 1000 次 ≈ ¥1-2/天
→ 一个月 ≈ ¥30-60
GPT-4o: ~¥18/百万输入, ~¥72/百万输出
→ 同样用量贵 20-40 倍
→ 一个月 ≈ ¥600-2400
→ 选模型和写 Prompt 时必须考虑 token 成本
影响 2: 速度
模型生成速度 = tokens/秒(通常 30-100 tok/s)
→ 100 token 的回复 ≈ 1-3 秒
→ 1000 token 的回复 ≈ 10-30 秒
→ 用户等待时间直接和输出 token 数挂钩
影响 3: 上下文
Context Window = 128K(GPT-4o)
→ 对话历史 + 系统 Prompt + 用户输入 + 模型输出 ≤ 128K
→ 对话越长,历史消息越多,可用空间越小
→ 超出就要截断/摘要(Part E 会讲)
自测:
- "ChatGPT" 大概是几个 token?为什么?
- 为什么中文 Prompt 比英文贵?
- 如果你的 Agent 每天调用 500 次 GPT-4o,每次约 2000 token,月成本大概多少?
3. Part B:Token 实验室(30 分钟)
动手实验时间。用代码把抽象概念变直观。
3.1 环境确认
bash
# 确认 tiktoken 已安装(Day 1 应该装过了)
pip install tiktoken
# 如果报错,换源重装
pip install tiktoken -i https://pypi.tuna.tsinghua.edu.cn/simple
3.2 实验 1:中英文 Token 对比
创建 code/token_experiment.py:
python
"""
Day 3 实验 1: 中英文 Token 对比
理解不同语言的 Token 消耗差异
"""
import tiktoken
# GPT-4o / GPT-3.5 使用的分词器
# 注意:不同模型分词器不同
# DeepSeek 有自己的分词器,但 tiktoken 可以做近似估计
enc = tiktoken.encoding_for_model("gpt-4o")
print("=" * 65)
print("实验 1: 中英文 Token 对比")
print("=" * 65)
texts = [
("Hello, world!", "英文短句"),
("你好,世界!", "中文短句"),
("I am learning AI agent development.", "英文长句"),
("我正在学习 AI 智能体开发。", "中文长句"),
("Artificial intelligence is transforming the world.", "英文更长"),
("人工智能正在改变世界。", "中文更长"),
("def hello():\n print('Hello')", "Python 代码"),
("定义函数:\n打印你好", "中文描述代码"),
]
print(f"\n{'文本':<45} {'字符':>4} {'Token':>5} {'比值':>6}")
print("-" * 65)
for text, desc in texts:
tokens = enc.encode(text)
ratio = len(tokens) / len(text) if len(text) > 0 else 0
print(f" {text:<43} {len(text):>4} {len(tokens):>5} {ratio:>5.2f}")
print(f"\n关键发现:")
print(f" 1. 英文 token/字符比通常 < 0.3(4 字符约 1 token)")
print(f" 2. 中文 token/字符比通常 > 1.0(1 字符约 1-2 token)")
print(f" 3. 代码的 token 效率介于英文和中文之间")
运行:
bash
python code/token_experiment.py
预期输出:
=================================================================
实验 1: 中英文 Token 对比
=================================================================
文本 字符 Token 比值
-----------------------------------------------------------------
Hello, world! 13 4 0.31
你好,世界! 6 6 1.00
I am learning AI agent development. 35 10 0.29
我正在学习 AI 智能体开发。 14 14 1.00
Artificial intelligence is transforming the w 50 12 0.24
人工智能正在改变世界。 10 10 1.00
def hello(): 13 6 0.46
print('Hello') 19 6 0.32
定义函数: 5 6 1.20
打印你好 4 4 1.00
注意:实际数字会略有不同(tiktoken 版本差异),但趋势一致:中文 token/字符比远高于英文。
3.3 实验 2:Token 可视化------看每个 Token 长什么样
在 token_experiment.py 末尾添加:
python
print("\n" + "=" * 65)
print("实验 2: Token 到底长什么样")
print("=" * 65)
text = "Hello, 世界!I love 编程"
tokens = enc.encode(text)
print(f"\n原文: {text}")
print(f"Token 数: {len(tokens)}")
print(f"\n逐个 Token 展示:")
print(f"{'序号':>4} {'Token ID':>10} {'内容':>10} {'说明':>20}")
print("-" * 50)
for i, token_id in enumerate(tokens):
token_text = enc.decode([token_id])
# 把不可见字符显示出来
display = token_text.replace('\n', '\\n').replace(' ', '·')
print(f" [{i}] {token_id:>8} '{display}'")
print(f"\n观察:")
print(f" - 英文常见词 'Hello' = 1 个 token")
print(f" - 标点 ',' '!' 各自是独立 token")
print(f" - 中文字符可能被拆成多个 token(看分词器)")
print(f" - 空格通常和后面的词合在一起")
预期输出:
实验 2: Token 到底长什么样
原文: Hello, 世界!I love 编程
Token 数: 14
逐个 Token 展示:
序号 Token ID 内容 说明
--------------------------------------------------
[0] 15496 'Hello' 英文常见词,整体1个token
[1] 11 ',' 标点,独立token
[2] 220 '·' 空格
[3] 105721 '世' 中文字符
[4] 104865 '界' 中文字符
[5] 6447 '!' 中文标点
...
观察:
- 英文常见词 'Hello' = 1 个 token
- 标点 ',' '!' 各自是独立 token
- 中文字符可能被拆成多个 token(看分词器)
- 空格通常和后面的词合在一起
3.4 实验 3:Prompt 压缩------同义不同写法的 Token 差异
继续添加:
python
print("\n" + "=" * 65)
print("实验 3: Prompt 压缩------同一意思,不同写法")
print("=" * 65)
variants = [
("啰嗦版",
"请你帮我写一个函数,这个函数的功能是实现两个数字的相加,"
"然后返回它们的和。请使用 Python 语言来实现这个函数。"),
("正常版",
"写一个 Python 加法函数,返回两数之和。"),
("极简版",
"def add(a, b): return a + b"),
("英文版",
"Write a Python function that adds two numbers and returns the sum."),
]
print(f"\n{'版本':<8} {'Token':>5} {'文本'}")
print("-" * 75)
results = []
for label, text in variants:
tokens = enc.encode(text)
results.append((label, len(tokens), text))
print(f"{label:<8} {len(tokens):>5} {text[:50]}...")
# 计算节省比例
verbose_tokens = results[0][1]
print(f"\nToken 节省分析(以啰嗦版为基准):")
for label, count, _ in results[1:]:
saved = (1 - count / verbose_tokens) * 100
print(f" {label}: {count} token(节省 {saved:.0f}%)")
print(f"\n结论:")
print(f" 1. 精简表达可节省 50-80% 的 token")
print(f" 2. 英文版通常比中文版 token 更少")
print(f" 3. 在大量调用场景下,Prompt 优化 = 真金白银")
预期输出:
实验 3: Prompt 压缩------同一意思,不同写法
版本 Token 文本
---------------------------------------------------------------------------
啰嗦版 65 请你帮我写一个函数,这个函数的功能是实现两个数字的相加,然后返回它们...
正常版 18 写一个 Python 加法函数,返回两数之和。
极简版 12 def add(a, b): return a + b
英文版 14 Write a Python function that adds two numbers and returns the sum.
Token 节省分析(以啰嗦版为基准):
正常版: 18 token(节省 72%)
极简版: 12 token(节省 82%)
英文版: 14 token(节省 78%)
3.5 实验 4:不同模型分词器差异
继续添加:
python
print("\n" + "=" * 65)
print("实验 4: 不同模型的分词器差异")
print("=" * 65)
# 对比不同编码
encodings = {
"gpt-4o (cl100k_base)": tiktoken.encoding_for_model("gpt-4o"),
"gpt-3.5 (cl100k_base)": tiktoken.encoding_for_model("gpt-3.5-turbo"),
}
# 如果有 gpt-2 的编码器,也对比一下
try:
encodings["gpt-2 (r50k_base)"] = tiktoken.encoding_for_model("gpt-2")
except:
pass
test_texts = [
"Hello, world!",
"你好,世界!",
"def add(a, b): return a + b",
"ChatGPT is amazing",
]
for text in test_texts:
print(f"\n 原文: '{text}'")
for name, encoder in encodings.items():
count = len(encoder.encode(text))
print(f" {name}: {count} tokens")
print(f"\n结论:")
print(f" 1. 不同模型的分词器可能不同 → token 数有差异")
print(f" 2. 新模型的分词器通常对多语言更友好")
print(f" 3. 精确计费要以目标模型官方的分词器为准")
print(f" 4. tiktoken 是近似工具,DeepSeek/Qwen 有自己的分词器")
3.6 常见报错处理
| 报错 | 原因 | 解决 |
|---|---|---|
ModuleNotFoundError: No module named 'tiktoken' |
没安装 | pip install tiktoken |
tiktoken.Encoding.for_model 报错 |
模型名写错 | 用 tiktoken.get_encoding("cl100k_base") |
AttributeError: module 'tiktoken' has no attribute |
版本太旧 | pip install --upgrade tiktoken |
| 中文显示乱码 | 终端编码问题 | Windows 用 chcp 65001 切 UTF-8 |
| 数字和预期不符 | tiktoken 版本不同 | 正常现象,不同版本词表可能微调 |
3.7 实验后回答这些问题
- "你好,世界!"用多少 token?和 "Hello, world!" 差几倍?
- 同一个意思的 Prompt,啰嗦版和极简版差多少 token?如果每天调用 1000 次,一个月省多少钱?
- 为什么 "ChatGPT" 可能被拆成多个 token?
- 代码的 token 效率和自然语言比怎么样?
4. Part C:Embedding 深度理解(30 分钟)
这部分是今天最关键的概念------Embedding 是 RAG、向量搜索、Agent 记忆系统的基石。
4.1 为什么需要把文本变成向量(8 分钟)
问题:计算机不"理解"文字,只懂数字。
人类看文本:
"我喜欢吃苹果" → 理解了含义
"我爱吃水果" → 知道和上一句意思接近
"今天天气真好" → 知道和前两句无关
计算机看文本:
"我喜欢吃苹果" → [我, 喜, 欢, 吃, 苹, 果] → 一堆字符编码
"我爱吃水果" → [我, 爱, 吃, 水, 果] → 字符不一样,怎么判断意思接近?
"今天天气真好" → [今, 天, 天, 气, 真, 好] → 完全不同的字符
问题: 字符层面的比较无法反映"语义相似度"
→ "我喜欢吃苹果" 和 "I love eating apples" 字符完全不同,但意思一样
解决方案:把文本映射到高维向量空间,让"意思相近"变成"向量相近"。
Embedding 做的事:
"我喜欢吃苹果" → [0.12, -0.34, 0.56, ..., 0.78] (1536 维)
"我爱吃水果" → [0.15, -0.31, 0.52, ..., 0.75] (很接近 ↑)
"今天天气真好" → [-0.20, 0.45, -0.12, ..., 0.03] (差很远 ↓)
"I love apples" → [0.13, -0.32, 0.55, ..., 0.77] (和第一句很接近 ↑)
向量之间的距离 = 语义之间的距离
→ 距离近 = 意思相近
→ 距离远 = 意思无关
4.2 向量空间直觉理解(10 分钟)
不需要线性代数基础,用空间想象来理解。
想象一个二维空间(实际是 1536 维,但原理一样):
↑ "悲伤"
|
| · "难过"
|
|
·"愤怒"| · "开心"
|
| · "满意"
|
--------+----------------→ "快乐"
|
| · "兴奋"
|
观察:
- "开心"和"兴奋"距离近 → 语义相似
- "开心"和"悲伤"距离远 → 语义相反
- "难过"和"悲伤"很近 → 同义词
- "愤怒"在另一个区域 → 相关但不同
实际的 Embedding 空间是 1536 维(或更高):
- 每一维代表某种"语义特征"(但不是人类能理解的明确特征)
- 模型在训练中自动学到了这些维度
- 我们不需要知道每一维代表什么,只需要知道:
→ 意思相近的文本,向量也相近
→ 这就够了,可以用来做搜索、推荐、分类
Embedding 向量的特点:
1. 固定维度
text-embedding-3-small: 1536 维
text-embedding-3-large: 3072 维
DeepSeek Embedding: 1024 维
→ 不管输入多长(一个词还是一篇文章),输出都是固定维度
2. 浮点数
每一维是 -1 到 1 之间的浮点数
→ 不是 0/1 的 one-hot 编码,是连续的"语义坐标"
3. 不是随机的
→ 是模型在数十亿文本上学到的
→ 相似语义的文本经过训练后自然聚集到相近的位置
4. 可计算的
→ 两个向量可以算距离(余弦相似度、欧氏距离)
→ 这就是"语义搜索"的数学基础
4.3 余弦相似度------怎么算"近不近"(7 分钟)
这是最常用的相似度计算方法,理解直觉就够了。
余弦相似度(Cosine Similarity):
两个向量之间的"夹角的余弦值"
→ 夹角越小(方向越一致),相似度越高
取值范围: -1 到 1
1 = 方向完全一致(语义高度相似)
0 = 垂直(语义无关)
-1 = 方向相反(语义相反,实践中很少出现)
计算公式(不用背,理解直觉就行):
cos(A, B) = (A · B) / (|A| × |B|)
= 两个向量的点积 / 各自长度的乘积
直觉理解:
想象两个箭头从原点出发
→ 如果指向同一个方向: cos = 1(完全相似)
→ 如果成 90 度: cos = 0(无关)
→ 如果指向反方向: cos = -1(相反)
为什么不直接用欧氏距离?
→ 余弦相似度只看"方向",不看"长度"
→ "苹果"和"苹果手机"虽然长度不同,但方向接近
→ 对文本语义比较更合理
numpy 实现(后面代码实验会用):
python
import numpy as np
def cosine_similarity(a, b):
"""计算两个向量的余弦相似度"""
# np.dot: 点积(对应位置相乘再求和)
# np.linalg.norm: 向量的长度(模)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 示例
a = np.array([1, 2, 3])
b = np.array([2, 4, 6]) # 和 a 同方向(b = 2a)
sim = cosine_similarity(a, b)
print(f"相似度: {sim:.4f}") # 1.0000(完全相似)
c = np.array([-1, -2, -3]) # 和 a 反方向
sim2 = cosine_similarity(a, c)
print(f"相似度: {sim2:.4f}") # -1.0000(完全相反)
4.4 Embedding 模型 vs 生成模型(5 分钟)
你可能会困惑: Embedding 模型和 GPT/DeepSeek 是什么关系?
生成模型(GPT, DeepSeek, Claude):
输入: Token 序列
输出: 下一个 Token(概率分布)
用途: 对话、写作、代码生成
Embedding 模型(text-embedding-3, BGE, E5):
输入: 一段文本
输出: 一个固定维度的向量
用途: 语义搜索、文本分类、聚类、推荐
特点:
- 不生成文字,只输出向量
- 速度快(比生成快 10-100 倍)
- 便宜(通常是生成模型的 1/10-1/100 价格)
- 不可逆(不能从向量还原文本)
它们的关系:
- 底层都是 Transformer 架构
- 但训练目标不同:
→ 生成模型: 学会"预测下一个词"
→ Embedding 模型: 学会"让相似文本的向量接近"
实际项目中:
→ 用 Embedding 模型做"检索"(找相关文档)
→ 用生成模型做"生成"(基于文档回答问题)
→ 这就是 RAG 的基本架构(第 4 个月深入)
自测:
- 为什么不能直接用字符比较来判断两句话意思是否相近?
- Embedding 向量的维度是 1536,这个数字代表什么?
- 余弦相似度为 0.85 和 0.30 分别代表什么?
- Embedding 模型和 GPT 的核心区别是什么?
5. Part D:Embedding 实验 + 语义搜索初体验(30 分钟)
这是今天最 exciting 的部分------你将亲手让计算机"理解"语义。
5.1 准备:API 配置
bash
# 确认依赖
pip install numpy openai python-dotenv
重要提示:DeepSeek 目前不支持 Embedding API。你需要以下任一方案:
| 方案 | 平台 | 模型 | 价格 | 注册地址 |
|---|---|---|---|---|
| A | OpenAI | text-embedding-3-small | ~¥0.1/百万token | platform.openai.com |
| B | 阿里百炼 | text-embedding-v3 | 免费额度 | bailian.console.aliyun.com |
| C | 智谱 | embedding-3 | 免费额度 | open.bigmodel.cn |
如果用阿里百炼(推荐,国内直连 + 免费额度):
python
# .env 中添加
DASHSCOPE_API_KEY=你的key
python
# Python 配置
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
EMBEDDING_MODEL = "text-embedding-v3"
如果用 OpenAI:
python
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
EMBEDDING_MODEL = "text-embedding-3-small"
如果用智谱:
python
client = OpenAI(
api_key=os.getenv("ZHIPU_API_KEY"),
base_url="https://open.bigmodel.cn/api/paas/v4"
)
EMBEDDING_MODEL = "embedding-3"
5.2 实验 1:语义相似度计算
创建 code/embedding_experiment.py:
python
"""
Day 3 实验: Embedding 与语义相似度
让计算机理解文本的"意思"有多接近
"""
import os
import numpy as np
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
# ============ 选择你的 Embedding 服务(取消注释一个) ============
# 方案 A: OpenAI
# client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# EMBEDDING_MODEL = "text-embedding-3-small"
# 方案 B: 阿里百炼(推荐,国内直连)
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
EMBEDDING_MODEL = "text-embedding-v3"
# 方案 C: 智谱
# client = OpenAI(
# api_key=os.getenv("ZHIPU_API_KEY"),
# base_url="https://open.bigmodel.cn/api/paas/v4"
# )
# EMBEDDING_MODEL = "embedding-3"
def get_embedding(text):
"""获取文本的 Embedding 向量"""
response = client.embeddings.create(
model=EMBEDDING_MODEL,
input=text
)
return response.data[0].embedding
def cosine_similarity(a, b):
"""计算两个向量的余弦相似度(-1 到 1,越接近 1 越相似)"""
a = np.array(a)
b = np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# ============ 实验 1: 语义相似度 ============
print("=" * 65)
print("实验 1: 语义相似度实验")
print("=" * 65)
pairs = [
("我喜欢吃苹果", "我爱吃水果", "近义句"),
("我喜欢吃苹果", "苹果是一家科技公司", "同词不同义"),
("我喜欢吃苹果", "今天天气真好", "完全不相关"),
("AI 改变世界", "人工智能改变世界", "中英文混用近义"),
("Python 是编程语言", "Java 是编程语言", "同类概念"),
("Python 是编程语言", "我喜欢吃面条", "不相关"),
("这个电影太好看了", "这部电影非常精彩", "近义表达"),
("这个电影太好看了", "这部电影太烂了", "反义表达"),
("我想学编程", "如何开始学习写代码", "意图相似"),
("我想学编程", "今天去超市买菜", "无关"),
]
print("\n正在计算 Embedding(需要调用 API)...\n")
for text1, text2, desc in pairs:
emb1 = get_embedding(text1)
emb2 = get_embedding(text2)
sim = cosine_similarity(emb1, emb2)
# 可视化条
bar_len = max(0, int(sim * 30))
bar = "█" * bar_len
print(f"[{desc}]")
print(f" A: {text1}")
print(f" B: {text2}")
print(f" 相似度: {sim:.4f} {bar}")
print()
print("分析:")
print(" 1. 近义句相似度通常 > 0.80")
print(" 2. 同词不同义(苹果)相似度中等 0.50-0.70")
print(" 3. 完全不相关相似度 < 0.40")
print(" 4. 反义表达有时相似度也不低(因为话题相同)")
print(" 5. 这就是 RAG 的基础: 用相似度找到最相关的知识")
运行:
bash
python code/embedding_experiment.py
预期输出:
[近义句]
A: 我喜欢吃苹果
B: 我爱吃水果
相似度: 0.8732 ██████████████████████████
[同词不同义]
A: 我喜欢吃苹果
B: 苹果是一家科技公司
相似度: 0.6521 ███████████████████
[完全不相关]
A: 我喜欢吃苹果
B: 今天天气真好
相似度: 0.3214 █████████
[反义表达]
A: 这个电影太好看了
B: 这部电影太烂了
相似度: 0.7843 ███████████████████████
← 注意: 反义句相似度可能也不低!因为话题相同
这说明 Embedding 捕捉的是"话题相似"而非"情感相似"
...
重要发现:反义句相似度可能也不低!因为 Embedding 主要捕捉"话题/主题"层面的相似性,而非情感。这在实际项目中要注意------如果你需要区分情感,需要额外的处理。
5.3 实验 2:语义搜索初体验
继续在 embedding_experiment.py 末尾添加:
python
# ============ 实验 2: 语义搜索 ============
print("\n" + "=" * 65)
print("实验 2: 语义搜索------找到最相关的文档")
print("=" * 65)
# 模拟一个小知识库
documents = [
"Python 是一种广泛使用的高级编程语言,以简洁易读著称。",
"JavaScript 主要用于网页前端开发,也可以用 Node.js 做后端。",
"机器学习是人工智能的一个分支,通过数据训练模型来做出预测。",
"Docker 是一种容器化技术,可以将应用及其依赖打包在一起。",
"Git 是分布式版本控制系统,用于跟踪代码变更和协作开发。",
"React 是 Facebook 开发的前端框架,用于构建用户界面。",
"Kubernetes 是容器编排工具,用于管理大规模容器化应用。",
"SQL 是结构化查询语言,用于管理和操作关系型数据库。",
]
# 预计算所有文档的 Embedding(实际项目会存到向量数据库)
print("\n正在为知识库生成 Embedding...")
doc_embeddings = []
for doc in documents:
emb = get_embedding(doc)
doc_embeddings.append(emb)
print(f"已完成 {len(documents)} 篇文档的 Embedding\n")
# 搜索函数
def semantic_search(query, docs, doc_embs, top_k=3):
"""语义搜索:找到和 query 最相关的 top_k 篇文档"""
query_emb = get_embedding(query)
# 计算 query 和每篇文档的相似度
similarities = []
for i, doc_emb in enumerate(doc_embs):
sim = cosine_similarity(query_emb, doc_emb)
similarities.append((i, sim, docs[i]))
# 按相似度排序
similarities.sort(key=lambda x: x[1], reverse=True)
return similarities[:top_k]
# 测试多个查询
queries = [
"怎么学网页开发?",
"怎么部署应用到服务器?",
"什么是人工智能?",
"怎么管理代码版本?",
]
for query in queries:
print(f"\n查询: '{query}'")
print("-" * 55)
results = semantic_search(query, documents, doc_embeddings, top_k=3)
for rank, (idx, sim, doc) in enumerate(results, 1):
bar = "█" * int(sim * 30)
print(f" #{rank} [{sim:.4f}] {bar}")
print(f" {doc[:40]}...")
print(f"\n观察:")
print(f" 1. '怎么学网页开发' → JavaScript/React 排前面(语义匹配)")
print(f" 2. '怎么部署应用' → Docker/Kubernetes 排前面")
print(f" 3. 搜索没有用关键词匹配,纯靠语义理解")
print(f" 4. 这就是 RAG 的核心: 用 Embedding 找相关文档")
预期输出:
查询: '怎么学网页开发?'
-------------------------------------------------------
#1 [0.6832] ███████████████████
JavaScript 主要用于网页前端开发...
#2 [0.6124] █████████████████
React 是 Facebook 开发的前端框架...
#3 [0.4521] █████████████
Python 是一种广泛使用的高级编程语言...
查询: '怎么部署应用到服务器?'
-------------------------------------------------------
#1 [0.7234] █████████████████████
Docker 是一种容器化技术...
#2 [0.6891] ███████████████████
Kubernetes 是容器编排工具...
#3 [0.3856] ███████████
Git 是分布式版本控制系统...
观察:
1. '怎么学网页开发' → JavaScript/React 排前面(语义匹配)
2. '怎么部署应用' → Docker/Kubernetes 排前面
3. 搜索没有用关键词匹配,纯靠语义理解
4. 这就是 RAG 的核心: 用 Embedding 找相关文档
5.4 Embedding 响应结构理解
添加以下代码理解 API 返回的数据结构:
python
# ============ 实验 3: 理解 Embedding 响应结构 ============
print("\n" + "=" * 65)
print("实验 3: Embedding API 响应结构")
print("=" * 65)
response = client.embeddings.create(
model=EMBEDDING_MODEL,
input="Hello, world!"
)
print(f"\n响应结构:")
print(f" response.model = {response.model}")
print(f" response.usage.prompt_tokens = {response.usage.prompt_tokens}")
print(f" response.usage.total_tokens = {response.usage.total_tokens}")
print(f" len(response.data) = {len(response.data)}")
print(f" response.data[0].index = {response.data[0].index}")
emb = response.data[0].embedding
print(f"\n Embedding 向量:")
print(f" 类型: {type(emb)}")
print(f" 维度: {len(emb)}")
print(f" 前 5 个值: {emb[:5]}")
print(f" 最小值: {min(emb):.6f}")
print(f" 最大值: {max(emb):.6f}")
print(f" 平均值: {sum(emb)/len(emb):.6f}")
# 向量长度(模)
import numpy as np
norm = np.linalg.norm(emb)
print(f" 向量长度(模): {norm:.6f}")
print(f"\n 注意: Embedding 向量通常会被归一化(模 ≈ 1)")
print(f" 这让余弦相似度计算更高效")
5.5 常见报错处理
| 报错 | 原因 | 解决 |
|---|---|---|
AuthenticationError |
API Key 没填对 | 检查 .env 文件 |
BadRequestError: model not found |
模型名写错 | 确认平台的 Embedding 模型名 |
RateLimitError |
免费额度用完 | 换平台或充值 |
APIConnectionError |
网络问题 | 阿里/智谱检查网络;OpenAI 需梯子 |
IndexError: list index out of range |
response.data 为空 | 检查输入是否为空字符串 |
| 相似度全是负数 | 向量未归一化 | 检查 cosine_similarity 实现 |
| 相似度全是 1.0 | 比较了同一个向量 | 检查是否传入了相同的 embedding |
6. Part E:Context Window 与上下文管理(25 分钟)
这部分决定你的 Agent 能"记住"多少东西,直接影响成本和效果。
6.1 Context Window 是什么(5 分钟)
Context Window = 模型一次能处理的最多 Token 数(输入 + 输出)
它是模型的"工作记忆"
→ 所有内容必须在窗口内
→ 超出就要处理(截断/摘要/RAG)
和 Day 2 讲的 Attention 的关系:
→ Attention 可以"看到"窗口内所有 token
→ 窗口越大,Attention 能处理的范围越大
→ 但计算量也越大(Attention 是 O(n²) 复杂度)
一次 API 调用的 Token 构成:
┌──────────────── Context Window (如 128K) ─────────────────┐
│ │
│ System Prompt 对话历史 用户输入 模型输出 │
│ (500 tok) (5000 tok) (200 tok) (1000 tok) │
│ │
│ 总计: 6700 tokens ≤ 128K ✓ │
└───────────────────────────────────────────────────────────┘
→ 对话历史越多,可用空间越小
→ 最终历史 + 新输入会撑满窗口,需要管理
6.2 各模型 Context Window 对比(5 分钟)
| 模型 | Context Window | 约等于中文 | 约等于英文 | 备注 |
|---|---|---|---|---|
| GPT-4o | 128K | ~6 万字 | ~10 万词 | 主流标配 |
| GPT-4o-mini | 128K | ~6 万字 | ~10 万词 | 便宜版 |
| Claude 3.5 Sonnet | 200K | ~10 万字 | ~15 万词 | 最长之一 |
| Claude 3 Opus | 200K | ~10 万字 | ~15 万词 | |
| Gemini 1.5 Pro | 1M | ~50 万字 | ~75 万词 | 超长上下文 |
| Gemini 1.5 Flash | 1M | ~50 万字 | ~75 万词 | 便宜版 |
| DeepSeek-V3 | 64K | ~3 万字 | ~5 万词 | 性价比高 |
| Qwen-Plus | 128K | ~6 万字 | ~10 万词 | |
| GLM-4 | 128K | ~6 万字 | ~10 万词 |
直观对比:
DeepSeek 64K ████████████████
GPT-4o 128K ████████████████████████████████
Claude 200K ████████████████████████████████████████████████
Gemini 1M ████████████████████████████████████████████████████
████████████████████████████████████████████████████
████████████████████████████████████████████████████
████████████████████████████████████████████████████
████████████████████████████████████████████████████
→ Gemini 的 1M 是 16 倍于 DeepSeek
→ 但更大不等于更好(后面会分析)
6.3 超出 Context Window 会怎样(5 分钟)
情况 1: API 直接报错(大多数情况)
Error: This model's maximum context length is 128000 tokens.
However, your messages resulted in 135000 tokens.
→ 你需要在代码中做截断或摘要
情况 2: 静默截断(少数平台)
→ 平台自动丢掉最早的消息
→ 你可能不知道丢了什么
→ 对话可能"失忆"
情况 3: 长文本处理失败
→ 输入太长导致模型"走神"(注意力分散)
→ 回答质量下降
→ 关键信息被忽略("中间遗忘"现象)
"中间遗忘"现象(Lost in the Middle):
研究表明,LLM 对长文本的处理有这样的规律:
文档位置: 开头 ─────────────── 中间 ─────────────── 结尾
注意力: ████████░░░░░░░░░░░░░░░░░░░░░░████████
↑ 高 低 ↑ 高
→ 模型对开头和结尾的信息记得清楚
→ 中间的信息容易被忽略
→ 这就是为什么:
- System Prompt 放在最前面
- 重要的用户输入放在最后
- 长文档的中间内容要特别注意
实际影响:
→ 给模型 10 万字的资料让它找答案
→ 如果答案在第 5 万字处,可能被忽略
→ 这就是 RAG 的价值: 只给最相关的片段,不放全文
6.4 三种上下文管理策略(10 分钟)
策略 1: 截断(Truncation)------最简单
保留最近的 N 条消息,丢弃最早的
对话: [msg1, msg2, msg3, msg4, msg5, msg6, msg7]
保留最近 4 条: [msg4, msg5, msg6, msg7]
优点: 简单、快速、零成本
缺点: 丢失早期信息(用户可能问"之前说的那个方案呢?")
适用: 闲聊、简单问答
代码示例:
messages = messages[-10:] # 只保留最近 10 条
策略 2: 摘要(Summarization)------平衡
定期让模型把旧对话总结成摘要,用摘要替代原始消息
原始: [msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8]
第 4 轮后:
summary = LLM("请总结以下对话: msg1-msg4")
更新后: [summary, msg5, msg6, msg7, msg8]
优点: 保留了关键信息,大幅减少 token
缺点: 摘要本身有 API 成本;可能丢失细节
适用: 长对话、客服 Agent
→ 第 3 周的命令行聊天工具会实现这个
策略 3: RAG(检索增强生成)------最强
把历史对话存到向量数据库,每次只检索最相关的片段
存储阶段:
→ 每条消息 → Embedding → 存入向量数据库
检索阶段:
→ 用户问 "之前说的 Python 方案呢?"
→ 用 Embedding 找到最相关的历史消息
→ 只把相关消息放入 Context Window
优点: 理论上可以记住无限历史;精准检索
缺点: 实现复杂;需要向量数据库;检索不一定准
适用: 长期记忆、知识库问答
→ 第 4 个月深入
三种策略对比:
| 策略 | 实现难度 | Token 节省 | 信息保留 | 适用场景 |
|---|---|---|---|---|
| 截断 | 极低 | 高 | 差(丢旧消息) | 简单聊天 |
| 摘要 | 中 | 中高 | 中(有损压缩) | 客服/长对话 |
| RAG | 高 | 最高 | 好(精准检索) | 知识库/长期记忆 |
6.5 成本估算实战
python
# 在脑海中(或代码中)这样估算成本
# 场景: 一个客服 Agent,每天 500 次对话
# 每次对话平均 10 轮,每轮约 500 token
# 使用 DeepSeek(¥1/百万输入token, ¥2/百万输出token)
daily_conversations = 500
rounds_per_conversation = 10
tokens_per_round = 500 # 输入 + 输出
input_ratio = 0.6 # 60% 是输入
output_ratio = 0.4 # 40% 是输出
daily_tokens = daily_conversations * rounds_per_conversation * tokens_per_round
daily_input = daily_tokens * input_ratio
daily_output = daily_tokens * output_ratio
daily_cost = daily_input / 1_000_000 * 1 + daily_output / 1_000_000 * 2
monthly_cost = daily_cost * 30
print(f"每日 Token: {daily_tokens:,}")
print(f"每日成本: ¥{daily_cost:.2f}")
print(f"每月成本: ¥{monthly_cost:.2f}")
# 输出:
# 每日 Token: 2,500,000
# 每日成本: ¥3.50
# 每月成本: ¥105.00
# 如果换 GPT-4o(¥18/百万输入, ¥72/百万输出):
gpt4o_daily = daily_input / 1_000_000 * 18 + daily_output / 1_000_000 * 72
print(f"GPT-4o 每月成本: ¥{gpt4o_daily * 30:.2f}")
# 输出: GPT-4o 每月成本: ¥1890.00
自测:
- DeepSeek 的 64K Context Window 能装多少中文字?
- "中间遗忘"现象是什么?对写 Prompt 有什么启发?
- 三种上下文管理策略各适合什么场景?
- 如果你的 Agent 月成本 ¥2000(GPT-4o),换 DeepSeek 能省多少?
7. Part F:综合实验------迷你语义搜索引擎(20 分钟)
把今天学的 Token + Embedding + 相似度搜索组合起来,做一个能用的迷你语义搜索引擎。这是 RAG 的雏形。
创建 code/mini_search.py:
python
"""
Day 3 综合实验: 迷你语义搜索引擎
综合应用 Token 计数 + Embedding + 余弦相似度
功能:
1. 输入一批文档建立知识库
2. 输入查询,返回最相关的文档
3. 显示 token 用量和成本估算
4. 支持中英文混合查询
"""
import os
import json
import numpy as np
import tiktoken
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
# ============ 配置 ============
# 方案 B: 阿里百炼(推荐)
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
EMBEDDING_MODEL = "text-embedding-v3"
# Token 计数器(近似估计)
token_counter = tiktoken.encoding_for_model("gpt-4o")
def count_tokens(text):
"""估算文本的 token 数"""
return len(token_counter.encode(text))
def get_embedding(text):
"""获取文本的 Embedding"""
response = client.embeddings.create(
model=EMBEDDING_MODEL,
input=text
)
return response.data[0].embedding
def cosine_similarity(a, b):
"""余弦相似度"""
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
class MiniSearchEngine:
"""迷你语义搜索引擎"""
def __init__(self):
self.documents = []
self.embeddings = []
self.total_tokens_used = 0
def add_documents(self, docs):
"""添加文档到知识库"""
print(f"\n正在为 {len(docs)} 篇文档生成 Embedding...")
for doc in docs:
tokens = count_tokens(doc)
emb = get_embedding(doc)
self.documents.append({
"text": doc,
"tokens": tokens
})
self.embeddings.append(emb)
self.total_tokens_used += tokens
print(f" ✓ [{tokens:>4} tok] {doc[:40]}...")
print(f"知识库构建完成: {len(self.documents)} 篇文档, "
f"共 {self.total_tokens_used} tokens")
def search(self, query, top_k=3):
"""语义搜索"""
query_tokens = count_tokens(query)
query_emb = get_embedding(query)
self.total_tokens_used += query_tokens
# 计算相似度
results = []
for i, doc_emb in enumerate(self.embeddings):
sim = cosine_similarity(query_emb, doc_emb)
results.append({
"index": i,
"similarity": sim,
"text": self.documents[i]["text"],
"tokens": self.documents[i]["tokens"]
})
# 排序
results.sort(key=lambda x: x["similarity"], reverse=True)
# 输出结果
print(f"\n{'=' * 60}")
print(f"查询: '{query}' [{query_tokens} tokens]")
print(f"{'=' * 60}")
print(f"\nTop {top_k} 结果:")
for rank, r in enumerate(results[:top_k], 1):
bar = "█" * int(r["similarity"] * 30)
print(f"\n #{rank} 相似度: {r['similarity']:.4f} {bar}")
print(f" 文档: {r['text']}")
print(f" Token: {r['tokens']}")
# 阈值提示
best_sim = results[0]["similarity"]
if best_sim > 0.7:
print(f"\n [高置信] 最佳匹配相似度 {best_sim:.2f},结果可靠")
elif best_sim > 0.5:
print(f"\n [中置信] 最佳匹配相似度 {best_sim:.2f},可能相关")
else:
print(f"\n [低置信] 最佳匹配相似度 {best_sim:.2f},知识库中可能没有相关内容")
return results[:top_k]
def stats(self):
"""显示统计信息"""
print(f"\n{'=' * 60}")
print(f"搜索引擎统计")
print(f"{'=' * 60}")
print(f" 文档数量: {len(self.documents)}")
print(f" 总 Token 消耗: {self.total_tokens_used}")
print(f" Embedding 维度: {len(self.embeddings[0]) if self.embeddings else 0}")
# 阿里 text-embedding-v3: 约 ¥0.7/百万 token
cost = self.total_tokens_used / 1_000_000 * 0.7
print(f" 预估成本: ¥{cost:.4f}")
# ============ 运行 ============
if __name__ == "__main__":
engine = MiniSearchEngine()
# 知识库(AI 开发相关)
knowledge_base = [
"LangChain 是一个用于构建 LLM 应用的开源框架,支持链式调用、Agent 和工具集成。",
"RAG(检索增强生成)通过先检索相关文档再生成回答,减少 LLM 的幻觉问题。",
"Function Calling 允许 LLM 调用外部函数,是实现 Agent 工具使用的关键能力。",
"Vector Database(向量数据库)如 Pinecone、Milvus 用于存储和检索 Embedding 向量。",
"Prompt Engineering 是设计和优化提示词的技术,包括 Few-shot、Chain-of-Thought 等技巧。",
"Fine-tuning(微调)是在预训练模型基础上用领域数据进一步训练,适配特定任务。",
"MCP(Model Context Protocol)是连接 LLM 和外部工具/数据源的标准化协议。",
"Multi-Agent 系统中多个 AI 智能体协作完成复杂任务,如 AutoGPT 和 CrewAI。",
"Token 是 LLM 处理文本的最小单位,API 按 token 数计费,中文比英文贵 2-3 倍。",
"Embedding 将文本映射为高维向量,使计算机能计算语义相似度,是语义搜索的基础。",
]
# 建立知识库
engine.add_documents(knowledge_base)
# 搜索测试
queries = [
"怎么让 AI 调用外部工具?",
"如何减少 AI 胡说八道?",
"向量数据库是干什么的?",
"怎么优化 Prompt?",
]
for q in queries:
engine.search(q, top_k=3)
# 统计
engine.stats()
print(f"\n这就是 RAG 的核心流程!")
print(f" 1. 文档 → Embedding → 存入知识库")
print(f" 2. 查询 → Embedding → 计算相似度 → 返回最相关文档")
print(f" 3. 下一步: 把检索到的文档 + 用户问题一起发给 LLM 生成回答")
print(f" → 这就是第 4 个月要深入的内容!")
运行:
bash
python code/mini_search.py
预期输出:
正在为 10 篇文档生成 Embedding...
✓ [ 35 tok] LangChain 是一个用于构建 LLM 应用的开源框架...
✓ [ 42 tok] RAG(检索增强生成)通过先检索相关文档再生成回答...
✓ [ 38 tok] Function Calling 允许 LLM 调用外部函数...
...
知识库构建完成: 10 篇文档, 共 380 tokens
============================================================
查询: '怎么让 AI 调用外部工具?' [12 tokens]
============================================================
Top 3 结果:
#1 相似度: 0.7234 █████████████████████
文档: Function Calling 允许 LLM 调用外部函数,是实现 Agent 工具使用的关键能力。
Token: 38
#2 相似度: 0.5891 █████████████████
文档: MCP(Model Context Protocol)是连接 LLM 和外部工具/数据源的标准化协议。
Token: 35
#3 相似度: 0.4521 █████████████
文档: LangChain 是一个用于构建 LLM 应用的开源框架,支持链式调用、Agent 和工具集成。
Token: 35
[高置信] 最佳匹配相似度 0.72,结果可靠
...
============================================================
搜索引擎统计
============================================================
文档数量: 10
总 Token 消耗: 432
Embedding 维度: 1024
预估成本: ¥0.0003
这就是 RAG 的核心流程!
1. 文档 → Embedding → 存入知识库
2. 查询 → Embedding → 计算相似度 → 返回最相关文档
3. 下一步: 把检索到的文档 + 用户问题一起发给 LLM 生成回答
→ 这就是第 4 个月要深入的内容!
实验后思考:
- 如果知识库有 10 万篇文档,每次搜索都要计算 10 万次相似度,怎么优化?(提示:向量数据库)
- 相似度阈值设多少合适?太高会怎样?太低会怎样?
- 这个搜索引擎和关键词搜索(如 Elasticsearch)有什么本质区别?
- 如果用户的问题在知识库中没有相关内容,怎么处理?
8. 今日笔记模板
在 notes/day3.md 中写:
markdown
# Day 3 笔记
## 今天学到的 3 件事
1.
2.
3.
## Token 实验数据
| 文本 | 字符数 | Token数 | 比值 |
|------|--------|---------|------|
| "Hello, world!" | | | |
| "你好,世界!" | | | |
| 我的测试文本 | | | |
## 中英文 Token 差异总结
(2-3 句话)
## Embedding 实验数据
| 文本对 | 相似度 | 预期 | 是否符合 |
|-------|--------|------|---------|
| "我喜欢吃苹果" vs "我爱吃水果" | | 高 | |
| "我喜欢吃苹果" vs "今天天气真好" | | 低 | |
| 自选文本对 | | | |
## 我对 Embedding 的理解(200 字内)
## Context Window 三种管理策略
1. 截断:
2. 摘要:
3. RAG:
## 迷你搜索引擎的发现
- 哪个查询效果最好?
- 哪个查询效果不好?为什么?
- 如果要改进,你会怎么做?
## 还不清楚的点
-
-
## 明天的问题
-
9. 验收清单
概念验收
- 能解释 Token 是什么,为什么不是"词"也不是"字"
- 理解 BPE 分词的基本思路(合并最常见的相邻对)
- 知道为什么中文比英文贵 2-3 倍 token
- 能解释 Embedding 是什么,为什么能表示"语义"
- 知道余弦相似度的取值范围和含义
- 理解 Context Window 是什么,超出会怎样
- 能说出三种上下文管理策略的区别
- 知道"中间遗忘"现象及其影响
代码验收
-
token_experiment.py运行成功,记录了中英文 token 对比数据 - 看到 token 的 ID 和原始文本片段
-
embedding_experiment.py运行成功,看到相似度数值和可视化条 - 语义搜索实验成功,查询结果合理
-
mini_search.py运行成功,搜索引擎能返回相关文档 - 理解 Embedding API 响应结构(维度、向量值范围)
产出验收
-
notes/day3.md写完,包含实验数据和思考 - 4 个脚本推送到 GitHub(token_experiment / embedding_experiment / mini_search)
- 能回答"RAG 的基本原理是什么"(用今天学的知识)
10. 常见问题 FAQ
Q1: tiktoken 算出来的 token 数和 DeepSeek 实际计费的不一样怎么办?
A: tiktoken 是 OpenAI 的分词器,DeepSeek/Qwen 有自己的分词器,数字会有差异(通常 ±20%)。精确计费需要用各平台官方的分词器。tiktoken 适用于做近似估算和趋势分析。DeepSeek 官方 API 返回的 usage.total_tokens 是准确值。
Q2: DeepSeek 不支持 Embedding,我只有 DeepSeek 的 Key 怎么办?
A: 两个选择:
- 注册阿里百炼或智谱(都有免费额度),专门用来做 Embedding
- 用开源 Embedding 模型本地跑(如 BGE、E5),不需要 API:
bash
pip install sentence-transformers
python
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('BAAI/bge-small-zh-v1.5')
emb = model.encode("你好世界")
但本地跑需要一定硬件资源,初学建议先用 API。
Q3: Embedding 向量维度越高越好吗?
A: 不一定。维度高 → 表达能力更强,但 → 存储/计算成本更高。1536 维(text-embedding-3-small)对大多数场景够用了。3072 维(large)在精度要求高的场景有用,但存储翻倍。实际选择要在效果和成本间平衡。
Q4: 余弦相似度多少算"相关"?
A: 没有绝对标准,取决于模型和数据。经验值:
- > 0.80:高度相关(很可能是同义/近义)
- 0.60-0.80:相关(话题相同)
- 0.40-0.60:弱相关
- < 0.40:基本无关
但不同 Embedding 模型的分数分布不同,建议用自己的数据测一批,找到适合的阈值。
Q5: 为什么反义句的相似度也不低?
A: 因为 Embedding 主要捕捉"话题/主题"层面的语义,而非情感。"这个电影太好看了"和"这部电影太烂了"讨论的是同一话题(电影评价),所以向量方向接近。如果需要区分情感,需要:用专门的情感分析模型、或在 Prompt 中让 LLM 判断情感、或微调 Embedding 模型。
Q6: Context Window 128K 够用吗?
A: 取决于场景:
- 普通对话:绰绰有余(一轮对话几百 token)
- 长文档分析:可能不够(一本书 20-50 万字)
- RAG:够用(只放最相关的几个片段,通常 5K-20K token)
- 代码库分析:可能不够(大型项目代码量大)
大多数 Agent 场景 128K 完全够用,因为 RAG 会做过滤。
Q7: Embedding 会过时吗?需要重新生成吗?
A: 不会"过时",但如果换了 Embedding 模型,必须重新生成所有向量(不同模型的向量空间不兼容)。知识库内容更新时,只需要为新文档生成 Embedding,旧的不用动。
Q8: 向量数据库是什么?和今天做的一样吗?
A: 今天做的是"暴力搜索"------对每个文档算一遍相似度。文档少时没问题,但 10 万+ 文档时太慢。向量数据库(Pinecone、Milvus、Chroma)用近似最近邻(ANN)算法,能在毫秒级从百万向量中找到最相似的。原理一样,但工程优化天差地别。第 4 个月会学。
11. 扩展资源
必看
| 资源 | 类型 | 时长 | 价值 |
|---|---|---|---|
| OpenAI Tokenizer | 在线工具 | 5 min | 可视化看到任意文本的 token 切分 |
| tiktoken 文档 | 文档 | 10 min | 官方分词器文档 |
| OpenAI Embedding Guide | 文档 | 15 min | Embedding 最佳实践 |
推荐
| 资源 | 类型 | 时长 | 价值 |
|---|---|---|---|
| 3Blue1Brown「Vectors」 | 视频 | 14 min | 向量直觉理解,和 Day 2 配套 |
| Jay Alammar「Illustrated Word2Vec」 | 文章 | 20 min | Embedding 经典图解 |
| 阿里百炼 Embedding 文档 | 文档 | 10 min | 中文 Embedding 模型文档 |
可选
| 资源 | 类型 | 时长 | 价值 |
|---|---|---|---|
| BPE 原始论文 | 论文 | 30 min | 理解分词算法细节 |
| OpenAI Cookbook - Embedding | 代码 | 30 min | 官方 Embedding 示例 |
| Chroma DB Quickstart | 文档 | 20 min | 轻量级向量数据库入门 |
在线工具
- OpenAI Tokenizer: https://platform.openai.com/tokenizer --- 可视化 token 切分
- HuggingFace Tokenizer: https://huggingface.co/spaces/Xenova/the-tokenizer-playground --- 对比不同模型的分词
12. 核心概念速查卡
┌─────────────────────────────────────────────────────────────┐
│ Day 3 核心概念速查卡 │
├─────────────────────────────────────────────────────────────┤
│ │
│ TOKEN │
│ ├─ 定义: LLM 处理文本的最小单位(介于字和词之间) │
│ ├─ 分词: BPE 算法(合并最频繁的相邻字符对) │
│ ├─ 中文: 1 字 ≈ 1-2 token(比英文贵 2-3 倍) │
│ ├─ 英文: 4 字符 ≈ 1 token │
│ ├─ 计费: API 按 token 数收费 │
│ └─ 工具: tiktoken(近似估算) │
│ │
│ EMBEDDING │
│ ├─ 定义: 把文本映射为固定维度的浮点向量 │
│ ├─ 维度: 1024-3072 维(模型不同维度不同) │
│ ├─ 原理: 语义相近 → 向量相近(方向一致) │
│ ├─ 相似度: 余弦相似度(-1 到 1,越接近 1 越相似) │
│ ├─ 用途: 语义搜索、RAG、推荐、分类 │
│ └─ 模型: text-embedding-3-small / v3 / BGE │
│ │
│ CONTEXT WINDOW │
│ ├─ 定义: 模型一次能处理的最大 token 数(输入+输出) │
│ ├─ 范围: 64K(DeepSeek)~ 1M(Gemini) │
│ ├─ 超出: 报错 / 静默截断 / 质量下降 │
│ ├─ 中间遗忘: 长文本中间内容容易被忽略 │
│ └─ 管理: 截断(简单)/ 摘要(平衡)/ RAG(最强) │
│ │
│ 关键公式 │
│ ├─ 余弦相似度 = (A·B) / (|A|×|B|) │
│ ├─ 日成本 = 日token数 / 1M × 单价 │
│ └─ 1 中文字 ≈ 1-2 token ≈ ¥0.000001-0.000002 │
│ │
│ 速记口诀 │
│ ├─ Token: "不是词不是字,是模型切的最小块" │
│ ├─ Embedding: "文字变数字,意思变距离" │
│ ├─ Context Window: "模型的工作台,放不下就得收" │
│ └─ RAG: "先搜后答,只给相关的" │
│ │
└─────────────────────────────────────────────────────────────┘
13. 明日预告
Day 4:主流大模型对比与选择
明天你将:
- 了解 2026 年主流大模型的全面对比(GPT-4o / Claude / DeepSeek / Qwen / Gemini)
- 学会根据任务类型、成本、性能选择合适的模型
- 用代码对比不同模型在相同 Prompt 下的表现差异
- 建立自己的模型选择决策框架
今天和明天的关系:
- 今天你理解了 Token(计费单位)和 Context Window(使用限制)
- 明天你会用这些知识来评估不同模型的性价比
- 理解了 Token 才能看懂模型定价表
- 理解了 Context Window 才能判断模型是否满足你的需求
预习建议:
- 去各平台看看当前的模型定价(DeepSeek / OpenAI / 阿里 / 智谱)
- 想想你的项目场景需要什么样的模型(要便宜?要质量?要长上下文?)
时间安排建议
| 模式 | 安排 | 适合 |
|---|---|---|
| 一次性学完 | 约 2.5-3 小时 | 周末/有大块时间 |
| 分两次 | Day 3a: Part A-D(2h)+ Day 3b: Part E-F(1h) | 工作日晚上 |
| 分三次 | Part A-B / Part C-D / Part E-F | 每次 50-60 分钟 |
| 纯概念 | 只看 Part A/C/E,跳过代码 | 先建立框架,代码周末补 |
优先级建议(如果时间不够):
- 必做:Part A + Part C(概念理解)+ Part B 实验 1
- 重要:Part D 实验 1(Embedding 相似度)
- 推荐:Part F(迷你搜索引擎)
- 可选:Part E 的成本估算实战