前文《基于 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 源并更新缓存,随后安装数据库编译、运行及插件部署所需的全套依赖包,为后续源码编译提供环境。
bash
# 开启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,同时编译内置扩展组件,最大化数据库原生能力,编译全程约五分钟。
bash
[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 用于进程托管,同时创建独立数据存储目录,并配置严格权限,保障数据库数据安全。
bash
[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 等核心命令。
bash
[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 密码,完成数据库基础初始化。
bash
[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 标准运维命令。
bash
[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、加载系统服务、启动数据库进程,配置开机自启,并查看服务运行状态,确认数据库正常启动。
bash
[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,保证版本兼容性。
bash
[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 对接存储向量数据。
bash
[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 仅支持本地访问,本文修改配置开启全网远程访问,适配外部程序、服务器对接向量数据库,配置完成后重启服务并验证公网连通性。
bash
# 启动远程访问权限
[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 向量持久化存储与检索。
bash
[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 等专用向量库,优势是无需额外维护独立向量服务,同时原生支持元数据过滤,可基于部门、文档类型、权限标签实现企业多租户场景。
安装所需要的依赖包如下所示:
bash
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 链路连通性,仅实现基础入库与问答,没有封装增量更新、文档删除、元数据过滤等生产级能力。
python
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)
代码运行输出提示信息:
bash
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 实例、清空历史向量、加载文档入库、执行带元数据过滤的问答查询。相比前面的基础示例,代码模块化,支持文档动态维护,具备生产环境所需的基础容错与权限过滤能力。
python
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}")
代码运行输出提示信息:
bash
[+] 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'
}