原始电影资料不是直接交给 LLM,而是先被转换成 Document,再加工成 TextNode。VectorStoreIndex 负责触发 embedding 生成,StorageContext 负责把索引过程连接到 PGVectorStore,最终落到 PostgreSQL + pgvector。
用户提问时,QueryEngine 把问题向量化,用 pgvector 找到最接近的 TextNode,再把这些上下文交给 LLM 生成回答。
这篇文章基于一个 PostgreSQL + pgvector 的 RAG demo:6 条中文电影资料先被包装成 Document,再转换成 TextNode,由 VectorStoreIndex 生成 embedding,并通过 StorageContext 写入 PGVectorStore。查询时,QueryEngine 复用同一套索引,把 Top-K 电影节点交给 DeepSeek 生成推荐理由。
在这个 demo 里,LlamaIndex 做的事情可以分成两层:上层把业务资料变成统一的数据对象,下层把对象连接到 embedding 模型、向量存储和 LLM。PostgreSQL + pgvector 负责存储和相似度搜索,DeepSeek 负责生成自然语言答案,LlamaIndex 负责把它们串成一条可靠的数据流水线。
原始电影资料不是直接交给 LLM,而是先被转换成 Document,再加工成 TextNode。VectorStoreIndex 负责触发 embedding 生成,StorageContext 负责把索引过程连接到 PGVectorStore,最终落到 PostgreSQL + pgvector。
用户提问时,QueryEngine 把问题向量化,用 pgvector 找到最接近的 TextNode,再把这些上下文交给 LLM 生成回答。
把它们放回 demo 的运行顺序里,概念会清楚很多:Document 是输入形态,TextNode 是检索颗粒度,VectorStoreIndex 是索引执行者,StorageContext 是存储接线,PGVectorStore 是 PostgreSQL 适配器,QueryEngine 是面向问题的入口。
电影 JSON 被包装成 text + metadata。它保留业务语义,是资料进入 LlamaIndex 的入口。
Document 被转换成节点,获得 node_id。embedding、召回和上下文引用都围绕它发生。
写入时生成节点向量;查询时从已有 vector store 恢复成可检索索引视图。
把索引过程连接到指定后端,告诉 LlamaIndex 节点、元数据和向量要写到哪里。
把 LlamaIndex 节点落到 PostgreSQL + pgvector,形成 data_movie_rag 表。
接收用户问题,检索 Top-K TextNode,再把上下文交给 DeepSeek 生成答案。
ingestion 阶段的目标不是“回答”,而是“准备可被回答的知识”。demo 中 6 条中文电影资料会被整理成 Document,再转换成 TextNode,每个节点生成一个 512 维 embedding,最终写入 PostgreSQL。
Document(
text="""
电影名:火星救援
类型:科幻
关键词:火星, 宇航员, 科学求生, 工程, 救援
简介:一名宇航员被困火星,依靠植物学、
工程能力和团队救援计划努力生存。
""",
metadata={
"title": "火星救援",
"genre": "科幻"
}
)
vector_store = PGVectorStore.from_params(
database=settings.db_name,
host=settings.db_host,
port=settings.db_port,
user=settings.db_user,
password=settings.db_password,
table_name=settings.pgvector_table,
embed_dim=512
)
storage_context = StorageContext.from_defaults(
vector_store=vector_store
)
VectorStoreIndex.from_documents(
documents,
storage_context=storage_context,
show_progress=True
)
查询阶段不会重新写入 6 部电影,而是连接已有 PGVectorStore。VectorStoreIndex.from_vector_store(...) 创建的是一个查询视图;index.as_query_engine(similarity_top_k=3) 才是面向用户问题的接口。
当用户问“太空科幻、宇航员、火星或星际旅行”时,QueryEngine 会先把问题送进同一个 embedding 模型,生成 query vector。接着,PGVectorStore 让 PostgreSQL 在 data_movie_rag.embedding 上执行相似度检索,返回最接近的 3 个 TextNode。
这里有一个很重要的边界:pgvector 返回的是相关节点,DeepSeek 负责把这些节点组织成自然语言推荐理由。也就是说,LLM 的工作不是遍历数据库,而是基于检索出的上下文做表达。
index = VectorStoreIndex.from_vector_store(
vector_store=vector_store
)
query_engine = index.as_query_engine(
similarity_top_k=3
)
response = query_engine.query(
"我想看太空科幻电影,最好有宇航员、火星或星际旅行"
)
直接命中“火星”“宇航员”“科学求生”“救援”等核心语义,是最贴近问题的节点。
命中“星际旅行”“宇航员”“虫洞”“黑洞”等空间探索语义,与问题意图高度相关。
属于太空科幻和人类未来主题,相关性成立,但比前两部少了火星或宇航员的直接匹配。
demo 的 PostgreSQL 容器由 pgvector/pgvector:pg16 镜像启动,数据库启用 vector extension。校验脚本能看到 vector 0.8.6,并通过 <-> 运算符完成一次向量距离测试。
| 字段或对象 | 来自哪里 | 在 RAG 中的意义 |
|---|---|---|
text | TextNode.text | LLM 最终读取和引用的上下文内容。embedding 本身不能解释电影,文本才是答案材料。 |
metadata_ | Document.metadata 传递到 TextNode | 记录 title、genre、document_id、ref_doc_id 等信息,方便展示来源、过滤和后续治理。 |
node_id | LlamaIndex 创建的节点标识 | 让系统能追踪每个检索颗粒,支持更新、删除、引用、去重等工程操作。 |
embedding | HuggingFace embedding 模型 | 512 维语义向量,pgvector 用它计算问题和节点之间的距离。 |
PGVectorStore | LlamaIndex vector store integration | 把 LlamaIndex 的节点写入 PostgreSQL,也把相似度检索结果转回 LlamaIndex 可用的节点。 |
这个 demo 小到只有 6 部电影,但它展示的是可以扩展到真实系统的分工:数据对象、索引、存储、检索和生成各管一段,彼此通过清晰接口连接。
Document 让资料进入系统,TextNode 决定检索颗粒度,VectorStoreIndex 发起索引构建,StorageContext 决定写到哪里,PGVectorStore 把向量落进 PostgreSQL,QueryEngine 把问题变成有上下文的回答。
这是一个面向学习的 RAG demo:用 Docker 启动 PostgreSQL + pgvector,用 HuggingFace embedding 把中文电影文本转成向量,用 LlamaIndex 编排 ingestion 和 query 流程,最后让 DeepSeek 基于检索结果生成中文电影推荐理由。