Prompt 与 RAG:从模板到可引用检索
0. 元信息
- 主题路径:
docs/topics/llm-applications/subtopics/prompt-and-rag/ - 父主题:
llm-applications - 适合对象:具备 Python、HTTP 和 JSON 基础的学习者
- 建议周期:2~3 周
- 前置知识:无额外前置(继承父主题依赖)
- 最终目标:能设计可复用 Prompt,完成文档切分、Embedding、向量检索、引用和检索评测
1. 学习路线
Prompt 模板与约束
→ 结构化输出与注入防护
→ 文档清洗与切分
→ Embedding 与向量库
→ 召回、重排与引用
→ 检索评测与 RAG 问答
2. 阶段周数分配(2~3 周,每天 1.5~2 小时)
| 阶段 | 2 周方案 | 3 周方案 | 备注 |
|---|---|---|---|
| 1. Prompt 基础 | 0.25 周 | 0.5 周 | 模板、约束、JSON schema |
| 2. 文档处理 | 0.25 周 | 0.5 周 | 清洗、切分、metadata |
| 3. 向量检索 | 0.5 周 | 0.75 周 | Embedding、索引、top-k |
| 4. RAG 全流程 | 0.5 周 | 0.75 周 | 召回、重排、引用、拒答 |
| 5. 检索评测 | 0.5 周 | 0.5 周 | recall@k、MRR、人工抽样 |
分阶段概述:
- 第 1 周:完成 Prompt 基础 + 文档处理;本地文档能跑通关键词检索;同模型同 prompt 跑 3 次确认输出稳定。
- 第 2 周(2 周方案) 或 第 2~3 周(3 周方案):完成 Embedding、向量库、RAG 全流程;上线前补 recall@k / MRR / 引用命中率三类指标。
2 周方案每天 2 小时;3 周方案每天 1.5 小时,留出复盘与重测时间。建议首轮按 3 周方案打基础,迭代后再压缩到 2 周。
3. 阶段表
| 阶段 | 核心知识 | 实践产出 | 可观察学会标准 |
|---|---|---|---|
| 1. Prompt 基础 | 角色、任务、约束、少样本、schema | 三类可复用模板 | 输出能通过 JSON/schema 校验,失败样例有记录 |
| 2. 文档处理 | 清洗、切分、metadata、chunk overlap | 可重复的导入脚本 | 能解释切分粒度取舍,空文档和超长文档不崩溃 |
| 3. 向量检索 | Embedding、距离、索引、top-k、过滤 | 小型向量库 | 能用 3 组问题检查召回结果并解释误召回 |
| 4. RAG 全流程 | query rewrite、召回、重排、上下文拼接、引用、拒答 | 带来源问答 API | 能区分检索错、上下文错和生成错 |
| 5. 检索评测 | recall@k、MRR、命中率、人工抽样 | 评测集与报告 | 能报告指标并基于数据改切分或检索参数 |
4. 第一周(每天 1.5~2 小时)
环境约定:本子主题统一使用 Python 3.11+ 与
python -m venv .venv;anthropic、qdrant-client等通过pip install -r requirements.txt安装;API key 通过export ANTHROPIC_API_KEY=...共享 .env 模板:父主题attachments/.env.example含 Anthropic / OpenAI / Voyage / Qdrant / Ollama / OTel / Langfuse 全套变量;本地cp ../../attachments/.env.example .env后填值,.env加入.gitignore,加载用python-dotenv。禁止把真实 key 提交到仓库或写入 trace / 日志。 写入环境变量,不要硬编码到代码。
| 日 | 任务 | 当天交付 | 自检 |
|---|---|---|---|
| Day 1 | 配 venv、安装 anthropic SDK,写最小对话脚本;记录模型 claude-haiku-4-5 与 token 用量 | notes/prompt-lab/day1.md + app.py | echo $ANTHROPIC_API_KEY 非空;运行 python app.py 拿到一次回复 |
| Day 2 | 写角色 + 约束 + JSON schema 三件套 Prompt;同一输入跑 5 次并保存到 notes/prompt-lab/day2.jsonl | notes/prompt-lab/day2.jsonl(5 行) | 5 次输出均通过 json.loads 校验;schema 必填字段无缺失 |
| Day 3 | 加入少样本(3~5 个示例)与边界 case:空字符串、过长字段、敏感词 | prompts/with_fewshot.md + 失败用例 3 条 | 边界 case 输出仍符合 schema;至少 2 条不再幻觉 |
| Day 4 | 文档清洗与切分:写 ingest.py,按段落切分 + chunk overlap;空文档与超大文档单独测 | notes/doc-proc/day4.md + ingest.py | 10 篇本地 md / txt 全部导入成功;空文件不崩 |
| Day 5 | 接入 Embedding(先用 voyage-3 或本地 sentence-transformers),把向量保存到内存列表;实现余弦召回 top-5 | notes/vec-search/day5.md | 3 个手写问题能召回相关 chunk;输出含 chunk_id 与 score |
| Day 6 | 切换到 Qdrant 或 pgvector:建 collection、写 metadata 过滤、混合检索(关键词 + 向量) | notes/vec-search/day6.md + 一组 filter 测试 | metadata 过滤生效;带过滤召回的样本与不带过滤差异可解释 |
| Day 7 | 带引用的 RAG 最小 demo:拼接 context + 引用字段,强制 JSON 输出;覆盖空召回、超长 context、模型错误三类失败 | notes/rag-demo/day7.md + 10 条问答记录 | 10 条问答中至少 8 条引用 chunk_id 与 gold_source_ids 对齐;空召回走拒答分支 |
第一周复盘要求
把每日的输入、输出、命令、token 用量、失败用例写到 notes/prompt-lab/ 与 notes/rag-demo/ 下对应日期的 md 文件;Week 1 结束前用 30 分钟复盘:哪些 prompt 改了之后稳定了?哪些 chunk 大小导致召回变差?
5. 阶段通用验收
- 不看答案独立重写 Prompt 模板与切分 / 召回核心代码;
- 用自己的话解释 Prompt 约束、chunk 粒度、相似度与引用正确性的区别;
- 画 ingestion / query 两条数据流图,标注 metadata 与引用字段;
- 测试空文档、超长文档、空召回、无 gold、模型超时 5 类边界;
- 准备至少 3 组自定义问题并贴出实际回答与引用;
- 记录 token 用量、平均召回耗时、首 token 延迟;
- 能修改已有 RAG(改切分、加 reranker、加 metadata)并复测评测集。
交付存放:第 3 项的图、第 5 项的实际输出、第 6 项的指标统一存到
notes/子目录或 README 对应章节,便于复盘与综合项目引用。
6. 最终验收(学完 2~3 周后)
- 完成 ≥ 20 个 Prompt 模板(含角色 / 约束 / JSON schema / 少样本 4 类各 5 个),每个模板附 ≥ 3 条回归样例;
- 独立实现最小 RAG demo:能回答 10 个领域问题,每条回答带
chunk_id与来源文件路径,覆盖正常、空召回、超长 context、模型错误四类用例; - 建立至少 30 条评测样本,分别报告
recall@5/MRR/ 引用命中率 / 拒答率四项指标; - 完成 1 个综合项目(见 §7),含 README、失败恢复与成本记录;
- 能用 15 分钟讲清楚 Prompt、Embedding、向量检索、重排、引用与拒答之间的数据流与权衡。
API key 配置与回退方案
- 首选:Anthropic API(
claude-haiku-4-5做 Embedding 之外的对话与生成;Embedding 走voyage-3或本地sentence-transformers/all-MiniLM-L6-v2)。配置方式:export ANTHROPIC_API_KEY=...;向量库若用 Qdrant Cloud,再加export QDRANT_URL=...和export QDRANT_API_KEY=...。 - 回退 1(无 API key):本地
ollama pull qwen2.5:7b起一个 OpenAI-compatible 端点(默认http://localhost:11434),Embedding 用nomic-embed-text或bge-m3;评测指标同样可跑,只是首 token 延迟会因 GPU 不同有差异。 - 回退 2(纯离线):Embedding 与检索都用本地模型(FAISS +
sentence-transformers),不做生成;改写 §6 第 2 项为”返回 top-5 chunk + 引用”,验证检索闭环。 - 记录:每次实验保存
{model, version, endpoint, prompt_hash, chunk_size, overlap, top_k}到notes/runs/<date>.json,便于复现。
7. 综合项目
首选:可引用知识库问答(必做:文档导入 → 切分 → Embedding → 检索 → 引用生成 → 拒答,覆盖 5 类失败用例)。
备选:FAQ 检索增强客服(必做:固定 FAQ 集合 → 召回 + 重排 → 答案 + 来源 + 转人工分支)。
任何综合项目都必须包含:
- 需求说明:要解决的问题、用户故事、输入输出约定(包含拒答、超长输入、敏感词);
- 数据流与模型选择理由:为什么选这个 Embedding / 向量库 / LLM;记录权衡;
- Prompt 与检索算法说明:关键 prompt 模板、chunk 粒度、top-k、reranker 选择;
- 模块化源码:
ingest.py/retriever.py/answer.py/eval.py等职责单一; - 边界与安全测试:空召回、敏感词、超长 context、模型错误、向量库不可达;
- 运行说明:
make run或清晰python -m ...命令,注明环境变量与依赖; - README:项目介绍、运行步骤、目录结构、复盘(踩过的坑、可改进点);
- 评测与复盘记录:
notes/retrospective.md(用时、难点、收获、下一步)。
notes/ 与 README 存放规范
所有”画图(数据流 / 检索链路 / 引用链路)“和”贴输出”类交付物统一存放在项目根目录的 notes/ 子目录或 README 的对应章节;提交时一并带上,避免散落在聊天或临时文件里。综合项目的 notes/ 至少包含:
notes/design.md:数据流图、Prompt 模板、Embedding / 向量库选型理由;notes/test.md:每组测试数据的输入、期望输出(gold)、实际输出与引用对齐情况;notes/retrospective.md:复盘(用时、难点、收获、下一步)。
本主题贡献(Loop 6-D · llm-applications / prompt-and-rag)
本主题把”chunk 策略、embedding 选型、向量库、reranker、引用对齐”五条独立工程线拧成一条验收闭环,输出可被父主题与下游子主题直接复用的 RAG 模板。
3 项核心职责
- chunk + embedding:按文档类型选 chunk 粒度(短问答 256 token / 长文 512~1024 token + overlap),embedding 用 voyage-3 / OpenAI text-embedding-3-large,按 batch 异步写入向量库。
- 检索 + rerank:Qdrant(向量库,配 HNSW 索引 + payload 过滤)召回 top-k(默认 50),再用 reranker(bge-reranker / cohere rerank)精排到 top-5;空召回 / 长尾问题配 fallback(直接 LLM 答)。
- 引用对齐 + 防幻觉:prompt 强制要求”必须基于引用片段回答”,每段回答配 citation(
[1]/[2]+ 来源文件名 / 段落),未引用则不答;用 LLM-as-judge 抽检引用准确性。
4 项交付物
- ingest + chunk 策略:
ingest.py(按文档类型选 chunk 粒度 + overlap)+ voyage-3 异步 embedding + Qdrant upsert,配 batch + retry + 断点续传。 - retriever + rerank 链路:
retriever.py(Qdrant HNSW top-50)+ reranker(bge-reranker / cohere rerank)精排到 top-5;payload 过滤(时间 / 来源 / tag)配 Qdrant 字段索引。 - answer + citation 对齐:
answer.py(prompt 强制引用 + citation 格式)+eval.py(LLM-as-judge 抽检引用准确性 + faithfulness),未引用强制拒答。 - 边界测试 + 必读交付:空召回 / 敏感词 / 超长 context / 模型错误 / 向量库不可达 5 类各 1 例,配
notes/design.md(数据流 + prompt + embedding / 向量库选型)+notes/retrospective.md。
3 个验收指标
- 检索召回:top-5 命中率 ≥ 85%(gold 评测集 ≥ 100 条),rerank 后 NDCG@5 提升 ≥ 10%。
- 引用对齐:LLM-as-judge 抽检引用准确性 ≥ 90%,未引用的回答率 = 0;Qdrant 检索 P95 ≤ 200ms(含 embedding)。
- voyage-3 异步写入吞吐 ≥ 1000 docs/min,敏感词命中走拒答分支,向量库不可达走 fallback。
8. 推荐开源资料(按角色分工,避免堆链接)
| 阶段 | 角色 | 资料 | 链接 | 用法 |
|---|---|---|---|---|
| 1 | Prompt 工程 | OpenAI Cookbook(Prompting guide) | https://cookbook.openai.com/examples/prompting_guide_zh | 中文友好,覆盖基础模板与少样本;先动手再查 |
| 1 | Prompt 工程 | Anthropic Prompt Engineering | https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview | 官方文档;查 role / schema / system prompt 设计 |
| 2 | 文档切分 | LangChain Text Splitters | https://python.langchain.com/docs/concepts/text_splitters | 对照 Recursive / Token 切分;不要直接绑定,先自己实现 |
| 3 | Embedding | Hugging Face sentence-transformers | https://huggingface.co/sentence-transformers | 选本地 Embedding 模型;先用 all-MiniLM-L6-v2 跑通再换大模型 |
| 3 | 向量检索 | Qdrant 文档 | https://qdrant.tech/documentation/ | collection、filter、payload 设计;使用前确认 LICENSE(Apache-2.0) |
| 3 | 向量检索 | FAISS Wiki | https://github.com/facebookresearch/faiss/wiki | 索引类型与距离度量;用于离线对比,MIT 许可证 |
| 3 | 向量检索 | pgvector | https://github.com/pgvector/pgvector | 在已有 Postgres 的项目里替代独立向量库;Postgres 许可证 |
| 4 | RAG 框架对照 | LlamaIndex RAG 指南 | https://docs.llamaindex.ai/en/stable/getting_started/starter_example/ | 最小实现后比较抽象层;不要直接当底层原理 |
| 4 | RAG 框架对照 | LangChain RAG 教程 | https://python.langchain.com/docs/tutorials/rag/ | 看 chain / retriever 设计;不要照抄代码 |
| 5 | 评测指标 | Ragas 文档 | https://docs.ragas.io/ | faithfulness / answer_relevancy 等指标;必须配合人工抽样,避免单独使用 |
| 5 | 评测参考 | BEIR Benchmark | https://github.com/beir-cellar/beir | 标准检索评测集与指标;用于离线对比 |
| 通识 | 模型查阅 | Hugging Face Hub | https://huggingface.co/docs | 查模型卡、tokenizer、推理参数 |
| 通识 | API 文档 | Anthropic API 文档 | https://docs.anthropic.com/en/api/overview | 查参数、token、限制与价格;不替代实验 |
许可证提示:复制或参考 Qdrant / FAISS / pgvector / LangChain / LlamaIndex / sentence-transformers 等开源仓库代码前,先打开 LICENSE 确认:Qdrant(Apache-2.0)、FAISS(MIT)、pgvector(Postgres)、LangChain(MIT)、LlamaIndex(MIT)、sentence-transformers(Apache-2.0)。默认做法是读思路后自己重写,而不是复制粘贴;GPL 类代码用于商业 / 闭源项目前请逐条阅读。
API key 安全:示例代码统一使用
os.environ["ANTHROPIC_API_KEY"]或等价方式;不要把 key 提交到仓库;本地用.env(加入.gitignore)+python-dotenv加载;CI 上用 secret 注入。
默认使用顺序:先用 Anthropic / OpenAI 官方文档查 Prompt 与 API → 自己实现 chunk + Embedding + 召回 → 用 Qdrant / FAISS 做对照实验 → 引入 LangChain / LlamaIndex 比较抽象层 → 建评测集跑 recall@k / MRR / 引用命中率 → 引入 Ragas 做辅助评测并人工抽样校准 → 复盘到 notes/retrospective.md。
9. 学习资料汇聚(v0.3 自包含)
9.1 背景与动机
Prompt 决定模型如何执行任务,RAG 则把模型连接到可更新的外部知识。可靠应用必须同时验证指令遵循、证据召回和最终引用,而不是只看一句回答是否流畅。
9.2 概念地图
flowchart LR
Prompt --> Query
Docs[文档] --> Chunk[切分]
Chunk --> Embed[Embedding]
Embed --> DB[向量库]
Query --> Embed
DB --> Retrieve[召回]
Retrieve --> Rerank[重排]
Rerank --> Context[上下文+来源]
Context --> Answer[回答/拒答]
9.3 基础知识讲解
主推官方 Embedding/模型 API 文档、Qdrant 文档;备查 FAISS 和 LlamaIndex 文档。先用内存列表完成检索,再引入向量库;复制代码前确认 LICENSE。
9.4 经典问题与经典案例
| 问题 | 最简答案 |
|---|---|
| chunk 太大 | 召回包含噪声,按标题/段落和 token 预算切分 |
| chunk 太小 | 语义断裂,增加 overlap 或保留父文档关系 |
| 相似但不相关 | 加 metadata 过滤、重排或混合检索 |
| 没有答案 | 明确拒答,不让模型凭空补全 |
| 引用不可信 | 保存 chunk id、来源和偏移,输出前校验 |
| 评测只看生成 | 分开测 recall@k、引用正确性和答案质量 |
9.5 学习难点
- 概念难点:向量相似度不是事实正确性;把召回和生成分开测。
- 思维难点:RAG 是数据流,不是一个黑盒 API;先画 ingestion/query 两条链路。
- 工程难点:文档更新、索引版本、权限和来源一致性需要可追踪。
9.6 技术标准与接口
Entity
Embedding API、向量数据库 collection/index、metadata filter、top-k 检索和来源引用对象。
Scope
Embedding 把文本映射为向量;向量库负责近邻检索,不负责回答事实和权限判断。
Structure
必须掌握文本、向量、维度、距离度量、chunk id、source、score、top-k、filter、引用片段等字段。
Ecosystem
FAISS、Qdrant、Milvus、pgvector 等实现各有索引和持久化取舍;框架封装不等于统一行为。
Depth Tiers
L0 知道存在;L1 看懂检索示例;L2 能导入和查询;L3 能定位召回错误;L4 能设计更新、权限和评测方案。本子主题要求 L3。
Source
官方 Qdrant、FAISS、模型 Embedding 文档;版本快照日期:2026-07-28。
9.7 关键代码
9.7.1 结构化 Prompt 模板
# 9.7.1 结构化 Prompt + JSON Schema 校验
import json
from typing import Any
PROMPT_TEMPLATE = """你是 {role}。根据用户问题只输出 JSON,不要任何额外说明。
必须遵守:
1. 字段名严格匹配 schema;
2. confidence 介于 0.0 到 1.0;
3. 不确定时 confidence <= 0.3 并把 answer 设为 null。
schema:
{schema}
用户问题:{question}
"""
def build_prompt(role: str, schema: dict[str, Any], question: str) -> str:
return PROMPT_TEMPLATE.format(
role=role,
schema=json.dumps(schema, ensure_ascii=False, indent=2),
question=question,
)
if __name__ == "__main__":
schema = {
"type": "object",
"required": ["answer", "citations", "confidence"],
"properties": {
"answer": {"type": ["string", "null"]},
"citations": {"type": "array", "items": {"type": "string"}},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
},
}
print(build_prompt("知识库助手", schema, "RAG 的全称是什么?")[:200])
9.7.2 基于内存向量的最小 RAG 检索
# 9.7.2 纯 Python 实现余弦相似度召回 + 来源引用
import math
from dataclasses import dataclass
@dataclass
class Chunk:
chunk_id: str
source: str
text: str
vec: list[float]
def cosine(a: list[float], b: list[float]) -> float:
dot = sum(x * y for x, y in zip(a, b))
na = math.sqrt(sum(x * x for x in a))
nb = math.sqrt(sum(x * x for x in b))
return dot / (na * nb + 1e-12)
def retrieve(query_vec: list[float], chunks: list[Chunk], top_k: int = 3) -> list[tuple[Chunk, float]]:
scored = [(c, cosine(query_vec, c.vec)) for c in chunks]
scored.sort(key=lambda x: x[1], reverse=True)
return scored[:top_k]
if __name__ == "__main__":
chunks = [
Chunk("c1", "kb/rag.md", "RAG 指检索增强生成。", [0.9, 0.1, 0.0]),
Chunk("c2", "kb/llm.md", "LLM 是一类语言模型。", [0.2, 0.8, 0.1]),
]
for c, s in retrieve([0.85, 0.15, 0.0], chunks):
print(f"{c.chunk_id}\t{s:.3f}\t{c.source}\t{c.text}")
9.7.3 真实可运行:Anthropic SDK 做一次带引用的问答
# 9.7.3 用 Anthropic SDK 做一次带 schema 约束的 RAG 问答
# 运行:export ANTHROPIC_API_KEY=...; python rag_anthropic.py
import os
import json
import anthropic
client = anthropic.Anthropic()
QUESTION = "RAG 的全称是什么?只用一个词回答。"
CHUNKS = [
{"id": "c1", "source": "kb/rag.md", "text": "RAG 指检索增强生成(Retrieval-Augmented Generation)。"},
{"id": "c2", "source": "kb/misc.md", "text": "本节与问题无关。"},
]
context = "\n".join(f"[{c['id']}] {c['text']}" for c in CHUNKS)
prompt = (
"仅根据以下 context 回答,若 context 没有答案,回答 NOT_FOUND。\n"
f"context:\n{context}\n\n问题:{QUESTION}\n"
"严格用 JSON 输出:{\"answer\": str, \"citation_id\": str}"
)
resp = client.messages.create(
model="claude-haiku-4-5",
max_tokens=128,
messages=[{"role": "user", "content": prompt}],
)
text = resp.content[0].text.strip()
data = json.loads(text)
print(data) # {'answer': '...', 'citation_id': 'c1'}
10. 常见误区
- 把”模型能听懂”当 Prompt 写对的证据:只换 prompt 不重测,结果只是换种风格出错;
- chunk 切到 8000 token 还嫌召回差:问题在切分粒度,不在 embedding;
- chunk 切到 100 token 召回全是”段首”:丢掉段落上下文,应保留父文档指针或加 overlap;
- 用 cosine 相似度排行第一就当答案:相似度不是事实正确性,必须再过引用校验;
- prompt 里直接拼接整篇文档:上下文一长模型开始编造,截断或重排比加大上下文有效;
- 引用字段不存 chunk id 和偏移:审计时无法回查来源,合规审查必卡;
- 评测只看”答得好不好”:忽略 recall@k、引用命中率、拒答率,三类指标独立测;
- 拒答不写在 schema 里:模型倾向于补全,缺少强约束就会幻觉;
- Embedding 模型上线后不锁版本:模型一升级,向量不可比,整库要重灌;
- 把 RAG 当万能解药:需要 100% 准确或多步推理的查询,RAG 仍会失败,要加工具调用兜底。
11. 所有知识点分类(统一规则)
- 编程语言
- 数据结构与算法
- 计算机基础
- 工程技术
- Web 与后端
- 前端与客户端
- 数据与人工智能
- 项目与职业能力
本计划归属:数据与人工智能 主 + 工程技术 辅。