RAG代码简单实践

提问前的准备工作

分片

需要导入多种文本格式,如:PDF、DOCX、Markdown 和 TXT等,然后对文档按照自定义的分割规则进行文本分片。 使用UnstructuredRecursiveCharacterTextSplitter

python 复制代码
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的FakeEmbeddingsDeterministicFakeEmbedding可以快速搭建RAG流程,注意2者都不是真正的Embedding模型,只适用于跑通流程的。具体差别如下:

特性 FakeEmbeddings DeterministicFakeEmbedding
文本敏感 否(完全忽略输入内容) 是(基于文本的 SHA-256 哈希生成种子)
结果确定性 否(每次调用都是随机向量) 是(相同文本必定生成相同向量)
适用场景 管道连通性测试、验证基本逻辑 一致性测试、需要可复现结果的单元测试

阅读其源码,可以知道都是继承了Embeddings, BaseModel这两个类,所以自定义Embedding类时,可以仿照LangChain源码写。

python 复制代码
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_documentsembed_query

python 复制代码
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向量数据库。 在这个阶段主要是初始化与数据导入到向量数据库中。

python 复制代码
from langchain_chroma import Chroma

vector_store = Chroma.from_documents( documents=文本分片, embedding=embeddings模型, persist_directory="本地具体存储数据库路径" )

提问时

召回

加载向量数据库,然后在向量数据库中按照相似度找出相似片段

python 复制代码
# 加载
vector_store = Chroma(
    persist_directory="向量数据库的路径",
    embedding_function=embeddings
)

# 查找
raw_results = vector_store.similarity_search_with_score(user_query, k=10)

重排

使用简单策略进行重排,直接按分数排序

python 复制代码
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()得到结果

相关推荐
范闲2 小时前
第二章:Octo v0.1.1:从「能用」到「好用」——终端 AI 聊天体验的全面升级
人工智能·后端
念雨思2 小时前
每日穿搭助手:鸿蒙AI应用开发实战——AI衣橱,每日穿搭不再愁
人工智能·华为·powerpoint·harmonyos·鸿蒙
yangdaxiageo2 小时前
算法时代的“心智置顶“:《功夫女足》如何构建GEO正向飞轮
人工智能·科技·算法·aigc
用户6369790981432 小时前
RFM客群细分AI:从数据到策略的自动化
人工智能
workflower2 小时前
人形机器人全身运动控制能力大幅提升
人工智能·深度学习·机器学习·设计模式·机器人·自动化
何时梦醒2 小时前
一文搞懂 RAG(检索增强生成):从 LLM 幻觉到智能检索,手把手带你理解核心原理与代码实现
人工智能
l134062082352 小时前
鸿蒙实战:从 rawfile 加载 AI 模型资源
人工智能·华为·harmonyos
0xkg2 小时前
一句 `grep . .env`,你的密钥就喂给 AI 了——readignore 堵上这个缺口
人工智能
Token炼金师2 小时前
梯度的呼吸:激活、归一化、反传与残流 —— 深度网络的四道命门
人工智能·深度学习·llm
Svan L2 小时前
文献综述工具比较:沁言学术与 ChatGPT Plus 的可用性分析
人工智能·chatgpt