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()得到结果

相关推荐
东方佑9 分钟前
事件发生与智能:微观一致性与宏观涌现性
人工智能·深度学习·自然语言处理·架构·gru
明志数科12 分钟前
机器人训练数据采集中的任务拆解与原子动作清单SOP
人工智能·机器学习·机器人
蜗牛互联网12 分钟前
长任务多Agent共享文件系统的Manifest交接模式
java·人工智能·后端
skywalk816328 分钟前
给DeepSeek harness发布R107任务撰写工作:还有一些其它遗留问题,你也一并放到这一轮任务里!你写并行任务文档,我来分发!
人工智能·调试·deepseek
EatFan32 分钟前
AI 从「能生成」到「能交付」:2026年9月智能体(Agentic)成为产业主线的多源证据与开发者应对清单
人工智能·大模型·rag·智能体·mcp·agentic ai
水如烟33 分钟前
孤能子视角:从 EIS 的意识论、感质论与认知论解读病理
人工智能
百度一下吧43 分钟前
Codex 使用操作指南
人工智能
正经教主43 分钟前
【FDE系列】阶段3:Day 55:综合实战 — 巡检报告生成器与本周收官
人工智能·fde
和裕1 小时前
全纸结构重型纸箱能否满足 1 吨以上设备出口熏蒸豁免要求?通关合规性全解析
大数据·运维·网络·人工智能·算法
Ai-_Man1 小时前
您您这可以把Microsofat Copilot的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑·copilot