• 基于 LlamaIndex+PostgreSQL 实现RAG持久化


    前文《基于 LlamaIndex 实现 RAG 向量检索入门》已完成本地大模型与 Embedding 向量检索的基础搭建,但仅支持临时向量存储,无法持久化复用。本文将在此基础上实现 RAG 持久化存储方案,借助 PostgreSQL + pgvector 向量插件对接 LlamaIndex。环境采用 CentOS Stream 10,演示数据库编译安装与插件配置,基于 VectorStoreRetriever 实现持久化向量检索,搭建可稳定复用的本地 RAG 服务。

    一、编译数据库

    PostgreSQL(简称 PG)是一款成熟开源的对象关系型数据库,具备强大的可扩展能力。借助 pgvector 插件,它可以新增向量数据类型,实现向量存储与相似度检索,非常适合用来给 RAG 系统做向量持久化。本节内容基于 CentOS Stream 10 环境,采用源码编译方式安装 PostgreSQL 18.6,并部署 pgvector 向量插件,完成向量数据库基础环境搭建。

    1、系统默认未开启编译所需的 CRB 软件源,首先开启 CRB 源并更新缓存,随后安装数据库编译、运行及插件部署所需的全套依赖包,为后续源码编译提供环境。

    # 开启CRB源
    [root@localhost ~]# dnf config-manager --enable crb
    [root@localhost ~]# dnf clean all && dnf makecache
    
    # 开发工具 + PG编译依赖
    [root@localhost ~]# dnf groupinstall -y "Development Tools"
    [root@localhost ~]# dnf install -y readline-devel zlib-devel openssl-devel libxml2-devel libxslt-devel bison flex git wget libicu-devel systemd-devel perl-core perl-FindBin
    
    Last metadata expiration check: 0:01:56 ago on Thu 17 Sep 2026 06:14:31 PM CST.
    Package readline-devel-8.2-11.el10.x86_64 is already installed.
    Package zlib-ng-compat-devel-2.2.3-3.el10.x86_64 is already installed.
    Package openssl-devel-1:3.5.8-1.el10.x86_64 is already installed.
    Package libxml2-devel-2.12.5-15.el10.x86_64 is already installed.
    Package libxslt-devel-1.1.39-10.el10.x86_64 is already installed.
    Package bison-3.8.2-9.el10.x86_64 is already installed.
    Package flex-2.6.4-19.el10.x86_64 is already installed.
    Package git-2.52.0-1.el10.x86_64 is already installed.
    Package wget-1.24.5-8.el10.x86_64 is already installed.
    Package libicu-devel-74.2-5.el10.x86_64 is already installed.
    Package systemd-devel-257-33.el10.x86_64 is already installed.
    Package perl-4:5.40.2-515.el10.x86_64 is already installed.
    Package perl-FindBin-1.54-515.el10.noarch is already installed.
    Dependencies resolved.
    Nothing to do.
    Complete!
    

    2、本文采用官方稳定版 PostgreSQL 18.6 源码包进行编译安装,自定义安装路径至 /usr/local/pgsql,同时编译内置扩展组件,最大化数据库原生能力,编译全程约五分钟。

    [root@localhost ~]# wget https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.gz
    [root@localhost ~]# tar -zxvf postgresql-18.6.tar.gz
    [root@localhost ~]# cd postgresql-18.6
    
    # 编译配置
    [root@localhost ~]# ./configure --prefix=/usr/local/pgsql --with-openssl --with-libxml --with-systemd --without-icu
    
    # 编译
    [root@localhost ~]# make -j$(nproc)
    [root@localhost ~]# make install
    
    # 编译安装contrib扩展
    [root@localhost ~]# cd contrib
    [root@localhost ~]# make -j$(nproc)
    [root@localhost ~]# make install
    

    3、PostgreSQL 禁止 root 用户直接运行服务,需创建专用普通用户 postgres 用于进程托管,同时创建独立数据存储目录,并配置严格权限,保障数据库数据安全。

    [root@localhost ~]# useradd -M -s /usr/sbin/nologin postgres
    [root@localhost ~]# mkdir -p /var/lib/pgsql
    [root@localhost ~]# chown postgres:postgres /var/lib/pgsql
    [root@localhost ~]# chmod 700 /var/lib/pgsql
    

    4、为方便后续数据库命令全局调用、识别数据目录路径,需为 postgres 用户配置环境变量,刷新后即可正常使用 pg_config、initdb 等核心命令。

    [root@localhost ~]# su - postgres
    Last login: Thu Sep 17 18:31:18 CST 2026 on pts/1
    
    [postgres@localhost ~]$ vi ~/.bashrc
    
    export PATH=/usr/local/pgsql/bin:$PATH
    export PGDATA=/usr/local/pgsql/data
    
    [postgres@localhost ~]$ source ~/.bashrc
    [postgres@localhost ~]$ pg_config
    [postgres@wintcp ~]$ pg_config
    

    5、通过 initdb 命令初始化数据库核心数据目录、系统表与配置文件,同时设置超级管理员 postgres 密码,完成数据库基础初始化。

    [postgres@localhost ~]$ initdb -D /usr/local/pgsql/data -U postgres -W
    
    The files belonging to this database system will be owned by user "postgres".
    This user must also own the server process.
    The database cluster will be initialized with locale "en_US.UTF-8".
    The default database encoding has accordingly been set to "UTF8".
    The default text search configuration will be set to "english".
    
    Data page checksums are enabled.
    Enter new superuser password: 1233
    Enter it again: 1233
    
    fixing permissions on existing directory /usr/local/pgsql/data ... ok
    creating subdirectories ... ok
    selecting dynamic shared memory implementation ... posix
    selecting default "max_connections" ... 100
    selecting default "shared_buffers" ... 128MB
    selecting default time zone ... Asia/Shanghai
    creating configuration files ... ok
    running bootstrap script ... ok
    performing post-bootstrap initialization ... ok
    syncing data to disk ... ok
    
    [postgres@localhost ~]$ exit
    logout
    

    6、为实现 PostgreSQL 开机自启、进程统一管理,编写 systemd 服务配置文件,支持 start/stop/restart/reload 标准运维命令。

    [root@localhost ~]# vi /etc/systemd/system/postgresql.service
    
    [Unit]
    Description=PostgreSQL 18.6 database server
    Documentation=https://www.postgresql.org/docs/
    After=network.target
    
    [Service]
    Type=simple
    User=postgres
    Group=postgres
    Environment=PGDATA=/usr/local/pgsql/data
    ExecStart=/usr/local/pgsql/bin/postgres -D ${PGDATA}
    ExecReload=/usr/local/pgsql/bin/pg_ctl reload -D ${PGDATA}
    ExecStop=/usr/local/pgsql/bin/pg_ctl stop -D ${PGDATA}
    TimeoutSec=300
    
    [Install]
    WantedBy=multi-user.target
    

    7、加载系统服务、启动数据库进程,配置开机自启,并查看服务运行状态,确认数据库正常启动。

    [root@localhost ~]# ln -s /usr/local/pgsql/bin/postgres /usr/local/pgsql/bin/postmaster
    [root@localhost ~]# systemctl daemon-reload
    [root@localhost ~]# systemctl start postgresql
    [root@localhost ~]# systemctl enable postgresql
    
    [root@localhost ~]# systemctl status postgresql
    ● postgresql.service - PostgreSQL 18.6 database server
         Loaded: loaded (/etc/systemd/system/postgresql.service; enabled; preset: disabled)
         Active: active (running) since Thu 2026-09-17 18:29:22 CST; 21s ago
     Invocation: f6131d3c463748a5a5a4dd3ca09407c0
           Docs: https://www.postgresql.org/docs/
       Main PID: 16676 (postgres)
          Tasks: 9 (limit: 10318)
         Memory: 20.8M (peak: 20.8M)
            CPU: 35ms
         CGroup: /system.slice/postgresql.service
                 ├─16676 /usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data
                 ├─16677 "postgres: io worker 1"
                 ├─16678 "postgres: io worker 0"
                 ├─16679 "postgres: io worker 2"
                 ├─16680 "postgres: checkpointer "
                 ├─16681 "postgres: background writer "
                 ├─16683 "postgres: walwriter "
                 ├─16684 "postgres: autovacuum launcher "
                 └─16685 "postgres: logical replication launcher "
    
    Sep 17 18:29:22 wintcp systemd[1]: Started postgresql.service - PostgreSQL 18.6 database server.
    Sep 17 18:29:22 wintcp postgres[16676]: 2026-09-17 18:29:22.565 CST [16676] LOG:  starting PostgreSQL >
    Sep 17 18:29:22 wintcp postgres[16676]: 2026-09-17 18:29:22.566 CST [16676] LOG:  listening on IPv6 ad>
    Sep 17 18:29:22 wintcp postgres[16676]: 2026-09-17 18:29:22.566 CST [16676] LOG:  listening on IPv4 ad>
    Sep 17 18:29:22 wintcp postgres[16676]: 2026-09-17 18:29:22.569 CST [16676] LOG:  listening on Unix so>
    Sep 17 18:29:22 wintcp postgres[16682]: 2026-09-17 18:29:22.575 CST [16682] LOG:  database system was >
    Sep 17 18:29:22 wintcp postgres[16676]: 2026-09-17 18:29:22.578 CST [16676] LOG:  database system is r>
    

    8、pgvector 是 PostgreSQL 专用向量检索插件,可拓展向量数据类型、相似度计算能力。通过源码编译方式安装,适配当前编译版 PostgreSQL 18.6,保证版本兼容性。

    [root@localhost ~]# git clone https://github.com/pgvector/pgvector.git
    [root@localhost ~]# cd pgvector
    
    [root@localhost ~]# make PG_CONFIG=/usr/local/pgsql/bin/pg_config -j$(nproc)
    [root@localhost ~]# make install PG_CONFIG=/usr/local/pgsql/bin/pg_config
    

    9、登录数据库启用 vector 扩展,验证插件安装成功,同时创建专属业务数据库 storage_db 和业务用户 storage_user,并授予完整权限,用于后续 LlamaIndex 对接存储向量数据。

    [root@localhost ~]# su - postgres
    [postgres@localhost ~]$ psql -h 127.0.0.1
    psql (16.14, server 18.6)
    WARNING: psql major version 16, server major version 18.
             Some psql features might not work.
    Type "help" for help.
    
    postgres=# CREATE EXTENSION IF NOT EXISTS vector;
    CREATE EXTENSION
    
    postgres=# SELECT extname, extversion FROM pg_extension WHERE extname='vector';
     extname | extversion 
    ---------+------------
     vector  | 0.8.6
    (1 row)
    
    postgres=# CREATE DATABASE storage_db;
    CREATE DATABASE
    
    postgres=# CREATE USER storage_user WITH PASSWORD '1233';
    CREATE ROLE
    
    postgres=# GRANT ALL PRIVILEGES ON DATABASE storage_db TO storage_user;
    GRANT
    
    postgres=# \c storage_db
    psql (16.14, server 18.6)
    You are now connected to database "storage_db" as user "postgres".
    
    storage_db=# GRANT ALL ON SCHEMA public TO storage_user;
    GRANT
    
    postgres=# exit
    [postgres@localhost ~]$ exit
    logout
    

    10、默认 PostgreSQL 仅支持本地访问,本文修改配置开启全网远程访问,适配外部程序、服务器对接向量数据库,配置完成后重启服务并验证公网连通性。

    # 启动远程访问权限
    [root@localhost ~]# vi /usr/local/pgsql/data/postgresql.conf
    
    listen_addresses = '*'
    
    # 添加一行访问控制,按需修改网段
    [root@localhost ~]# vi /usr/local/pgsql/data/pg_hba.conf
    
    host    all             all             0.0.0.0/0            scram-sha-256
    

    11、重启服务并验证公网连接,可正常登录即代表 PostgreSQL + pgvector 向量持久化数据库环境搭建完成,可直接对接 LlamaIndex 实现 RAG 向量持久化存储与检索。

    [root@localhost ~]# systemctl restart postgresql
    
    [root@localhost ~]# psql -h 8.122.231.178 -p 5432 -U postgres -d postgres
    Password for user postgres: 1233
    psql (16.14, server 18.6)
    Type "help" for help.
    
    postgres=# 
    

    二、使用数据库

    在企业落地场景中,一般会搭配元数据过滤实现文档权限隔离,检索结果再接入 Rerank 重排优化召回精度,是企业知识库最常用的基线方案。下面我们通过 LlamaIndex 对接前面部署好的 PostgreSQL+pgvector 环境,演示基础 RAG 示例,再封装成可复用的 RAG 服务类,实现文档增量更新、按文档 ID 删除、元数据过滤查询等实用能力。

    • 向量检索的完整链路:文档分块 → Embedding 向量化 → 向量存入向量库 → 生成查询向量返回

    PGVector 作为 PostgreSQL 扩展,可直接在关系型数据库中承载向量数据,对比 Qdrant、Milvus 等专用向量库,优势是无需额外维护独立向量服务,同时原生支持元数据过滤,可基于部门、文档类型、权限标签实现企业多租户场景。

    安装所需要的依赖包如下所示:

    pip install llama-index llama-index-vector-stores-postgres pgvector psycopg2-binary llama-index-embeddings-openai llama-index-llms-openai-like -i https://pypi.org/simple
    

    最小化示例

    本示例为最小可用 Demo,基于 LlamaIndex 对接 PGVector。代码中自定义 Embedding 实现类调用本地 Embedding 服务,使用 OpenAILike 接入本地大模型;配置 PGVector 数据库连接参数,创建向量存储对象。首次执行读取 data 目录下文档,自动分块、生成向量并持久化存入 PostgreSQL;

    后续运行可直接从数据库加载向量索引,无需重复向量化。最后构建查询引擎,执行向量相似度检索,将召回的上下文交给大模型,完成文档问答。该示例适合验证整套 RAG 链路连通性,仅实现基础入库与问答,没有封装增量更新、文档删除、元数据过滤等生产级能力。

    import os
    import requests
    from typing import List
    from llama_index.core import Settings, SimpleDirectoryReader, VectorStoreIndex, StorageContext
    from llama_index.core.embeddings import BaseEmbedding
    from llama_index.llms.openai_like import OpenAILike
    from llama_index.vector_stores.postgres import PGVectorStore
    
    class LocalLlamaServerEmbedding(BaseEmbedding):
        api_base: str
        api_key: str = "dummy"
        def _get_embedding(self, text: str) -> List[float]:
            url = f"{self.api_base}/embeddings"
            payload = {
                "input": text,
                "model": "Qwen3-Embedding-0.6B-Q8_0.gguf"
            }
            headers = {"Authorization": f"Bearer {self.api_key}"}
            resp = requests.post(url, json=payload, headers=headers)
            resp.raise_for_status()
            return resp.json()["data"][0]["embedding"]
    
        def _get_text_embedding(self, text: str) -> List[float]:
            return self._get_embedding(text)
    
        def _get_query_embedding(self, query: str) -> List[float]:
            return self._get_embedding(query)
    
        async def _aget_query_embedding(self, query: str) -> List[float]:
            return self._get_embedding(query)
    
        async def _aget_text_embedding(self, text: str) -> List[float]:
            return self._get_embedding(text)
    
    os.environ["OPENAI_API_KEY"] = "dummy"
    os.environ["OPENAI_BASE_URL"] = "http://127.0.0.1:11433/v1"
    
    llm = OpenAILike(
        model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
        api_base=os.environ["OPENAI_BASE_URL"],
        api_key=os.environ["OPENAI_API_KEY"],
        is_chat_model=True,
        context_window=1024
    )
    
    Settings.llm = llm
    Settings.embed_model = LocalLlamaServerEmbedding(api_base="http://127.0.0.1:11434/v1")
    
    # PGVector 数据库配置
    db_name = "storage_db"
    host = "8.122.231.178"
    password = "1233"
    port = "5432"
    user = "storage_user"
    vector_table_name = "llama_rag_vector"
    
    # Qwen3-Embedding-0.6B 维度 1024
    vector_store = PGVectorStore.from_params(
        database=db_name,
        host=host,
        password=password,
        port=port,
        user=user,
        table_name=vector_table_name,
        embed_dim=1024,
    )
    
    storage_context = StorageContext.from_defaults(vector_store=vector_store)
    
    # 从PG加载索引函数
    def load_index_from_pg():
        storage_context = StorageContext.from_defaults(vector_store=vector_store)
        index = VectorStoreIndex.from_vector_store(
            vector_store,
            storage_context=storage_context
        )
        return index
    
    if __name__ == "__main__":
        # 第一次运行:构建索引,写入PG向量库
        documents = SimpleDirectoryReader(
            "./data/",
            required_exts=[".pdf", ".docx", ".txt"]
        ).load_data()
    
        index = VectorStoreIndex.from_documents(
            documents,
            storage_context=storage_context,
            show_progress=True
        )
    
        # 第二次及以后运行:直接从PG加载
        # index = load_index_from_pg()
    
        query_engine = index.as_query_engine(similarity_top_k=3)
        response = query_engine.query("请总结文档里面的核心内容,使用汉语回复")
        print("回答:")
        print(response)
    

    代码运行输出提示信息:

    Applying transformations: 100%|███████████████████████████| 1/1 [00:00<00:00, 797.55it/s]
    Generating embeddings: 100%|██████████████████████████████
    Generating embeddings: 100%|██████████████████████████████| 1/1 [00:01<00:00,  1.46s/it]
    
    回答:
    文档的核心内容是关于人工智能大模型如何理解和处理自然语言,以及如何通过向量数据库和向量检索技术来处理和查找文本内容。
    

    封装示例

    本案例将 RAG 业务逻辑封装为独立 RAGService 类,基于 LlamaIndex 与 PGVector。类内部封装模型初始化、数据库连接、文档加载、向量新增 / 删除、问答检索等功能。支持增量添加文档、按文档 ID 删除向量、元数据条件过滤检索,并增加请求异常重试机制。

    主函数演示完整调用流程:初始化 RAG 实例、清空历史向量、加载文档入库、执行带元数据过滤的问答查询。相比前面的基础示例,代码模块化,支持文档动态维护,具备生产环境所需的基础容错与权限过滤能力。

    import os
    import time
    import psycopg2
    import requests
    from pathlib import Path
    from typing import List
    from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
    from llama_index.core import Settings, SimpleDirectoryReader, VectorStoreIndex, StorageContext, Document
    from llama_index.core.embeddings import BaseEmbedding
    from llama_index.core.node_parser import SentenceSplitter
    from llama_index.llms.openai_like import OpenAILike
    from llama_index.vector_stores.postgres import PGVectorStore
    from llama_index.core.retrievers import VectorIndexRetriever
    from llama_index.core.query_engine import RetrieverQueryEngine
    from llama_index.core.vector_stores import MetadataFilter, MetadataFilters, FilterOperator
    
    class LocalLlamaServerEmbedding(BaseEmbedding):
        api_base: str
        embed_model_name: str
        api_key: str = "dummy"
    
        def _get_embedding(self, text: str) -> List[float]:
            max_text_len = 2048
            text = text[:max_text_len]
            url = f"{self.api_base}/embeddings"
            payload = {
                "input": text,
                "model": self.embed_model_name
            }
            headers = {"Authorization": f"Bearer {self.api_key}"}
            try:
                resp = requests.post(url, json=payload, headers=headers, timeout=30)
                resp.raise_for_status()
            except requests.exceptions.RequestException as e:
                raise RuntimeError(f"Embedding服务调用失败: {e}")
            return resp.json()["data"][0]["embedding"]
    
        def _get_text_embedding(self, text: str) -> List[float]:
            return self._get_embedding(text)
    
        def _get_query_embedding(self, query: str) -> List[float]:
            return self._get_embedding(query)
    
        async def _aget_query_embedding(self, query: str) -> List[float]:
            return self._get_embedding(query)
    
        async def _aget_text_embedding(self, text: str) -> List[float]:
            return self._get_embedding(text)
    
    # RAG检索服务类
    class RAGService:
        def __init__(
                self,
                db_config: dict,
                embed_api_base: str,
                llm_base_url: str,
                llm_model: str,
                embed_model_name: str,
                chunk_size: int = 512,
                chunk_overlap: int = 50,
                batch_size: int = 10
        ):
            self.db_config = db_config
            self.embed_api_base = embed_api_base
            self.llm_base_url = llm_base_url
            self.llm_model = llm_model
            self.embed_model_name = embed_model_name
            self.chunk_size = chunk_size
            self.chunk_overlap = chunk_overlap
            self.batch_size = batch_size
            self._vector_store = None
            self.splitter = SentenceSplitter(chunk_size=self.chunk_size, chunk_overlap=self.chunk_overlap)
            self._init_settings()
    
        def _init_settings(self):
            """初始化LLM与Embedding全局配置"""
            os.environ["OPENAI_API_KEY"] = "dummy"
            os.environ["OPENAI_BASE_URL"] = self.llm_base_url
            llm = OpenAILike(
                model=self.llm_model,
                api_base=os.environ["OPENAI_BASE_URL"],
                api_key=os.environ["OPENAI_API_KEY"],
                is_chat_model=True,
                context_window=4096,
                temperature=0.1
            )
            Settings.llm = llm
            Settings.embed_model = LocalLlamaServerEmbedding(
                api_base=self.embed_api_base,
                embed_model_name=self.embed_model_name
            )
            print("[+] LLM与Embedding模型初始化完成")
    
        def _get_vector_store(self) -> PGVectorStore:
            """单例获取PGVectorStore"""
            if self._vector_store is None:
                print("[+] 初始化PGVectorStore连接")
                self._vector_store = PGVectorStore.from_params(
                    database=self.db_config["database"],
                    host=self.db_config["host"],
                    password=self.db_config["password"],
                    port=self.db_config["port"],
                    user=self.db_config["user"],
                    table_name=self.db_config["table_name"],
                    embed_dim=self.db_config["embed_dim"],
                    hnsw_kwargs={
                        "hnsw_m": 16,
                        "hnsw_ef_construction": 64,
                        "hnsw_ef_search": 40,
                        "hnsw_dist_method": "vector_cosine_ops",
                    },
                )
            return self._vector_store
    
        def load_index_from_pg(self) -> VectorStoreIndex:
            """从PG加载已有索引"""
            vector_store = self._get_vector_store()
            storage_context = StorageContext.from_defaults(vector_store=vector_store)
            index = VectorStoreIndex.from_vector_store(
                vector_store,
                storage_context=storage_context
            )
            return index
    
        def add_or_update_knowledge(self, docs: List[Document]) -> VectorStoreIndex:
            """增量新增/更新文档:存在则删除旧chunk,再写入新文档"""
            vector_store = self._get_vector_store()
            doc_ids = [doc.metadata["doc_id"] for doc in docs]
            print(f"待处理文档doc_ids: {doc_ids}")
            filters = MetadataFilters(
                filters=[
                    MetadataFilter(
                        key="doc_id",
                        value=doc_ids,
                        operator=FilterOperator.IN
                    )
                ]
            )
            exist_nodes = vector_store.get_nodes(filters=filters)
            exist_doc_ids = {n.metadata["doc_id"] for n in exist_nodes}
            print(f"数据库中已存在的doc_ids: {exist_doc_ids}")
            new_docs = []
            update_doc_ids = []
            for d in docs:
                if d.metadata["doc_id"] in exist_doc_ids:
                    update_doc_ids.append(d.metadata["doc_id"])
                else:
                    new_docs.append(d)
            if update_doc_ids:
                print(f"删除旧文档向量,doc_ids={update_doc_ids}")
                del_filters = MetadataFilters(
                    filters=[
                        MetadataFilter(
                            key="doc_id",
                            value=update_doc_ids,
                            operator=FilterOperator.IN
                        )
                    ]
                )
                vector_store.delete_nodes(filters=del_filters)
            if len(docs) > 0:
                storage_context = StorageContext.from_defaults(vector_store=vector_store)
                index = VectorStoreIndex.from_documents(
                    docs,
                    storage_context=storage_context,
                    transformations=[self.splitter],
                    show_progress=True
                )
                print("[+] 知识库写入完成")
                return index
            else:
                print("[-] 没有待处理文档")
                return self.load_index_from_pg()
    
        def delete_knowledge(self, doc_id: str):
            """根据doc_id删除文档全部向量片段"""
            vector_store = self._get_vector_store()
            del_filters = MetadataFilters(
                filters=[
                    MetadataFilter(key="doc_id", value=doc_id, operator=FilterOperator.EQ)
                ]
            )
            vector_store.delete_nodes(filters=del_filters)
            print(f"[+] 已删除 doc_id={doc_id} 的所有向量片段")
    
        def clear_all_vector(self) -> None:
            """清空整张向量表,如果表不存在则直接跳过"""
            vector_store = self._get_vector_store()
            table_name = vector_store.table_name
            print(f"[-] 准备清空向量表 [{table_name}] 全部数据")
            try:
                conn = psycopg2.connect(
                    database=self.db_config["database"],
                    host=self.db_config["host"],
                    password=self.db_config["password"],
                    port=self.db_config["port"],
                    user=self.db_config["user"]
                )
                cur = conn.cursor()
                # 判断主表是否存在
                cur.execute("""
                    SELECT EXISTS (
                        SELECT FROM information_schema.tables
                        WHERE table_name = %s
                    );
                """, (table_name,))
                exists = cur.fetchone()[0]
                if exists:
                    cur.execute(f"TRUNCATE TABLE {table_name};")
                    conn.commit()
                    print(f"[+] 向量表 {table_name} 已全部清空")
                else:
                    print(f"[*] 表 {table_name} 不存在,无需清空")
                cur.close()
                conn.close()
            except Exception as e:
                print(f"清空向量表失败: {str(e)}")
                raise
    
        @retry(
            stop=stop_after_attempt(3),
            wait=wait_exponential(multiplier=1, min=1, max=5),
            retry=retry_if_exception_type((psycopg2.OperationalError, requests.exceptions.RequestException, RuntimeError))
        )
        def rag_query(self, query_str: str, filter_meta: dict = None, top_k: int = 3):
            """RAG问答查询,支持元数据过滤,带重试"""
            start_time = time.time()
            index = self.load_index_from_pg()
            filters = None
            if filter_meta:
                filter_list = []
                for k, v in filter_meta.items():
                    if isinstance(v, list):
                        op = FilterOperator.IN
                    else:
                        op = FilterOperator.EQ
                    filter_list.append(MetadataFilter(key=k, value=v, operator=op))
                filters = MetadataFilters(filters=filter_list)
            retriever = VectorIndexRetriever(
                index=index,
                similarity_top_k=top_k,
                filters=filters
            )
            query_engine = RetrieverQueryEngine.from_args(retriever)
            response = query_engine.query(query_str)
            cost = time.time() - start_time
            print(f"Query: {query_str}, cost={cost:.2f}s, hit_chunk_count={len(response.source_nodes)}")
            return response
    
    # -------------------------- 全局配置 --------------------------
    DB_CONFIG = {
        "database": "storage_db",
        "host": "8.122.231.178",
        "password": "1233",
        "port": "5432",
        "user": "storage_user",
        "table_name": "llama_rag_vector",
        "embed_dim": 1024
    }
    
    EMBEDDING_API_BASE = "http://127.0.0.1:11434/v1"
    LLM_BASE_URL = "http://127.0.0.1:11433/v1"
    
    LLM_MODEL = "qwen2.5-1.5b-instruct-q4_k_m.gguf"
    EMBED_MODEL_NAME = "Qwen3-Embedding-0.6B-Q8_0.gguf"
    
    CHUNK_SIZE = 512
    CHUNK_OVERLAP = 50
    BATCH_SIZE = 10
    
    # -------------------------- 主程序入口示例 --------------------------
    if __name__ == "__main__":
        # 实例化RAG服务
        rag_service = RAGService(
            db_config=DB_CONFIG,
            embed_api_base=EMBEDDING_API_BASE,
            llm_base_url=LLM_BASE_URL,
            llm_model=LLM_MODEL,
            embed_model_name=EMBED_MODEL_NAME,
            chunk_size=CHUNK_SIZE,
            chunk_overlap=CHUNK_OVERLAP,
            batch_size=BATCH_SIZE
        )
        # 清空向量表
        rag_service.clear_all_vector()
        # 读取本地文档
        docs = SimpleDirectoryReader(
            "./data/",
            required_exts=[".pdf", ".docx", ".txt"]
        ).load_data()
    
        # 同一个文件所有分片共用同一个doc_id,方便按文件整体删除
        file_to_docid = {}
        for doc in docs:
            fname = Path(doc.metadata["file_path"]).name
            if fname not in file_to_docid:
                file_to_docid[fname] = f"file_{len(file_to_docid)}"
            doc.metadata["doc_id"] = file_to_docid[fname]
            doc.metadata["source"] = "./data/"
            doc.metadata["upload_time"] = time.strftime("%Y-%m-%d %H:%M:%S")
    
        # 增量入库
        index = rag_service.add_or_update_knowledge(docs)
        print(f"[+] 文档 {len(docs)} 条已成功入库")
        print(index)
    
        # 测试问答 并过滤出前Top1个
        resp = rag_service.rag_query("概括文档内容,并返回中文。", filter_meta={"source": "./data/"}, top_k=1)
        print("---- LLM回答 ----")
        print(resp.response)
    
        # 调用index实例删除指定文件的所有分片
        rag_service.delete_knowledge("file_0")
    
        # 检索删除后的分片
        print("---- 检索到的源片段 ----")
        for node in resp.source_nodes:
            print(f"相似度分数:{node.score:.4f}")
            print(f"元数据:{node.metadata}")
    

    代码运行输出提示信息:

    [+] LLM与Embedding模型初始化完成
    [+] 初始化PGVectorStore连接
    [-] 准备清空向量表 [llama_rag_vector] 全部数据
    [*] 表 llama_rag_vector 不存在,无需清空
    
    待处理文档doc_ids: ['file_0', 'file_1']
    数据库中已存在的doc_ids: set()
    Applying transformations: 100%|█████████████████████████████| 1/1 [00:00<00:00, 590.00it/s]
    Generating embeddings: 100%|████████████████████████████████
    Generating embeddings: 100%|███████████████████████████████| 2/2 [00:01<00:00,  1.73it/s]
    
    [+] 知识库写入完成
    [+] 文档 2 条已成功入库
    <llama_index.core.indices.vector_store.base.VectorStoreIndex object at 0x000001EFF45DDE80>
    
    Query: 概括文档内容,并返回中文。, cost=2.24s, hit_chunk_count=1
    ---- LLM回答 ----
    你好,世界。
    
    [+] 已删除 doc_id=file_0 的所有向量片段
    
    ---- 检索到的源片段 ----
    相似度分数:0.6165
    元数据:
    {
        'file_path': 'C: \\Users\\Admin\\Documents\\data\\post2.txt',
        'file_name': 'post2.txt',
        'file_type': 'text/plain',
        'file_size': 18,
        'creation_date': '2026-09-18',
        'last_modified_date': '2026-09-18',
        'doc_id': 'file_1',
        'source': './data/',
        'upload_time': '2026-09-1812: 02: 43'
    }
    
  • 相关阅读:
    学生HTML个人网页作业作品:基于web在线汽车网站的设计与实现 (宝马轿车介绍)
    React基础学习-Day04
    SpringBoot整合SpringSession实现分布式登录
    LeetCode 面试题 16.03. 交点
    「Python循环结构」阿凡提拿工资
    Java中线程池的创建与使用
    chatgpt赋能python:Python如何快速取出所有元素?
    RobotFramework测试框架(11)--变量文件
    k8s:部署k8s单master节点集群(kubeadm)v1.23.13
    ncbi-datasets-cli-高效便捷下载NCBI数据
  • 原文地址:https://www.cnblogs.com/LyShark/p/22996705