Vespa
Vespa 是一个功能齐全的搜索引擎和矢量数据库。它支持向量搜索 (ANN)、词法搜索和结构化数据搜索,所有这些都在同一个查询中。
此笔记本演示如何使用Vespa.ai作为 LangChain 向量存储。
您需要安装langchain-community跟pip install -qU langchain-community使用此集成
为了创建向量存储,我们使用 pyvespa 创建一个
连接 AVespa服务。
%pip install --upgrade --quiet pyvespa
使用pyvespa包中,您可以连接到 Vespa Cloud 实例或本地 Docker 实例。
在这里,我们将创建一个新的 Vespa 应用程序并使用 Docker 进行部署。
创建 Vespa 应用程序
首先,我们需要创建一个应用程序包:
from vespa.package import ApplicationPackage, Field, RankProfile
app_package = ApplicationPackage(name="testapp")
app_package.schema.add_fields(
Field(
name="text", type="string", indexing=["index", "summary"], index="enable-bm25"
),
Field(
name="embedding",
type="tensor<float>(x[384])",
indexing=["attribute", "summary"],
attribute=["distance-metric: angular"],
),
)
app_package.schema.add_rank_profile(
RankProfile(
name="default",
first_phase="closeness(field, embedding)",
inputs=[("query(query_embedding)", "tensor<float>(x[384])")],
)
)
这将设置一个 Vespa 应用程序,其中包含每个文档的架构
两个字段:text用于保存文档文本,以及embedding用于持有
嵌入向量。这text字段设置为使用 BM25 索引
高效的文本检索,我们将了解如何使用 this 和 hybrid search a
稍后。
这embeddingfield 设置了一个长度为 384 的向量来保存
嵌入文本的表示形式。有关 Vespa 中张量的更多信息,请参阅 Vespa 的张量指南。
最后,我们将排名配置文件添加到 指导 Vespa 如何订购文件。在这里,我们使用最近邻搜索进行设置。
现在我们可以在本地部署此应用程序:
from vespa.deployment import VespaDocker
vespa_docker = VespaDocker()
vespa_app = vespa_docker.deploy(application_package=app_package)
这将部署并创建与Vespa服务。如果你
已经有一个 Vespa 应用程序正在运行,例如在云中,
有关如何连接,请参阅 PyVespa 应用程序。
创建 Vespa 矢量存储
现在,让我们加载一些文档:
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
loader = TextLoader("../../how_to/state_of_the_union.txt")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
from langchain_community.embeddings.sentence_transformer import (
SentenceTransformerEmbeddings,
)
embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
在这里,我们还设置了本地句子嵌入器,将文本转换为嵌入
向量。也可以使用 OpenAI 嵌入,但向量长度需要
更新为1536以反映该嵌入的较大大小。
要将它们提供给 Vespa,我们需要配置向量存储应如何映射到 字段中的字段。然后我们直接从 vector store 创建 vector store 这组文档:
vespa_config = dict(
page_content_field="text",
embedding_field="embedding",
input_field="query_embedding",
)
from langchain_community.vectorstores import VespaStore
db = VespaStore.from_documents(docs, embedding_function, app=vespa_app, **vespa_config)
这将创建一个 Vespa 矢量存储,并将该组文档提供给 Vespa。 向量存储负责调用每个文档的嵌入函数 并将它们插入到数据库中。
我们现在可以查询 vector store:
query = "What did the president say about Ketanji Brown Jackson"
results = db.similarity_search(query)
print(results[0].page_content)
这将使用上面给出的 embedding 函数来创建表示
对于查询,并使用它来搜索 Vespa。请注意,这将使用defaultranking 函数,我们在 application 包中设置
以上。您可以使用rankingargument 设置为similarity_search自
指定要使用的排名函数。
有关更多信息,请参阅 pyvespa 文档。
这涵盖了 LangChain 中 Vespa store 的基本用法。 现在,您可以返回结果并继续在 LangChain 中使用这些结果。
更新文档
调用from_documents中,您可以创建向量
store 并调用add_texts从那个。这也可以用于更新
文件:
query = "What did the president say about Ketanji Brown Jackson"
results = db.similarity_search(query)
result = results[0]
result.page_content = "UPDATED: " + result.page_content
db.add_texts([result.page_content], [result.metadata], result.metadata["id"])
results = db.similarity_search(query)
print(results[0].page_content)
但是,pyvespa库包含要作的方法
Vespa 上的内容,您可以直接使用。
删除文档
您可以使用delete功能:
result = db.similarity_search(query)
# docs[0].metadata["id"] == "id:testapp:testapp::32"
db.delete(["32"])
result = db.similarity_search(query)
# docs[0].metadata["id"] != "id:testapp:testapp::32"
同样,pyvespaconnection 还包含用于删除文档的方法。
返回分数
这similarity_searchmethod 仅返回
关联。要检索实际分数:
results = db.similarity_search_with_score(query)
result = results[0]
# result[1] ~= 0.463
这是使用"all-MiniLM-L6-v2"embedding 模型使用
余弦距离函数(由参数angular在
application 函数)。
不同的 emadding 函数需要不同的 distance 函数,而 Vespa 需要知道在订购文档时使用哪个距离函数。 有关更多信息,请参阅有关距离函数的文档。
作为 retriever
要将此向量存储用作 LangChain 检索器,只需调用as_retriever函数,它是一个标准的向量存储
方法:
db = VespaStore.from_documents(docs, embedding_function, app=vespa_app, **vespa_config)
retriever = db.as_retriever()
query = "What did the president say about Ketanji Brown Jackson"
results = retriever.invoke(query)
# results[0].metadata["id"] == "id:testapp:testapp::32"
这允许从 vector store 进行更通用的、非结构化的检索。
元数据
在到目前为止的示例中,我们只使用了文本和嵌入 发短信。文档通常包含附加信息,这些信息在 LangChain 中 称为元数据。
通过将 Vespa 添加到应用程序,可以包含许多不同类型的字段 包:
app_package.schema.add_fields(
# ...
Field(name="date", type="string", indexing=["attribute", "summary"]),
Field(name="rating", type="int", indexing=["attribute", "summary"]),
Field(name="author", type="string", indexing=["attribute", "summary"]),
# ...
)
vespa_app = vespa_docker.deploy(application_package=app_package)
我们可以在文档中添加一些元数据字段:
# Add metadata
for i, doc in enumerate(docs):
doc.metadata["date"] = f"2023-{(i % 12)+1}-{(i % 28)+1}"
doc.metadata["rating"] = range(1, 6)[i % 5]
doc.metadata["author"] = ["Joe Biden", "Unknown"][min(i, 1)]
并让 Vespa 矢量商店了解这些字段:
vespa_config.update(dict(metadata_fields=["date", "rating", "author"]))
现在,在搜索这些文档时,将返回这些字段。 此外,还可以根据以下条件筛选这些字段:
db = VespaStore.from_documents(docs, embedding_function, app=vespa_app, **vespa_config)
query = "What did the president say about Ketanji Brown Jackson"
results = db.similarity_search(query, filter="rating > 3")
# results[0].metadata["id"] == "id:testapp:testapp::34"
# results[0].metadata["author"] == "Unknown"
自定义查询
如果相似性搜索的默认行为不适合您的 要求,则始终可以提供自己的查询。因此,您不需要 需要向 vector store 提供所有配置,但 宁愿自己写这个。
首先,让我们向应用程序添加一个 BM25 排名函数:
from vespa.package import FieldSet
app_package.schema.add_field_set(FieldSet(name="default", fields=["text"]))
app_package.schema.add_rank_profile(RankProfile(name="bm25", first_phase="bm25(text)"))
vespa_app = vespa_docker.deploy(application_package=app_package)
db = VespaStore.from_documents(docs, embedding_function, app=vespa_app, **vespa_config)
然后,要执行基于 BM25 的常规文本搜索:
query = "What did the president say about Ketanji Brown Jackson"
custom_query = {
"yql": "select * from sources * where userQuery()",
"query": query,
"type": "weakAnd",
"ranking": "bm25",
"hits": 4,
}
results = db.similarity_search_with_score(query, custom_query=custom_query)
# results[0][0].metadata["id"] == "id:testapp:testapp::32"
# results[0][1] ~= 14.384
可以使用 Vespa 的所有强大搜索和查询功能 通过使用自定义查询。有关更多详细信息,请参阅有关其查询 API 的 Vespa 文档。
混合搜索
混合搜索是指同时使用基于字词的经典搜索,例如 BM25 和向量搜索并组合结果。我们需要创建 Vespa 上混合搜索的新排名配置文件:
app_package.schema.add_rank_profile(
RankProfile(
name="hybrid",
first_phase="log(bm25(text)) + 0.5 * closeness(field, embedding)",
inputs=[("query(query_embedding)", "tensor<float>(x[384])")],
)
)
vespa_app = vespa_docker.deploy(application_package=app_package)
db = VespaStore.from_documents(docs, embedding_function, app=vespa_app, **vespa_config)
在这里,我们将每个文档作为其 BM25 分数及其 distance 分数。我们可以使用自定义查询进行查询:
query = "What did the president say about Ketanji Brown Jackson"
query_embedding = embedding_function.embed_query(query)
nearest_neighbor_expression = (
"{targetHits: 4}nearestNeighbor(embedding, query_embedding)"
)
custom_query = {
"yql": f"select * from sources * where {nearest_neighbor_expression} and userQuery()",
"query": query,
"type": "weakAnd",
"input.query(query_embedding)": query_embedding,
"ranking": "hybrid",
"hits": 4,
}
results = db.similarity_search_with_score(query, custom_query=custom_query)
# results[0][0].metadata["id"], "id:testapp:testapp::32")
# results[0][1] ~= 2.897
Vespa 中的原生嵌入器
到目前为止,我们使用了 Python 中的 embedding 函数来提供 文本的嵌入向量。Vespa 本身就支持 embedding 函数,因此 您可以将此计算推迟到 Vespa 中。一个好处是能够使用 GPU。
有关更多信息,请参阅 Vespa 嵌入。
首先,我们需要修改我们的应用程序包:
from vespa.package import Component, Parameter
app_package.components = [
Component(
id="hf-embedder",
type="hugging-face-embedder",
parameters=[
Parameter("transformer-model", {"path": "..."}),
Parameter("tokenizer-model", {"url": "..."}),
],
)
]
Field(
name="hfembedding",
type="tensor<float>(x[384])",
is_document_field=False,
indexing=["input text", "embed hf-embedder", "attribute", "summary"],
attribute=["distance-metric: angular"],
)
app_package.schema.add_rank_profile(
RankProfile(
name="hf_similarity",
first_phase="closeness(field, hfembedding)",
inputs=[("query(query_embedding)", "tensor<float>(x[384])")],
)
)
请参阅有关添加嵌入器模型的嵌入文档
和应用程序的分词器。请注意,hfembedding田
包括使用hf-embedder.
现在我们可以使用自定义查询进行查询:
query = "What did the president say about Ketanji Brown Jackson"
nearest_neighbor_expression = (
"{targetHits: 4}nearestNeighbor(internalembedding, query_embedding)"
)
custom_query = {
"yql": f"select * from sources * where {nearest_neighbor_expression}",
"input.query(query_embedding)": f'embed(hf-embedder, "{query}")',
"ranking": "internal_similarity",
"hits": 4,
}
results = db.similarity_search_with_score(query, custom_query=custom_query)
# results[0][0].metadata["id"], "id:testapp:testapp::32")
# results[0][1] ~= 0.630
请注意,此处的查询包括一个embed嵌入查询的说明
使用与文档相同的模型。
近似最近邻
在上面的所有例子中,我们使用了 查找结果。但是,对于大型文档集合,这是 不可行,因为必须扫描所有文档才能找到 最佳匹配。为了避免这种情况,我们可以使用近似最近邻。
首先,我们可以更改 embedding 字段以创建 HNSW 索引:
from vespa.package import HNSW
app_package.schema.add_fields(
Field(
name="embedding",
type="tensor<float>(x[384])",
indexing=["attribute", "summary", "index"],
ann=HNSW(
distance_metric="angular",
max_links_per_node=16,
neighbors_to_explore_at_insert=200,
),
)
)
这会在嵌入数据上创建一个 HNSW 索引,从而实现高效的
搜索。有了这个集合,我们可以通过设置
这approximateargument 设置为True:
query = "What did the president say about Ketanji Brown Jackson"
results = db.similarity_search(query, approximate=True)
# results[0][0].metadata["id"], "id:testapp:testapp::32")
这涵盖了 LangChain 中 Vespa 矢量存储中的大部分功能。