RAG代码简单实践

提问前的准备工作

分片

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

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的FakeEmbeddings,DeterministicFakeEmbedding可以快速搭建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_documents、embed_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()得到结果

相关推荐
珠海西格电力11 分钟前
零碳园区管理系统的性能评估体系的行业标准
大数据·网络·人工智能·物联网·机器学习·架构·能源
wangruofeng32 分钟前
黑色十月:AI 找到的漏洞,两小时数十万次利用
人工智能·安全·php
二川bro34 分钟前
1美金撬动完整开源项目,Gemini+DeepSeek双AI组队写软件
人工智能
一线数智43 分钟前
朱鹰:人的判断力,比算力更重要
大数据·人工智能
IT_陈寒43 分钟前
Java性能优化完全指南
前端·人工智能·后端
liubin18401 小时前
一个人做多端产品,怎么少写一遍代码
人工智能
雨中飘荡的记忆1 小时前
Claude Code 入门必会:@、!、Slash Commands,一篇讲清楚
人工智能·后端
满眼星河1 小时前
RAG应用功能边界
人工智能
阿里云大数据AI技术1 小时前
Hologres Agent:让数仓运维、开发和分析更高效
人工智能·agent
诚小纯1 小时前
qwen-deep-research 接入实录:两阶段调用、SSE 解析、报告结构与 35 处不可追溯的引用
人工智能·github