提问前的准备工作
分片
需要导入多种文本格式,如:PDF、DOCX、Markdown 和 TXT等,然后对文档按照自定义的分割规则进行文本分片。
使用Unstructured和RecursiveCharacterTextSplitter
from langchain_community.document_loaders import UnstructuredFileLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 1. 使用 Unstructured 解析文档,保留元素结构
loader = UnstructuredFileLoader(
file_path="financial_report.pdf", # 文件路径
mode="elements", # 按语义单元拆分
strategy="fast", # 底层解析文本时分区策略
languages=["chi_sim"] # 文档的语言类型,指定:简体中文
)
documents = loader.load()
# 2. 使用 RecursiveCharacterTextSplitter 进行文本切块
# 针对中文文档,可以在分隔符中加入中文标点
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 文本块最大长度
chunk_overlap=200, # 文本块重叠长度
length_function=len, # 长度计算函数,用于计算文本长度
separators=["\n\n", "\n", "。", ",", " ", ""] # 递归分割
)
splits = text_splitter.split_documents(documents)
Embedding模型
利用LangChain的FakeEmbeddings,DeterministicFakeEmbedding可以快速搭建RAG流程,注意2者都不是真正的Embedding模型,只适用于跑通流程的。具体差别如下:
| 特性 | FakeEmbeddings | DeterministicFakeEmbedding |
|---|---|---|
| 文本敏感 | 否(完全忽略输入内容) | 是(基于文本的 SHA-256 哈希生成种子) |
| 结果确定性 | 否(每次调用都是随机向量) | 是(相同文本必定生成相同向量) |
| 适用场景 | 管道连通性测试、验证基本逻辑 | 一致性测试、需要可复现结果的单元测试 |
阅读其源码,可以知道都是继承了Embeddings, BaseModel这两个类,所以自定义Embedding类时,可以仿照LangChain源码写。
class FakeEmbeddings(Embeddings, BaseModel):
"""Fake embedding model for unit testing purposes.
This embedding model creates embeddings by sampling from a normal distribution.
!!! danger "Toy model"
Do not use this outside of testing, as it is not a real embedding model.
Instantiate:
```python
from langchain_core.embeddings import FakeEmbeddings
embed = FakeEmbeddings(size=100)
```
Embed single text:
```python
input_text = "The meaning of life is 42"
vector = embed.embed_query(input_text)
print(vector[:3])
```
```python
[-0.700234640213188, -0.581266257710429, -1.1328482266445354]
```
Embed multiple texts:
```python
input_texts = ["Document 1...", "Document 2..."]
vectors = embed.embed_documents(input_texts)
print(len(vectors))
# The first 3 coordinates for the first vector
print(vectors[0][:3])
```
```python
2
[-0.5670477847544458, -0.31403828652395727, -0.5840547508955257]
```
"""
size: int
"""The size of the embedding vector."""
def _get_embedding(self) -> list[float]:
return list(np.random.default_rng().normal(size=self.size))
@override
def embed_documents(self, texts: list[str]) -> list[list[float]]:
return [self._get_embedding() for _ in texts]
@override
def embed_query(self, text: str) -> list[float]:
return self._get_embedding()
接下来自定义自己需要的Embedding类,需要重写成员函数/方法(embed_documents、embed_query)
from typing import *
from langchain_core.embeddings import Embeddings
class MyCustomEmbeddings(Embeddings):
def __init__(self, openai_client: OpenAI, model: str):
"""初始化自定义Embedding客户端
Args:
openai_client (OpenAI): OpenAI 客户端实例
model (str, optional): 指定使用Embedding模型名称
"""
self.client = openai_client
self.model = model
@override
def embed_documents(self, texts: list[str]) -> list[list[float]]:
"""多条信息查询"""
response = self.client.embeddings.create(
model=self.model,
input=texts
)
return [data.embedding for data in response.data]
@override
def embed_query(self, text: str) -> list[float]:
"""单条信息查询"""
response = self.client.embeddings.create(
model=self.model,
input=[text]
)
print(f"单条消息查询转换向量,得到:{response}") # 得到 CreateEmbeddingResponse(data=[Embedding(embedding=[], ...)
query_vector = response.data[0].embedding
return query_vector
向量数据库
使用ChromaDB向量数据库。
在这个阶段主要是初始化与数据导入到向量数据库中。
from langchain_chroma import Chroma
vector_store = Chroma.from_documents( documents=文本分片, embedding=embeddings模型, persist_directory="本地具体存储数据库路径" )
提问时
召回
加载向量数据库,然后在向量数据库中按照相似度找出相似片段
# 加载
vector_store = Chroma(
persist_directory="向量数据库的路径",
embedding_function=embeddings
)
# 查找
raw_results = vector_store.similarity_search_with_score(user_query, k=10)
重排
使用简单策略进行重排,直接按分数排序
def doc_filter(input: list[tuple[Document, float]], DISTANCE_THRESHOLD:float= 0.65):
"""相似度分数硬阈值过滤
物理含义:分值越低(越接近 0),代表向量越相似;分值越高,代表差异越大。
不能盲目取前 3 个。如果第 2 和第 3 个文档的距离已经高达 1.5(说明完全不相关),强行喂给 LLM 反而会干扰回答。
我们需要加一个距离阈值截断
Args:
input (list[tuple[Document, float]]): _description_
DISTANCE_THRESHOLD (float, optional): _description_. Defaults to 0.65.
"""
fine_tuned_docs = []
for doc, score in input:
if score < DISTANCE_THRESHOLD:
fine_tuned_docs.append(doc)
if len(fine_tuned_docs) == 3:
break
return fine_tuned_docs
生成
将重排结果通过字符串拼接在上下文后面,通过chain.invoke()得到结果