AI 智能体开发 · Day 3 详细学习手册

AI 智能体开发 · Day 3 详细学习手册

主题 :Token、Embedding、Context Window------从"文本怎么进入模型"到"语义搜索初体验"

总时长 :2.5-3 小时(可分两到三次完成)

难度 :中等偏实践(概念 + 大量代码实验)

产出 :4 个实验脚本 + 1 个迷你语义搜索引擎 + 结构化笔记

前置条件 :完成 Day 1(环境搭建、API 可调用)和 Day 2(理解 Transformer/Attention)

今天最重要:Token 和 Embedding 是后面所有内容的基础------RAG、向量数据库、Agent 记忆系统全靠它们


目录

  1. 学习目标与知识地图
  2. [Part A:Token 深度理解(30 分钟)](#Part A:Token 深度理解(30 分钟))
  3. [Part B:Token 实验室(30 分钟)](#Part B:Token 实验室(30 分钟))
  4. [Part C:Embedding 深度理解(30 分钟)](#Part C:Embedding 深度理解(30 分钟))
  5. [Part D:Embedding 实验 + 语义搜索初体验(30 分钟)](#Part D:Embedding 实验 + 语义搜索初体验(30 分钟))
  6. [Part E:Context Window 与上下文管理(25 分钟)](#Part E:Context Window 与上下文管理(25 分钟))
  7. [Part F:综合实验------迷你语义搜索引擎(20 分钟)](#Part F:综合实验——迷你语义搜索引擎(20 分钟))
  8. 今日笔记模板
  9. 验收清单
  10. [常见问题 FAQ](#常见问题 FAQ)
  11. 扩展资源
  12. 核心概念速查卡
  13. 明日预告

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 会讲)

自测

  1. "ChatGPT" 大概是几个 token?为什么?
  2. 为什么中文 Prompt 比英文贵?
  3. 如果你的 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 实验后回答这些问题

  1. "你好,世界!"用多少 token?和 "Hello, world!" 差几倍?
  2. 同一个意思的 Prompt,啰嗦版和极简版差多少 token?如果每天调用 1000 次,一个月省多少钱?
  3. 为什么 "ChatGPT" 可能被拆成多个 token?
  4. 代码的 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 个月深入)

自测

  1. 为什么不能直接用字符比较来判断两句话意思是否相近?
  2. Embedding 向量的维度是 1536,这个数字代表什么?
  3. 余弦相似度为 0.85 和 0.30 分别代表什么?
  4. 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

自测

  1. DeepSeek 的 64K Context Window 能装多少中文字?
  2. "中间遗忘"现象是什么?对写 Prompt 有什么启发?
  3. 三种上下文管理策略各适合什么场景?
  4. 如果你的 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 个月要深入的内容!

实验后思考

  1. 如果知识库有 10 万篇文档,每次搜索都要计算 10 万次相似度,怎么优化?(提示:向量数据库)
  2. 相似度阈值设多少合适?太高会怎样?太低会怎样?
  3. 这个搜索引擎和关键词搜索(如 Elasticsearch)有什么本质区别?
  4. 如果用户的问题在知识库中没有相关内容,怎么处理?

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: 两个选择:

  1. 注册阿里百炼或智谱(都有免费额度),专门用来做 Embedding
  2. 用开源 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 轻量级向量数据库入门

在线工具


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,跳过代码 先建立框架,代码周末补

优先级建议(如果时间不够):

  1. 必做:Part A + Part C(概念理解)+ Part B 实验 1
  2. 重要:Part D 实验 1(Embedding 相似度)
  3. 推荐:Part F(迷你搜索引擎)
  4. 可选:Part E 的成本估算实战
相关推荐
zzzzzz3101 小时前
当客户说「用AI帮我写个和Notion一模一样的,预算5000,三天上线」时,我在想什么
人工智能·程序员·产品经理
AI星桥小王子2 小时前
支持人物一致性的 AI 视频生成方案分享:怎么能解决 “人脸漂移” 痛点
大数据·人工智能
数智化管理手记3 小时前
手工统计指标误差大、效率低?指标管理系统如何告别人工算数痛点?
大数据·运维·数据库·人工智能·云计算
fthux7 小时前
RenoPit 能为普通业主做什么?看懂图纸、审查合同,提前发现装修坑
javascript·人工智能·ai·开源·github·chrome扩展·open source·edge扩展·firefox扩展
火山引擎开发者社区9 小时前
火山引擎混合云 veStack 智算平台 Day0 适配 Kimi K3
人工智能
RSABLOCKCHAIN9 小时前
AI Agents in LangGraph-2
人工智能·python
cd_9492172110 小时前
哪些AI工具适合生成游戏开发用的3D道具模型?从道具草稿到引擎测试的选择方法
人工智能·3d
FII工业富联科技服务10 小时前
工业设备运维如何Agentic化?拆解Factory Brain的设备运维架构
大数据·运维·人工智能·架构·制造
糖果店的幽灵10 小时前
人已经用 WorkBuddy 找工作拿了面试,你还在一份份手工改简历
人工智能·面试·职场和发展·langgraph