什么是 vector store?
与专门用于存储结构化数据(如 JSON 文档或符合关系型数据库模式的数据)的传统数据库不同,vector stores处理的是非结构化数据,包括文本和图像。像传统数据库一样,vector stores也能执行创建、读取、更新、删除(CRUD)以及搜索操作。
Vector stores解锁了各种各样的用例,包括可扩展的应用程序,这些应用程序利用AI来回答有关大型文档的问题,如图2-4所示。
目前,有大量的向量存储提供商可供选择,每一家都擅长不同的功能。你应根据应用程序的关键需求来进行选择,这些需求包括多租户支持、元数据筛选能力、性能、成本以及可扩展性。
尽管 vector store是专门用于管理向量数据的细分数据库,但使用它们仍存在一些缺点:
-
大多数 vector store相对较新,可能无法经受时间的考验。
-
管理和优化 vector store可能存在相对陡峭的学习曲线。
-
管理一个独立的数据库会增加应用程序的复杂性,并可能消耗宝贵的资源。
幸运的是,向量存储的能力最近已经通过 pgvector 扩展,被扩展到 PostgreSQL(一个流行的开源关系型数据库)。这使你可以使用已经熟悉的同一个数据库,来同时支持事务表(例如用户表)和向量搜索表。
使用 PGVector
要使用 Postgres 和 PGVector,你需要按照以下步骤进行设置:
-
确保在您的计算机上安装了 Docker
-
在终端中运行以下命令; 它将在您的计算机上启动一个在端口 6024 运行的 Postgres 实例:
docker run \ --name pgvector-container \ -e POSTGRES_USER=langchain \ -e POSTGRES_PASSWORD=langchain \ -e POSTGRES_DB=langchain \ -p 6024:5432 \ -d pgvector/pgvector:pg16
-
打开您的 Docker 仪表板容器,您应该在 pgvector-container 旁边看到一个绿色的运行状态。
-
将连接字符串保存到代码中以供以后使用;我们以后需要使用它:
postgresql+psycopg://langchain:langchain@localhost:6024/langchain
如果你是刚开始接触向量数据库或 LangChain,强烈建议先用 Docker,后期再考虑更复杂的本地部署或云服务集成(如 AWS RDS + pgvector)。Docker 是一个“容器”工具,它可以把应用程序和它运行所需要的一切(比如操作系统、依赖、库等)打包在一个“箱子”里运行。
假设你要运行一个 PostgreSQL + PGVector 的数据库,你需要:
-
安装 PostgreSQL
-
配置好数据库用户和端口
-
安装 pgvector 插件
-
保证版本兼容
这些都很麻烦,对吧?
但用了 Docker,只需要一条命令,它就帮你:
-
自动下载带 pgvector 的 PostgreSQL
-
自动配置好用户密码端口
-
独立运行在它自己的“容器”里,不影响你电脑上其他软件
现在我们从第一步安装Docker开始,
1. 安装 Docker
去官网下载并安装:Docker官网
2. 运行 Docker 容器
```bash
docker run \
--name pgvector-container \
-e POSTGRES_USER=langchain \
-e POSTGRES_PASSWORD=langchain \
-e POSTGRES_DB=langchain \
-p 6024:5432 \
-d pgvector/pgvector:pg16
```
这会创建一个名为 pgvector-container 的容器,在本地的 6024 端口 启动 PostgreSQL 数据库,数据库名和账户密码均为 langchain。
3. 确认数据库是否运行正常
打开 Docker 桌面,确认容器 pgvector-container 显示为绿色状态(Running)。
4. LangChain 中连接 pgvector
确保你已经安装了必要的包:
pip install langchain psycopg2-binary sqlalchemy
上面的步骤全都配置结束,大约花费50分钟。
现在我们来在langchain中连接pgvector
记得首先pip install langchain-postgres
from langchain_community.document_loaders import TextLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_postgres.vectorstores import PGVector
from langchain_core.documents import Document
import uuid# Load the document, split it into chunks
loader = TextLoader("./test.txt", encoding="utf-8")
raw_documents = loader.load()text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
documents = text_splitter.split_documents(raw_documents)# embed each chunk and insert it into the vector store
model_name = "sentence-transformers/all-mpnet-base-v2"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': False}embeddings_model = HuggingFaceEmbeddings(model_name=model_name,model_kwargs=model_kwargs,encode_kwargs=encode_kwargs
)connection = 'postgresql+psycopg://langchain:langchain@localhost:6024/langchain'db = PGVector.from_documents(documents, embeddings_model, connection=connection)
报错:ModuleNotFoundError: No module named ‘psycopg’
你这个错误信息非常明确,是因为 psycopg 无法正确加载 libpq 库(PostgreSQL 的底层通信库),所以在导入 PGVector 时报错。
你使用的是新版的 psycopg(也叫 psycopg3),它和旧版 psycopg2 不一样,需要本地系统有 PostgreSQL 的 C 库支持(libpq)。
改写为以下用法:
-
确保你安装了官方 vectorstore 依赖
pip install langchain psycopg2-binary sqlalchemy
-
修改你的 import 语句
换成官方推荐的 PGVector 接入方式:
from langchain.vectorstores.pgvector import PGVector
from langchain_community.document_loaders import TextLoader
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.vectorstores.pgvector import PGVector
from langchain_core.documents import Document
import uuid# Load the document, split it into chunks
loader = TextLoader("./test.txt", encoding="utf-8")
raw_documents = loader.load()text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
documents = text_splitter.split_documents(raw_documents)# embed each chunk and insert it into the vector store
model_name = "sentence-transformers/all-mpnet-base-v2"
model_kwargs = {'device': 'cpu'}
encode_kwargs = {'normalize_embeddings': False}embeddings_model = HuggingFaceEmbeddings(model_name=model_name,model_kwargs=model_kwargs,encode_kwargs=encode_kwargs
)connection = 'postgresql+psycopg2://langchain:langchain@localhost:6024/langchain'db = PGVector.from_documents(documents=documents,embedding=embeddings_model,connection_string=connection, # ✅ 这里改成 connection_stringcollection_name="my_collection", # 可选,但推荐加
)
这个查询会在你之前加载的文档中,返回与“为什么会有“上下文窗口”(context window)”这个问题最相似的段落,如果它能正常返回结果,就说明数据确实已经存入 pgvector 并可用啦。
# 查询一下看看有没有数据,k表示打印几条结果
results = db.similarity_search("为什么会有“上下文窗口”(context window)", k=2)for i, doc in enumerate(results, 1):print(f"【第{i}条结果】", doc.page_content)
【第1条结果】 > [!NOTE] **为什么会有“上下文窗口”(context window)这个限制?**
>
>1. 资源限制:计算成本高> 每次你和大语言模型对话,它其实是在处理一个非常复杂的数学计算过程,背后是巨大的矩阵运算。 > 输入越长,占用的 显存(GPU memory) 和 计算时间 就越多。
>输入+输出的长度太长,会让显卡扛不住,或者处理速度变得非常慢。
>所以模型必须设置一个“上限”——这就是 context window,也叫“上下文窗口”。>
>
>2. 模型设计:Transformer 架构的限制> 大语言模型(如 GPT)基于 Transformer 架构。> Transformer 是靠“注意力机制”处理每一个 token 的。> 每加一个 token,就要考虑它和之前所有 token 的关系,所以计算量是平方级别增长的(O(n²))。> 这意味着:> 上下文越长,注意力机制越慢,成本越高,效果也可能下降。
>3. 训练方式决定了这个限制> 模型训练时,是在固定长度的上下文窗口中学习的,比如 2048、4096 或 8192 个 token。> 如果你测试时突然给它一个比训练时长得多的输入,模型可能根本不知道怎么处理。> 所以训练时设置了窗口,使用时就要遵守这个规则。
>
> ---------------------
>一个简单类比:
>
>想象你在读一本书,但你只能记住最近的一页内容(上下文窗口),太久以前的内容就记不清了。
>
>模型也是这样,它记得的“最近内容”数量是有限的。
【第2条结果】 > [!NOTE] **为什么会有“上下文窗口”(context window)这个限制?**
>
>1. 资源限制:计算成本高> 每次你和大语言模型对话,它其实是在处理一个非常复杂的数学计算过程,背后是巨大的矩阵运算。 > 输入越长,占用的 显存(GPU memory) 和 计算时间 就越多。
>输入+输出的长度太长,会让显卡扛不住,或者处理速度变得非常慢。
>所以模型必须设置一个“上限”——这就是 context window,也叫“上下文窗口”。>
>
>2. 模型设计:Transformer 架构的限制> 大语言模型(如 GPT)基于 Transformer 架构。> Transformer 是靠“注意力机制”处理每一个 token 的。> 每加一个 token,就要考虑它和之前所有 token 的关系,所以计算量是平方级别增长的(O(n²))。> 这意味着:> 上下文越长,注意力机制越慢,成本越高,效果也可能下降。
>3. 训练方式决定了这个限制> 模型训练时,是在固定长度的上下文窗口中学习的,比如 2048、4096 或 8192 个 token。> 如果你测试时突然给它一个比训练时长得多的输入,模型可能根本不知道怎么处理。> 所以训练时设置了窗口,使用时就要遵守这个规则。
>
> ---------------------
>一个简单类比:
>
>想象你在读一本书,但你只能记住最近的一页内容(上下文窗口),太久以前的内容就记不清了。
>
>模型也是这样,它记得的“最近内容”数量是有限的。
db.similarity_search() 这个方法将通过以下过程找到最相关的文档(之前已对其进行索引):
-
搜索查询(在这种情况下是一个词或短语)将被发送到嵌入模型以生成其向量表示。
-
接着,系统会在 Postgres 数据库中运行查询,找出与该向量最相似的 N 个已存储的嵌入(本例中为2个)。
-
最后,它会提取每个相似嵌入所对应的原始文本内容和元数据。
-
模型将返回一个文档列表,并按照它们与查询向量的相似度高低进行排序——最相似的排在前面,依此类推。
[!NOTE] 如何找出与该向量最相似的 N 个已存储的嵌入?
你现在已经进入了「向量数据库的核心机制」——**相似度搜索(vector similarity search)**的本质。
简要回答:是通过 向量之间的相似度计算公式 + pgvector 插件中的索引与比较操作符 实现的。
步骤拆解如下:
① 查询向量的生成(embedding)
你输入一段查询(query,比如“向量数据库的作用是什么?”),LangChain 会:
query_embedding = embeddings_model.embed_query("向量数据库的作用是什么?")
这个 query_embedding 是一个 高维向量数组,比如:
[0.123, -0.341, ..., 0.442] # 维度可能是 384、768、1024...
② 和数据库中所有嵌入进行相似度对比
PostgreSQL + pgvector 使用 SQL 语句执行相似度计算,例如:
SELECT *, embedding <#> '[0.123, 0.341, ..., 0.442]' FROM langchain_pg_embedding_my_collection ORDER BY embedding <#> '[...]' LIMIT 2;
<#>
是 pgvector 提供的 “余弦距离”操作符。值越小,表示越相似。相似度通常用 余弦相似度(cosine) 或 欧氏距离(L2)
③ 查找最相似的 Top-N 条
执行 SQL 时用 ORDER BY + LIMIT N,就能找出最相似的 N 条记录。
可视化向量数据库中的数据:使用pgAdmin
假如我们想可视化向量数据库中的数据,可以下载单独的可视化软件,比如:
-
pgAdmin
-
dbeaver
这里我选择第一个免费开源软件的windows版。
下载安装好后,连接到server。
随后我想要查看一下向量数据库存储的是什么数据,接下来手把手带你操作如何查看:
方法一:图形化方式(适合浏览数据)
步骤:
1.左侧树状结构中,点击展开:
-
langchain → Databases → langchain
-
然后继续展开:Schemas → public → Tables
2.找到你插入文档时自动创建的表,名称通常是类似于你设定的 collection_name,例如:
langchain_pg_embedding
3.右键点击这个表 → 选择 View/Edit Data → All Rows
然后你就可以看到你之前插入的向量、文本内容、metadata 等字段了!
一共有16条数据:
方法二:用 SQL 查询(适合精准操作)
步骤:
在 pgAdmin 顶部点击:🧾 SQL(或按快捷键 F5快速执行)
输入并执行下面这句 SQL:
SELECT * FROM langchain_pg_embedding LIMIT 10;
可以看到已经显示前10条记录了。
接下来介绍几条常用的SQL语句(以表名langchain_pg_embedding为例):
1.查询(Read)
-- 查询所有数据
SELECT * FROM langchain_pg_embedding;-- 查询包含关键词 “向量” 的记录(全文内容)
SELECT * FROM langchain_pg_embedding
WHERE document LIKE '%向量%';-- 查询某个 ID 的向量内容
SELECT * FROM langchain_pg_embedding
WHERE id = 'your-vector-id';
- 插入(Insert)
一般不建议手写插入向量(除非你很熟),但可以这样手动插入一个文档:
INSERT INTO langchain_pg_embedding (document, metadata, embedding)
VALUES ('这是一段测试文本','{"source": "manual"}','[0.1, 0.2, 0.3, ...]' -- 注意这里向量必须是 float[],长度必须匹配模型维度
);
- 删除(Delete)
-- 根据 ID 删除
DELETE FROM langchain_pg_embedding
WHERE id = 'your-vector-id';-- 删除所有数据(⚠️危险操作)
DELETE FROM langchain_pg_embedding;
- 修改(Update)
-- 修改 metadata
UPDATE langchain_pg_embedding
SET metadata = '{"source": "updated"}'
WHERE id = 'your-vector-id';-- 修改文档内容
UPDATE langchain_pg_embedding
SET document = '这是新的文档内容'
WHERE id = 'your-vector-id';
我们的数据库中有这些字段名:
还可以使用langchain语句向现有数据库添加更多文档。在这个例子中,我们使用可选的 ids 参数为每个文档分配标识符,这使我们可以在以后更新或删除它们。
让我们看这个例子:
# 生成两个唯一的 UUID 作为向量的主键(id)
# ids 是可选的,如果你不传,LangChain 会自动生成 UUID
# 但传 ids 的好处是后面可以用:db.delete(ids=["your-id"]) # 删除指定向量
# 这样你能精确管理向量库中的内容
ids = [str(uuid.uuid4()), str(uuid.uuid4())]# 将新的文本片段(两个句子)嵌入,并插入到你已经连接的 pgvector 数据表中
db.add_documents([Document(page_content="there are cats in the pond",metadata={"location": "pond", "topic": "animals"},),Document(page_content="ducks are also found in the pond",metadata={"location": "pond", "topic": "animals"},),],ids=ids,
)
['0cdf295d-5e3c-4757-bc8c-457c05927eb7','a7a67123-a7bf-4b16-990b-56d430a78f67']
我们的table中的embedding数量从16增加到了18。
我们在这里使用的db.add_documents() 方法将遵循与PGVector.from_documents() 类似的过程:
- 为传递的每个文档创建嵌入,使用选择的模型。
- 将嵌入、文档元数据和文档文本内容存储在 Postgres 中,以便进行搜索。
删除操作的例子:
db.delete(ids=['0cdf295d-5e3c-4757-bc8c-457c05927eb7'])
db.delete(ids=['a7a67123-a7bf-4b16-990b-56d430a78f67'])
这样就从向量数据库中删除指定 ID 的嵌入向量记录,就是我们刚刚插入的那两条。
总结
本小节我们学习了如何在vector store中存储embeddings,并且使用了PostgreSQL数据库。
通过docker连接数据库,还通过插件PGVector这个为 PostgreSQL 数据库设计的向量(embedding)存储与相似度搜索插件,使得你可以在 PostgreSQL 中直接存储向量,并进行高效的相似度检索(vector similarity search)。
最后还使用了免费的可视化PostgreSQL数据库的软件pgAdmin对数据库的tables中的数据进行查看。
收获满满的一章。感谢阅读!下一节我们来讲讲如何跟踪文档的更改。