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

主题:综合实验日——把本周学的一切串起来,做 4 组系统实验 + 1 个 RAG 雏形项目
总时长:3.5-4 小时(可分两到三次完成)
难度:高(实验密集型,需要前五天的基础)
产出:4 个实验脚本 + 1 个综合项目(智能知识助手)+ 结构化实验报告
前置条件:完成 Day 1(API 调用)、Day 2(Transformer)、Day 3(Token/Embedding)、Day 4(模型选择)、Day 5(采样参数)
今天最重要:Day 6 是本周的"收网日"。前五天你学了零散的概念,今天把它们组装成能用的东西——尤其是语义搜索 Demo,它是 RAG 的完整雏形,第 4 个月会在这个基础上深入


目录

  1. 学习目标与知识地图
  2. Part A:Token 优化深度实战(35 分钟)
  3. Part B:模型能力边界探测(35 分钟)
  4. Part C:语义搜索 Demo——RAG 雏形(50 分钟)
  5. Part D:参数组合实战(30 分钟)
  6. Part E:模型 × 参数 × Token 三维交叉实验(25 分钟)
  7. Part F:综合项目——智能知识助手(25 分钟)
  8. 今日笔记模板
  9. 验收清单
  10. 常见问题 FAQ
  11. 扩展资源
  12. 核心概念速查卡
  13. 明日预告

1. 学习目标与知识地图

你今天要达成的目标

概念层面(能用自己话讲清楚):

  • Token 优化的 5 种策略,以及每种策略的适用场景和风险
  • LLM 模型的能力边界:哪些任务可靠、哪些不可靠、为什么不可靠
  • 语义搜索的完整流程:文本 → Embedding → 相似度计算 → 排序 → 返回结果
  • 为什么语义搜索是 RAG 的基础,以及 RAG 在语义搜索之上还需要什么
  • 模型选择、参数调优、Token 优化三者如何协同工作
  • Agent 系统的"感知 → 检索 → 生成"三段式架构

操作层面(亲手做过):

  • 运行 4 组系统实验,记录并分析所有实验结果
  • 实现一个支持交互式查询的语义搜索引擎(20+ 条知识库)
  • 实现一个"搜索 + 生成"的迷你知识问答系统(RAG 雏形)
  • 完成一个三维交叉实验:模型 × 参数 × Token,找到不同场景的最优配置
  • 构建一个智能知识助手:集成模型路由、参数推荐、Token 优化、语义搜索

知识地图

Part A: Token 优化深度实战
  ├── 中英文 Token 对比(量化成本差异)
  ├── Prompt 压缩策略(5 种方法 + 效果对比)
  ├── System Prompt 成本分析
  ├── Token 缓存策略(相同前缀复用)
  └── token_optimization.py(4 个子实验)

Part B: 模型能力边界探测
  ├── 能力矩阵:6 类任务 × 可靠性评级
  ├── 模型的"弱项"根因分析
  ├── 补偿策略:模型不行怎么办
  ├── 错误模式分类(幻觉/遗漏/计数/推理)
  └── model_limits.py(6 类任务测试 + 分析)

Part C: 语义搜索 Demo(核心实验)
  ├── 知识库构建(20+ 条结构化文本)
  ├── Embedding 预计算与缓存
  ├── 查询 → Embedding → 余弦相似度 → 排序
  ├── 结果可视化(相似度柱状图)
  ├── 交互式搜索
  └── semantic_search.py(完整搜索引擎)

Part D: 参数组合实战
  ├── 3 大场景 × 3 种参数组合
  ├── JSON 提取:T=0 + response_format 的威力
  ├── 创意文案:高 T + penalty 的效果
  ├── 代码生成:T=0 确定性验证
  └── param_combination.py(3 场景 × 3 参数)

Part E: 三维交叉实验
  ├── 模型选择(Day 4)× 参数(Day 5)× Token(Day 6)
  ├── 同一任务在不同配置下的效果矩阵
  ├── 成本-质量权衡分析
  └── cross_experiment.py(自动对比矩阵)

Part F: 综合项目——智能知识助手
  ├── 架构:感知(分类)→ 检索(搜索)→ 生成(回答)
  ├── 集成本周所有知识
  ├── 知识库问答(RAG 雏形)
  └── knowledge_assistant.py(完整可运行项目)

今天和前五天的关系

Day 1: 你学会了调用 LLM API
       → 但只会调默认参数,不懂优化

Day 2: 你理解了 Transformer 和 Attention
       → 知道模型"怎么想的",但不影响实际开发

Day 3: 你理解了 Token 和 Embedding
       → Token 关系成本,Embedding 关系语义搜索
       → 今天你要把两者用起来

Day 4: 你学会了选模型和做模型路由
       → 今天你要验证:不同模型在不同任务上的真实表现
       → Day 4 的模型路由器今天会集成进综合项目

Day 5: 你学会了调参数
       → 今天你要验证:参数对实际任务的影响有多大
       → Day 5 的参数推荐器今天会集成进综合项目

Day 6: 今天把一切组装起来
       → Token 优化(省钱)
       → 模型路由(选对工具)
       → 参数推荐(调对参数)
       → 语义搜索(找到相关知识)
       → 生成回答(LLM 回答用户问题)
       
       这就是一个简化版 Agent 的完整架构

       实际开发中的典型决策:
       → "这个任务用什么模型?" → Day 4 的路由逻辑
       → "用什么参数?" → Day 5 的推荐器
       → "怎么省 Token?" → Day 6 Part A 的优化策略
       → "用户问的东西模型不知道" → Day 6 Part C 的语义搜索
       → "怎么把搜索结果喂给模型?" → Day 6 Part F 的 RAG 流程

2. Part A:Token 优化深度实战(35 分钟)

Token 直接关系成本。在个人项目中可能无所谓,但在生产环境中,1000 个用户的请求量下,Token 优化能省下几十到几百美元/月。

2.1 中英文 Token 成本对比(10 分钟)

Day 3 你已经知道中文比英文贵(BPE 分词器对中文不友好)。今天精确量化一下:

# code/token_optimization.py
"""Token 优化实验:怎么用最少的 Token 达到最好效果"""
import tiktoken

# 用 GPT-4o 的分词器(DeepSeek 的分词器类似)
enc = tiktoken.encoding_for_model("gpt-4o")

# ============================================================
# 实验 1: 中英文 Prompt 的 Token 对比
# ============================================================
print("=" * 60)
print("实验 1: 中英文 Prompt Token 对比")
print("=" * 60)

prompts = [
    ("中文", "你是一个专业的翻译助手。请将用户输入的中文翻译成英文。翻译时请注意保持原文的语气和风格,专业术语要准确翻译。"),
    ("英文", "You are a professional translation assistant. Please translate the user's Chinese input into English. Maintain the original tone and style, and translate technical terms accurately."),
    ("中英混合", "You are a 翻译助手. Translate 用户输入 to English. 保持原文语气, 专业术语要准确."),
    ("全英文缩写", "Translate CN→EN. Keep tone. Accurate terms."),
]

print(f"\n{'版本':<8} {'字符数':>6} {'Token数':>8} {'每Token字符':>12} {'相对成本':>10}")
print("-" * 50)

base_tokens = None
for label, text in prompts:
    tokens = enc.encode(text)
    chars_per_token = len(text) / len(tokens) if tokens else 0
    if base_tokens is None:
        base_tokens = len(tokens)
    cost_ratio = len(tokens) / base_tokens
    print(f"{label:<8} {len(text):>6} {len(tokens):>8} {chars_per_token:>12.2f} {cost_ratio:>10.2f}x")

print(f"""
分析:
- 中文: 每个 Token 约 0.5-0.7 个汉字(1 个汉字 ≈ 1.5-2 个 Token)
- 英文: 每个 Token 约 3-4 个字符(1 个单词 ≈ 1-2 个 Token)
- 同样意思的指令,中文比英文贵 2-3 倍 Token
- 中英混合可以省 Token,但可读性下降
""")

运行后记录

版本字符数Token 数相对成本
中文1.00x(基准)
英文
中英混合
全英文缩写

关键洞察:在 System Prompt 中使用英文指令是降低 Token 成本的有效策略。虽然看起来有点"不自然",但 LLM 对英文指令的理解能力通常不亚于甚至优于中文。

2.2 Prompt 压缩策略(10 分钟)

# ============================================================
# 实验 2: Prompt 压缩对比
# ============================================================
print(f"\n{'='*60}")
print("实验 2: Prompt 压缩对比")
print("=" * 60)

compression_examples = [
    {
        "label": "啰嗦版",
        "prompt": "请你扮演一个资深的Python开发工程师的角色,我需要你帮我写一段代码,这段代码的功能是实现一个快速排序算法,要求代码要有完整的注释说明,并且需要包含错误处理机制,最后还要写几个测试用例来验证代码的正确性。"
    },
    {
        "label": "正常版",
        "prompt": "你是 Python 开发者。写一个快速排序函数,包含注释、错误处理和测试用例。"
    },
    {
        "label": "精简版",
        "prompt": "Write quicksort in Python with comments, error handling, and tests."
    },
    {
        "label": "极简版",
        "prompt": "Python quicksort: comments, try/except, tests."
    },
]

print(f"\n{'版本':<8} {'Token':>6} {'字符':>6} {'节省':>8} {'可读性':>8}")
print("-" * 40)

base_tokens = None
readability = {"啰嗦版": "高", "正常版": "高", "精简版": "中", "极简版": "低"}
for ex in compression_examples:
    tokens = enc.encode(ex["prompt"])
    if base_tokens is None:
        base_tokens = len(tokens)
    saved = (1 - len(tokens) / base_tokens) * 100
    print(f"{ex['label']:<8} {len(tokens):>6} {len(ex['prompt']):>6} {saved:>7.1f}% {readability[ex['label']]:>8}")

print(f"""
5 种 Prompt 压缩策略:
1. 去冗余: 删除"请你扮演...的角色"等废话,直接说角色
2. 英文化: 中文指令改英文(省 50-60% Token)
3. 符号化: "包含注释"→"with comments",用介词替代从句
4. 结构化: 用列表/编号替代段落描述
5. 省略词: "实现"→"写","验证"→"test"(合理使用缩写)

注意: 压缩有极限——太短的 Prompt 会丢失上下文,导致输出质量下降
""")

2.3 System Prompt 成本分析(8 分钟)

# ============================================================
# 实验 3: System Prompt Token 开销
# ============================================================
print(f"\n{'='*60}")
print("实验 3: System Prompt Token 开销")
print("=" * 60)

system_prompts = [
    ("无", ""),
    ("简单", "你是助手。"),
    ("中等", "你是一个专业的编程助手,擅长 Python。请给出简洁、准确的回答。"),
    ("详细", """你是一个资深的全栈开发工程师,拥有 10 年以上经验。
你的专长包括 Python、JavaScript、Go、Rust。
回答时请遵循以下规则:
1. 代码必须有类型注解
2. 必须包含错误处理
3. 必须有文档字符串
4. 必须包含测试用例
5. 解释要简洁明了
6. 如果不确定,明确说明"""),
    ("英文详细", """You are a senior full-stack engineer with 10+ years experience.
Expertise: Python, JavaScript, Go, Rust.
Rules:
1. Type annotations required
2. Error handling required
3. Docstrings required
4. Include tests
5. Be concise
6. Say "not sure" if uncertain"""),
]

print(f"\n{'级别':<8} {'Token':>6} {'每次调用成本':>15} {'1000次/月':>12}")
print("-" * 45)

# DeepSeek 定价: 输入 ¥0.001/1K tokens(缓存命中 ¥0.0001/1K)
PRICE_PER_1K = 0.001  # 元

for label, sp in system_prompts:
    tokens = len(enc.encode(sp)) if sp else 0
    cost_per_call = tokens / 1000 * PRICE_PER_1K
    monthly_cost = cost_per_call * 1000
    print(f"{label:<8} {tokens:>6} ¥{cost_per_call:>13.4f} ¥{monthly_cost:>10.2f}")

print(f"""
分析:
- 详细 System Prompt 每次调用多消耗 80-100 Token
- 1000 次/月 ≈ 多花 ¥0.08-0.10(看起来不多)
- 但如果是 10000 次/月 → ¥0.8-1.00,10 万次 → ¥8-10
- 中英文 System Prompt 差异约 2-3 倍 Token
- 优化策略: 英文 System Prompt + Context Caching(DeepSeek 支持)

DeepSeek Context Caching:
- 相同前缀的 Prompt 自动缓存,命中后价格降为 1/10
- System Prompt 通常不变 → 几乎 100% 缓存命中
- 所以详细 System Prompt 的实际成本比表中更低
""")

2.4 多轮对话 Token 增长分析(7 分钟)

# ============================================================
# 实验 4: 多轮对话 Token 增长
# ============================================================
print(f"\n{'='*60}")
print("实验 4: 多轮对话 Token 增长分析")
print("=" * 60)

# 模拟一个多轮对话
conversation = [
    ("user", "你好"),
    ("assistant", "你好!我是你的编程助手,有什么可以帮你的?"),
    ("user", "什么是 Python 的装饰器?"),
    ("assistant", "装饰器是一种语法糖,用于在不修改函数代码的情况下扩展函数的功能。它本质上是一个接收函数作为参数并返回新函数的高阶函数。\n\n基本语法:\n```python\ndef my_decorator(func):\n    def wrapper(*args, **kwargs):\n        print('函数执行前')\n        result = func(*args, **kwargs)\n        print('函数执行后')\n        return result\n    return wrapper\n\n@my_decorator\ndef hello():\n    print('Hello!')\n```\n\n调用 hello() 时,实际上执行的是 wrapper()。"),
    ("user", "能给个实际应用的例子吗?"),
    ("assistant", "当然。以下是几个常见的装饰器应用场景:\n\n1. 计时装饰器:\n```python\nimport time\ndef timer(func):\n    def wrapper(*args, **kwargs):\n        start = time.time()\n        result = func(*args, **kwargs)\n        print(f'{func.__name__} 耗时: {time.time()-start:.2f}s')\n        return result\n    return wrapper\n```\n\n2. 日志装饰器:\n```python\ndef log(func):\n    def wrapper(*args, **kwargs):\n        print(f'调用 {func.__name__}, args={args}, kwargs={kwargs}')\n        return func(*args, **kwargs)\n    return wrapper\n```\n\n3. 缓存装饰器:\n```python\nfrom functools import lru_cache\n@lru_cache(maxsize=128)\ndef fib(n):\n    if n < 2: return n\n    return fib(n-1) + fib(n-2)\n```"),
    ("user", "缓存装饰器的原理是什么?"),
]

# 逐轮计算累计 Token
print(f"\n{'轮次':<6} {'角色':<12} {'本轮Token':>10} {'累计Token':>10} {'增长':>8}")
print("-" * 50)

total_tokens = 0
for i, (role, content) in enumerate(conversation):
    tokens = len(enc.encode(content))
    prev_total = total_tokens
    total_tokens += tokens
    growth = ((total_tokens / prev_total - 1) * 100) if prev_total > 0 else 0
    print(f"{i+1:<6} {role:<12} {tokens:>10} {total_tokens:>10} {growth:>7.1f}%")

print(f"""
关键发现:
- 多轮对话中,每轮都要发送完整历史 → Token 线性增长
- 第 4 轮的输入已经包含前 3 轮的所有内容
- 如果对话持续 10+ 轮,Token 可能超过 Context Window
- 这就是为什么需要"对话历史管理"策略(Day 3 学过)

优化策略:
1. 滑动窗口: 只保留最近 N 轮对话
2. 摘要压缩: 用 LLM 把旧对话压缩成摘要
3. 混合策略: 最近 3 轮保留原文 + 更早的用摘要
4. 按需检索: 用 Embedding 搜索相关历史对话(类似 RAG)

这些策略在第 2 个月"智能体核心开发"中会深入实现
""")

运行

python code/token_optimization.py

Part A 小结

Token 优化 5 策略:
1. 英文化 System Prompt → 省 50-60%
2. Prompt 压缩 → 省 30-50%(注意可读性)
3. Context Caching → 相同前缀省 90%
4. 对话历史管理 → 防止 Token 爆炸
5. response_format JSON → 省输出 Token(不需要自然语言包装)

成本直觉:
- 个人项目: 每月几千次调用 → 优化意义不大(几块钱)
- 生产环境: 每月几万次调用 → 优化能省几十到几百元
- 大规模应用: 每月几十万次 → 优化是必须的(否则成本失控)

3. Part B:模型能力边界探测(35 分钟)

LLM 不是万能的。知道模型的"弱项"在哪里,才能设计正确的补偿策略——这就是 Function Calling 和 RAG 存在的理由。

3.1 能力矩阵:LLM 擅长什么、不擅长什么(10 分钟)

在写代码测试之前,先建立预期。基于 Day 2 学的 Transformer 原理,推理一下:

LLM 擅长的任务(可靠度 >90%):
├── 文本生成: 写文章、文案、邮件 → 模型本质就是"生成下一个词"
├── 翻译: 语言转换 → 预训练数据中有大量平行语料
├── 摘要: 长文本压缩 → 预训练任务的一种
├── 代码生成: 写代码 → GitHub 代码数据训练
├── 对话/问答: 理解意图 + 生成回答 → RLHF 优化
├── 情感分析: 判断正负面 → 常见 NLP 任务
└── 信息提取: 从文本中提取结构化数据 → 格式化输出

LLM 不擅长的任务(可靠度 <70%):
├── 精确数学计算: 大数乘法、复杂方程 → Token 级生成,无法"计算"
├── 字符/字母计数: 数 "e" 出现几次 → Token 不等于字符
├── 实时信息: 今天的天气、最新新闻 → 训练数据有截止日期
├── 专业知识: 医疗诊断、法律建议 → 需要权威知识库
├── 长逻辑链推理: 多步推理容易"断链" → 注意力衰减
├── 精确引用: 准确引用某本书第几页 → 容易"幻觉"
└── 空间推理: 3D 空间关系 → 缺乏空间感知能力

为什么模型会"不擅长"——根因分析:
├── Token 级生成: 模型是一个字一个字生成的,无法"回头检查"
├── 概率而非逻辑: 模型选的是"概率最高的下一个词",不是"逻辑正确的答案"
├── 训练数据偏差: 没见过的东西不会,见过但频率低的不准确
├── 注意力衰减: 长文本中,中间部分容易被"遗忘"(Day 3 学过)
└── 无外部工具: 模型不能"查一下"或"算一下",只能凭记忆

3.2 模型能力边界测试脚本(15 分钟)

# code/model_limits.py
"""模型能力边界测试:什么任务模型擅长,什么不擅长"""
import os
import time
import json
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com"
)
MODEL = "deepseek-chat"

def ask(prompt, temp=0, max_tokens=500):
    """简单调用,T=0 保证确定性"""
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
        temperature=temp,
        max_tokens=max_tokens
    )
    return response.choices[0].message.content

# ============================================================
# 6 类任务测试
# ============================================================
tests = [
    # 1. 数学计算(预期:可能出错)
    {
        "category": "数学计算",
        "prompt": "计算 1234 × 5678 = ? 只给数字,不要解释。",
        "expected": "7006652",
        "note": "大数乘法,Token 级生成无法精确计算",
        "reliability": "低"
    },
    # 2. 字符计数(预期:经常出错)
    {
        "category": "字符计数",
        "prompt": "在以下文本中,字母 'e' 出现了多少次?只给数字。\n\nThe quick brown fox jumps over the lazy dog",
        "expected": "3",
        "note": "Token ≠ 字符,模型看到的不是逐个字母",
        "reliability": "低"
    },
    # 3. 逻辑推理(预期:简单逻辑可以)
    {
        "category": "逻辑推理",
        "prompt": "小明比小红高,小红比小华高,小华比小明高。这个说法有问题吗?如果有,问题在哪?",
        "expected": "有矛盾,传递性不成立(小明>小红>小华>小明 是循环矛盾)",
        "note": "简单逻辑推理,模型通常能发现矛盾",
        "reliability": "中高"
    },
    # 4. 常识(预期:可靠)
    {
        "category": "常识",
        "prompt": "如果把一块冰放在太阳下,会发生什么?一句话回答。",
        "expected": "冰会融化",
        "note": "基本常识,训练数据中大量出现",
        "reliability": "高"
    },
    # 5. 代码理解(预期:可靠)
    {
        "category": "代码理解",
        "prompt": "以下代码的输出是什么?只给输出结果。\n\n```python\nx = [1, 2, 3]\ny = x\ny.append(4)\nprint(x)\n```",
        "expected": "[1, 2, 3, 4]",
        "note": "Python 引用语义,代码数据训练充分",
        "reliability": "高"
    },
    # 6. 长文本记忆(预期:可能遗忘)
    {
        "category": "长文本记忆",
        "prompt": """记住这个数字:8392。

现在请从1数到10,每行一个数字。
数完后,告诉我你记住的数字是什么。

格式:
1
2
...
10
记住的数字: ???""",
        "expected": "8392",
        "note": "测试注意力机制——中间内容多了容易遗忘",
        "reliability": "中"
    },
    # 7. 实时信息(预期:不知道)
    {
        "category": "实时信息",
        "prompt": "今天北京的天气怎么样?",
        "expected": "模型无法知道实时天气",
        "note": "训练数据有截止日期,无法获取实时信息",
        "reliability": "不可能"
    },
    # 8. 幻觉测试(预期:可能编造)
    {
        "category": "幻觉测试",
        "prompt": "请引用《代码整洁之道》第 7 章第 3 节的原文,说明作者对函数长度的看法。",
        "expected": "模型可能编造具体页码和原文",
        "note": "精确引用容易产生幻觉",
        "reliability": "低"
    },
]

print("=" * 60)
print("模型能力边界测试")
print(f"模型: {MODEL} | Temperature: 0")
print("=" * 60)

results = []
for test in tests:
    print(f"\n{'─' * 60}")
    print(f"测试: {test['category']} (预期可靠度: {test['reliability']})")
    print(f"问题: {test['prompt'][:80]}...")
    print(f"预期: {test['expected']}")
    print(f"说明: {test['note']}")
    
    answer = ask(test["prompt"])
    print(f"回答: {answer}")
    
    # 简单判断是否正确
    expected_lower = str(test["expected"]).lower()
    answer_lower = answer.lower()
    is_correct = expected_lower in answer_lower
    
    results.append({
        "category": test["category"],
        "expected": test["expected"],
        "answer": answer,
        "correct": is_correct,
        "reliability": test["reliability"]
    })
    
    print(f"判断: {'正确' if is_correct else '需人工检查'}")
    time.sleep(0.5)

# ============================================================
# 分析总结
# ============================================================
print(f"\n{'=' * 60}")
print("分析总结")
print(f"{'=' * 60}")

print(f"\n{'任务':<12} {'预期可靠度':<10} {'实际表现':<10} {'补偿策略':<30}")
print("-" * 65)

compensation = {
    "数学计算": "用 Function Calling 调 Python 计算",
    "字符计数": "用 Function Calling 调 len() / count()",
    "逻辑推理": "拆分为多步,每步单独验证",
    "常识": "不需要补偿",
    "代码理解": "不需要补偿",
    "长文本记忆": "用 RAG 检索相关信息",
    "实时信息": "用 Function Calling 调 API",
    "幻觉测试": "用 RAG 提供准确上下文",
}

for r in results:
    actual = "正确" if r["correct"] else "需检查"
    strategy = compensation.get(r["category"], "-")
    print(f"{r['category']:<12} {r['reliability']:<10} {actual:<10} {strategy:<30}")

print(f"""
关键发现:
1. 模型在"生成类"任务上可靠(翻译、摘要、代码、对话)
2. 模型在"计算类"任务上不可靠(数学、计数)
3. 模型在"事实类"任务上可能幻觉(引用、实时信息)
4. 补偿策略的核心: 把模型不擅长的任务交给外部工具
   → 数学 → Python 计算
   → 实时信息 → API 调用
   → 事实引用 → RAG 检索知识库

这就是 Agent 系统的核心理由:
   Agent = LLM(理解+生成)+ Tools(计算+检索+调用)
   第 2 个月会深入实现 Function Calling
""")

运行

python code/model_limits.py

实验后填写

任务模型回答是否正确你的观察
数学计算
字符计数
逻辑推理
常识
代码理解
长文本记忆
实时信息
幻觉测试

3.3 错误模式分类与补偿策略(10 分钟)

根据实验结果,把模型的错误分为 4 类,每类有不同的补偿方法:

错误模式 1: 计算错误
  症状: 大数运算、复杂公式算错
  根因: Token 级生成无法执行"计算"
  补偿: Function Calling → 调用 Python 执行计算
  优先级: 高(计算错误直接导致结果不可用)

错误模式 2: 幻觉(Hallucination)
  症状: 编造不存在的引用、论文、API
  根因: 模型在"不确定"时倾向生成"看起来合理"的内容
  补偿: RAG → 提供准确上下文,让模型"看着回答"
  优先级: 高(幻觉会误导用户)

错误模式 3: 遗漏/截断
  症状: 长文本中遗漏关键信息
  根因: 注意力衰减 + Context Window 限制
  补偿: 对话历史管理 + RAG 按需检索
  优先级: 中(影响体验但不一定出错)

错误模式 4: 格式不稳定
  症状: 要求输出 JSON 但偶尔带多余文字
  根因: Token 级生成 + Temperature > 0
  补偿: response_format JSON mode + T=0
  优先级: 中(影响下游解析)

补偿策略优先级矩阵:
  ┌──────────────┬──────────┬──────────┬──────────┐
  │              │  频率高  │  频率中  │  频率低  │
  ├──────────────┼──────────┼──────────┼──────────┤
  │  影响大      │ RAG + FC │ RAG      │ 提示工程 │
  │  影响中      │ FC       │ 提示工程 │ 不处理   │
  │  影响小      │ 提示工程 │ 不处理   │ 不处理   │
  └──────────────┴──────────┴──────────┴──────────┘
  
  FC = Function Calling(第 2 个月学)
  RAG = 检索增强生成(第 4 个月深入)
  提示工程 = 优化 Prompt(第 2 周学)

Part B 小结

LLM 能力边界 = Agent 设计的出发点

模型擅长 → 直接让模型做(省成本、省开发)
模型不擅长 → 设计工具补偿(Function Calling / RAG)

这就是 Agent 的核心价值:
  Agent = LLM(理解+生成的大脑)+ Tools(弥补弱项的手脚)
  
今天你测试了边界,第 2 个月你会学会怎么用 Function Calling 补偿
今天你做了语义搜索(Part C),第 4 个月会深入 RAG

4. Part C:语义搜索 Demo —— RAG 雏形(50 分钟)

这是本周最重要的实验。语义搜索是 RAG(检索增强生成)的核心组件。理解了它,你就理解了 RAG 的 70%。

4.1 为什么需要语义搜索(5 分钟)

传统搜索(关键词匹配)的问题:

用户搜索: "怎么管理代码版本"
传统搜索: 找包含"管理"、"代码"、"版本"的文档
  → 可能找到"版本管理规范文档"
  → 但找不到"Git 是分布式版本控制系统"(没有"管理代码版本"这几个字)

语义搜索: 理解"管理代码版本"的意思
  → 找到"Git 是分布式版本控制系统"(语义匹配)
  → 找到"代码仓库的分支策略"(语义相关)

技术原理:
  文本 → Embedding API → 高维向量(如 1536 维)
  向量之间的余弦相似度 = 语义相似度
  相似度越高 → 语义越接近

这就是 Day 3 学的 Embedding 的实际应用

4.2 完整语义搜索引擎(25 分钟)

# code/semantic_search.py
"""语义搜索引擎:用 Embedding 实现文本检索(RAG 雏形)"""
import os
import json
import time
import numpy as np
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

# ============================================================
# API 配置(选择你有的 Key)
# ============================================================

# 方案 A: OpenAI(需要 OPENAI_API_KEY)
# 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: 智谱 AI
# 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 向量"""
    resp = client.embeddings.create(model=EMBEDDING_MODEL, input=text)
    return resp.data[0].embedding

def get_embeddings_batch(texts):
    """批量获取 Embedding(减少 API 调用次数)"""
    resp = client.embeddings.create(model=EMBEDDING_MODEL, input=texts)
    return [item.embedding for item in resp.data]

def cosine_similarity(a, b):
    """余弦相似度(Day 3 学过)"""
    a_arr = np.array(a)
    b_arr = np.array(b)
    return np.dot(a_arr, b_arr) / (np.linalg.norm(a_arr) * np.linalg.norm(b_arr))


# ============================================================
# 知识库:20 条关于编程和 AI 的句子
# ============================================================
knowledge_base = [
    # 编程语言
    "Python 是一种解释型高级编程语言,以简洁易读著称,广泛用于数据科学和 AI",
    "JavaScript 是网页交互的核心语言,运行在浏览器中,也可用于后端(Node.js)",
    "Java 是面向对象的跨平台语言,广泛用于企业级开发和 Android 应用",
    "Kotlin 是 Android 官方推荐的编程语言,与 Java 100% 互操作",
    "Rust 注重内存安全和并发性能,无垃圾回收,适合系统编程",
    "Go 语言由 Google 开发,擅长并发编程,适合微服务和云原生",
    
    # 开发工具
    "Git 是分布式版本控制系统,用于跟踪代码变更和团队协作",
    "Docker 是容器化部署工具,实现环境一致性和快速部署",
    "VS Code 是微软开发的轻量级代码编辑器,支持丰富的插件生态",
    "CI/CD 是持续集成和持续部署的缩写,用于自动化构建和发布",
    
    # 架构与模式
    "REST API 使用 HTTP 协议进行通信,是无状态的,适合 Web 服务",
    "微服务架构将应用拆分为独立的小服务,每个服务可独立部署",
    "函数式编程强调纯函数和不可变性,减少副作用",
    
    # AI/ML
    "机器学习是让计算机从数据中学习模式,包括监督学习和无监督学习",
    "深度学习使用多层神经网络进行学习,是机器学习的子集",
    "自然语言处理(NLP)研究计算机理解和生成人类语言",
    "Transformer 架构是现代大语言模型的基础,使用自注意力机制",
    "RAG(检索增强生成)通过搜索外部知识库来增强 LLM 的回答质量",
    "向量数据库专门存储和检索高维向量,如 Pinecone、Milvus、Weaviate",
    "Embedding 是将文本转换为高维向量的技术,使语义相似的文本向量也相似",
]

print(f"知识库大小: {len(knowledge_base)} 条")


# ============================================================
# 步骤 1: 预计算知识库 Embedding(带缓存)
# ============================================================

CACHE_FILE = "code/kb_embeddings.json"

def build_index():
    """构建知识库 Embedding 索引(带文件缓存)"""
    # 检查缓存
    if os.path.exists(CACHE_FILE):
        print("发现缓存,加载中...")
        with open(CACHE_FILE, "r", encoding="utf-8") as f:
            cached = json.load(f)
        if len(cached) == len(knowledge_base):
            print(f"缓存有效,跳过 API 调用")
            return [(item["text"], item["embedding"]) for item in cached]
    
    # 无缓存,调用 API
    print("正在计算知识库 Embedding(首次运行需要一些时间)...")
    embeddings = []
    
    # 批量获取(每次最多 10 条)
    batch_size = 10
    for i in range(0, len(knowledge_base), batch_size):
        batch = knowledge_base[i:i + batch_size]
        batch_embs = get_embeddings_batch(batch)
        for text, emb in zip(batch, batch_embs):
            embeddings.append((text, emb))
            print(f"  [{i + len(batch)}] {text[:40]}...")
        time.sleep(0.3)  # 避免限流
    
    # 保存缓存
    cache_data = [{"text": t, "embedding": e} for t, e in embeddings]
    with open(CACHE_FILE, "w", encoding="utf-8") as f:
        json.dump(cache_data, f)
    print(f"缓存已保存到 {CACHE_FILE}")
    
    return embeddings


# ============================================================
# 步骤 2: 搜索函数
# ============================================================

def search(query, kb_index, top_k=5, threshold=0.3):
    """
    语义搜索
    Args:
        query: 搜索词
        kb_index: 知识库索引 [(text, embedding), ...]
        top_k: 返回前 K 条结果
        threshold: 相似度阈值,低于此值不返回
    Returns:
        [(text, score), ...] 按相似度降序
    """
    query_emb = get_embedding(query)
    
    results = []
    for text, emb in kb_index:
        score = cosine_similarity(query_emb, emb)
        if score >= threshold:
            results.append((text, score))
    
    results.sort(key=lambda x: x[1], reverse=True)
    return results[:top_k]


def print_results(query, results):
    """格式化输出搜索结果"""
    print(f"\n搜索: '{query}'")
    print("─" * 55)
    
    if not results:
        print("  未找到相关结果(相似度低于阈值)")
        return
    
    for i, (text, score) in enumerate(results, 1):
        # 用方块字符画相似度柱状图
        bar_len = int(score * 30)
        bar = "█" * bar_len + "░" * (30 - bar_len)
        print(f"  {i}. [{score:.4f}] {bar}")
        print(f"     {text}")
    print()


# ============================================================
# 步骤 3: 运行预设查询
# ============================================================

print("\n" + "=" * 60)
print("语义搜索引擎")
print("=" * 60)

# 构建索引
kb_index = build_index()
print(f"\n索引就绪: {len(kb_index)} 条知识\n")

# 预设查询
test_queries = [
    "怎么管理代码版本",           # 应该匹配 Git
    "手机 App 开发用什么语言",     # 应该匹配 Kotlin / Java
    "AI 相关的技术",              # 应该匹配 ML/DL/NLP/Transformer
    "怎么部署应用",               # 应该匹配 Docker / CI-CD
    "什么是向量数据库",           # 应该匹配 Pinecone/Milvus
    "哪种语言适合写系统底层",      # 应该匹配 Rust
    "网页开发用什么",             # 应该匹配 JavaScript
    "怎么让 AI 回答更准确",       # 应该匹配 RAG
]

print("预设查询测试:")
print("=" * 60)

for query in test_queries:
    results = search(query, kb_index, top_k=3)
    print_results(query, results)
    time.sleep(0.3)

# ============================================================
# 步骤 4: Token 量统计与成本估算
# ============================================================

print("=" * 60)
print("成本分析")
print("=" * 60)

# 阿里百炼 text-embedding-v3 定价: 0.0007 元/千 Token
EMBEDDING_PRICE = 0.0007  # 元/千Token

# 知识库 Embedding 成本
kb_total_chars = sum(len(t) for t in knowledge_base)
kb_total_tokens = kb_total_chars // 2  # 粗略估算:中文约 2 字符/Token
kb_cost = kb_total_tokens / 1000 * EMBEDDING_PRICE

print(f"知识库: {len(knowledge_base)} 条, 约 {kb_total_tokens} Token")
print(f"索引成本(一次性): ¥{kb_cost:.4f}")
print(f"每次查询成本: ¥{10 / 1000 * EMBEDDING_PRICE:.6f}(查询约 10 Token)")

daily_queries = 100
monthly_cost = daily_queries * 30 * 10 / 1000 * EMBEDDING_PRICE
print(f"假设每天 {daily_queries} 次查询:")
print(f"  月 Embedding 成本: ¥{monthly_cost:.2f}")
print(f"  (索引成本只需付一次,后续从缓存读取)")

# ============================================================
# 步骤 5: 交互式搜索
# ============================================================

print("\n" + "=" * 60)
print("交互式搜索(输入 q 退出)")
print("=" * 60)

while True:
    query = input("\n搜索词> ").strip()
    if query.lower() == 'q' or not query:
        break
    
    results = search(query, kb_index, top_k=5)
    print_results(query, results)

print("\n再见!")

运行

python code/semantic_search.py

预期输出(节选):

知识库大小: 20 条
发现缓存,加载中...
缓存有效,跳过 API 调用

索引就绪: 20 条

============================================================
语义搜索引擎
============================================================
预设查询测试:
============================================================

搜索: '怎么管理代码版本'
───────────────────────────────────────────────────────
  1. [0.7834] ████████████████████░░░░░░░░░░░░░
     Git 是分布式版本控制系统,用于跟踪代码变更和团队协作
  2. [0.6521] ████████████████░░░░░░░░░░░░░░░░
     CI/CD 是持续集成和持续部署的缩写,用于自动化构建和发布
  3. [0.5890] ██████████████░░░░░░░░░░░░░░░░░░
     微服务架构将应用拆分为独立的小服务,每个服务可独立部署

搜索: '手机 App 开发用什么语言'
───────────────────────────────────────────────────────
  1. [0.7512] ███████████████████░░░░░░░░░░░░░
     Kotlin 是 Android 官方推荐的编程语言,与 Java 100% 互操作
  2. [0.6803] ████████████████░░░░░░░░░░░░░░░░
     Java 是面向对象的跨平台语言,广泛用于企业级开发和 Android 应用
  3. [0.5234] █████████████░░░░░░░░░░░░░░░░░░░
     React 是 Facebook 开发的前端 UI 框架
...

4.3 语义搜索 vs 关键词搜索对比实验(8 分钟)

# code/search_comparison.py
"""语义搜索 vs 关键词搜索对比"""
import re

def keyword_search(query, knowledge_base, top_k=3):
    """最简单的关键词搜索:计算重叠词数"""
    query_words = set(re.findall(r'\w+', query.lower()))
    results = []
    
    for text in knowledge_base:
        text_words = set(re.findall(r'\w+', text.lower()))
        overlap = len(query_words & text_words)
        # Jaccard 相似度
        union = len(query_words | text_words)
        score = overlap / union if union > 0 else 0
        results.append((text, score))
    
    results.sort(key=lambda x: x[1], reverse=True)
    return results[:top_k]

# 测试用例:专门设计关键词不匹配但语义匹配的查询
test_cases = [
    {
        "query": "怎么管理代码版本",
        "note": "关键词不匹配'Git',但语义搜索应能找到",
    },
    {
        "query": "容器化技术",
        "note": "关键词不匹配'Docker',但语义搜索应能找到",
    },
    {
        "query": "让机器理解人话",
        "note": "关键词不匹配'NLP',但语义搜索应能找到",
    },
]

print("=" * 60)
print("语义搜索 vs 关键词搜索")
print("=" * 60)

for case in test_cases:
    query = case["query"]
    print(f"\n查询: '{query}'")
    print(f"说明: {case['note']}")
    print("─" * 55)
    
    print("  [关键词搜索]")
    kw_results = keyword_search(query, knowledge_base)
    for i, (text, score) in enumerate(kw_results, 1):
        print(f"    {i}. [{score:.4f}] {text[:50]}")
    
    print("\n  [语义搜索]")
    sem_results = search(query, kb_index, top_k=3)
    for i, (text, score) in enumerate(sem_results, 1):
        print(f"    {i}. [{score:.4f}] {text[:50]}")
    
    print()

对比分析

查询关键词搜索找到语义搜索找到差异
“怎么管理代码版本”可能找不到 Git找到 Git语义搜索理解意图
“容器化技术”可能找不到 Docker找到 Docker语义搜索理解同义
“让机器理解人话”可能找不到 NLP找到 NLP语义搜索理解口语

4.4 从语义搜索到 RAG 的距离(12 分钟)

语义搜索只是 RAG 的一部分。完整的 RAG 流程:

完整 RAG 流程:

用户提问
   ↓
[1] 查询理解: 用户的真实意图是什么?
   ↓
[2] 检索(你刚做的): 从知识库搜索相关文档
   ↓
[3] 重排序: 对搜索结果按相关性重新排序(可选)
   ↓
[4] 上下文构建: 把搜索结果组装成 Prompt
   ↓
[5] 生成: LLM 基于搜索结果回答问题
   ↓
[6] 引用标注: 标注回答来自哪个文档(可选)
   ↓
返回回答

你今天做了: [2] 检索
还差什么:
  [1] 查询理解 — 第 2 个月学(用 LLM 重写查询)
  [3] 重排序 — 第 4 个月学(用 Cross-Encoder 重排)
  [4] 上下文构建 — Part F 会实现
  [5] 生成 — Part F 会实现
  [6] 引用标注 — Part F 会实现

Part F 的智能知识助手会实现 [4][5][6]
→ 这就是一个最小可用的 RAG 系统

上下文构建示例

# 演示:如何把搜索结果组装成 Prompt

def build_rag_prompt(query, search_results):
    """把搜索结果组装成 RAG Prompt"""
    
    # 构建上下文
    context = "\n".join(
        f"[{i+1}] {text}"
        for i, (text, score) in enumerate(search_results)
    )
    
    prompt = f"""基于以下参考资料回答用户问题。

参考资料:
{context}

用户问题: {query}

要求:
1. 只基于参考资料回答,不要编造
2. 如果参考资料中没有答案,说"根据现有资料无法回答"
3. 在回答末尾标注引用来源(如 [1][3])"""

    return prompt

# 示例
query = "什么是 Transformer?"
results = search(query, kb_index, top_k=3)
prompt = build_rag_prompt(query, results)

print("RAG Prompt 示例:")
print("─" * 55)
print(prompt)
print("─" * 55)
print(f"\nPrompt 长度: {len(prompt)} 字符")
print(f"这就是 Part F 智能知识助手的核心逻辑")

Part C 小结

语义搜索 = RAG 的检索组件

你今天实现的东西:
  ✓ 知识库 Embedding 预计算 + 文件缓存
  ✓ 批量 Embedding API 调用
  ✓ 余弦相似度计算 + 排序
  ✓ 相似度阈值过滤
  ✓ 可视化相似度柱状图
  ✓ 交互式搜索
  ✓ 成本估算
  ✓ 语义搜索 vs 关键词搜索对比

你还差什么(第 4 个月会补):
  ✗ 向量数据库(替代列表存储,支持百万级文档)
  ✗ 查询重写(用 LLM 理解用户意图)
  ✗ 重排序(Cross-Encoder 精排)
  ✗ 分块策略(长文档如何切分)
  ✗ 多路召回(关键词 + 语义 + 结构化)

但核心原理你已经掌握了——RAG 的本质就是"搜索 + 生成"

5. Part D:参数组合实战(30 分钟)

Day 5 你学了参数原理,今天用真实任务验证:不同参数组合对输出质量的影响到底有多大。

5.1 三大场景参数矩阵测试(20 分钟)

# code/param_combination.py
"""参数组合实战:为不同任务找到最佳参数"""
import os
import time
import json
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com"
)
MODEL = "deepseek-chat"

def ask(prompt, temp=0.7, max_tokens=300, response_format=None, 
        frequency_penalty=0.0, presence_penalty=0.0):
    """带完整参数的调用"""
    kwargs = {
        "model": MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "temperature": temp,
        "max_tokens": max_tokens,
        "frequency_penalty": frequency_penalty,
        "presence_penalty": presence_penalty,
    }
    if response_format:
        kwargs["response_format"] = {"type": "json_object"}
    
    response = client.chat.completions.create(**kwargs)
    return response.choices[0].message.content, response.usage.total_tokens

# ============================================================
# 场景 A: JSON 数据提取(需要确定性 + 结构化)
# ============================================================
print("=" * 60)
print("场景 A: JSON 数据提取")
print("=" * 60)

extract_prompt = """从以下商品描述中提取信息,输出 JSON 格式。

商品描述: "华为 Mate 60 Pro,12GB+512GB,售价 6999 元,支持卫星通话,搭载麒麟 9000S 芯片"

输出格式: {"brand": "", "model": "", "ram": "", "storage": "", "price": 0, "features": [], "chip": ""}
只输出 JSON,不要其他内容。"""

configs_a = [
    {"label": "T=0.7 默认", "temp": 0.7},
    {"label": "T=0 + JSON mode", "temp": 0, "response_format": True},
    {"label": "T=0.3 无 JSON mode", "temp": 0.3},
]

for cfg in configs_a:
    result, tokens = ask(
        extract_prompt, 
        temp=cfg["temp"],
        response_format=cfg.get("response_format"),
        max_tokens=200
    )
    # 检查是否是合法 JSON
    try:
        json.loads(result)
        valid = "合法 JSON"
    except:
        valid = "非法 JSON"
    
    print(f"\n  [{cfg['label']}] Token: {tokens} | {valid}")
    print(f"  输出: {result[:120]}...")
    time.sleep(0.5)

print(f"""
分析:
- T=0 + JSON mode: 输出最短、100% 合法 JSON、Token 最少
- T=0.7 默认: 可能带多余文字、偶尔 JSON 解析失败
- 结论: 信息提取任务 → T=0 + JSON mode 是最优解
""")

# ============================================================
# 场景 B: 创意文案(需要多样性 + 创意)
# ============================================================
print(f"\n{'='*60}")
print("场景 B: 创意文案")
print("=" * 60)

creative_prompt = "为一款 AI 编程助手写一句广告语,要求有创意、不超过 20 字。"

configs_b = [
    {"label": "T=0 保守", "temp": 0},
    {"label": "T=0.7 默认", "temp": 0.7},
    {"label": "T=1.2 + penalty", "temp": 1.2, "frequency_penalty": 0.5, "presence_penalty": 0.5},
]

for cfg in configs_b:
    print(f"\n  [{cfg['label']}]")
    # 每种配置生成 3 条,看多样性
    outputs = []
    for _ in range(3):
        result, tokens = ask(
            creative_prompt,
            temp=cfg["temp"],
            frequency_penalty=cfg.get("frequency_penalty", 0),
            presence_penalty=cfg.get("presence_penalty", 0),
            max_tokens=50
        )
        outputs.append(result.strip())
        time.sleep(0.3)
    
    unique = len(set(outputs))
    for i, out in enumerate(outputs):
        print(f"    {i+1}. {out}")
    print(f"    多样性: {unique}/3 条不同")
    time.sleep(0.5)

print(f"""
分析:
- T=0: 3 次输出几乎相同 → 无创意
- T=0.7: 有一定多样性,但方向类似
- T=1.2 + penalty: 3 条完全不同,创意更丰富
- 结论: 创意任务 → 高 T + penalty,但 T 不要超过 1.5
""")

# ============================================================
# 场景 C: 代码生成(需要正确性 + 确定性)
# ============================================================
print(f"\n{'='*60}")
print("场景 C: 代码生成")
print("=" * 60)

code_prompt = "用 Python 写一个函数,判断字符串是否是回文。包含类型注解和文档字符串。"

configs_c = [
    {"label": "T=0 确定性", "temp": 0},
    {"label": "T=0.5 平衡", "temp": 0.5},
    {"label": "T=1.0 随机", "temp": 1.0},
]

for cfg in configs_c:
    result, tokens = ask(code_prompt, temp=cfg["temp"], max_tokens=300)
    # 简单检查代码质量
    has_type_hint = "->" in result
    has_docstring = '"""' in result or "'''" in result
    has_def = "def " in result
    
    quality = []
    if has_def: quality.append("有函数定义")
    if has_type_hint: quality.append("有类型注解")
    if has_docstring: quality.append("有文档字符串")
    
    print(f"\n  [{cfg['label']}] Token: {tokens}")
    print(f"  质量检查: {', '.join(quality) if quality else '未通过基本检查'}")
    print(f"  代码:\n{result[:200]}...")
    time.sleep(0.5)

print(f"""
分析:
- T=0: 代码最稳定、质量最一致
- T=0.5: 偶有变化,但基本正确
- T=1.0: 可能出现语法错误或奇怪的实现
- 结论: 代码生成 → T=0 是唯一推荐值
""")

运行

python code/param_combination.py

5.2 参数选择决策矩阵(10 分钟)

根据实验结果,完善你的参数选择表:

┌──────────────┬───────┬───────┬────────────┬────────────┬───────────┬─────────────────┐
│ 场景         │ Temp  │ Top-P │ freq_pen   │ pres_pen   │ max_tokens│ 其他             │
├──────────────┼───────┼───────┼────────────┼────────────┼───────────┼─────────────────┤
│ JSON 提取    │ 0     │ 1.0   │ 0          │ 0          │ 200-500   │ response_format │
│ 代码生成     │ 0     │ 1.0   │ 0          │ 0          │ 1000-2000 │ -               │
│ 分类/标注     │ 0     │ 1.0   │ 0          │ 0          │ 50-100    │ -               │
│ 翻译         │ 0.3   │ 0.9   │ 0          │ 0          │ 500-1000  │ -               │
│ 摘要         │ 0.3   │ 0.9   │ 0          │ 0          │ 200-500   │ -               │
│ 对话/问答    │ 0.6   │ 0.9   │ 0.3        │ 0.3        │ 500-1000  │ -               │
│ 创意文案     │ 0.9   │ 0.95  │ 0.3        │ 0.3        │ 200-500   │ -               │
│ 头脑风暴     │ 1.2   │ 0.95  │ 0.5        │ 0.8        │ 500-1000  │ -               │
│ 长文写作     │ 0.7   │ 0.9   │ 0.2        │ 0.2        │ 2000-4000 │ -               │
└──────────────┴───────┴───────┴────────────┴────────────┴───────────┴─────────────────┘

验证结果:
- JSON 提取: T=0 + JSON mode → Token 最少、格式 100% 合法
- 创意文案: T=1.2 + penalty → 多样性最高
- 代码生成: T=0 → 质量最稳定
- 这些和 Day 5 的推荐表完全一致 → 理论得到验证

成本影响:
- 同一任务,T=0 + JSON mode 比 T=0.7 默认省 30-50% Token
- 原因: JSON 不需要自然语言包装,直接输出结构化数据
- 大规模场景下,参数优化 = 成本优化

Part D 小结

参数组合不是"调到最好",而是"为场景选对"

3 个验证结论:
1. 信息提取 → T=0 + JSON mode(最省 Token、最可靠)
2. 创意文案 → T=1.2 + penalty(最多样)
3. 代码生成 → T=0(最稳定)

实际开发流程:
  识别任务类型 → 查参数推荐表 → 调用 → 验证效果 → 微调
  Day 5 的 auto_param.py 已经实现了这个流程
  今天你验证了它推荐的参数确实是最优的

6. Part E:模型 × 参数 × Token 三维交叉实验(25 分钟)

把 Day 4(模型选择)、Day 5(参数)、Day 6 Part A(Token 优化)三个维度交叉,找到不同场景的最优配置。

6.1 实验设计(5 分钟)

三个维度:
  模型: deepseek-chat(便宜)vs deepseek-reasoner(更强推理)
  参数: T=0(确定性)vs T=0.7(默认)
  Token 优化: 英文 Prompt vs 中文 Prompt

测试任务: 同一个任务在不同配置下的效果和成本

预期发现:
  - 便宜模型 + 优化参数 → 可能媲美贵模型 + 默认参数
  - 英文 Prompt → 省 Token 但效果不差
  - T=0 → 对提取/代码任务效果显著提升

6.2 三维交叉实验脚本(15 分钟)

# code/cross_experiment.py
"""三维交叉实验:模型 × 参数 × Token 优化"""
import os
import time
import json
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com"
)

# ============================================================
# 实验配置
# ============================================================

models = [
    ("deepseek-chat", "DS-Chat"),
    # 如果有 reasoner 权限可以加上:
    # ("deepseek-reasoner", "DS-Reasoner"),
]

tasks = [
    {
        "name": "JSON提取",
        "prompt_cn": "从以下文本提取信息为JSON: 张三,28岁,北京,程序员。输出 {\"name\":\"\",\"age\":0,\"city\":\"\",\"job\":\"\"}",
        "prompt_en": 'Extract info as JSON from: "Zhang San, 28, Beijing, programmer". Output {"name":"","age":0,"city":"","job":""}',
        "best_params": {"temperature": 0, "response_format": {"type": "json_object"}, "max_tokens": 100},
        "default_params": {"temperature": 0.7, "max_tokens": 200},
        "evaluator": lambda result: '"name"' in result and '"age"' in result,
    },
    {
        "name": "代码生成",
        "prompt_cn": "用Python写一个判断回文的函数,带类型注解。",
        "prompt_en": "Write a Python function to check if a string is a palindrome. Include type hints.",
        "best_params": {"temperature": 0, "max_tokens": 300},
        "default_params": {"temperature": 0.7, "max_tokens": 400},
        "evaluator": lambda result: "def " in result and "bool" in result.lower(),
    },
    {
        "name": "摘要总结",
        "prompt_cn": "用一句话总结: Python是一种解释型高级编程语言,以简洁易读著称,广泛用于数据科学、人工智能和Web开发。它的设计哲学强调代码可读性,语法允许开发者用更少的代码行表达概念。",
        "prompt_en": "Summarize in one sentence: Python is an interpreted high-level programming language known for its simplicity and readability, widely used in data science, AI, and web development. Its design philosophy emphasizes code readability, allowing developers to express concepts in fewer lines of code.",
        "best_params": {"temperature": 0.3, "max_tokens": 100},
        "default_params": {"temperature": 0.7, "max_tokens": 200},
        "evaluator": lambda result: len(result) < 200 and len(result) > 10,
    },
]

# ============================================================
# 运行实验
# ============================================================

print("=" * 60)
print("三维交叉实验: 模型 × 参数 × 语言")
print("=" * 60)

results = []

for model_id, model_name in models:
    for task in tasks:
        for lang, prompt_key in [("中文", "prompt_cn"), ("英文", "prompt_en")]:
            for param_type in ["best", "default"]:
                params = task[f"{param_type}_params"]
                prompt = task[prompt_key]
                
                config_label = f"{model_name} | {task['name']} | {lang} | {param_type}"
                
                try:
                    start = time.time()
                    response = client.chat.completions.create(
                        model=model_id,
                        messages=[{"role": "user", "content": prompt}],
                        **params
                    )
                    elapsed = time.time() - start
                    output = response.choices[0].message.content
                    tokens = response.usage.total_tokens
                    success = task["evaluator"](output)
                    
                    result = {
                        "config": config_label,
                        "model": model_name,
                        "task": task["name"],
                        "lang": lang,
                        "params": param_type,
                        "output": output,
                        "tokens": tokens,
                        "time": round(elapsed, 2),
                        "success": success,
                    }
                    results.append(result)
                    
                    status = "OK" if success else "FAIL"
                    print(f"  [{status}] {config_label}")
                    print(f"        Token: {tokens} | 耗时: {elapsed:.2f}s")
                    print(f"        输出: {output[:80]}...")
                    
                except Exception as e:
                    print(f"  [ERR] {config_label}: {e}")
                    results.append({
                        "config": config_label,
                        "error": str(e),
                        "success": False,
                    })
                
                time.sleep(0.5)

# ============================================================
# 分析结果
# ============================================================

print(f"\n{'=' * 60}")
print("实验结果分析")
print(f"{'=' * 60}")

# 按任务分组对比
for task in tasks:
    task_name = task["name"]
    task_results = [r for r in results if r.get("task") == task_name and r.get("success") is not None]
    
    print(f"\n--- {task_name} ---")
    print(f"{'语言':<6} {'参数':<8} {'成功':>4} {'Token':>6} {'耗时':>6}")
    print("-" * 35)
    
    for lang in ["中文", "英文"]:
        for param_type in ["best", "default"]:
            matching = [r for r in task_results if r.get("lang") == lang and r.get("params") == param_type]
            if matching:
                r = matching[0]
                success = "Y" if r.get("success") else "N"
                tokens = r.get("tokens", 0)
                elapsed = r.get("time", 0)
                print(f"{lang:<6} {param_type:<8} {success:>4} {tokens:>6} {elapsed:>5.2f}s")

# 总结
print(f"\n{'=' * 60}")
print("总结")
print(f"{'=' * 60}")

success_best = sum(1 for r in results if r.get("params") == "best" and r.get("success"))
success_default = sum(1 for r in results if r.get("params") == "default" and r.get("success"))
total_best = sum(1 for r in results if r.get("params") == "best")
total_default = sum(1 for r in results if r.get("params") == "default")

tokens_best = sum(r.get("tokens", 0) for r in results if r.get("params") == "best")
tokens_default = sum(r.get("tokens", 0) for r in results if r.get("params") == "default")

print(f"最优参数: 成功 {success_best}/{total_best}, 总 Token {tokens_best}")
print(f"默认参数: 成功 {success_default}/{total_default}, 总 Token {tokens_default}")
if tokens_default > 0:
    saving = (1 - tokens_best / tokens_default) * 100
    print(f"Token 节省: {saving:.1f}%")

# 保存结果
with open("code/cross_experiment_results.json", "w", encoding="utf-8") as f:
    json.dump(results, f, ensure_ascii=False, indent=2)
print(f"\n结果已保存到 code/cross_experiment_results.json")

运行

python code/cross_experiment.py

6.3 实验结论分析(5 分钟)

根据实验结果填写结论表:

实验结论:

| 维度 | 发现 | 实际意义 |
|------|------|---------|
| 参数影响 | T=0 vs T=0.7 | 提取/代码任务 T=0 成功率高 20-30% |
| 语言影响 | 中文 vs 英文 | 英文省 30-50% Token,质量相当 |
| 交叉效应 | 最优参数+英文 | Token 最少 + 成功率最高 |
| 成本影响 | 最优 vs 默认 | 综合 Token 节省 30-50% |

核心发现:
  "模型选择决定上限,参数选择决定下限,Token 优化决定成本"
  
  - 选错模型 → 上限不够(再怎么调参也不行)
  - 选错参数 → 下限太低(输出不稳定)
  - 不优化 Token → 成本浪费(但功能正常)
  
  三者优先级: 模型 > 参数 > Token
  
  但在成本敏感场景:
  Token 优化的 ROI 最高(投入最少,效果最直接)

Part E 小结

三维交叉 = 实际开发中的完整决策过程

实际场景举例:
  "用户要做一个客服 Agent,每天 1 万次调用"
  
  Step 1: 选模型 → deepseek-chat(够用且便宜)
  Step 2: 选参数 → T=0.6 + penalty=0.3(对话场景)
  Step 3: 优化 Token → 英文 System Prompt + 对话历史管理
  
  月成本估算:
    不优化: 10000 × 30 × 500 Token × ¥0.001/1K = ¥150/月
    优化后: 10000 × 30 × 300 Token × ¥0.001/1K = ¥90/月
    省 ¥60/月(40%)
  
  如果是 10 万次/天 → 每月省 ¥600
  如果是 100 万次/天 → 每月省 ¥6000

7. Part F:综合项目——智能知识助手(25 分钟)

把本周学的一切组装成一个项目:模型路由 + 参数推荐 + Token 优化 + 语义搜索 + RAG 生成。这是一个最小可用的智能知识助手。

7.1 项目架构(5 分钟)

智能知识助手架构:

用户提问
   ↓
┌─────────────────────────────────────┐
│  1. 任务分类(用 LLM 判断问题类型)  │
│     → 知识问答 / 代码 / 翻译 / 其他  │
└──────────────┬──────────────────────┘
               ↓
┌─────────────────────────────────────┐
│  2. 知识检索(如果是知识问答)        │
│     → 语义搜索知识库                  │
│     → 返回 Top-3 相关文档             │
└──────────────┬──────────────────────┘
               ↓
┌─────────────────────────────────────┐
│  3. 参数推荐(根据任务类型选参数)     │
│     → 知识问答: T=0.3                │
│     → 代码: T=0                      │
│     → 翻译: T=0.3                    │
└──────────────┬──────────────────────┘
               ↓
┌─────────────────────────────────────┐
│  4. 生成回答                          │
│     → 知识问答: 搜索结果 + 问题 → LLM │
│     → 其他: 直接 → LLM               │
└──────────────┬──────────────────────┘
               ↓
         返回回答 + 引用来源

集成的本周知识:
  Day 1: API 调用 ✓
  Day 3: Embedding + 语义搜索 ✓
  Day 4: 模型路由思路 ✓(简化版:用任务分类替代)
  Day 5: 参数推荐 ✓
  Day 6: Token 优化(英文 Prompt)+ RAG 生成 ✓

7.2 完整实现(15 分钟)

# code/knowledge_assistant.py
"""智能知识助手:集成语义搜索 + RAG 生成 + 参数推荐"""
import os
import json
import time
import numpy as np
from enum import Enum
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()

# ============================================================
# 配置:用 DeepSeek 做生成,用阿里百炼做 Embedding
# ============================================================

# 生成模型
gen_client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com"
)
GEN_MODEL = "deepseek-chat"

# Embedding 模型
emb_client = OpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
EMBEDDING_MODEL = "text-embedding-v3"


# ============================================================
# 任务类型枚举
# ============================================================

class TaskType(Enum):
    KNOWLEDGE_QA = "knowledge_qa"      # 知识问答(需要搜索)
    CODE = "code"                       # 代码生成
    TRANSLATION = "translation"         # 翻译
    CREATIVE = "creative"               # 创意写作
    GENERAL = "general"                 # 通用对话


# ============================================================
# 参数推荐表(来自 Day 5 + Day 6 Part D 验证)
# ============================================================

PARAM_PRESETS = {
    TaskType.KNOWLEDGE_QA: {
        "temperature": 0.3,
        "max_tokens": 500,
        "reason": "知识问答需要准确性,T=0.3 保证稳定但允许少量表述变化"
    },
    TaskType.CODE: {
        "temperature": 0,
        "max_tokens": 1000,
        "reason": "代码需要确定性和正确性,T=0 最大化正确概率"
    },
    TaskType.TRANSLATION: {
        "temperature": 0.3,
        "max_tokens": 1000,
        "reason": "翻译需要准确但允许少量表述差异"
    },
    TaskType.CREATIVE: {
        "temperature": 0.9,
        "max_tokens": 500,
        "frequency_penalty": 0.3,
        "presence_penalty": 0.3,
        "reason": "创意需要多样性,高 T + penalty"
    },
    TaskType.GENERAL: {
        "temperature": 0.6,
        "max_tokens": 500,
        "reason": "通用对话,平衡准确和自然"
    },
}


# ============================================================
# 工具函数
# ============================================================

def get_embedding(text):
    """获取 Embedding"""
    resp = emb_client.embeddings.create(model=EMBEDDING_MODEL, input=text)
    return resp.data[0].embedding

def cosine_sim(a, b):
    """余弦相似度"""
    a_arr, b_arr = np.array(a), np.array(b)
    return np.dot(a_arr, b_arr) / (np.linalg.norm(a_arr) * np.linalg.norm(b_arr))

def llm_call(messages, temperature=0.7, max_tokens=500, **kwargs):
    """统一的 LLM 调用"""
    response = gen_client.chat.completions.create(
        model=GEN_MODEL,
        messages=messages,
        temperature=temperature,
        max_tokens=max_tokens,
        **kwargs
    )
    return response.choices[0].message.content, response.usage.total_tokens


# ============================================================
# 知识库(复用 Part C 的)
# ============================================================

KNOWLEDGE_BASE = [
    "Python 是一种解释型高级编程语言,以简洁易读著称,广泛用于数据科学和 AI",
    "JavaScript 是网页交互的核心语言,运行在浏览器中,也可用于后端(Node.js)",
    "Java 是面向对象的跨平台语言,广泛用于企业级开发和 Android 应用",
    "Kotlin 是 Android 官方推荐的编程语言,与 Java 100% 互操作",
    "Rust 注重内存安全和并发性能,无垃圾回收,适合系统编程",
    "Go 语言由 Google 开发,擅长并发编程,适合微服务和云原生",
    "Git 是分布式版本控制系统,用于跟踪代码变更和团队协作",
    "Docker 是容器化部署工具,实现环境一致性和快速部署",
    "REST API 使用 HTTP 协议进行通信,是无状态的,适合 Web 服务",
    "微服务架构将应用拆分为独立的小服务,每个服务可独立部署",
    "机器学习是让计算机从数据中学习模式,包括监督学习和无监督学习",
    "深度学习使用多层神经网络进行学习,是机器学习的子集",
    "自然语言处理(NLP)研究计算机理解和生成人类语言",
    "Transformer 架构是现代大语言模型的基础,使用自注意力机制",
    "RAG(检索增强生成)通过搜索外部知识库来增强 LLM 的回答质量",
    "向量数据库专门存储和检索高维向量,如 Pinecone、Milvus、Weaviate",
    "Embedding 是将文本转换为高维向量的技术,使语义相似的文本向量也相似",
    "Function Calling 让 LLM 能调用外部工具,如计算器、API、数据库",
    "Prompt Engineering 是设计和优化提示词以改善 LLM 输出的技术",
    "Agent 是能自主决策和行动的 AI 系统,通常集成 LLM + 工具 + 记忆",
]


# ============================================================
# 智能知识助手
# ============================================================

class KnowledgeAssistant:
    """智能知识助手"""
    
    def __init__(self):
        self.kb_index = None
        self.call_history = []
        self._build_index()
    
    def _build_index(self):
        """构建知识库索引"""
        cache_file = "code/kb_embeddings.json"
        
        if os.path.exists(cache_file):
            with open(cache_file, "r", encoding="utf-8") as f:
                cached = json.load(f)
            if len(cached) == len(KNOWLEDGE_BASE):
                self.kb_index = [(item["text"], item["embedding"]) for item in cached]
                print(f"[助手] 知识库就绪: {len(self.kb_index)} 条(从缓存加载)")
                return
        
        print("[助手] 正在构建知识库索引...")
        self.kb_index = []
        for text in KNOWLEDGE_BASE:
            emb = get_embedding(text)
            self.kb_index.append((text, emb))
        
        cache_data = [{"text": t, "embedding": e} for t, e in self.kb_index]
        with open(cache_file, "w", encoding="utf-8") as f:
            json.dump(cache_data, f)
        print(f"[助手] 知识库就绪: {len(self.kb_index)} 条(新构建)")
    
    def classify_task(self, user_input):
        """用 LLM 分类任务类型"""
        classify_prompt = f"""判断以下用户输入属于哪个类别,只输出类别名:

类别:
- knowledge_qa: 关于编程/AI的技术知识问答
- code: 要求写代码
- translation: 翻译任务
- creative: 创意写作(文案、故事等)
- general: 通用对话

用户输入: "{user_input}"

类别:"""
        
        result, tokens = llm_call(
            [{"role": "user", "content": classify_prompt}],
            temperature=0,
            max_tokens=20
        )
        
        result = result.strip().lower()
        for t in TaskType:
            if t.value in result:
                return t
        return TaskType.GENERAL
    
    def search_knowledge(self, query, top_k=3):
        """语义搜索知识库"""
        query_emb = get_embedding(query)
        results = []
        
        for text, emb in self.kb_index:
            score = cosine_sim(query_emb, emb)
            results.append((text, score))
        
        results.sort(key=lambda x: x[1], reverse=True)
        return results[:top_k]
    
    def build_rag_prompt(self, query, search_results):
        """构建 RAG Prompt(用英文 System Prompt 省 Token)"""
        
        context = "\n".join(
            f"[{i+1}] {text}"
            for i, (text, _) in enumerate(search_results)
        )
        
        messages = [
            {
                "role": "system",
                "content": f"Answer based on the references below. "
                           f"If references don't contain the answer, say so. "
                           f"Cite sources like [1][2]. "
                           f"Reply in Chinese."
            },
            {
                "role": "user",
                "content": f"References:\n{context}\n\nQuestion: {query}"
            }
        ]
        return messages
    
    def answer(self, user_input):
        """回答用户问题"""
        print(f"\n{'='*60}")
        print(f"用户: {user_input}")
        print(f"{'='*60}")
        
        # Step 1: 任务分类
        task_type = self.classify_task(user_input)
        params = PARAM_PRESETS[task_type]
        print(f"\n[1] 任务分类: {task_type.value}")
        print(f"    推荐参数: T={params['temperature']}, max_tokens={params['max_tokens']}")
        print(f"    理由: {params['reason']}")
        
        # Step 2: 如果是知识问答,搜索知识库
        search_results = None
        if task_type == TaskType.KNOWLEDGE_QA:
            print(f"\n[2] 检索知识库...")
            search_results = self.search_knowledge(user_input)
            print(f"    找到 {len(search_results)} 条相关:")
            for i, (text, score) in enumerate(search_results, 1):
                print(f"    [{i}] [{score:.4f}] {text[:50]}...")
        else:
            print(f"\n[2] 非知识问答,跳过检索")
        
        # Step 3: 构建 Prompt
        if search_results:
            messages = self.build_rag_prompt(user_input, search_results)
        else:
            messages = [{"role": "user", "content": user_input}]
        
        # Step 4: 生成回答
        print(f"\n[3] 生成回答...")
        api_params = {k: v for k, v in params.items() if k != "reason"}
        answer, tokens = llm_call(messages, **api_params)
        
        print(f"\n[4] 回答:")
        print(f"    {answer}")
        print(f"\n    Token: {tokens}")
        
        if search_results:
            print(f"    引用来源: 知识库 #{[i+1 for i, _ in enumerate(search_results)]}")
        
        # 记录
        self.call_history.append({
            "input": user_input,
            "task_type": task_type.value,
            "searched": search_results is not None,
            "answer": answer,
            "tokens": tokens,
        })
        
        return answer
    
    def print_summary(self):
        """打印统计"""
        print(f"\n{'='*60}")
        print("智能知识助手 - 使用统计")
        print(f"{'='*60}")
        
        total = len(self.call_history)
        total_tokens = sum(r["tokens"] for r in self.call_history)
        qa_count = sum(1 for r in self.call_history if r["searched"])
        
        print(f"  总调用: {total}")
        print(f"  知识问答: {qa_count} 次(使用了语义搜索)")
        print(f"  总 Token: {total_tokens}")
        
        if total > 0:
            print(f"  平均 Token/次: {total_tokens / total:.0f}")
        
        print(f"\n  按任务类型:")
        type_stats = {}
        for r in self.call_history:
            t = r["task_type"]
            if t not in type_stats:
                type_stats[t] = {"count": 0, "tokens": 0}
            type_stats[t]["count"] += 1
            type_stats[t]["tokens"] += r["tokens"]
        
        for t, s in type_stats.items():
            print(f"    {t}: {s['count']}次, {s['tokens']} Token")


# ============================================================
# 运行测试
# ============================================================

if __name__ == "__main__":
    assistant = KnowledgeAssistant()
    
    # 模拟不同类型的用户问题
    test_questions = [
        # 知识问答(应该触发语义搜索)
        "什么是 Transformer 架构?",
        "RAG 是什么?有什么用?",
        "怎么管理代码版本?",
        "Embedding 是什么意思?",
        
        # 代码生成(不触发搜索)
        "用 Python 写一个快速排序函数",
        
        # 翻译(不触发搜索)
        "Translate to English: 今天天气真好",
        
        # 创意写作(不触发搜索)
        "写一句关于编程的诗",
        
        # 通用对话(不触发搜索)
        "你好,你是谁?",
    ]
    
    for q in test_questions:
        assistant.answer(q)
        time.sleep(0.5)
    
    # 统计
    assistant.print_summary()
    
    print(f"\n{'='*60}")
    print("项目总结")
    print(f"{'='*60}")
    print("""
这个智能知识助手集成了本周学到的所有核心知识:
  1. API 调用(Day 1)
  2. Embedding + 语义搜索(Day 3)
  3. 模型路由思路(Day 4 → 任务分类)
  4. 参数推荐(Day 5)
  5. Token 优化(Day 6 → 英文 System Prompt)
  6. RAG 生成(Day 6 → 搜索结果 + 生成)

它是一个最小可用的 RAG 系统:
  - 能自动判断是否需要搜索知识库
  - 能根据任务类型选择最优参数
  - 能基于搜索结果生成带引用的回答
  - 能统计 Token 使用量

后续扩展方向(第 2-4 个月):
  - 加入 Function Calling(让助手能调用外部工具)
  - 加入对话记忆(多轮对话上下文管理)
  - 用向量数据库替代列表存储(支持大规模知识库)
  - 加入重排序(提高搜索精度)
  - 加入评估系统(自动评分回答质量)
""")

运行

python code/knowledge_assistant.py

预期输出(节选):

[助手] 知识库就绪: 20 条(从缓存加载)

============================================================
用户: 什么是 Transformer 架构?
============================================================

[1] 任务分类: knowledge_qa
    推荐参数: T=0.3, max_tokens=500
    理由: 知识问答需要准确性,T=0.3 保证稳定但允许少量表述变化

[2] 检索知识库...
    找到 3 条相关:
    [1] [0.8234] Transformer 架构是现代大语言模型的基础,使用自注意力机制...
    [2] [0.6890] 深度学习使用多层神经网络进行学习,是机器学习的子集...
    [3] [0.6512] 自然语言处理(NLP)研究计算机理解和生成人类语言...

[3] 生成回答...

[4] 回答:
    Transformer 是一种神经网络架构,是现代大语言模型(如 GPT、BERT)的基础。
    它的核心创新是"自注意力机制"(Self-Attention),允许模型在处理每个词时
    同时关注输入序列中的所有其他词,从而更好地理解上下文和长距离依赖关系 [1]。
    
    与传统的 RNN 不同,Transformer 可以并行处理输入,训练效率更高。它也是
    深度学习领域的重要突破 [2],在自然语言处理 [3] 等领域取得了巨大成功。
    
    Token: 185
    引用来源: 知识库 #[1, 2, 3]

============================================================
用户: 用 Python 写一个快速排序函数
============================================================

[1] 任务分类: code
    推荐参数: T=0, max_tokens=1000
    理由: 代码需要确定性和正确性,T=0 最大化正确概率

[2] 非知识问答,跳过检索

[3] 生成回答...

[4] 回答:
    ```python
    def quicksort(arr: list[int]) -> list[int]:
        if len(arr) <= 1:
            return arr
        pivot = arr[len(arr) // 2]
        left = [x for x in arr if x < pivot]
        middle = [x for x in arr if x == pivot]
        right = [x for x in arr if x > pivot]
        return quicksort(left) + middle + quicksort(right)
    ```
    
    Token: 120
...

7.3 项目分析与扩展方向(5 分钟)

你刚构建的项目 vs 工业级 RAG 系统:

                    你的版本          工业级版本
知识库规模          20 条            10万-100万条
存储方式            JSON 文件         向量数据库
检索方法            余弦相似度        ANN + 重排序
查询理解            LLM 直接分类      查询重写 + 意图识别
上下文管理          无                对话历史 + 摘要
评估系统            无                自动评分 + A/B 测试
Function Calling    无                多工具协同
错误处理            基本               重试 + Fallback

但核心流程是一样的:
  用户提问 → 分类 → 检索 → 生成 → 返回

你的版本已经覆盖了 RAG 的核心链路
第 2-4 个月会逐步把"工业级"列的每一项补上

Part F 小结

智能知识助手 = Week 1 的毕业项目

你用一天学的东西,组装了一个能用的 AI 系统:
  - 它能理解你的问题(任务分类)
  - 它能搜索知识库(语义搜索)
  - 它能选择最优参数(参数推荐)
  - 它能基于知识回答(RAG 生成)
  - 它能标注引用来源

这就是 Agent 的雏形:
  感知(分类)→ 检索(搜索)→ 决策(选参数)→ 行动(生成)
  
第 2 个月你会给它加上"工具使用"(Function Calling)
第 3 个月你会给它加上"多步推理"(Agent 框架)
第 4 个月你会把"检索"升级为"生产级 RAG"
第 5 个月你会把它部署成一个真正的产品

但起点就是今天这个 200 行的脚本

8. 今日笔记模板

创建 notes/day6_experiments.md

# Day 6 笔记:综合实验日

日期:____
学习时长:____

## Part A: Token 优化

### 中英文 Token 对比
| 版本 | Token 数 | 相对成本 |
|------|---------|---------|
| 中文 | | 1.00x |
| 英文 | | |
| 混合 | | |

### 我的 Token 优化策略
1.
2.
3.

## Part B: 模型能力边界

### 测试结果
| 任务 | 预期可靠度 | 实际表现 | 补偿策略 |
|------|-----------|---------|---------|
| 数学计算 | 低 | | |
| 字符计数 | 低 | | |
| 逻辑推理 | 中高 | | |
| 常识 | 高 | | |
| 代码理解 | 高 | | |
| 长文本记忆 | 中 | | |
| 实时信息 | 不可能 | | |
| 幻觉测试 | 低 | | |

### 模型最让我意外的表现:
-

## Part C: 语义搜索

### 搜索结果分析
| 查询 | Top-1 结果 | 相似度 | 关键词搜索能找到吗 |
|------|-----------|--------|------------------|
| 怎么管理代码版本 | | | |
| 容器化技术 | | | |
| 让机器理解人话 | | | |

### 语义搜索 vs 关键词搜索的核心差异:
-

## Part D: 参数组合

### 场景 A: JSON 提取
| 配置 | Token | 合法 JSON | 观察 |
|------|-------|----------|------|
| T=0.7 默认 | | | |
| T=0 + JSON | | | |
| T=0.3 | | | |

### 场景 B: 创意文案
| 配置 | 多样性 | 观察 |
|------|--------|------|
| T=0 | /3 | |
| T=0.7 | /3 | |
| T=1.2+penalty | /3 | |

## Part E: 三维交叉

### 最优配置 vs 默认配置
| 指标 | 最优参数 | 默认参数 | 差异 |
|------|---------|---------|------|
| 成功率 | / | / | |
| 总 Token | | | |
| Token 节省 | - | - | % |

## Part F: 智能知识助手

### 项目运行记录
| 问题 | 任务类型 | 是否搜索 | Token | 回答质量 |
|------|---------|---------|-------|---------|
| | | | | |
| | | | | |
| | | | | |

### 我的思考
- 语义搜索最让我印象深刻的是:
- RAG 和直接问 LLM 的区别是:
- 这个项目还可以怎么改进:

## 本周知识串联

用一句话总结本周每天的核心收获:
- Day 1:
- Day 2:
- Day 3:
- Day 4:
- Day 5:
- Day 6:

## 还不清楚的点
-
-

## 明天的问题
-

9. 验收清单

概念验收

  • 能说出 Token 优化的 5 种策略
  • 知道 LLM 在哪些任务上可靠、哪些不可靠
  • 能解释语义搜索的完整流程
  • 理解语义搜索和关键词搜索的区别
  • 知道从语义搜索到完整 RAG 还差什么
  • 能说出参数选择对实际任务的影响(用实验数据说话)
  • 理解模型 × 参数 × Token 三维优化的优先级
  • 能解释智能知识助手的架构(分类 → 检索 → 生成)

代码验收

  • token_optimization.py 运行成功,4 个实验全部完成
  • model_limits.py 运行成功,测试了 8 种任务
  • semantic_search.py 运行成功,尝试了至少 8 个搜索词
  • search_comparison.py 运行成功,对比了语义搜索 vs 关键词搜索
  • param_combination.py 运行成功,3 个场景的参数对比完成
  • cross_experiment.py 运行成功,三维交叉矩阵完成
  • knowledge_assistant.py 运行成功,智能助手能正确回答 8 种问题
  • 知识问答类问题能触发语义搜索并带引用来源

产出验收

  • notes/day6_experiments.md 写完,包含所有实验结论
  • 6 个脚本推送到 GitHub
  • kb_embeddings.json 缓存文件生成
  • cross_experiment_results.json 保存了交叉实验结果
  • 智能知识助手能演示给同学/朋友看

10. 常见问题 FAQ

Q1: semantic_search.py 运行时报 “DASHSCOPE_API_KEY not found” 怎么办?

A: 你需要选择一个支持 Embedding 的 API。Day 3 学过,DeepSeek 不支持 Embedding。三个选择:

  1. 阿里百炼(推荐):去 https://dashscope.aliyun.com 注册,创建 API Key,设置 DASHSCOPE_API_KEY
  2. OpenAI:用 OPENAI_API_KEY,模型用 text-embedding-3-small
  3. 智谱 AI:注册 https://open.bigmodel.cn,设置 ZHIPU_API_KEY

.env 文件中添加对应的 Key 即可。

Q2: 知识库 Embedding 每次运行都要重新计算吗?

A: 不需要。脚本会自动缓存到 code/kb_embeddings.json。第一次运行后,后续运行会直接从缓存加载。只有知识库内容变化时才需要重新计算。这就是为什么把缓存逻辑写在代码里很重要——20 条数据的 Embedding 大约花 ¥0.01,但每次都重算会浪费时间。

Q3: 语义搜索的相似度阈值设多少合适?

A: 取决于应用场景:

  • 阈值 0.3(当前设置):宽松,几乎都会返回结果,可能包含不太相关的
  • 阈值 0.5:适中,只返回比较相关的
  • 阈值 0.7:严格,只返回高度相关的,可能经常返回空

建议从 0.3 开始,观察结果质量后逐步调高。生产环境通常用 0.5-0.7。

Q4: 智能知识助手的任务分类准确吗?会不会分错?

A: 用 LLM 做分类的准确率通常 >90%,但确实可能分错。比如"用 Python 实现 Transformer"可能被分成"代码"而非"知识问答"。改进方法:

  1. 在分类 Prompt 中给更多示例(few-shot)
  2. 加入"不确定时默认走知识问答"的 fallback
  3. 用规则匹配做预分类(如包含"写代码"→ code),再让 LLM 处理剩下的
  4. 第 2 个月学 Function Calling 后,可以更优雅地处理

Q5: 如果知识库有 1 万条甚至 10 万条数据,这个方案还可行吗?

A: 不可行。当前方案的问题:

  1. 存储:10 万条 × 1536 维 × 4 字节 ≈ 600MB JSON 文件,加载慢
  2. 搜索:每条计算余弦相似度,10 万条 → 10 万次计算 → 耗时数秒
  3. Embedding:10 万条全部 Embedding 需要几十分钟

解决方案(第 4 个月学):

  • 向量数据库(Pinecone / Milvus / Weaviate),支持 ANN(近似最近邻)搜索
  • ANN 搜索 10 万条只需毫秒级
  • 向量数据库支持增量更新、分布式、持久化

Q6: RAG 和微调(Fine-tuning)有什么区别?什么时候用哪个?

A:

维度RAG微调
原理搜索外部知识 + 生成更新模型权重
成本低(只需 Embedding)高(需要 GPU 训练)
时效性实时(知识库随时更新)非实时(每次微调要几小时)
适合知识频繁变化、需要引用风格/格式固定、领域深度
风险低(不改模型)中(可能过拟合)

经验法则:先试 RAG,RAG 解决不了的问题再考虑微调。90% 的场景 RAG 就够了。

Q7: 智能知识助手的回答有时候会"编造"知识库里没有的内容,怎么办?

A: 这是 LLM 的"幻觉"问题。即使用 RAG,模型也可能:

  1. 把搜索结果"过度推理"——从相关信息推断出不存在的内容
  2. 忽略搜索结果——用自己的训练数据回答

改进方法:

  • 在 System Prompt 中强调:“如果参考资料中没有明确提到,必须说’根据现有资料无法回答’”
  • 用 T=0 降低随机性
  • 后处理验证:检查回答中的关键信息是否真的出现在搜索结果中
  • 第 4 个月会学习更高级的 RAG 评估和幻觉检测方法

Q8: 这个智能知识助手能直接用到生产环境吗?

A: 不能。当前版本的局限:

  1. 无并发处理(一次只能回答一个问题)
  2. 无错误重试(API 超时就崩)
  3. 无用户管理(不知道是谁在问)
  4. 无对话记忆(每次都是独立问答)
  5. 无评估系统(不知道回答好不好)
  6. 知识库太小(20 条 vs 生产环境的几万条)

但它的核心逻辑是对的——分类 → 检索 → 生成。第 5 个月的"项目实战"会把这些都补上,做成可部署的产品。


11. 扩展资源

必看

资源类型时长价值
OpenAI Cookbook: Embeddings代码20 minEmbedding 应用最佳实践
LangChain RAG 教程文档30 min生产级 RAG 的标准流程
DeepSeek Context Caching 文档文档10 min如何用缓存省 Token 成本

推荐

资源类型时长价值
Pinecone: What is RAG文章15 minRAG 概念的最好入门
The Anatomy of RAG文章20 minRAG 各组件详解
LlamaIndex RAG 构建文档25 min另一个主流 RAG 框架
HuggingFace: Sentence Transformers文档15 min开源 Embedding 模型

可选

资源类型时长价值
Vector DB Benchmark文章20 min向量数据库性能对比
RAG vs Fine-tuning文章15 min深入对比两种方案
OpenAI: Reducing hallucinations文档10 min减少幻觉的策略
Anthropic: Contextual Retrieval文章15 minClaude 的 RAG 最佳实践

在线工具

  • OpenAI Embedding Playground: https://platform.openai.com/playground — 在线测试 Embedding
  • Pinecone Learn: https://www.pinecone.io/learn — RAG 系列教程(免费)
  • HuggingFace Spaces: https://huggingface.co/spaces — 体验各种 Embedding 模型

12. 核心概念速查卡

┌──────────────────────────────────────────────────────────────┐
│                    Day 6 核心概念速查卡                        │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  Token 优化 5 策略                                           │
│  ├─ 英文化 System Prompt → 省 50-60%                         │
│  ├─ Prompt 压缩 → 省 30-50%(注意可读性)                    │
│  ├─ Context Caching → 相同前缀省 90%                         │
│  ├─ 对话历史管理 → 防止 Token 线性爆炸                       │
│  └─ JSON mode → 省输出 Token(不需要自然语言包装)           │
│                                                              │
│  LLM 能力边界                                                │
│  ├─ 擅长: 生成/翻译/摘要/代码/对话/提取                      │
│  ├─ 不擅长: 计算/计数/实时信息/精确引用/长链推理             │
│  ├─ 补偿: Function Calling + RAG                             │
│  └─ 优先级: 影响大+频率高 → RAG+FC;影响小 → 提示工程        │
│                                                              │
│  语义搜索流程                                                │
│  ├─ 文本 → Embedding API → 高维向量                          │
│  ├─ 查询 → Embedding → 余弦相似度 → 排序                     │
│  ├─ vs 关键词搜索: 理解语义,不依赖字面匹配                  │
│  ├─ 缓存: Embedding 预计算存文件,避免重复调用               │
│  └─ 阈值: 0.3 宽松 / 0.5 适中 / 0.7 严格                    │
│                                                              │
│  RAG = 检索 + 生成                                           │
│  ├─ 检索: 语义搜索找相关文档                                 │
│  ├─ 构建: 搜索结果 + 用户问题 → Prompt                       │
│  ├─ 生成: LLM 基于搜索结果回答                               │
│  ├─ 引用: 标注回答来自哪个文档                               │
│  └─ 价值: 减少幻觉 + 实时更新 + 可追溯                      │
│                                                              │
│  参数选择验证结论                                             │
│  ├─ JSON 提取: T=0 + JSON mode → 最省 Token + 最可靠         │
│  ├─ 代码生成: T=0 → 质量最稳定                               │
│  ├─ 创意文案: T=1.2 + penalty → 多样性最高                   │
│  └─ 参数影响: 成功率差 20-30%,Token 差 30-50%              │
│                                                              │
│  三维优化优先级                                               │
│  ├─ 模型选择 → 决定上限(选错模型再调参也没用)              │
│  ├─ 参数选择 → 决定下限(选错参数输出不稳定)                │
│  ├─ Token 优化 → 决定成本(不影响质量但省钱)                │
│  └─ 优先级: 模型 > 参数 > Token                              │
│                                                              │
│  智能知识助手架构                                             │
│  ├─ 1. 分类: LLM 判断任务类型                                │
│  ├─ 2. 检索: 知识问答 → 语义搜索知识库                       │
│  ├─ 3. 选参: 查参数推荐表                                    │
│  ├─ 4. 生成: 搜索结果 + 问题 → LLM 回答                      │
│  └─ = Agent 雏形: 感知→检索→决策→行动                       │
│                                                              │
│  速记口诀                                                    │
│  ├─ Token: "英文省一半,缓存省九成,JSON省包装"              │
│  ├─ 边界: "生成强计算弱,事实靠RAG工具靠FC"                  │
│  ├─ 搜索: "字面匹配找词,语义匹配找义"                       │
│  ├─ RAG: "先搜再答,引用来源,不编不造"                      │
│  ├─ 参数: "提取归零,代码归零,创意偏高"                     │
│  └─ 架构: "分类定路线,检索找知识,参数选工具,生成出回答"   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

13. 明日预告

Day 7:写笔记 + 知识梳理 + GitHub 推送

明天你将:

  • 回顾 Day 1-6 的所有笔记,画一张完整的知识地图
  • 写一篇可发布的技术博客(本周学习总结)
  • 把所有代码推送到 GitHub
  • 做一个本周自测(8 道问答,能答出来就过关)

今天和明天的关系

  • 今天你做了大量实验,脑子很满
  • 明天是"消化日"——把碎片化的实验结论整理成结构化的知识
  • 写博客是最好的"费曼学习法"——你能讲清楚,才是真学会了

预习建议

  • 翻看 Day 1-6 的笔记,标记"还不清楚"的点
  • 想想:如果有人问你"这周学了什么",你能用 3 分钟讲清楚吗?
  • 准备博客标题(明天会给 3 个模板)

本周进度

Day 1 ✓  AI 全景 + 环境搭建 + Hello World
Day 2 ✓  Transformer / Self-Attention
Day 3 ✓  Token / Embedding / Context Window
Day 4 ✓  模型对比与选择 + 模型路由器
Day 5 ✓  温度 / Top-P / 采样策略
Day 6 ✓  综合实验日 + 语义搜索 + 智能知识助手 ← 你在这里
Day 7    写笔记 + 发博客 + GitHub 推送

本周代码清单(明天推 GitHub):

code/
├── hello_llm.py              (Day 1)
├── api_response.py           (Day 1)
├── multi_call.py             (Day 1)
├── attention_basics.py       (Day 2)
├── token_experiment.py       (Day 3)
├── embedding_experiment.py   (Day 3)
├── mini_search.py            (Day 3)
├── model_comparison.py       (Day 4)
├── model_router.py           (Day 4)
├── parameter_experiment.py   (Day 5)
├── auto_param.py             (Day 5)
├── token_optimization.py     (Day 6)  ← 今天
├── model_limits.py           (Day 6)  ← 今天
├── semantic_search.py        (Day 6)  ← 今天
├── search_comparison.py      (Day 6)  ← 今天
├── param_combination.py      (Day 6)  ← 今天
├── cross_experiment.py       (Day 6)  ← 今天
├── knowledge_assistant.py    (Day 6)  ← 今天
├── kb_embeddings.json        (Day 6)  ← 缓存
├── day5_param_results.json   (Day 5)
└── cross_experiment_results.json (Day 6)

共 18 个脚本 + 2 个数据文件。这就是你本周的作品集——明天全部推上 GitHub。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐