无 RAG 基线 —— 暴力塞全文
让模型找到资料。无 RAG 基线 —— 暴力塞全文,边读边运行配套 Python 代码。
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 工程小细节
-
========== 知识库开始/结束 ==========分隔符 让模型清楚"哪部分是参考资料、哪部分是它要做的事"。这种边界标记能显著降低模型把示例文档当成 user 指令来跟着做的概率。 -
"如果答案不在知识库里,必须明确说没有" 这是为了防止模型瞎编(业内俗称"幻觉")。问题 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 库。