当前位置: 首页 > news >正文

二:RAG 的 “语义密码”:向量、嵌入模型与 Milvus 向量数据库实操

上一篇我们知道 RAG 的核心是 “先检索再生成”,但 “怎么精准找到相关文档” 是个技术活 —— 比如用户问 “设备的最大承压”,系统要能定位到文档中 “工作压力上限 100MPa” 的片段,而不是 “维护流程” 的内容。

这背后依赖三大核心技术:向量(文本的数字密码)嵌入模型(密码生成器)向量数据库(密码检索柜)。今天从 “原理 + 代码” 双维度拆解,还会手把手教部署 Milvus 并测试相似性检索。

一、向量:让计算机 “理解语义” 的数字语言

人类通过 “意思” 判断文本相关性(比如 “承压” 和 “工作压力” 是一回事),但计算机只能处理数字 —— 向量就是把 “语义” 翻译成 “数字数组” 的工具。

1. 向量的核心特性:3 个关键指标

  • 维度:向量是 N 个数字组成的数组,N 就是维度。你的文档中用了 BGE-small-zh 模型,输出向量是 384 维;OpenAI 的 ada-002 是 1536 维。维度越高,语义捕捉越细,但存储 / 计算成本越高。
  • 语义相关性:相似文本的向量 “距离近”,不相关的 “距离远”。比如:
    • “设备最大工作压力 100MPa”→向量 A:[0.21, 0.35, -0.12, ..., 0.47](384 维)
    • “设备的最大承压是 100MPa”→向量 B:[0.22, 0.34, -0.11, ..., 0.46]
    • 两者的 “余弦相似度” 接近 0.98(最大值 1),计算机就知道它们语义几乎一致。
  • 唯一性:即使是微小的语义差异,向量也会不同。比如 “100MPa” 改成 “120MPa”,向量中至少 10% 的数字会变化。

2. 向量计算的关键方法:余弦相似度

判断两个向量是否相关,最常用 “余弦相似度”—— 计算两个向量在高维空间中的夹角余弦值,范围是 [-1,1]:

  • 0.8~1.0:极相似(如 “工作压力” 和 “承压”);
  • 0.5~0.8:较相似(如 “工作压力” 和 “运行压力”);
  • 0~0.5:弱相关(如 “工作压力” 和 “维护周期”);
  • 负数:不相关(如 “工作压力” 和 “员工考勤”)。

Milvus 就是用这个方法快速找到 “与问题向量最像” 的 Top-K 个文档向量。

二、文本分割:不止 “固定长度”,更要 “语义完整”

新文档《2-LLamaindex RAG 基础聊天实现.pdf》2.2.1 节指出,文本分割是 “影响检索精度的关键步骤”—— 若把 “设备最大工作压力 100MPa” 拆成 “设备最大工作压力” 和 “100MPa”,检索时就会丢失关键信息。我们补充 3 种实用的分割策略:

1. 3 种核心分割方式对比

分割方式原理优点缺点适用场景
句分割按句号、感叹号、换行符分割(如 “。!?\n”)保留完整语义(如一个完整的参数描述)块大小不一(短句 10 字,长句 200 字)技术手册、法律文档(语义连贯优先)
固定长度分割按固定 token 数分割(如 512token / 块)块大小均匀,便于向量化和存储可能拆分完整语义(如拆分参数句)新闻、报告(长文本需均匀分块)
语义窗口分割按段落、标题分割,块间保留 10%~20% 重叠兼顾语义完整与检索精度(重叠避免拆分)实现稍复杂,需解析文档结构带标题的文档(如产品手册章节)

2. LlamaIndex 实操:语义窗口分割代码

新文档《4-LlamaIndex 传统 RAG 系统设计及开发.pdf》2.4 节使用SentenceSplitter实现分割,我们补充完整代码(含重叠配置):

from llama_index.core.node_parser import SentenceSplitterdef get_splitter(file_type: str):"""根据文件类型选择分割器:- file_type: "tech"(技术文档)/"normal"(普通文档)"""if file_type == "tech":# 技术文档:块小(300token)、重叠高(20%),避免参数拆分return SentenceSplitter(chunk_size=300,chunk_overlap=60,  # 300*20%=60separator="\n"  # 按换行符分割,保留段落语义)else:# 普通文档:块大(512token)、重叠低(10%),减少冗余return SentenceSplitter(chunk_size=512,chunk_overlap=51,  # 512*10%≈51separator="。"  # 按句号分割,保留句子语义)# 示例:分割技术文档
splitter = get_splitter("tech")
nodes = splitter.get_nodes_from_documents(documents)  # documents是读取的文档列表

优化:分割时可添加 “文档标题 + 章节” 前缀,让块包含上下文(如 “【文档:设备手册】【章节:参数规格】设备最大工作压力 100MPa”),避免检索时 “不知道块来自哪里”。

三、嵌入模型:文本转向量的 “密码生成器”

向量不会自己生成,需要 “嵌入模型” 来完成 “文本→向量” 的转换。你的文档中提到了 2 类常用模型,我们对比它们的差异和实操代码:

1. 嵌入模型的 2 大分类

类型代表模型优势劣势你的文档使用场景
闭源模型OpenAI ada-002语义捕捉准,支持多语言需 API 调用,有成本,无法本地化快速测试、小批量数据
开源模型BGE-small-zh、M3E免费,可本地部署,支持中文优化大模型(如 BGE-large)速度慢私有化部署、中文文档(你的选择)

2. 本地嵌入模型实操:BGE-small-zh 配置(来自你的 embeddings.py)

文档中embeddings.py专门配置了本地嵌入模型,代码可直接复用,关键步骤如下:

# embeddings.py:本地嵌入模型配置
from llama_index.embeddings.huggingface import HuggingFaceEmbeddingdef embed_model_local_bge_small():"""加载本地BGE-small-zh嵌入模型"""# model_name: HuggingFace上的模型地址# embed_batch_size:批量处理文本的大小,根据内存调整(建议16/32)# max_length:模型支持的最大文本长度(BGE-small-zh默认512)embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-zh-v1.5",embed_batch_size=16,max_length=512,# 模型参数:device="cuda"用GPU,"cpu"用CPU(本地测试用cpu)device="cpu")return embed_model# 在base_rag.py中全局配置:
from llama_index.core import Settings
from embeddings import embed_model_local_bge_small
Settings.embed_model = embed_model_local_bge_small()  # 所有分块/查询都用这个模型

3. 模型调用测试:看文本如何转向量

# 测试嵌入模型:文本→向量
embed_model = embed_model_local_bge_small()
text = "设备最大工作压力100MPa"
vector = embed_model.get_text_embedding(text)
print(f"向量维度:{len(vector)}")  # 输出384,符合BGE-small-zh的配置
print(f"前5个数字:{vector[:5]}")  # 输出如[0.023, -0.051, 0.124, 0.087, -0.032]

四、Milvus 向量数据库:存储与检索的 “高速抽屉”

当你有 10 万份文档,每份拆成 100 个片段,会生成 1000 万个向量 —— 普通数据库(如 MySQL)无法高效查询这些向量,必须用 Milvus 这类专门的向量数据库。

1. Milvus 工具类(utils/milvus.py)

# utils/milvus.py:Milvus向量数据库工具类
import os
from pymilvus import MilvusClient
from rag.config import RagConfig  # 从配置文件读取参数# 单例模式:全局唯一Milvus客户端
class MilvusUtils:_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super().__new__(cls)# 从配置文件初始化客户端(避免硬编码)cls._instance.client = MilvusClient(uri=RagConfig.milvus_uri)return cls._instancedef list_collections(self) -> list:"""查看所有集合(类似数据库的“表”)"""return self.client.list_collections()def create_collection(self, collection_name: str, dim: int):"""创建集合:- collection_name:集合名- dim:向量维度(BGE-small-zh是384维)"""if not self.client.has_collection(collection_name):self.client.create_collection(collection_name=collection_name,dimension=dim,# 索引配置:小数据用IVF_FLAT(精准),大数据用HNSW(快速)index_params={"index_type": "IVF_FLAT", "nlist": 1024})print(f"集合 {collection_name} 创建成功")else:print(f"集合 {collection_name} 已存在")def insert_vectors(self, collection_name: str, vectors: list, metadatas: list):"""插入向量:- vectors:向量列表(如[[0.1,0.2,...],[0.3,0.4,...]])- metadatas:元数据列表(如[{"file_path":"xxx.pdf","page":1}])"""data = [{"vector": vec, "metadata": meta, "id": i} for i, (vec, meta) in enumerate(zip(vectors, metadatas))]self.client.insert(collection_name=collection_name, data=data)print(f"成功插入 {len(vectors)} 个向量到 {collection_name}")def search_vectors(self, collection_name: str, query_vector: list, top_k: int = 3):"""搜索相似向量:- query_vector:查询向量- top_k:返回前k个相似结果"""return self.client.search(collection_name=collection_name,data=[query_vector],limit=top_k,output_fields=["metadata"]  # 返回元数据(如文件路径))[0]  # 返回第一个查询的结果def drop_collection(self, collection_name: str):"""删除集合(谨慎使用!)"""if self.client.has_collection(collection_name):self.client.drop_collection(collection_name=collection_name)print(f"集合 {collection_name} 删除成功")else:print(f"集合 {collection_name} 不存在")

2. 工具类使用示例

# 初始化工具类
milvus_utils = MilvusUtils()# 1. 创建集合(向量维度384,对应BGE-small-zh)
milvus_utils.create_collection("device_manual", dim=384)# 2. 插入向量(假设vectors是文本块向量,metadatas是元数据)
vectors = [embed_model.get_text_embedding(chunk.text) for chunk in chunks]
metadatas = [{"file_path": chunk.metadata["file_path"]} for chunk in chunks]
milvus_utils.insert_vectors("device_manual", vectors, metadatas)# 3. 搜索相似向量(用户问题向量)
query_vector = embed_model.get_query_embedding("设备最大工作压力是多少?")
results = milvus_utils.search_vectors("device_manual", query_vector, top_k=3)# 4. 打印结果
for res in results:print(f"相似度:{res['distance']:.3f},来源:{res['entity']['metadata']['file_path']}")

3. Milvus 的核心优势(你的文档重点强调)

  • 高速检索:支持每秒百万级向量的相似性搜索,比传统数据库快 100 倍以上;
  • 分布式部署:可横向扩展,支持 TB 级向量存储(适合企业级数据);
  • 多索引类型:支持 IVF_FLAT(精准搜索)、HNSW(快速搜索)等,兼顾精度和速度;
  • 与 LlamaIndex 无缝集成:你的文档中base_rag.py直接调用 MilvusVectorStore,无需额外适配。

4. Milvus 部署与连接(详细实操步骤)

步骤 1:安装 Milvus(本地测试用 Docker)

# 1. 下载Milvus Docker Compose文件
wget https://github.com/milvus-io/milvus/releases/download/v2.3.0/milvus-standalone-docker-compose.yml -O docker-compose.yml# 2. 启动Milvus服务(需Docker已安装)
docker-compose up -d# 3. 检查服务状态(确保milvus-standalone启动成功)
docker-compose ps

步骤 2:Milvus 连接配置(来自你的 rag/config.py)

config.py用 Pydantic 定义了 Milvus 的配置,支持从环境变量读取参数,避免硬编码:

# rag/config.py:Milvus及其他组件的配置
import os
from pydantic import BaseModel, Fieldclass RAGConfig(BaseModel):# Milvus连接地址:默认本地19530端口(Docker启动的默认端口)milvus_uri: str = Field(default=os.getenv("MILVUS_URI", "http://localhost:19530"), description="Milvus服务地址")# 嵌入模型维度:必须与BGE-small-zh的384维一致,否则报错embedding_model_dim: int = Field(default=384, description="嵌入模型输出向量的维度")# Milvus集合名:相当于数据库的“表”,默认用defaultmilvus_collection: str = Field(default=os.getenv("MILVUS_COLLECTION", "rag_collection"),description="Milvus中的集合名")# 单例实例:全局唯一配置对象
rag_config = RAGConfig()

步骤 3:Milvus 向量存储集成(来自你的 base_rag.py)

base_rag.py中,你定义了create_index方法,专门用于将向量存入 Milvus,关键代码解析:

# base_rag.py:创建Milvus远程索引
from llama_index.vector_stores.milvus import MilvusVectorStore
from llama_index.core import VectorStoreIndex, StorageContext
from .config import rag_configasync def create_index_milvus(self, data):"""把文档数据存入Milvus:data:经OCR/分块后的文档列表"""# 1. 初始化Milvus向量存储vector_store = MilvusVectorStore(uri=rag_config.milvus_uri,  # 从配置读取连接地址collection_name=rag_config.milvus_collection,  # 集合名dim=rag_config.embedding_model_dim,  # 向量维度(384)overwrite=False,  # 避免覆盖已有集合(首次创建可设为True)# 索引配置:用HNSW索引,适合快速搜索index_params={"index_type": "HNSW", "M": 8, "efConstruction": 64})# 2. 创建存储上下文:关联Milvus向量存储storage_context = StorageContext.from_defaults(vector_store=vector_store)# 3. 生成向量并存入Milvusindex = VectorStoreIndex.from_documents(data,storage_context=storage_context,show_progress=True  # 显示处理进度(方便调试))return index

5. Milvus 检索测试:找 “最像” 的向量

# 测试Milvus检索:用问题向量找相似文档向量
from llama_index.core import QueryBundle
from .base_rag import RAG# 1. 加载之前存入Milvus的索引
rag = RAG(files=[])
index = await rag.load_index_milvus()  # 自定义加载方法,参考你的base_rag.py# 2. 构造问题向量
query_text = "设备的最大工作压力是多少?"
query_bundle = QueryBundle(query_text)
query_vector = Settings.embed_model.get_query_embedding(query_text)# 3. 检索Top-3相似片段
retriever = index.as_retriever(similarity_top_k=3)
retrieved_nodes = await retriever.aretrieve(query_bundle)# 4. 输出结果
for i, node in enumerate(retrieved_nodes):print(f"第{i+1}个相似片段:")print(f"文本:{node.text}")print(f"相似度得分:{node.score}")  # 得分越高越相似(0~1)print(f"来源文件:{node.metadata['file_path']}")  # 从PostgreSQL关联的元数据

五、多模态 RAG 初步:不止文本,还能处理图片

新文档《2-LLamaindex RAG 基础聊天实现.pdf》3.4 节和《4-LlamaIndex 传统 RAG 系统设计及开发.pdf》1 节,介绍了多模态 RAG 的概念 —— 传统 RAG 仅处理文本,而多模态 RAG 可处理图片、图表等,这是企业场景的重要扩展。

1. 多模态 RAG 的核心流程(图片处理)

  1. 图片预处理:用户上传设备图片(如标注 “100MPa” 的压力表照片),调用 OCR 工具(如 Umi-OCR)提取文字;
  2. 多模态向量化:用视觉语言模型(VLM,如 NeVA 22B)将图片转换为向量,同时将 OCR 文本也转换为向量;
  3. 混合检索:用户提问 “图片中设备的压力是多少?” 时,同时检索图片向量和文本向量,找到最相关结果;
  4. 生成答案:将图片向量对应的原文(OCR 结果)和文本片段一起送入 LLM,生成答案。

2. 图片 OCR 提取实操(基于 Umi-OCR)

新文档《5-LlamaIndex RAG 知识库对话及 OCR 识别.pdf》2.3 节提供了 Umi-OCR 的调用代码,我们补充简化版(需先启动 Umi-OCR 的 HTTP 服务):

# ocr.py:Umi-OCR图片文字提取
import requests
import json
from rag.config import RagConfig  # 从配置读取OCR服务地址def ocr_image_to_text(image_path: str) -> str:"""用Umi-OCR提取图片文字:- image_path:图片路径"""# 1. 图片转Base64(Umi-OCR要求)import base64with open(image_path, "rb") as f:base64_str = base64.b64encode(f.read()).decode("utf-8")# 2. 调用Umi-OCR HTTP接口url = f"{RagConfig.ocr_base_url}/api/ocr"data = {"base64": base64_str,"options": {"data.format": "text"}}response = requests.post(url, data=json.dumps(data), headers={"Content-Type": "application/json"})response.raise_for_status()  # 报错时抛出异常# 3. 返回提取的文本return response.json().get("data", "")# 示例:提取压力表图片中的文字
text = ocr_image_to_text("pressure_gauge.jpg")
print("OCR结果:", text)  # 预期输出:"设备最大工作压力:100MPa"

六、向量与元数据的 “联动”:Milvus+PostgreSQL 的配合

你的文档中特别强调:Milvus 只存向量,原文和元数据(文件路径、页数)存在 PostgreSQL,两者通过 “向量 ID” 关联。这个设计的核心逻辑是:

  1. 存储分工:Milvus 擅长向量检索,PostgreSQL 擅长结构化数据(元数据)查询,各司其职;
  2. 检索流程
    • 第一步:问题向量→Milvus→返回相似向量的 ID 列表;
    • 第二步:向量 ID→PostgreSQL→查询对应的原文片段 + 文件路径 + 页数;
    • 第三步:原文片段→组合 Prompt→发给 LLM 生成答案。

PostgreSQL 的表结构设计:

-- PostgreSQL建表语句:存储向量ID与元数据的映射
CREATE TABLE rag_document_nodes (node_id VARCHAR(64) PRIMARY KEY,  -- 与Milvus的向量ID一一对应text TEXT NOT NULL,  -- 文档片段原文file_path VARCHAR(255) NOT NULL,  -- 原始文件在MinIO的路径page_num INT,  -- 片段所在的页码(PDF适用)created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP  -- 创建时间
);

小结

向量是 “语义密码”,嵌入模型是 “密码生成器”,Milvus 是 “密码检索柜”—— 这三者构成了 RAG 检索能力的基石。我们不仅掌握了向量和嵌入模型的基础,还学会了 “语义优先” 的文本分割策略、Milvus 工具类的封装(避免重复代码),甚至初步接触了多模态 RAG 的图片处理流程。下一篇我们将搭建 Web 界面,用 Chainlit 实现 “文件上传→OCR→检索→生成” 的完整交互,让 RAG 从 “代码” 变成 “可用工具”。

http://www.dtcms.com/a/457834.html

相关文章:

  • 一元夺宝网站建设企业网站建设方案对比汇报
  • 《道德经》第十二章
  • 点击图片进入网站要怎么做做网站老板嫌弃太丑谁的锅
  • 如何在ubuntu20.04配置动态壁纸和bongoCat
  • 网站建设中外链与内链的技巧三视觉平面设计网
  • 小公司要不要建设网站网页打不开怎么处理
  • p2vr做的网站怎么再次打开163企业邮箱免费版
  • deepseek vs 元宝--人工智能还是人工智障?
  • 【C语言基础详细版】07. 字符串处理函数完全指南
  • 科室建设网站织梦怎么做的网站
  • 科研配色|信仰的颜色—中国红!
  • wordpress建站菜单栏 二级标题免费做问卷的网站好
  • Python print() 函数完整说明
  • 游戏攻略新闻资讯主题模板源码 YK一点资讯模版 Zblog主题模版(源码下载)
  • 计算机网络资源网站建设论文网站空间 推荐
  • 自助建站的平台wordpress 文章循环
  • Photoshop - Photoshop 工具栏(9)裁剪工具
  • 【练】C程序设计-01程序设计和C语言
  • 使用 Gunicorn 部署 FastAPI 应用程序:快速而强大的组合
  • JavaScript是web开发中一种功能强大的编程语言,
  • 网站建设策划实施要素无锡常州网络推广
  • 东莞建外贸网站龙华专业做网站公司
  • 通俗易懂的理解Vue.js
  • 【开题答辩全过程】以 爱心慈善公益网站为例,包含答辩的问题和答案
  • 【云基础】容器管理工具Containerd
  • 大数据毕业设计-基于大数据的BOSS直聘岗位招聘数据可视化分析系统(高分计算机毕业设计选题·定制开发·真正大数据·机器学习毕业设计)
  • 网站开发建设培训网站策划书预期风险
  • aspnet网站模板人人秀h5页面制作软件
  • mysql数据库学习之高级进阶(七)
  • 做网站的书知乎建地方的网站前景