本文目录导读:

针对Python脚本数据知识的问答场景,检索增强生成(Retrieval-Augmented Generation, RAG) 是一种非常有效的方式,它通过先检索外部知识库中的相关文档片段,再将这些上下文信息提供给大语言模型(LLM)来生成更准确、可信的答案。
下面我将从原理、实现步骤和一个完整的代码示例来讲解如何使用RAG进行Python脚本数据知识问答。
RAG 的核心流程
- 文档加载与分割:将Python相关文档(如官方文档、教程、代码注释、Stack Overflow问答)加载并分割成较小的文本块(Chunks)。
- 向量化与存储:将每个文本块通过Embedding模型转换为向量,并存入向量数据库(如Chroma、FAISS)。
- 检索:当用户提问时,将问题也转换为向量,并在向量数据库中搜索最相似的Top-K个文本块。
- 生成:将检索到的文本块作为“上下文”与问题一起发送给LLM,让LLM基于这些信息生成答案。
你需要准备的工具
- 向量数据库:Chroma(轻量、本地运行)、FAISS(高效)、Pinecone(云端)。
- Embedding模型:
text-embedding-ada-002(OpenAI)、BAAI/bge-small-en(开源)、sentence-transformers库。 - LLM:GPT系列(OpenAI/DeepSeek/通义千问 API)、开源模型(Llama 3、Qwen 2.5)。
- 框架:LangChain 和 LlamaIndex 是两大主流框架,能大大简化RAG构建。
完整代码示例(使用 LangChain + Chroma + OpenAI)
假设我们有Python官方文档的片段(或者自己写的Python脚本示例文档),想要回答“Python中如何创建一个线程池?”。
import os
from langchain.document_loaders import TextLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# 1. 设置API密钥(建议用环境变量)
os.environ["OPENAI_API_KEY"] = "your-openai-api-key"
# 2. 加载文档(假设你有一个包含Python知识的Python目录)
# 可以使用 DirectoryLoader 加载整个目录
loader = DirectoryLoader("./python_docs/", glob="**/*.txt", loader_cls=TextLoader)
documents = loader.load()
# 3. 文本分割(考虑代码块和段落)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个块的大小(字符数)
chunk_overlap=50, # 块之间的重叠(避免切断关键信息)
separators=["\n\n", "\n", " ", ""] # 优先按段落、换行分割
)
chunks = text_splitter.split_documents(documents)
# 4. 创建向量存储(Embedding + 存入 Chroma)
embedding_model = OpenAIEmbeddings(model="text-embedding-ada-002")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embedding_model,
persist_directory="./chroma_db/" # 持久化到本地
)
# vectorstore.persist() # 旧版本需要显式持久化
# 5. 构建检索问答链
# retriever: 从向量数据库中检索 Top-4 相关文档块
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 使用一个较强的LLM进行生成(GPT-4 或 GPT-3.5)
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.2)
# 创建 QA 链(带检索功能)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # 将检索到的所有文档块拼接成一个上下文
retriever=retriever,
return_source_documents=True # 可选,用于调试或显示来源
)
# 6. 提出问题并得到答案
question = "如何在Python中使用线程池?请给出代码示例。"
response = qa_chain({"query": question})
print("答案:", response['result'])
print("\n来源文档:")
for doc in response['source_documents']:
print("-" * 30)
print(doc.page_content[:200])
print("---")
解释:
DirectoryLoader:加载指定目录下的所有.txt文件作为文档。RecursiveCharacterTextSplitter:使用递归分割,优先按段落(\n\n)分割,保持逻辑完整,并设置重叠以避免切断概念。Chroma.from_documents:将每个文档块转换为向量并存储。RetrievalQA:自动组装:检索 → 拼接上下文 → 调用LLM。chain_type="stuff":将所有检索到的文档块“塞”进提示词中。
进阶优化策略
为了让问答更准确,你还可以考虑以下优化:
改进文档分割
- 对于Python代码:可以先用注释分割块,或者保留代码块的缩进结构。
- 使用
LatexTextSplitter或PythonCodeTextSplitter(LangChain提供)来保持函数、类定义的完整性。
更好的检索策略
- HyDE(假设性文档检索):先让LLM根据问题生成一个假设性答案,再用这个答案去检索,有时能提高检索相关性。
- 多向量检索:将文档块的关键词、代码片段分别向量化,提高匹配准确度。
重排序(Reranking)
- 先用向量检索出Top-20个文档块,再用一个专门的重排序模型(如
Cohere rerank或BAAI/bge-reranker)对它们进行精细排序,只取Top-5送入LLM。
提示词优化
- 修改默认的 Prompt Template,让LLM只基于检索到的上下文回答,如果不确定就说“不知道”。
from langchain.prompts import PromptTemplate
prompt_template = """请基于以下提供的上下文来回答问题。
如果你无法从上下文中找到答案,请直接说“根据提供的材料,我无法回答这个问题”。
不要编造答案。
上下文:
{context}
问题:{question}
请回答:"""
PROMPT = PromptTemplate(
template=prompt_template, input_variables=["context", "question"]
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={"prompt": PROMPT}
)
如果你不想用大模型(本地化方案)
你也可以使用传统检索 + 规则的方法,无需调用LLM:
- BM25(词频-逆文档频率)检索:直接基于关键词匹配。
- TF-IDF + Cosine相似度:适合小规模文档。
- Elasticsearch:支持全文搜索和词向量混合检索。
但是RAG的优势在于能理解复杂的、不直接匹配的问题,如何并行执行我的任务?”能返回线程池相关内容。
- RAG是解决Python脚本知识问答的最流行方案,弥补了LLM对特定版本、特定库细节知识不足的问题。
- 核心步骤:文档分割 → 向量化 → 存储 → 检索 → 生成。
- 使用 LangChain + Chroma 可以在半小时内搭建一个原型。
- 进阶改进:优化分割、重排序、自定义提示词可以大幅提升准确率。
如果你有自己的Python文档库或代码注释,按照上面的代码示例,替换文档路径和API密钥即可运行,如果需要处理海量文档或实时更新索引,建议使用流式处理或增量更新。