RAG简单构建(ollama+uv+deepseek)
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)