企业实战:Milvues向量数据库实践

目录

  • [1 存入 Milvus (node_import_milvus)](#1 存入 Milvus (node_import_milvus))
  • [2 节点作用与实现思路](#2 节点作用与实现思路)
  • [3 导入与配置](#3 导入与配置)
  • [4 核心辅助函数](#4 核心辅助函数)
  • [5 主流程定义](#5 主流程定义)
  • [6 步骤 1: 检查输入](#6 步骤 1: 检查输入)
  • [7 步骤 2: 准备集合](#7 步骤 2: 准备集合)
  • [8 步骤 3: 清理旧数据](#8 步骤 3: 清理旧数据)
  • [9 步骤 4: 插入数据](#9 步骤 4: 插入数据)
  • [10 单元测试](#10 单元测试)

1 存入 Milvus (node_import_milvus)

文件 : app/import_process/node_import_milvus.py

2 节点作用与实现思路

节点作用: 数据加载流程的终点,负责将处理好的结构化数据(切片内容、元数据、向量)持久化存储到向量数据库中,构建可供即时查询的索引。

实现思路:

  1. 幂等性设计 : 在插入新数据前,根据 item_name 或文件 ID 清理旧数据,防止重复导入导致的数据污染。
  2. Schema 适配: 严格按照 Milvus 集合的 Schema 定义(主键、Dense字段、Sparse字段、JSON元数据字段)组织数据,确保插入成功率。
  3. 混合索引构建: 确保存入的数据能够支持 Milvus 的 Hybrid Search(Dense + Sparse 加权),最大化检索效果。

3 导入与配置

  • 目的 : 导入必要的库(如 pymilvus)和项目工具类,配置 Milvus 集合名称。
  • 关键点 :
    • CHUNKS_COLLECTION_NAME: 从环境变量获取集合名。
    • add_running_task: 记录任务执行状态。
python 复制代码
# 导入Milvus相关依赖
from pymilvus import DataType
# 导入自定义模块
from app.import_process.agent.state import ImportGraphState
from app.clients.milvus_utils import get_milvus_client
from app.utils.task_utils import add_running_task, add_done_task
from app.core.logger import logger,node_log,step_log
from app.conf.milvus_config import milvus_config

# 从配置文件读取切片集合名称,与配置解耦,便于环境切换
CHUNKS_COLLECTION_NAME = milvus_config.chunks_collection

4 核心辅助函数

功能: 处理幂等性清理(删除旧数据)和字符串转义。

python 复制代码
from app.utils.escape_milvus_string_utils import escape_milvus_string

5 主流程定义

函数 : node_import_milvus

逻辑:

  1. Step 1 : 检查输入 (step_1_check_input)。
  2. Step 2 : 准备环境 (step_2_prepare_collection)。
  3. Step 3 : 清理旧数据 (step_3_clean_old_data)。
  4. Step 4 : 插入数据 (step_4_insert_data)。
python 复制代码
"""
  1. 检查数据 chunks是否存在
  2. 前置准备工作 准备 milvus的集合和字段等
  3. 删除旧数据
  4. 查询chunks的数据即可
"""
@node_log("node_import_milvus")
def node_import_milvus(state: ImportGraphState) -> ImportGraphState:
    """
    节点: 导入向量库 (node_import_milvus)
    为什么叫这个名字: 将处理好的向量数据写入 Milvus 数据库。
    """
    # 准备日志和任务列表
    add_running_task(state['task_id'], "node_import_milvus")
    # 1. 检查数据 chunks是否存在
    chunks = state.get('chunks')
    if not chunks:
        logger.error("node_import_milvus: chunks数据不存在")
        raise ValueError("node_import_milvus: chunks数据不存在")
    # 2. 前置准备工作 创建 Milvus 集合和字段
    milvus_client = get_milvus_client()
    step_2_prepare_collection(milvus_client)
    # 3. 删除旧数据
    step_3_delete_old_data(milvus_client, state['item_name'])
    # 4. 插入chunks的数据即可
    with_id_chunks = step_4_insert_collections(milvus_client, chunks)
    state['chunks'] = with_id_chunks
    add_done_task(state['task_id'], 'node_import_milvus')
    return state

6 步骤 1: 检查输入

功能 : 验证 chunks 是否存在,并提取 dense_vector 维度和 item_name

python 复制代码
 # 1. 检查数据 chunks是否存在
    chunks = state.get('chunks')
    if not chunks:
        logger.error("node_import_milvus: chunks数据不存在")
        raise ValueError("node_import_milvus: chunks数据不存在")

7 步骤 2: 准备集合

功能: 获取 Milvus 客户端,如果集合不存在则创建。

python 复制代码
@step_log("step_2_prepare_collection")
def step_2_prepare_collection(milvus_client):
    """
    准备和创建chunks对应的集合
    :param milvus_client:
    :return:
    """
    # 2. 判断是否存在集合(表),存在创建集合(表)
    if not milvus_client.has_collection(collection_name=milvus_config.chunks_collection):
        # 创建集合
        # 3.1. 创建集合对应的列的信息
        schema = milvus_client.create_schema(
            auto_id=True,  # 主键自增长
            enable_dynamic_field=True,  # 动态字段
        )

        # 3.2. Add fields to schema
        # pk file_title item_name dense_vector sparse_vector
        schema.add_field(field_name="chunk_id", datatype=DataType.INT64, is_primary=True, auto_id=True)
        schema.add_field(field_name="file_title", datatype=DataType.VARCHAR, max_length=65535)
        schema.add_field(field_name="item_name", datatype=DataType.VARCHAR, max_length=65535)
        schema.add_field(field_name="content", datatype=DataType.VARCHAR, max_length=65535)
        schema.add_field(field_name="title", datatype=DataType.VARCHAR, max_length=65535)
        schema.add_field(field_name="parent_title", datatype=DataType.VARCHAR, max_length=65535)
        schema.add_field(field_name="part", datatype=DataType.INT8)
        schema.add_field(field_name="dense_vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
        schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)
        # 3.3 查询快,配置索引
        index_params = milvus_client.prepare_index_params()

        index_params.add_index(
            field_name="dense_vector",  # 给哪个列创建索引 稠密
            index_name="dense_vector_index",  # 索引的名字
            index_type="HNSW",  # 配置查找所用的算法
            metric_type="COSINE",  # 配置向量匹配和对比的 IP COSINE
            params={"M": 32,  # Maximum number of neighbors each node can connect to in the graph
                    "efConstruction": 300},  # or "DAAT_WAND" or "TAAT_NAIVE"
        )
        """
           10000  M = 16  efConstruction = 200
           50000  M = 32  efConstruction = 300
           100000  M = 64  efConstruction = 400
           M:图中每个节点在层次结构的每个层级所能拥有的最大边数或连接数。M 越高,图的密度就越大,搜索结果的召回率和准确率也就越高,因为有更多的路径可以探索,但同时也会消耗更多内存,并由于连接数的增加而减慢插入时间。如上图所示,M = 5表示 HNSW 图中的每个节点最多与 5 个其他节点直接相连。这就形成了一个中等密度的图结构,节点有多条路径到达其他节点。
           efConstruction:索引构建过程中考虑的候选节点数量。efConstruction 越高,图的质量越好,但需要更多时间来构建。
        """
        index_params.add_index(
            field_name="sparse_vector",  # Name of the vector field to be indexed
            index_type="SPARSE_INVERTED_INDEX",  # Type of the index to create
            index_name="sparse_vector_index",  # Name of the index to create
            metric_type="IP",  # Metric type used to measure similarity
            # 只计算可能得高分的向量,跳过大量的 0
            params={"inverted_index_algo": "DAAT_MAXSCORE"},  # Algorithm used for building and querying the index
        )

        milvus_client.create_collection(
            collection_name=milvus_config.chunks_collection,
            schema=schema,  # 字段
            index_params=index_params  # 索引
        )
    return milvus_client

8 步骤 3: 清理旧数据

功能 : 根据 item_name 删除已存在的切片,确保幂等性。

python 复制代码
@step_log("step_3_delete_old_data")
def step_3_delete_old_data(milvus_client, item_name):
    """
    删除旧数据 根据item_name删除
    :param milvus_client:
    :param item_name:
    :return:
    """
    milvus_client.delete(collection_name=CHUNKS_COLLECTION_NAME,
                         filter=f"item_name=='{item_name}'")
    # 调用 load_collection() 会触发 Milvus 重新加载集合数据、刷新索引、清理已标记删除的数据,确保删除操作真正生效,避免新旧数据混杂导致检索错误。
    milvus_client.load_collection(collection_name=CHUNKS_COLLECTION_NAME)

9 步骤 4: 插入数据

功能 : 移除临时 chunk_id,批量插入数据,并回填生成的 ID。

python 复制代码
@step_log("step_4_insert_collections")
def step_4_insert_collections(milvus_client,chunks):
    """
    插入集合的数据!
    :param milvus_client
    :param chunks:
    :return:  chunks -> 主键回显
    """
    insert_result = milvus_client.insert(collection_name=CHUNKS_COLLECTION_NAME, data=chunks)
    # 成功插入了几条
    insert_count = insert_result.get("insert_count",0)
    logger.info(f"完成了数据插入,成功插入了 {insert_count} 条数据")

    # 获取回显的ids
    ids = insert_result.get("ids",[])

    if ids and len(ids) == len(chunks):
        for index,chunk in enumerate(chunks):
            chunk['chunk_id'] = ids[index]

    return chunks

10 单元测试

您可以在 node_import_milvus.py 文件底部直接运行以下测试代码:

python 复制代码
if __name__ == '__main__':
    # --- 单元测试 ---
    # 目的:验证 Milvus 导入节点的完整流程,包括连接、创建集合、清理旧数据和插入新数据。
    import sys
    import os
    from dotenv import load_dotenv

    # 加载环境变量 (自动寻找项目根目录的 .env)
    current_dir = os.path.dirname(os.path.abspath(__file__))
    project_root = os.path.dirname(os.path.dirname(current_dir))
    load_dotenv(os.path.join(project_root, ".env"))

    # 构造测试数据
    dim = 1024
    test_state = {
        "task_id": "test_milvus_task",
        "item_name":"测试项目_Milvus",
        "chunks": [
            {
                "content": "Milvus 测试文本 1",
                "title": "测试标题",
                "item_name": "测试项目_Milvus",  # 必须有 item_name,用于幂等清理
                "parent_title":"test.pdf",
                "part":1,
                "file_title": "test.pdf",
                "dense_vector": [0.1] * dim,  # 模拟 Dense Vector
                "sparse_vector": {1: 0.5, 10: 0.8}  # 模拟 Sparse Vector
            }
,
            {
                "content": "Milvus 测试文本 2",
                "title": "测试标题2",
                "item_name": "测试项目_Milvus2",  # 必须有 item_name,用于幂等清理
                "parent_title": "test.pdf2",
                "part": 1,
                "file_title": "test.pdf2",
                "dense_vector": [0.1] * dim,  # 模拟 Dense Vector
                "sparse_vector": {1: 0.5, 10: 0.8}  # 模拟 Sparse Vector
            }
        ]
    }

    print("正在执行 Milvus 导入节点测试...")
    try:
        # 检查必要的环境变量
        if not os.getenv("MILVUS_URL"):
            print("❌ 未设置 MILVUS_URL,无法连接 Milvus")
        elif not os.getenv("CHUNKS_COLLECTION"):
            print("❌ 未设置 CHUNKS_COLLECTION")
        else:
            # 执行节点函数
            result_state = node_import_milvus(test_state)

            # 验证结果
            chunks = result_state.get("chunks", [])
            if chunks and chunks[0].get("chunk_id"):
                print(f"✅ Milvus 导入测试通过,生成 ID: {chunks[0]['chunk_id']}")
            else:
                print("❌ 测试失败:未能获取 chunk_id")

    except Exception as e:
        print(f"❌ 测试失败: {e}")
相关推荐
微学AI1 小时前
把时间序列真正用起来:TimechoAI 使用与时序分析实战
数据库·人工智能·大模型
cspttty1 小时前
管理类专业证书含金量排名
数据库
Promising_GEO1 小时前
新电脑科研环境配置指南:Miniforge + PyCharm + GitHub 从安装到可用
ide·python·pycharm·github·地理
怪奇云呼军1 小时前
闪电智能VoiceAgent 如何管理呼入、接听、桥接和挂断状态?
java·前端·网络·数据库·人工智能
love530love2 小时前
彻底清理 Windows 右键“打开方式“中的重复/失效程序项(PyCharm 多版本残留实战 + 自动化脚本)
运维·人工智能·windows·pycharm·jetbrains·toolbox
秋田君2 小时前
QT_实战TCP 聊天程序说明文档以及实现
数据库·qt·tcp/ip
01_ice2 小时前
MySQL基本查询1
数据库·mysql
腾科IT教育3 小时前
Oracle OCP/OCM认证含金量实测2026版
数据库·oracle·ocp·ocm·甲骨文
不可求~11 小时前
多模型路由怎么落地:把任务分流写进项目的最小实现
java·前端·数据库