LLM

RAG 实战:让 LLM 准确引用你的文档

检索增强生成(RAG)是解决 LLM 幻觉问题的关键技术。本文深入实践向量检索与上下文注入。

为什么需要 RAG?

大语言模型虽然强大,但存在明显的局限性:

  • 知识截止:训练数据有时间边界
  • 幻觉问题:会编造看似合理但错误的内容
  • 私有数据:无法访问企业内部文档

RAG(Retrieval-Augmented Generation)通过在生成前检索相关文档来解决这些问题。

RAG 基本流程

用户提问 → 向量检索 → 获取相关文档 → 注入上下文 → LLM 生成回答

1. 文档处理

将文档切分为适当的 chunks:

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50
)
chunks = splitter.split_documents(documents)

2. 向量化与存储

使用 Embedding 模型将文本转为向量:

from openai import OpenAI

client = OpenAI()

def embed(text):
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding

3. 检索

根据用户问题检索最相关的文档片段:

def retrieve(query, top_k=5):
    query_vec = embed(query)
    # 使用向量数据库(如 Pinecone、ChromaDB)进行相似度搜索
    results = vector_db.similarity_search(query_vec, k=top_k)
    return results

4. 生成

将检索到的文档注入 prompt:

def rag_answer(question):
    docs = retrieve(question)
    context = "\n\n".join([doc.text for doc in docs])
    
    prompt = f"""根据以下参考资料回答问题。如果资料中没有相关信息,请说明。

参考资料:
{context}

问题:{question}"""

    response = client.chat.completions.create(
        model="claude-sonnet-4-20250514",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

优化技巧

  • Chunk 大小:太小丢失上下文,太大引入噪音。通常 300-800 tokens 效果较好
  • 重排序:先粗检索 top-20,再用 reranker 精排取 top-5
  • 混合检索:结合向量检索和关键词检索(BM25)
  • 查询改写:用 LLM 改写用户问题以提高检索质量

总结

RAG 是将 LLM 与私有数据结合的最实用方案。通过合理的 chunk 策略和检索优化,可以显著提升回答的准确性和可靠性。