RAG简单构建

环境构建 (UV)

虚拟环境

# 虚拟环境
uv venv rag --python 3.12.7
# 激活环境
source rag/bin/activate

Ollama 配置

  • 安装 ds 模型
ollama pull deepseek-r1:7b
  • 启动 ollama
ollama run deepseek-r1:7b
  • 安装依赖
 uv pip install langchain-ollama

后面会讲 LangChain 里接入 Ollama。

RAG实现(基于LangChain框架)

数据准备、索引构建、检索优化和生成集成

1. 数据准备

  • 文档加载:使用TextLoader加载该文件作为知识源。
loader = TextLoader(markdown_path)
docs = loader.load()
  • 文本分块:长文档被分割成较小的、可管理的文本块(chunks)。这里采用了递归字符分割策略,使用其默认参数进行分块。
text_splitter = RecursiveCharacterTextSplitter()
texts = text_splitter.split_documents(docs)

当不指定参数初始化 RecursiveCharacterTextSplitter() 时,其默认行为旨在最大程度保留文本的语义结构:

  • 默认分隔符与语义保留: 按顺序尝试使用一系列预设的分隔符 ["\n\n" (段落), "\n" (行), " " (空格), "" (字符)] 来递归分割文本。这种策略的目的是尽可能保持段落、句子和单词的完整性,因为它们通常是语义上最相关的文本单元,直到文本块达到目标大小。
  • 保留分隔符: 默认情况下 (keep_separator=True),分隔符本身会被保留在分割后的文本块中。
  • 默认块大小与重叠: 使用其基类 TextSplitter 中定义的默认参数 chunk_size=4000(块大小)和 chunk_overlap=200(块重叠)。这些参数确保文本块符合预定的大小限制,并通过重叠来减少上下文信息的丢失。

1️⃣ chunk_size(块大小)

  • 作用:控制每个文本块的最大字符数。
  • 影响
    • 小 → 文本切得碎,检索粒度细,但语义容易断裂。
    • 大 → 文本切得整,语义完整,但占用模型上下文多,检索不够精细。
  • 推荐值:300~500 字符(根据 LLM 上下文窗口调整)。

2️⃣ chunk_overlap(块重叠)

  • 作用:控制相邻块之间重复的字符数。
  • 影响
    • 小/0 → 上下文可能断开,模型回答可能缺少前后信息。
    • 大 → 上下文衔接好,但会增加冗余,重复计算。
  • 推荐值:50~100 字符,保证连续性又不太冗余。

2. 索引构建

  • 初始化中文嵌入模型,并启用嵌入归一化。
embeddings = HuggingFaceEmbeddings(
    model_name="BAAI/bge-small-zh-v1.5",
    model_kwargs={'device': device},
    encode_kwargs={'normalize_embeddings': True}
)
  • 构建向量存储: 将分割后的文本块 (texts) 通过初始化好的嵌入模型转换为向量表示,然后使用InMemoryVectorStore将这些向量及其对应的原始文本内容添加进去,从而在内存中构建出一个向量索引。
vectorstore = InMemoryVectorStore(embeddings)
vectorstore.add_documents(texts)

3. 查询与检索

  • 定义用户查询
question = "xxx?"
  • 在向量存储中查询相关文档
retrieved_docs = vectorstore.similarity_search(question, k=3)

embedding 模型(比如 bge-small-zh-v1.5)把 question 转换成一个向量。

和知识库中所有文本块(chunks)的向量做相似度计算(一般是余弦相似度)。

挑出最相似的 k=3 条文档(Top-3)。

  • 准备上下文: 将检索到的多个文本块的页面内容 (doc.page_content) 合并成一个单一的字符串,形成最终的上下文信息 (docs_content) 供大语言模型参考。
docs_content = "\n\n".join(doc.page_content for doc in retrieved_docs)

4. 生成集成

将检索到的上下文与用户问题结合,利用大语言模型(LLM)生成答案

  • 构建提示词模板: 使用ChatPromptTemplate.from_template创建一个结构化的提示模板。此模板指导LLM根据提供的上下文 (context) 回答用户的问题 (question),并明确指出在信息不足时应如何回应。
prompt = ChatPromptTemplate.from_template("""请根据下面提供的上下文信息来回答问题。
请确保你的回答完全基于这些上下文。
如果上下文中没有足够的信息来回答问题,请直接告知:“抱歉,我无法根据提供的上下文找到相关信息来回答此问题。”

上下文:
{context}

问题: {question}

回答:"""                                          )
  • 配置大语言模型
llm = ChatOllama(
    model="deepseek-r1:7b", 
    temperature=0.7,
    max_tokens=2048
)
  • 调用LLM生成答案并输出: 将用户问题 (question) 和先前准备好的上下文 (docs_content) 格式化到提示模板中,然后调用ChatDeepSeek的invoke方法获取生成的答案。
answer = llm.invoke(prompt.format(question=question, context=docs_content))
print(answer.content)
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐