返回指南
架构·2026年5月25日·更新于 2026年9月23日·阅读约 14 分钟

RAG 实现指南——使用 Claude 和 Gemini 构建生产级检索增强生成

构建真正可用的生产级 RAG 系统,而不只是 100 行演示代码。涵盖分块策略、嵌入模型选择、检索排序、提示词结构、幻觉控制,以及即使每天 10,000 次查询也能将月度成本控制在三位数的方法。

最后审核于 。

大多数 RAG 演示都会在生产环境中失败。它们在 10 个问题的演示文稿上运行良好,但用户一旦提出新问题就会崩溃。本指南介绍能够经受住实际使用的架构和模式:遵循语义边界的分块、同时捕获模糊查询和字面查询的混合检索、防止幻觉的提示词结构,以及在规模扩大时保持账单稳定的成本控制。

真正重要的五项决策

  1. 分块策略——对检索质量的影响大于模型
  2. 嵌入模型——决定召回率上限
  3. 检索策略——仅靠向量是不够的
  4. 提示词结构——决定模型是否会产生幻觉
  5. 生成模型 + 缓存——决定单位经济性

1. 分块——朴素胜过聪明

不要过度思考。使用每个分块 1000-1500 个字符、重叠 100-200 个字符的递归字符分割器。优先尝试按段落边界拆分,然后按句子,最后按单词拆分。它在不同领域都很稳定:

chunk.py
# Recursive character splitter with overlap — the boring choice that wins
from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1200,
    chunk_overlap=200,
    separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_text(document)
# 1200 chars ≈ 300-400 tokens — fits 5+ chunks in a Sonnet context window
# with room for system prompt + question + answer

看起来更聪明但很少真正有帮助的做法:具备句子感知能力的模型、具备 Markdown 感知能力的分割器、结合嵌入的滑动窗口。它们并不差——只是需要投入大量精力才能获得略微更好的效果。把这些精力投入检索。

2. 嵌入模型——text-embedding-3-large 是默认选择

Kunavo 不提供嵌入服务——此步骤请直接调用嵌入提供商。 /v1/embeddings 是已实现的线路格式,但其后没有启用任何模型,因此对它的请求会失败;GET /v1/models 始终是判断哪些内容可调用的权威来源。实际上,这对 RAG 管道来说除了多一把密钥外不会增加成本:嵌入调用和生成调用本来就是独立请求,因此可以使用 OpenAI、Voyage 或 Cohere 生成嵌入,并使用 Kunavo 进行生成。

text-embedding-3-large 在大多数生产环境用例中很好地平衡了多语言支持和准确性,价格以 OpenAI 自己公布的费率为准——请在其定价页面查看,而不是在此处查看,因为我们不转售该服务,也不应在此引用其价格。更改分块策略或模型本身时重新生成嵌入;内容更新时不需要重新生成(只需添加新的分块)。

适合使用较小嵌入模型的例外情况:纯英文短文本检索(FAQ、产品卡片)。对于这些场景,text-embedding-3-small便宜 4 倍,而质量损失可以忽略。对于多语言或技术内容,请继续使用 large。

3. 检索——纯向量不如混合检索

向量相似度非常擅长模糊语义匹配(“如何取消”→“订阅取消政策”)。但它非常不擅长处理字面术语(“SKU-A92837”或“订单 #4729”)。解决方案是混合检索:并行进行 BM25 关键词搜索,然后使用倒数排名融合:

retrieve.py
# Hybrid retrieval: vector similarity + BM25 keyword scoring
# Pure vector misses literal keywords (product SKUs, IDs, dates)
def retrieve(question: str, k: int = 5) -> list[dict]:
    q_embed = embed([question])[0]
    vector_hits = vector_db.similarity_search(q_embed, k=k * 2)
    keyword_hits = bm25_search(question, k=k * 2)

    # Reciprocal rank fusion — simple, robust
    scores: dict[str, float] = {}
    for rank, hit in enumerate(vector_hits):
        scores[hit["id"]] = scores.get(hit["id"], 0) + 1 / (rank + 60)
    for rank, hit in enumerate(keyword_hits):
        scores[hit["id"]] = scores.get(hit["id"], 0) + 1 / (rank + 60)

    top_ids = sorted(scores, key=scores.get, reverse=True)[:k]
    return [chunk_by_id[i] for i in top_ids]

选定一个合格嵌入模型之后,这是最大的单项质量提升。在留出的 100 个问题评估集上跟踪 recall@5——如果加入 BM25 后从 70% 提升到 90%,就相当于消除了三分之一的“我不知道”失败。

4. 提示词结构——防止幻觉的三种模式

answer.py
# Final RAG prompt structure — three sections, citations enforced
SYSTEM_PROMPT = """You answer based exclusively on the supplied Context.
- Cite the [doc_id] for each factual claim.
- If the context doesn't answer the question, say "I don't have that
  information" — do not extrapolate or use general knowledge.
- Be concise. No throat-clearing."""

def answer(question: str) -> dict:
    chunks = retrieve(question, k=5)
    context = "\n\n---\n\n".join(
        f"[doc:{c['id']}] {c['text']}" for c in chunks
    )
    resp = client.chat.completions.create(
        model="claude-sonnet-4-6",
        messages=[
            {"role": "system", "content": [{
                "type": "text",
                "text": SYSTEM_PROMPT,
                "cache_control": {"type": "ephemeral"},
            }]},
            {"role": "user", "content": f"# Context\n{context}\n\n# Question\n{question}"},
        ],
        max_tokens=600,
    )
    return {
        "text": resp.choices[0].message.content,
        "sources": [c["id"] for c in chunks],
    }

系统提示词中有三项不可妥协的要求:

  • 引用来源——让模型为每个陈述附加 [doc:42]。如果引用的 ID 不真实,就捕获到了一个幻觉
  • 明确拒答——“如果上下文无法回答,就说我没有相关信息。”没有这条要求,模型会根据世界知识补全答案
  • 简洁输出——答案越短,通常越准确。最多 600 个 token 是一个不错的生产默认值

5. 生成模型 + 缓存——成本层

Claude Sonnet 4.6 是默认选择。如果成本压力较大,Claude Haiku 4.5 是低价备用选项。始终将系统提示词包在 cache_control 中——系统提示词每次调用都相同,因此缓存可以将这部分成本降至输入费率的 10%。

生产规模下每次查询的实际成本(上下文 5K、输出 500、启用缓存):

模型每次查询成本每天 10,000 次查询时相对于 Sonnet 的质量
claude-sonnet-4-6~$0.007~$70/天基准
claude-haiku-4-5~$0.001~$10/天~85%,便宜 4 倍

每天 10,000 次查询时 → Sonnet $70/天,Haiku $10/天。高风险用例(面向客户、法律、医疗)选择 Sonnet,内部或批量处理选择 Haiku。

生产环境中会出现什么问题(以及如何捕获)

  • 分布漂移:训练语料不再匹配真实用户问题。每周抽样 100 个查询,手动检查 recall@5
  • 嵌入过时:源文档已更新,但嵌入未刷新。每周跟踪索引大小与源数据大小
  • 虚假引用:模型编造看似真实的文档 ID。渲染前验证每个 [doc:N] 是否属于实际检索到的 ID 列表
  • 延迟悬崖:向量数据库在超过一百万个向量后性能暴跌。使用 HNSW 索引;如果是多租户系统,则按租户分区

4 周生产路线图

  1. 第 1 周:使用 100 份文档和 10 个测试问题制作原型,进行人工评估
  2. 第 2 周:扩展到完整语料库,构建混合检索,编写 100 个问题的评估集
  3. 第 3 周:基于评估集迭代调优分块和系统提示词,发布给内部用户
  4. 第 4 周:监控、成本预算、带来源引用 UI 的公开发布

常见问题

生产环境中的 RAG 系统应使用多大的分块?

每个分块使用 1,000–1,500 个字符,重叠 100–200 个字符;使用递归字符分割器,优先按段落边界拆分,然后按句子,最后按单词拆分。具备句子感知能力的模型、具备 Markdown 感知能力的分割器和滑动窗口的效果略好,但所需工作量高得多——这些精力更值得投入到检索上。

RAG 管道应使用哪个嵌入模型?

对于多语言和技术内容,text-embedding-3-large 是默认选择;对于纯英文短文本检索,例如 FAQ 和产品卡片,text-embedding-3-small 的价格约便宜 4 倍,而质量损失可以忽略。请注意,Kunavo 不提供嵌入服务:/v1/embeddings 是已实现的数据传输格式,但其后没有启用任何模型,因此嵌入步骤直接调用 OpenAI、Voyage 或 Cohere,而生成步骤调用 Kunavo。由于两者无论如何都是独立请求,这对管道来说除了多一把密钥外不会增加成本。

仅靠向量搜索就足够完成 RAG 检索吗?

不够。向量相似度很擅长模糊语义匹配,但无法处理 SKU 或订单号等字面术语。并行运行 BM25 关键词搜索,再通过倒数排名融合进行合并,通常可以将 recall@5 从约 70% 提升到约 90%——这是选定一个合格嵌入模型之后最大的单项质量提升。

如何防止 RAG 系统产生幻觉?

在系统提示词中采用三种模式:要求每个事实性陈述都附带 [doc:N] 引用;指示模型在上下文无法回答时说明自己没有相关信息;并保持答案简短——生产环境中将输出限制为最多 600 个 token 是一个不错的默认值。然后,在渲染前验证每个被引用的 id 是否属于检索结果集;不存在的 id 就是已被捕获的幻觉。

生产环境中的一次 RAG 查询成本是多少?

在上下文 5K、输出 500 个 token 且启用提示词缓存的情况下:Claude Sonnet 4.6 每次查询约 $0.007,Claude Haiku 4.5 约 $0.001(便宜 4 倍,质量约为 85%)。每天 10,000 次查询时,Sonnet 约 $70/天,Haiku 约 $10/天。