AI面包君Learn · Build · Share
03Bread RAG · 第 1
总进度 16 / 33

无 RAG 基线 —— 暴力塞全文

让模型找到资料。无 RAG 基线 —— 暴力塞全文,边读边运行配套 Python 代码。

2026-09-0720 分钟50 行代码难度

1. 故事:在学 RAG 之前,先理解"为什么需要 RAG"

很多课程上来就给你讲"什么是 RAG"。但你要先体会"不用 RAG 怎么办"才有意义

最朴素的做法:把整篇文档塞进 system prompt,让 LLM 看着它回答。这就是本章做的事。

+------------------------------------+
|  system: 这里是整本知识库的全部内容 |  ← 几千字 / 几万字 / 几十万字...
|  user:   你问的问题                |
+------------------------------------+
                ↓
            LLM 回答

这种做法对短文档完全 work。问题是当文档变成"公司的全部产品手册"或"一整本法律法规"时:

  • 装不下:很多模型上下文窗口只有 8K~128K tokens
  • 太贵:每次提问都把整本"百科全书"重发一遍
  • 质量下降:无关内容稀释了模型注意力

后面 ch01 起我们才真正引入"先检索后生成"——只把最相关的几段塞进去,其余的留在向量库里。


2. 跑起来

cd ch00_baseline
python main.py

需要 .env。复用 bread-agent 的就行,main.py 已经写好了兜底。

预期输出:

[信息] 文档长度: 1200+ 字符 (~600 tokens)
[信息] 这些字符**每次提问都会被发给模型** —— 这就是无 RAG 的代价

=== 问题 1: 你们公司哪一年成立的? ===
AI: Bread 面包工坊成立于 2018 年。

=== 问题 2: 加盟费是多少?培训要多久? ===
AI: 加盟费 38 万元,需要在上海杨浦区中央厨房接受 14 天的总部培训。

=== 问题 3: Bread 面包工坊 2026 年 7 月会推什么新品? ===
AI: 根据知识库,2026 年 7 月会推出植物基(vegan)羊角面包,目前处于研发阶段。

=== 问题 4: 你们的吐司用什么面粉?黄油是哪个牌子? ===
AI: ... 日本鸟越制粉 ... 法国铁塔(Président)和爱乐薇(Elle & Vire)...

=== 问题 5: 你们能去新疆送货吗? ===
AI: 知识库里没有相关信息 (✓ 模型没瞎编)

5 个问题全部答对——证明对这种小文档(~600 tokens),暴力法完全够用。


3. 逐行精讲

整个 main.py 只有 1 个核心函数

def ask(question: str, full_document: str) -> str:
    messages = [
        {"role": "system", "content":
            f"你是 Bread 面包工坊的客服助理。请基于下面的内部知识库内容回答用户问题。"
            f"如果答案不在知识库里,**必须明确说"知识库里没有相关信息"**,不要瞎编。\n\n"
            f"========== 知识库开始 ==========\n{full_document}\n========== 知识库结束 =========="},
        {"role": "user", "content": question},
    ]
    resp = client.chat.completions.create(model=MODEL, messages=messages)
    return resp.choices[0].message.content

就这么简单。把整个文档作为 f-string 拼进 system prompt 里。

两个 prompt 工程小细节

  1. ========== 知识库开始/结束 ========== 分隔符 让模型清楚"哪部分是参考资料、哪部分是它要做的事"。这种边界标记能显著降低模型把示例文档当成 user 指令来跟着做的概率

  2. "如果答案不在知识库里,必须明确说没有" 这是为了防止模型瞎编(业内俗称"幻觉")。问题 5 验证了这一点——模型应该说"知识库里没有"而不是编造一个送货政策。

为什么 ch00 不需要 RAG?

因为文档够短。1200 字符 ≈ 600 tokens,连最便宜的 DeepSeek-Chat 都装得下整本,每次问问题的成本可以忽略。

但当:

  • 文档变成 100 页 PDF(约 50K~200K tokens)
  • 或者你有 1000 篇文档(百万 tokens 量级)

每次问问题都要把整堆塞进去,就是灾难。这就是 ch01 起要做的事。


4. 卡住了怎么办

❌ 模型答得很烂、答错事实

.env 里的模型可能太弱(很多 7B 以下的小模型在长上下文里会忘事)。换一个稍强一点的:

  • DeepSeek-Chat
  • 火山引擎 glm-5.1
  • OpenAI gpt-4o-mini

❌ 模型在问题 5 编了一个"我们能去新疆"的答案

system prompt 不够强。试着把那句话改成:

"最重要:如果答案不在知识库里,只能回答'知识库里没有相关信息',绝对不要自行推测、瞎编、或基于常识回答。"

实际生产里这种"约束 prompt"会更加严苛。

KeyError: 'OPENAI_API_KEY'

.env 没配。看顶层 README。

❌ 中文输出乱码

我们用了 sys.stdout.reconfigure(encoding="utf-8"),理论上 Windows 终端也能正常显示。如果还乱,看 Bread Agent README 里 FAQ。


5. 思考题

题 1:把文档长度乘 10 倍

复制 sample_doc.md 的内容粘贴 10 遍(或者用 doc_text = doc_text * 10),再跑一遍。观察:

  • 调用是否还成功?(如果模型上下文窗口够大,是的)
  • 速度变没变?(明显变慢——模型要读 10 倍内容)
  • 回答质量变没变?(可能变差——"大海捞针"问题,无关内容稀释注意力)

这就是 RAG 要解决的"长上下文之痛"

题 2:算一下钱

DeepSeek-Chat 当前定价大约 0.001 元 / 千 tokens。假设你的客服系统每天回答 10000 个问题:

  • 不用 RAG:每次都发 600 tokens 文档 + 50 tokens 问题
  • 用 RAG(假设只发 200 tokens 检索结果):每次发 200 + 50 tokens

算一下两种模式每天的成本差。这就是 RAG 的"性价比"

题 3:试试模型"读不下"会怎样

把 sample_doc.md 内容复制 1000 倍,观察 API 是不是直接报错(context length exceeded)。

不同模型的窗口:

  • DeepSeek-Chat:64K tokens
  • gpt-4o-mini:128K
  • 火山 glm-5.1:~128K

有上限就有需求——RAG 永远不会过时。


这一章你学会了什么

  • ✅ 最朴素的"用文档回答"方式:全文塞 prompt
  • ✅ 这种方式对短文档完全可用
  • ✅ 长文档的三大痛点:装不下、贵、质量下降
  • ✅ Prompt 工程小技巧:边界标记 + 防幻觉指令

下一章 ch01:把"塞全文"换成先检索后生成——切块 + 词袋向量 + 余弦相似度。手写一个最朴素的 RAG,不依赖任何 ML 库。