使用工具
- langchain框架
- bge-small-zh-v1.5 嵌入模型
- Chroma向量库
- LCEL的RAG链
整体过程
构建向量库
首先了解整个过程
- 收集数据:txt,pdf等数据格式均可以
- 加载数据,可以利用DirectoryLoader加载,但是文章使用的直接遍历加载
- 加载embeding模型,本文章使用的是bge-small-zh-v1.5模型
- 使用该模型进行向量化
- 储存到我们的chroma向量库
查询知识库
- 加载llm模型
- 加载上面一样的embedding模型
- 加载储存好的向量数据库
- 从数据库中创建一个检索器
- 定义prompt模板
- 利用lcel的rag链将他们连一起
- 示例问题
详细过程
构建知识库
- 导入必要库
import os
from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
2.下载模型
git install lfs
$env:GIT_CLONE_PROTECTION_ACTIVE="false"; git clone https://huggingface.co/BAAI/bge-small-zh-v1.5
- 选择embeding模型
下面的path需要更换为自己的path,也就是提前下载好path
model_kwargs = {'device': 'cpu'} # 如果你有支持CUDA的NVIDIA显卡,可以改成 {'device': 'cuda'}
encode_kwargs = {'normalize_embeddings': True} # 设置为True,返回归一化的向量,便于余弦相似度计算
embedding_function = HuggingFaceEmbeddings(
model_name=MODEL_PATH,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
- 加载文档
在加载文档的过程中,我们采用document的构建方式,是因为对于每一个获取到的文件,我们都用document进行格式化
- page_content (str): 文档的核心文本内容。
- metadata (dict): 描述这份文档的元数据,比如来源文件名、页码、作者等等。这个元数据在后续的高级应用中非常有用。
目前支持两种格式,当然可拓展性很高
documents = []
try:
for root, _, files in os.walk(SOURCE_DIRECTORY):
print(f"正在扫描文件夹: {root}")
for file in files:
file_path = os.path.join(root, file)
if file.endswith(".txt"):
try:
with open(file_path, 'r', encoding='utf-8') as f:
text = f.read()
doc = Document(page_content=text, metadata={"source": file_path})
documents.append(doc)
print(f"成功手动加载 TXT 文件: {file_path}")
except Exception as e:
print(f"加载 TXT 文件 {file_path} 时出错: {e}")
elif file.endswith(".pdf"):
try:
# 使用LangChain的PyPDFLoader来处理PDF
# 它会为PDF的每一页创建一个Document对象
pdf_loader = PyPDFLoader(file_path)
# .load()返回一个Document列表
pdf_docs = pdf_loader.load()
documents.extend(pdf_docs) # 将PDF中的所有页面文档添加到主列表中
print(f"成功加载 PDF 文件: {file_path} (共 {len(pdf_docs)} 页)")
except Exception as e:
print(f"加载 PDF 文件 {file_path} 时出错: {e}")
- 然后将加载的文档分成小块
- chunk_size=500: 这定义了每个文本块(Chunk)的最大长度,单位是字符。它会尽量保证每个块不超过500个字符。
- chunk_overlap=100: 这定义了相邻两个文本块之间的重叠字符数。也就是说,下一个块的开头会包含上一个块结尾的100个字符。
- 而用到的方法:它会尝试按一个优先级列表来进行分割,这个列表默认是 [“\n\n”, “\n”, " ", “”]。它会首先尝试用段落(两个换行符)来分割,如果分完的块还是太大,它就会退一步,在块内用句子(一个换行符)来分割,再不行就用空格,最后才会粗暴地按字符分割。这比简单的按字符分割效果好得多。
# 2. 将加载的文档切分成小块 (Chunking)
# 这对于后续的检索至关重要
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)
split_docs = text_splitter.split_documents(documents)
print(f"文档已切分为 {len(split_docs)} 个小块。")
- 向量化到ChromaDB中
db = Chroma.from_documents(
split_docs,
embedding_function,
persist_directory=PERSIST_DIRECTORY # 指定路劲
)
# 确保数据被写入磁盘
db.persist()
查询向量库
- 加载llm模型
这里自行加载储存的密码
secrets = load_secrets()
llm = ChatOpenAI(
api_key=secrets.get("API_KEY"),
base_url=secrets.get("BASE_URL"),
model_name=secrets.get("MODEL"),
temperature=0.0 # 对于RAG,我们希望答案基于事实,所以温度设为0
)
- 加载模型
与上一步一致
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': True}
embedding_function = HuggingFaceEmbeddings(
model_name=MODEL_PATH,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
- 加载数据库
db = Chroma(
persist_directory=PERSIST_DIRECTORY,
embedding_function=embedding_function
)
- 创建一个检索器
它将一个完整的向量数据库(db对象)转换成了一个专门负责检索的“检索器”(Retriever对象)
k: 3意味着:“当进行检索时,请返回与查询最相似的前3个文档块”
retriever = db.as_retriever(search_kwargs={"k": 3}) # 设置为返回最相关的3个结果
- 定义prompt模板
利用langchain中的模板进行攥写
template = """
请只根据以下提供的上下文信息来回答问题。
上下文:
{context}
问题:
{question}
"""
prompt = ChatPromptTemplate.from_template(template)
- 利用LCEL链构建rag链
- 其中的retriever是前面的查询向量库,
- RunnablePassthrough将问题传入
- | prompt: 上一步的字典被“管道”传给prompt对象。prompt对象接收到这个字典,并用里面的context和question值来填充它的模板,生成一个结构化的Prompt。
- | llm: 填充好的Prompt被传给llm对象,LLM执行并返回一个AIMessage对象。
- | StrOutputParser(): AIMessage对象被传给StrOutputParser,它会提取出其中的文本内容,最终返回一个干净的字符串。
rag_chain = (
# RunnablePassthrough()会将问题同时传给retriever和prompt
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser() # 将LLM的输出解析为字符串
)
7 . 回答
invoke是LCEL链条的“执行”方法。它是触发整个链条从头到尾运行的命令。
当传入question的时候,就会执行整一个链
response = rag_chain.invoke(question)
效果
txt:因果推断是统计学和数据科学中的一个核心领域,它的主要目标是确定一个事件(原因)对另一个事件(结果)的影响程度。这不仅仅是发现变量之间的相关性,而是要探究它们之间是否存在真正的因果关系。
一个经典的概念是“相关不等于因果”。例如,夏天的冰淇淋销量和溺水人数都同时上升,它们高度相关,但我们不能说是吃冰淇淋导致了溺水。这背后其实有一个共同的原因,即“炎热的天气”,这个变量被称为“混杂变量”(Confounding Variable)。
处理混杂变量是因果推断的关键挑战之一。常用的方法包括:
- 随机对照试验(RCT):这是黄金标准,通过随机分组来消除混杂因素的影响。
- 工具变量法(Instrumental Variables):当无法进行RCT时,寻找一个“工具变量”,它只影响原因而不直接影响结果。
- 回归不连续性设计(Regression Discontinuity):利用一个临界值来近似实现随机分组的效果。
RAG:
用户问题: 因果推断是什么?它和相关性有什么区别?
— RAG响应 —
因果推断是统计学和数据科学中的一个核心领域,它的主要目标是确定一个事件(原因)对另一个事件(结果)的影响程度。它不仅仅是发现变量之间的相关性,而是要探究它们之间是否存在真正的因果关系。
与相关性相比,因果推断的区别在于:
相关性指的是两个变量之间是否存在统计上的关联,即它们是否经常一起变化。例如,夏天的冰淇淋销量和溺水人数 可能同时上升,这表明它们之间存在相关性。
因果关系则进一步探究一个变量是否真正导致了另一个变量的变化。例如,虽然夏天的冰淇淋销量和溺水人数可能同 时上升,但因果推断会探究是否是吃冰淇淋导致了溺水,而不是其他共同的原因,如炎热的天气。
简而言之,相关性描述了变量之间的统计关联,而因果推断则试图确定一个变量是否是另一个变量的原因。
用户问题: 处理混杂变量的方法有哪些?
— RAG响应 —
处理混杂变量的方法包括:
- 随机对照试验(RCT):这是黄金标准,通过随机分组来消除混杂因素的影响。
- 工具变量法(Instrumental Variables):当无法进行RCT时,寻找一个“工具变量”,它只影响原因而不直接影响结果。
- 回归不连续性设计(Regression Discontinuity):利用一个临界值来近似实现随机分组的效果。
用户问题: Judea Pearl是谁?
— RAG响应 —
根据提供的知识库,我无法回答该问题。