LlamaIndex+PostgreSQL に基づいて RAG 永続性を実現 – lyshark

0
LlamaIndex+PostgreSQL に基づいて RAG 永続性を実現 – lyshark


前文《RAG ベクトル検索エントリ ゲートを実現するための LlamaIndex に基づく》は、ローカルの大規模モデルとベクトル検索基盤の組み込みを完了しましたが、一時的なベクトル ストレージのみをサポートしており、永続的に再利用することはできません。このテキストは、PostgreSQL + pgvector ベクター プラグインを使用して LlamaIndex に接続する、長期持続する RAG ストレージ ソリューションです。この環境は CentOS Stream 10 を使用し、永続的なベクトル取得を実現する VectorStoreRetriever に基づいたデータベースのコンパイル、インストール、プラグイン構成を示し、RAG サービスによってローカルで安定して再利用できます。

前文《RAG ベクトル検索エントリ ゲートを実現するための LlamaIndex に基づく》は、ローカルの大規模モデルとベクトル検索基盤の組み込みを完了しましたが、一時的なベクトル ストレージのみをサポートしており、永続的に再利用することはできません。このテキストは、PostgreSQL + pgvector ベクター プラグインを使用して LlamaIndex に接続する、長期持続する RAG ストレージ ソリューションです。この環境は CentOS Stream 10 を使用し、永続的なベクトル取得を実現する VectorStoreRetriever に基づいたデータベースのコンパイル、インストール、プラグイン構成を示し、RAG サービスによってローカルで安定して再利用できます。

PostgreSQL (PG と略称) は、強力で拡張可能なプラグインを備えた、成熟したオープンソースのオブジェクト リレーショナル データベースです。 pgvector プラグインは、ベクトル データ タイプを追加し、ベクトルの保存と類似性の検索を実現し、ベクトルの保存と類似性の検索システム、およびベクトルの永続化に非常に役立ちます。このコンテンツは CentOS Stream 10 環境に基づいており、ソース コードのコンパイル方法、PostgreSQL pgSQL ベクター プラグインに基づくインストールとインストール、完全なベクター データベースの基本環境を使用しています。

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 
[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_configinitdb 等コアオーダー。

[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 サービス構成は、標準動作の開始/シャットダウン/再起動/リロードをサポートします。

[root@localhost ~]# vi /etc/systemd/system/postgresql.service

[Unit]
Description=PostgreSQL 18.6 database server
Documentation=
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: 
   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 
[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 Vektor データ ストレージ。

[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=# 

エンタープライズ シナリオでは、データ フィルタリングを使用した総会は、文書権限の分離、効率の高い検索を実現し、エンタープライズ ナレッジ ベースの最も一般的に使用されるベースライン スキームです。以下では、LlamaIndex を使用して以前にデプロイされた PostgreSQL + pgvector 環境に接続し、再利用可能な RAG サービス クラスに再パッケージ化された基本的な RAG の例を示し、ドキュメント ID に応じたドキュメントの増加、更新、削除、データ フィルタリング、クエリなどを実現します。

  • 化电动电影国际:Documentation 分块 → Embed 化化化 → 化化存入化化库 → 电影最好尕量化

PostgreSQL 拡張機能としての PGVector は、リレーショナル データベースでベクトル データを直接運ぶことができ、Qdrant、Milvus などの特殊なベクトル ライブラリと比較して、独立したベクトル サービスを維持する必要がないという利点があり、同時に、元々データ フィルタリングをサポートしており、部門、ドキュメント タイプ、権限タグに基づいたエンタープライズ マルチレンタル シナリオを実現できます。

以下に示すように、必要な依存パッケージをインストールします。

pip install llama-index llama-index-vector-stores-postgres pgvector psycopg2-binary llama-index-embeddings-openai llama-index-llms-openai-like -i 

最小限の例

この例は、LlamaIndex PGVector に基づく、利用可能な最小のデモです。コードはカスタマイズされています。

後続の操作では、ベクトル化を繰り返す必要がなく、データベースからベクトル インデックスを直接ロードできます。最後に、構造を構築し、ベクトル類似性検索を確認し、コンテキストを大規模モデルに切り替え、ドキュメントを完成させ、質問に答えます。

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"] = "

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="

# 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]

回答:
文档的核心内容是关于人工智能大模型如何理解和处理自然语言,以及如何通过向量数据库和向量检索技术来处理和查找文本内容。

包装例

このケースでは、LlamaIndex と PGVector に基づいて、RAG ビジネス ロジックを独立した RAGService クラスとしてカプセル化します。このクラスは、初期化モデル、データベース接続、ドキュメントのロード、ベクトルの追加/削除、質問回答の取得などの機能を内部的にカプセル化します。ドキュメント 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 = "
LLM_BASE_URL = "

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 条已成功入库


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'
}

Leave a Reply

Your email address will not be published. Required fields are marked *