惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Last Watchdog
The Last Watchdog
博客园_首页
Martin Fowler
Martin Fowler
S
SegmentFault 最新的问题
美团技术团队
小众软件
小众软件
V
V2EX
博客园 - Franky
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
Attack and Defense Labs
Attack and Defense Labs
S
Security Affairs
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
T
The Exploit Database - CXSecurity.com
有赞技术团队
有赞技术团队
S
Schneier on Security
人人都是产品经理
人人都是产品经理
Security Archives - TechRepublic
Security Archives - TechRepublic
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
K
Kaspersky official blog
PCI Perspectives
PCI Perspectives
AI
AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
罗磊的独立博客
O
OpenAI News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The Register - Security
The Register - Security
V
Vulnerabilities – Threatpost
GbyAI
GbyAI
博客园 - 【当耐特】
C
Cisco Blogs
大猫的无限游戏
大猫的无限游戏
Help Net Security
Help Net Security
Google DeepMind News
Google DeepMind News
S
Securelist
Application and Cybersecurity Blog
Application and Cybersecurity Blog
P
Proofpoint News Feed
博客园 - 三生石上(FineUI控件)
雷峰网
雷峰网
L
LangChain Blog
SecWiki News
SecWiki News
博客园 - 叶小钗
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V2EX - 技术
V2EX - 技术
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
L
LINUX DO - 热门话题
Cisco Talos Blog
Cisco Talos Blog

博客园 - Yaopengfei

第五节:Claude Code的用法实操汇总 第十五节:LlamaIndex框架使用2() 第十四节:LlamaIndex框架介绍和入门1(基本用法、提示词、节点等) 第三节:Trae的基本使用 和 Skill的详细用法 第二节:Python基础2 第十三节:FastApi入门以及AI智能体案例落地实操 第二节:GitHub Copilot 用法(基于VSCode/VS、添加DeepSeek/千问等任意模型) 第一节:AI工具总览(Copilot、Cursor、Claude Code、OpenCode、Codex、Trae等) 第一节:Python基础1(数据类型、命名规范、输入输出、运算符、类型转换等) 第十节:预检索-索引优化(摘要、父子、假设问题、元数据、混合索引) 第十二节:MinerU处理复制PDF 和 综合性金融案例实战 第十一节:多检索查询、混合检索(多检索+RRF重排)、检索后优化(文档压缩) 第九节:RAG进阶和Advanced RAG简介 博文阅读密码验证 - 博客园 第七节:LangChain框架Chain链和Agent代理详解 第六节:LangChain框架Model和数据检索详解 第五节:LangChain框架简介和快速入门(模型、模板、解释器、向量、RAG、代理) 第四节:补充pip相关指令大全 博文阅读密码验证 - 博客园 第一节:Python相关环境安装和配置(Python、PyCharm、Anaconda) 第三十六节:EFCore10.0新增功能和中断性变更 第七节:框架版本大升级(CoreMvc10.x + EFCore10.x) 第五十二节:Core10.0中OpenApi自定义文档(Swagger) 博文阅读密码验证 - 博客园 第三节:C#13、C#14新语法(数字字符串比较、Null分配、扩展成员新写法等等) 第二节:如何理解Embedding以及基于内存库简单实操
第三节:RAG基础(概念、工作流程、文档分块、向量和Embedding、RAG案例等等)
Yaopengfei · 2026-03-09 · via 博客园 - Yaopengfei

一. 基础概念

1  LLM的缺陷

   (1) LLM有很强的时效性,比如该模型训练到25年12月,那么26年以后的知识它是不知道。

   (2) 在一些深层次的专业领域,LLM训练不到位。

   引入RAG的初衷:① 增强LLM的事实性和时效性;② 引入专业知识优化生成回复。

2  RAG概念

   (1) 顾名思义: 检索外部数据,增强大模型的生成

   (2) 举例:   我想询问LLM有关于我公司内部的请假流程它肯定不知道,但是我把公司的员工手册先给它看,当我在询问的时候他就会知道。

3  RAG的工作流程

  它主要包括三个步骤:

  (1) 向量化:将文档切分成片段,再编码为向量,存储在向量数据库里。

  (2) 检索:检索到与问题相似度最高的top k 的片段。

  (3) 生成:原始问题和检索得到的“块”都作为LLM的输入,令其生成最终的回答。

补充RAG与微调对比:

image

二. 文档分块

1 按句子切分

代码分享

import re

text = (
    "自然语言处理(NLP),作为计算机科学、人工智能与语言学的交融之地致力于赋予计算机解析和处理人类语言的能力。在这个领域,"
    "机器学习发挥着至关重要的作用。利用多样的算法,机器得以分析、领会乃至创造我们所理解的语言。"
    "从机器翻译到情感分析,从自动摘要到实体识别,NLP的应用已遍布各个领域。随着深度学习技术的飞速进步,"
    "NLP的精确度与效能均实现了巨大飞跃。如今,部分尖端的NLP系统甚至能够处理复杂的语言理解任务,"
    "如问答系统、语音识别和对话系统等。NLP的研究推进不仅优化了人机交流,也对提升机器的自主性和智能水平起到了关键作用。"
)

# 按中文句号、问号、感叹号、省略号分割文本
sent = re.split(r"(。|?|!|\...\...)", text)
print(sent)  # 将分隔符也保留下来

# 处理分隔符的问题1
chunks = []
for sen, pun in zip(sent[::2], sent[1::2]):  # sent[::2]取偶数下标  sent[1::2]取奇数下标
    chunks.append(sen + pun)
print(chunks)

# 处理分隔符的问题2
# pun2为空则不追加
chunks2 = []
for sen2, pun2 in zip(sent[::2], sent[1::2]):
    chunks2.append(sen2 + (pun2 if pun2 else ""))
print(chunks2)
# enumerate 遍历(带索引)
for i, chunk in enumerate(chunks2):
    print(f"块{i + 1}的长度为 {len(chunk)}: {chunk}")

2 按固定字符切分

代码分享

text = (
    "自然语言处理(NLP),作为计算机科学、人工智能与语言学的交融之地致力于赋予计算机解析和处理人类语言的能力。在这个领域,"
    "机器学习发挥着至关重要的作用。利用多样的算法,机器得以分析、领会乃至创造我们所理解的语言。"
    "从机器翻译到情感分析,从自动摘要到实体识别,NLP的应用已遍布各个领域。随着深度学习技术的飞速进步,"
    "NLP的精确度与效能均实现了巨大飞跃。如今,部分尖端的NLP系统甚至能够处理复杂的语言理解任务,"
    "如问答系统、语音识别和对话系统等。NLP的研究推进不仅优化了人机交流,也对提升机器的自主性和智能水平起到了关键作用。"
)
chunks = []
for i in range(0, len(text), 50):  # 从0开始,到len(text)结束,步长为50
    print(text[i : i + 50])
    chunks.append(text[i : i + 50])  # 每 50 个字符切一段
print(chunks)

3 封装固定字符分割

代码分享

def overlap_split(text, step):
    """
    对文本进行带步长的切片
    :param text: 原始文本
    :param step: 步长
    """
    list = []
    for i in range(0, len(text), step):
        list.append(text[i : i + step])
    return list


res = overlap_split(text, 30)
print(res)

4 递归分割

使用LangChain框架实现

代码分享

"""
1. 这里使用0.3.7 LangChain,内置了 angchain-text-splitters
   pip install langchain==0.3.7  -i https://pypi.tuna.tsinghua.edu.cn/simple
2. LangChain 1.x中 单独安装langchain-text-splitters, 因为 RecursiveCharacterTextSplitter 现在不在 langchain 里了
   pip install langchain-text-splitters -i https://pypi.tuna.tsinghua.edu.cn/simple
解决版本冲突
    pip uninstall langchain langchain-core langchain-text-splitters -y
强制安装,自动解决冲突
    pip install --force-reinstall langchain-text-splitters -i https://pypi.tuna.tsinghua.edu.cn/simple
"""

from langchain_text_splitters import RecursiveCharacterTextSplitter

text = (
    "自然语言处理(NLP),作为计算机科学、人工智能与语言学的交融之地致力于赋予计算机解析和处理人类语言的能力。在这个领域,"
    "机器学习发挥着至关重要的作用。利用多样的算法,机器得以分析、领会乃至创造我们所理解的语言。"
    "从机器翻译到情感分析,从自动摘要到实体识别,NLP的应用已遍布各个领域。随着深度学习技术的飞速进步,"
    "NLP的精确度与效能均实现了巨大飞跃。如今,部分尖端的NLP系统甚至能够处理复杂的语言理解任务,"
    "如问答系统、语音识别和对话系统等。NLP的研究推进不仅优化了人机交流,也对提升机器的自主性和智能水平起到了关键作用。"
)
#  chunk_size:每一段文本最多30个字符,你设置多少,就是多少
#  chunk_overlap:两段之间重叠的字数,主要是为了防止语义被切断,这里10表示:第一段末尾 10 个字 = 第二段开头 10 个字
#  length_function=len :表示按字符个数计算(中文、英文、数字都算 1 个)
fun = RecursiveCharacterTextSplitter(chunk_size=30, chunk_overlap=10, length_function=len)
data = fun.split_text(text)
print(data)

三. 向量与Embedding

1 向量

  向量在数学中,指的是:具有大小和方向的量,可以形象化的表示为带箭头的线段。 箭头所指:代表向量的方向;线段长度:代表向量的大小。

 (1)  将文本转成一组浮点数:每个下标 i,对应一个维度

 (2)  整个数组对应一个 n 维空间的一个点

 (3)  向量之间可以计算距离,距离的远近 对应 语义相似度的大小

2 Embedding

 嵌入: 表示学习的一种形式,通常用于将高维数据映射到低维空间中的表示形式。嵌入可以是词嵌入、图像嵌入、图嵌入等。

 例如,在自然语言处理中,词嵌入将词语映射到低维向量空间,以捕捉词语之间的语义和句法关系。在图像处理中,图像嵌入将图像映射到低维向量空间,以表示图像的视觉特征。

补充:向量和embeddings区别:

(1) embeddings可以理解成一个模型(text-embeddings-v3),一个工具,它可以把文本、图片、语音 → 变成固定长度的数字数组

(2) 向量就是一串数字,embeddings后的输出结果。

代码分享:

from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()
client = OpenAI(api_key=os.getenv("api_key"), base_url=os.getenv("base_url"))
def get_embedding(text):
    res = client.embeddings.create(input=text, model="text-embedding-v3")
    return [i.embedding for i in res.data]

test_query = ["我爱你"]
vec = get_embedding(test_query)
# print(vec)  # 形如[[xxx,xxx,]] 所以 vec[0],才是向量数组
print(vec[0])  # 输出向量
print(len(vec[0]))  # 输出向量的长度

3 向量间的相似度计算

    常用形似都计算的方法:

(1)  余弦距离Cosine

   基于两个向量夹角的余弦值来衡量相似度。

代码分享:

(2) 欧式距离L2

 通过计算向量之间的欧几里得距离来衡量相似度。

代码分享:

(3) 点积

 计算两个向量的点积,适合归一化后的向量。

代码分享:

查看代码
 import numpy as np
from numpy import dot
from numpy.linalg import norm
from openai import OpenAI
from dotenv import load_dotenv
import os

load_dotenv()
client = OpenAI(api_key=os.getenv("api_key"), base_url=os.getenv("base_url"))

# 余弦距离
def cos_sim(a, b):
    """余弦距离   越大越相似"""
    return dot(a, b) / (norm(a) * norm(b))

# 欧式距离
def l2(a, b):
    """距离关系 越小越相似"""
    x = np.asarray(a) - np.asarray(b)
    return norm(x)

# 封装获取embedding的方法
def get_embedding(text):
    data = client.embeddings.create(input=text, model="text-embedding-v3")
    return [i.embedding for i in data.data]


# # # # # # # # # # # # # # # # # #下面开始调用 # # # # # # # # # # # # # # # # # # # # # # # # # # # #

query = "我国开展舱外辐射生物学暴露实验"
documents = [
    "联合国就苏丹达尔富尔地区大规模暴力事件发出警告",
    "土耳其、芬兰、瑞典与北约代表将继续就瑞典“入约”问题进行谈判",
    "日本岐阜市陆上自卫队射击场内发生枪击事件 3人受伤",
    "国家游泳中心(水立方):恢复游泳、嬉水乐园等水上项目运营",
    "我国首次在空间站开展舱外辐射生物学暴露实验",
]

query_vec = get_embedding([query])[0]
doc_vec = get_embedding(documents)
# print(query_vec)

print("余弦距离")
# 余弦距离
print(f"完全一样的两个值的余弦距离:{cos_sim(query_vec, query_vec)}")  # 完全一样,最大值为1
for vec in doc_vec:
    print(cos_sim(query_vec, vec))

print("欧式距离")
# 欧式距离
print(f"完全一样的两个值的余弦距离:{l2(query_vec, query_vec)}")  # 距离越近,越相似 ,完全一样为 0
for vec in doc_vec:
    print(l2(query_vec, vec))

4 常用的向量数据库

(1) 常见向量数据库

image

(2) chromadb的使用

详见:https://zhuanlan.zhihu.com/p/680661442 

安装模块:

 (1)  需要先安装C++ 14的编译环境, 使用vs的安装包,安装C++开发桌面程序即可

 (2) 【pip install chromadb -i https://pypi.tuna.tsinghua.edu.cn/simple】           【速度非常快  安装的是 1.5.5 版本】

代码分享:

"""
安装步骤
    1 需要先安装C++ 14的编译环境, 使用vs的安装包,安装C++开发桌面程序即可
    2 pip install chromadb -i https://pypi.tuna.tsinghua.edu.cn/simple 【1.5.5 版本】  推荐使用这个哦
"""

import chromadb

# 初始化方式1:基于内存
client = chromadb.Client()
# 初始化方式2:存放到本地
# client = chromadb.PersistentClient(path=r"D:\DB")
# 初始化客户端 (这里的表叫做collection,表名为:test)
cll = client.get_or_create_collection(name="test")

# 添加数据
cll.add(
    # 文本内容列表,每个元素是一段文本(如文章、句子等)
    documents=["Article by john", "Article by Jack", "Article by Jill"],
    # 嵌入向量列表,每个元素是一个与 documents 对应的向量表示
    embeddings=[[1, 2, 3], [4, 5, 6], [7, 8, 9]],
    # 自定义 ID 列表,用于唯一标识每条记录
    ids=["1", "2", "3"],
)

# 查询数据--参数化查询,查询里面含有by的
aa = cll.get(ids=["1"], include=["embeddings"], where_document={"$contains": "by"})
print(aa)

# ========== 修正1:精准ID查询(用get方法,仅按ID查,无文本过滤) ==========
# get方法仅支持ids/where(元数据过滤),不支持where_document
result_by_id = cll.get(ids=["1"], include=["embeddings", "documents"])  # 仅按ID查询  # 指定返回向量+文本
print("【按ID精准查询结果】")
print(result_by_id)

# 删除数据
cll.delete(ids=["1"])
print(cll.get())

# 修改数据
cll.update(
    documents=["Article 12323456", "Article xxxxxxx", "Article by Jill"],  # 文本内容列表,每个元素是一段文本(如文章、句子等)
    embeddings=[[1, 2, 3], [4, 5, 6], [7, 8, 9]],  # 嵌入向量列表,每个元素是一个与 documents 对应的向量表示
    ids=["1", "2", "3"],  # 自定义 ID 列表,用于唯一标识每条记录
)
print(cll.get())

(3) redis的使用

   windows版本【仅测试】:https://github.com/tporadowski/redis/releases

安装模块:

     【pip install redis -i https://pypi.tuna.tsinghua.edu.cn/simple】

代码分享:

"""
如何查找这个库都有哪些版本 :直接去 https://pypi.org/    或者   pip index versions redis
安装    pip install redis -i https://pypi.tuna.tsinghua.edu.cn/simple
这里安装的  7.3.0
"""

import json
import redis

# 创建 Redis 连接
r = redis.Redis(host="60.xxx.xxx.80", port=6379, password="xxxx", db=2, decode_responses=True)


# 从JSON文件中读取数据
def read_data():
    with open("../04-RAG基础/train_zh.json", "r", encoding="utf-8") as f:
        res = [json.loads(line) for line in f]
    instructions1 = [entry["instruction"] for entry in res[0:1000]]
    outputs1 = [entry["output"] for entry in res[0:1000]]
    return instructions1, outputs1


# 将读取出来的数据存入Redis中
def set_redis_documents(instructions1, outputs1):
    for instruction, output in zip(instructions1, outputs1):
        r.set(instruction, output)


# 在Redis中根据关键词进行模糊搜索
def search_instructions(instruction_key, top_n=3):
    keys = r.keys(pattern="*" + instruction_key + "*")
    res = []
    for key in keys:
        res.append(r.get(key))
    return res[:top_n]


# 先从文件中读取数据
instructions, outputs = read_data()
# 在把数据存入到Redis中
set_redis_documents(instructions, outputs)
# 在Redis中进行检索
data = search_instructions("怀孕")
print(data)

四. 基于RAG知识库实现AI系统案例

大致内容:

 (1) 读取pdf 和 word中的内容,进行切割。

   (2) 将分块后的内容进行 embeddings,将输出的向量存到 chromadb 数据库中

   (3) 检索内容,获取匹配度最高的两个:top k=2

 (4) 将检索到的内容和问题,一起发送给LLM,获取最后的输出结果。

代码分享: 

# 先下载解析PDF和word的模块
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple python-docx
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pdfminer.six


import os
import chromadb
from dotenv import load_dotenv
from openai import OpenAI
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTTextContainer


# 按照固定字符切割文档
def sliding_window_chunks(text, chunk_size, stride):
    """
    滑动窗口切割文本
    :param text: 原始文本
    :param chunk_size: 每段长度
    :param stride: 滑动步长
    :return: 切割后的文本列表
    """
    return [text[i : i + chunk_size] for i in range(0, len(text), stride)]


# 读取PDF并割文档
def extract_text_from_pdf(filename, page_numbers=None, min_line_length=1):
    """从 PDF 文件中(按指定页码)提取文字"""
    """
    :param filename: PDF文件路径
    :param page_numbers: 要提取的页码,形如[1,2,3]
    :param min_line_length: 最小行长度
    :return: 分段后的文本列表
    """
    paragraphs = []
    full_text = ""
    # 提取全部文本
    for i, page_layout in enumerate(extract_pages(filename)):
        # 如果指定了页码范围,跳过范围外的页
        if page_numbers is not None and i not in page_numbers:
            continue
        for element in page_layout:
            # 检查element是不是文本
            if isinstance(element, LTTextContainer):
                # 将换行和空格去掉
                full_text += element.get_text().replace("\n", "").replace(" ", "")
    if full_text:
        # 调用切割函数
        text_chunks = sliding_window_chunks(full_text, 250, 100)
        for text in text_chunks:
            paragraphs.append(text)
    return paragraphs


# 向量类封装(与前面04类似)
class MyVectorDBConnector:
    # 默认构造函数
    def __init__(self):
        self.db = chromadb.Client()
        self.collection = self.db.get_or_create_collection(name="demo")

    # 向量化
    def get_embeddings(self, texts, model="text-embedding-v2"):
        data = client.embeddings.create(input=texts, model=model).data
        return [x.embedding for x in data]

    # 添加
    def add_documents(self, documents):
        embeddings = self.get_embeddings(documents)
        self.collection.add(
            embeddings=embeddings,
            documents=documents,
            ids=[f"id{i}" for i in range(len(documents))],
        )

    # 搜索
    def search(self, query, top_k):
        return self.collection.query(
            query_embeddings=self.get_embeddings([query]),
            n_results=top_k,
        )


# 封装机器人类--调用大模型
class RagBot:
    def __init__(self, vector_db, top_k=2):
        self.vector_db = vector_db
        self.top_k = top_k

    def get_completion(self, prompt, model="qwen-turbo"):
        messages = [{"role": "user", "content": prompt}]
        response = client.chat.completions.create(model=model, messages=messages, temperature=0)
        return response.choices[0].message.content

    def chat(self, user_query):
        # 1. 检索
        search_result = self.vector_db.search(user_query, self.top_k)
        context = "\n".join(search_result["documents"][0])
        # 2. 构建提示词
        prompt_template = """
            你是一个问答机器人。
            你的任务是根据下述给定的已知信息回答用户问题。
            确保你的回复完全依据下述已知信息。不要编造答案。
            如果下述已知信息不足以回答用户的问题,请直接回复"我无法回答您的问题"。

            已知信息:
            __INFO__

            用户问:
            __QUERY__

            请用中文回答用户问题。
            """
        prompt = prompt_template.replace("__INFO__", context).replace("__QUERY__", user_query)
        # 3. AI 回答
        reply = self.get_completion(prompt)
        print("AI回复:", reply)


# 主函数调用
if __name__ == "__main__":
    load_dotenv()
    client = OpenAI(api_key=os.getenv("api_key"), base_url=os.getenv("base_url"))
    page_data = extract_text_from_pdf("../04-RAG基础/财务管理文档.pdf", page_numbers=[0, 1, 2])

    # 创建向量数据库对象
    vector_db = MyVectorDBConnector()
    # 添加向量
    vector_db.add_documents(page_data)
    # 创建机器人对象
    rag_bot = RagBot(vector_db, top_k=2)
    rag_bot.chat("财务管理权限划分")

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。