Azure Search 和 Azure Cognitive Search)是一项云搜索服务,为开发者提供基础设施、API 和工具,用于大规模执行向量、关键词和混合查询的信息检索。
您需要使用 pip install -qU langchain-community 安装 langchain-community 才能使用此集成。
安装 Azure AI 搜索 SDK
使用 azure-search-documents 包版本 11.4.0 或更高版本。pip install -qU azure-search-documents
pip install -qU azure-identity
导入所需库
假设使用OpenAIEmbeddings,但如果您使用的是 Azure OpenAI,请改为导入 AzureOpenAIEmbeddings。
import os
from langchain_community.vectorstores.azuresearch import AzureSearch
from langchain_openai import AzureOpenAIEmbeddings, OpenAIEmbeddings
配置 OpenAI 设置
为您的 OpenAI 提供商设置变量。您需要一个 OpenAI 帐户 或一个 Azure OpenAI 帐户 来生成嵌入。# 选项 1:使用 OpenAI 帐户
openai_api_key: str = "YOUR API KEY 占位符"
openai_api_version: str = "2023-05-15"
model: str = "text-embedding-ada-002"
# 选项 2:使用部署了嵌入模型的 Azure OpenAI 帐户
azure_endpoint: str = "YOUR AZURE OPENAI ENDPOINT 占位符"
azure_openai_api_key: str = "YOUR AZURE OPENAI KEY 占位符"
azure_openai_api_version: str = "2023-05-15"
azure_deployment: str = "text-embedding-ada-002"
配置向量存储设置
您需要一个 Azure 订阅 和 Azure AI 搜索服务 才能使用此向量存储集成。对于小型和有限的工作负载,提供免费版本。 为您的 Azure AI 搜索 URL 和管理员 API 密钥设置变量。您可以从 Azure 门户 获取这些变量。vector_store_address: str = "YOUR_AZURE_SEARCH_ENDPOINT"
vector_store_password: str = "YOUR_AZURE_SEARCH_ADMIN_KEY"
创建嵌入和向量存储实例
创建OpenAIEmbeddings 和 AzureSearch 类的实例。完成此步骤后,您应该在 Azure AI 搜索资源上有一个空的搜索索引。集成模块提供了一个默认架构。
# 选项 1:使用 OpenAI 帐户的 OpenAIEmbeddings
embeddings: OpenAIEmbeddings = OpenAIEmbeddings(
openai_api_key=openai_api_key, openai_api_version=openai_api_version, model=model
)
# 选项 2:使用 Azure 帐户的 AzureOpenAIEmbeddings
embeddings: AzureOpenAIEmbeddings = AzureOpenAIEmbeddings(
azure_deployment=azure_deployment,
openai_api_version=azure_openai_api_version,
azure_endpoint=azure_endpoint,
api_key=azure_openai_api_key,
)
创建向量存储实例
使用上面的嵌入创建 AzureSearch 类的实例index_name: str = "langchain-vector-demo"
vector_store: AzureSearch = AzureSearch(
azure_search_endpoint=vector_store_address,
azure_search_key=vector_store_password,
index_name=index_name,
embedding_function=embeddings.embed_query,
)
# 为 Azure 客户端指定其他属性,例如以下 https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/core/azure-core/README.md#configurations
vector_store: AzureSearch = AzureSearch(
azure_search_endpoint=vector_store_address,
azure_search_key=vector_store_password,
index_name=index_name,
embedding_function=embeddings.embed_query,
# 配置 Azure 客户端的最大重试次数
additional_search_client_options={"retry_total": 4},
)
将文本和嵌入插入向量存储
此步骤加载、分块并矢量化示例文档,然后将内容索引到 Azure AI 搜索的搜索索引中。from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
loader = TextLoader("../../how_to/state_of_the_union.txt", encoding="utf-8")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
vector_store.add_documents(documents=docs)
['M2U1OGM4YzAtYjMxYS00Nzk5LTlhNDgtZTc3MGVkNTg1Mjc0',
'N2I2MGNiZDEtNDdmZS00YWNiLWJhYTYtYWEzMmFiYzU1ZjZm',
'YWFmNDViNTQtZTc4MS00MTdjLTkzZjQtYTJkNmY1MDU4Yzll',
'MjgwY2ExZDctYTUxYi00NjE4LTkxMjctZDA1NDQ1MzU4NmY1',
'NGE4NzhkNTAtZWYxOC00ZmI5LTg0MTItZDQ1NzMxMWVmMTIz',
'MTYwMWU3YjAtZDIzOC00NTYwLTgwMmEtNDI1NzA2MWVhMDYz',
'NGM5N2NlZjgtMTc5Ny00OGEzLWI5YTgtNDFiZWE2MjBlMzA0',
'OWQ4M2MyMTYtMmRkNi00ZDUxLWI0MDktOGE2NjMxNDFhYzFm',
'YWZmZGJkOTAtOGM3My00MmNiLTg5OWUtZGMwMDQwYTk1N2Vj',
'YTc3MTI2OTktYmVkMi00ZGU4LTgyNmUtNTY1YzZjMDg2YWI3',
'MTQwMmVlYjEtNDI0MS00N2E0LWEyN2ItZjhhYWU0YjllMjRk',
'NjJjYWY4ZjctMzgyNi00Y2I5LTkwY2UtZjRkMjJhNDQxYTFk',
'M2ZiM2NiYTMtM2ZiMS00YWJkLWE3ZmQtNDZiODcyOTMyYWYx',
'MzNmZTNkMWYtMjNmYS00Y2NmLTg3ZjQtYTZjOWM1YmJhZTRk',
'ZDY3MDc1NzYtY2YzZS00ZjExLWEyMjAtODhiYTRmNDUzMTBi',
'ZGIyYzA4NzUtZGM2Ni00MDUwLWEzZjYtNTg3MDYyOWQ5MWQy',
'NTA0MjBhMzYtOTYzMi00MDQ2LWExYWQtMzNiN2I4ODM4ZGZl',
'OTdjYzU2NGUtNWZjNC00N2ZmLWExMjQtNjhkYmZkODg4MTY3',
'OThhMWZmMjgtM2EzYS00OWZkLTk1NGEtZTdkNmRjNWYxYmVh',
'ZGVjMTQ0NzctNDVmZC00ZWY4LTg4N2EtMDQ1NWYxNWM5NDVh',
'MjRlYzE4YzItZTMxNy00OGY3LThmM2YtMjM0YmRhYTVmOGY3',
'MWU0NDA3ZDQtZDE4MS00OWMyLTlmMzktZjdkYzZhZmUwYWM3',
'ZGM2ZDhhY2MtM2NkNi00MzZhLWJmNTEtMmYzNjEwMzE3NmZl',
'YjBmMjkyZTItYTNlZC00MmY2LThiMzYtMmUxY2MyNDlhNGUw',
'OThmYTQ0YzEtNjk0MC00NWIyLWE1ZDQtNTI2MTZjN2NlODcw',
'NDdlOGU1ZGQtZTVkMi00M2MyLWExN2YtOTc2ODk3OWJmNmQw',
'MDVmZGNkYTUtNWI2OS00YjllLTk0YTItZDRmNWQxMWU3OTVj',
'YWFlNTVmNjMtMDZlNy00NmE5LWI0ODUtZTI3ZTFmZWRmNzU0',
'MmIzOTkxODQtODYxMi00YWM2LWFjY2YtNjRmMmEyM2JlNzMw',
'ZmI1NDhhNWItZWY0ZS00NTNhLWEyNDEtMTE2OWYyMjc4YTU2',
'YTllYTc5OTgtMzJiNC00ZjZjLWJiMzUtNWVhYzFjYzgxMjU2',
'ODZlZWUyOTctOGY4OS00ZjA3LWIyYTUtNDVlNDUyN2E4ZDFk',
'Y2M0MWRlM2YtZDU4Ny00MjZkLWE5NzgtZmRkMTNhZDg2YjEy',
'MDNjZWQ2ODEtMWZiMy00OTZjLTk3MzAtZjE4YjIzNWVhNTE1',
'OTE1NDY0NzMtODNkZS00MTk4LTk4NWQtZGVmYjQ2YjFlY2Q0',
'ZTgwYWQwMjEtN2ZlOS00NDk2LWIxNzUtNjk2ODE3N2U0Yzlj',
'ZDkxOTgzMGUtZGExMC00Yzg0LWJjMGItOWQ2ZmUwNWUwOGJj',
'ZGViMGI2NDEtZDdlNC00YjhiLTk0MDUtYjEyOTVlMGU1Y2I2',
'ODliZTYzZTctZjdlZS00YjBjLWFiZmYtMDJmNjQ0YjU3ZDcy',
'MDFjZGI1NzUtOTc0Ni00NWNmLThhYzYtYzRlZThkZjMwM2Vl',
'ZjY2ZmRiN2EtZWVhNS00ODViLTk4YjYtYjQ2Zjc4MDdkYjhk',
'ZTQ3NDMwODEtMTQwMy00NDFkLWJhZDQtM2UxN2RkOTU1MTdl']
执行向量相似性搜索
使用 similarity_search() 方法执行纯向量相似性搜索:# 执行相似性搜索
docs = vector_store.similarity_search(
query="总统对凯坦吉·布朗·杰克逊说了什么",
k=3,
search_type="similarity",
)
print(docs[0].page_content)
今晚。我呼吁参议院:通过《自由投票法案》。通过《约翰·刘易斯投票权法案》。并且,在你们这样做的时候,通过《披露法案》,以便美国人能够知道谁在资助我们的选举。
今晚,我想向一位毕生致力于为这个国家服务的人致敬:斯蒂芬·布雷耶大法官——一位陆军退伍军人、宪法学者,以及即将退休的美国最高法院大法官。布雷耶大法官,感谢您的服务。
总统最严肃的宪法职责之一是提名某人在美国最高法院任职。
而我在四天前就这样做了,当时我提名了巡回上诉法院法官凯坦吉·布朗·杰克逊。她是我们国家顶尖的法律头脑之一,将继续布雷耶大法官卓越的遗产。
执行带有相关性分数的向量相似性搜索
使用 similarity_search_with_relevance_scores() 方法执行纯向量相似性搜索。不满足阈值要求的查询将被排除。docs_and_scores = vector_store.similarity_search_with_relevance_scores(
query="总统对凯坦吉·布朗·杰克逊说了什么",
k=4,
score_threshold=0.80,
)
from pprint import pprint
pprint(docs_and_scores)
[(Document(page_content='今晚。我呼吁参议院:通过《自由投票法案》。通过《约翰·刘易斯投票权法案》。并且,在你们这样做的时候,通过《披露法案》,以便美国人能够知道谁在资助我们的选举。 \n\n今晚,我想向一位毕生致力于为这个国家服务的人致敬:斯蒂芬·布雷耶大法官——一位陆军退伍军人、宪法学者,以及即将退休的美国最高法院大法官。布雷耶大法官,感谢您的服务。 \n\n总统最严肃的宪法职责之一是提名某人在美国最高法院任职。 \n\n而我在四天前就这样做了,当时我提名了巡回上诉法院法官凯坦吉·布朗·杰克逊。她是我们国家顶尖的法律头脑之一,将继续布雷耶大法官卓越的遗产。', metadata={'source': '../../how_to/state_of_the_union.txt'}),
0.84402436),
(Document(page_content='一位前私人执业顶级诉讼律师。一位前联邦公设辩护人。并且来自一个公立学校教育工作者和警察的家庭。一位共识建立者。自从她被提名以来,她获得了广泛的支持——从警察兄弟会到民主党和共和党任命的前法官。 \n\n如果我们想要推进自由和正义,我们需要确保边境安全并修复移民系统。 \n\n我们可以两者兼顾。在我们的边境,我们安装了新技术,如尖端扫描仪,以更好地检测毒品走私。 \n\n我们与墨西哥和危地马拉建立了联合巡逻队,以抓捕更多的人口贩子。 \n\n我们正在设立专门的移民法官,以便逃离迫害和暴力的家庭能够更快地得到案件审理。 \n\n我们正在确保承诺并支持南美洲和中美洲的合作伙伴,以收容更多难民并确保他们自己的边境安全。', metadata={'source': '../../how_to/state_of_the_union.txt'}),
0.82128483),
(Document(page_content='对于我们的 LGBTQ+ 美国人,让我们最终将两党支持的《平等法案》送到我的办公桌。针对跨性别美国人及其家人的州法律攻击是错误的。 \n\n正如我去年所说,特别是对我们年轻的跨性别美国人,作为你们的总统,我将永远支持你们,这样你们就可以做自己并发挥你们上帝赋予的潜力。 \n\n虽然我们似乎从未达成一致,但事实并非如此。去年我签署了 80 项两党法案成为法律。从防止政府关门到保护亚裔美国人免受仍然过于常见的仇恨犯罪,再到改革军事司法。 \n\n很快,我们将加强我三十年前首次撰写的《针对妇女暴力法》。向国家表明我们可以团结一致并完成大事对我们很重要。 \n\n所以今晚我提出一个国家团结议程。我们可以一起做的四件大事。 \n\n首先,击败阿片类药物流行病。', metadata={'source': '../../how_to/state_of_the_union.txt'}),
0.8151042),
(Document(page_content='今晚,我宣布对这些向美国企业和消费者多收费的公司进行打击。 \n\n随着华尔街公司接管更多的养老院,这些养老院的质量下降,成本上升。 \n\n这在我的任期内将结束。 \n\n医疗保险将为养老院设定更高的标准,并确保您所爱的人得到他们应得和期望的护理。 \n\n我们还将通过给予工人公平的机会、提供更多培训和学徒机会、根据他们的技能而非学位来雇用他们,从而降低成本并保持经济强劲。 \n\n让我们通过《薪酬公平法案》和带薪休假。 \n\n将最低工资提高到每小时 15 美元,并延长儿童税收抵免,这样就没有人必须在贫困中抚养家庭。 \n\n让我们增加佩尔助学金,增加我们对历史性黑人大学和学院的支持,并投资于吉尔——我们的第一夫人全职教学——所说的美国最保守的秘密:社区学院。', metadata={'source': '../../how_to/state_of_the_union.txt'}),
0.8148832)]
执行混合搜索
使用 search_type 或 hybrid_search() 方法执行混合搜索。向量和非向量文本字段并行查询,结果被合并,并返回统一结果集中的最佳匹配项。# 使用 search_type 参数执行混合搜索
docs = vector_store.similarity_search(
query="总统对凯坦吉·布朗·杰克逊说了什么",
k=3,
search_type="hybrid",
)
print(docs[0].page_content)
今晚。我呼吁参议院:通过《自由投票法案》。通过《约翰·刘易斯投票权法案》。并且,在你们这样做的时候,通过《披露法案》,以便美国人能够知道谁在资助我们的选举。
今晚,我想向一位毕生致力于为这个国家服务的人致敬:斯蒂芬·布雷耶大法官——一位陆军退伍军人、宪法学者,以及即将退休的美国最高法院大法官。布雷耶大法官,感谢您的服务。
总统最严肃的宪法职责之一是提名某人在美国最高法院任职。
而我在四天前就这样做了,当时我提名了巡回上诉法院法官凯坦吉·布朗·杰克逊。她是我们国家顶尖的法律头脑之一,将继续布雷耶大法官卓越的遗产。
# 使用 hybrid_search 方法执行混合搜索
docs = vector_store.hybrid_search(
query="总统对凯坦吉·布朗·杰克逊说了什么", k=3
)
print(docs[0].page_content)
今晚。我呼吁参议院:通过《自由投票法案》。通过《约翰·刘易斯投票权法案》。并且,在你们这样做的时候,通过《披露法案》,以便美国人能够知道谁在资助我们的选举。
今晚,我想向一位毕生致力于为这个国家服务的人致敬:斯蒂芬·布雷耶大法官——一位陆军退伍军人、宪法学者,以及即将退休的美国最高法院大法官。布雷耶大法官,感谢您的服务。
总统最严肃的宪法职责之一是提名某人在美国最高法院任职。
而我在四天前就这样做了,当时我提名了巡回上诉法院法官凯坦吉·布朗·杰克逊。她是我们国家顶尖的法律头脑之一,将继续布雷耶大法官卓越的遗产。
自定义架构和查询
本节向您展示如何用自定义架构替换默认架构。使用自定义可筛选字段创建新索引
此架构显示字段定义。它是默认架构,加上几个标记为可筛选的新字段。因为它使用默认的向量配置,所以您不会在这里看到向量配置或向量配置文件覆盖。默认向量配置文件的名称是 “myHnswProfile”,它使用分层可导航小世界 (HNSW) 的向量配置来索引和查询 content_vector 字段。 此步骤中此架构没有数据。当您执行单元格时,您应该在 Azure AI 搜索上获得一个空索引。from azure.search.documents.indexes.models import (
ScoringProfile,
SearchableField,
SearchField,
SearchFieldDataType,
SimpleField,
TextWeights,
)
# 如果 Azure OpenAI 是您的提供商,请将 OpenAIEmbeddings 替换为 AzureOpenAIEmbeddings。
embeddings: OpenAIEmbeddings = OpenAIEmbeddings(
openai_api_key=openai_api_key, openai_api_version=openai_api_version, model=model
)
embedding_function = embeddings.embed_query
fields = [
SimpleField(
name="id",
type=SearchFieldDataType.String,
key=True,
filterable=True,
),
SearchableField(
name="content",
type=SearchFieldDataType.String,
searchable=True,
),
SearchField(
name="content_vector",
type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
searchable=True,
vector_search_dimensions=len(embedding_function("Text")),
vector_search_profile_name="myHnswProfile",
),
SearchableField(
name="metadata",
type=SearchFieldDataType.String,
searchable=True,
),
# 用于存储标题的附加字段
SearchableField(
name="title",
type=SearchFieldDataType.String,
searchable=True,
),
# 用于按文档来源筛选的附加字段
SimpleField(
name="source",
type=SearchFieldDataType.String,
filterable=True,
),
]
index_name: str = "langchain-vector-demo-custom"
vector_store: AzureSearch = AzureSearch(
azure_search_endpoint=vector_store_address,
azure_search_key=vector_store_password,
index_name=index_name,
embedding_function=embedding_function,
fields=fields,
)
添加数据并执行包含筛选器的查询
此示例根据自定义架构向向量存储添加数据。它将文本加载到 title 和 source 字段中。source 字段是可筛选的。本节中的示例查询根据 source 字段中的内容筛选结果。# 元数据字典中与索引中对应字段匹配的数据将被添加到索引中。
# 在此示例中,元数据字典包含一个 title、一个 source 和一个 random 字段。
# title 和 source 作为单独的字段添加到索引中,但 random 值被忽略,因为它未在架构中定义。
# random 字段仅存储在 metadata 字段中。
vector_store.add_texts(
["Test 1", "Test 2", "Test 3"],
[
{"title": "Title 1", "source": "A", "random": "10290"},
{"title": "Title 2", "source": "A", "random": "48392"},
{"title": "Title 3", "source": "B", "random": "32893"},
],
)
['ZjhmMTg0NTEtMjgwNC00N2M0LWFiZGEtMDllMGU1Mzk1NWRm',
'MzQwYWUwZDEtNDJkZC00MzgzLWIwMzItYzMwOGZkYTRiZGRi',
'ZjFmOWVlYTQtODRiMC00YTY3LTk2YjUtMzY1NDBjNjY5ZmQ2']
res = vector_store.similarity_search(query="Test 3 source1", k=3, search_type="hybrid")
res
[Document(page_content='Test 3', metadata={'title': 'Title 3', 'source': 'B', 'random': '32893'}),
Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'A', 'random': '10290'}),
Document(page_content='Test 2', metadata={'title': 'Title 2', 'source': 'A', 'random': '48392'})]
res = vector_store.similarity_search(
query="Test 3 source1", k=3, search_type="hybrid", filters="source eq 'A'"
)
res
[Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'A', 'random': '10290'}),
Document(page_content='Test 2', metadata={'title': 'Title 2', 'source': 'A', 'random': '48392'})]
使用评分配置文件创建新索引
这是另一个包含评分配置文件定义的自定义架构。评分配置文件用于非向量内容的相关性调整,这在混合搜索场景中很有帮助。from azure.search.documents.indexes.models import (
FreshnessScoringFunction,
FreshnessScoringParameters,
ScoringProfile,
SearchableField,
SearchField,
SearchFieldDataType,
SimpleField,
TextWeights,
)
# 如果 Azure OpenAI 是您的提供商,请将 OpenAIEmbeddings 替换为 AzureOpenAIEmbeddings。
embeddings: OpenAIEmbeddings = OpenAIEmbeddings(
openai_api_key=openai_api_key, openai_api_version=openai_api_version, model=model
)
embedding_function = embeddings.embed_query
fields = [
SimpleField(
name="id",
type=SearchFieldDataType.String,
key=True,
filterable=True,
),
SearchableField(
name="content",
type=SearchFieldDataType.String,
searchable=True,
),
SearchField(
name="content_vector",
type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
searchable=True,
vector_search_dimensions=len(embedding_function("Text")),
vector_search_profile_name="myHnswProfile",
),
SearchableField(
name="metadata",
type=SearchFieldDataType.String,
searchable=True,
),
# 用于存储标题的附加字段
SearchableField(
name="title",
type=SearchFieldDataType.String,
searchable=True,
),
# 用于按文档来源筛选的附加字段
SimpleField(
name="source",
type=SearchFieldDataType.String,
filterable=True,
),
# 用于最后文档更新的附加数据字段
SimpleField(
name="last_update",
type=SearchFieldDataType.DateTimeOffset,
searchable=True,
filterable=True,
),
]
# 添加带有新鲜度函数的自定义评分配置文件
sc_name = "scoring_profile"
sc = ScoringProfile(
name=sc_name,
text_weights=TextWeights(weights={"title": 5}),
function_aggregation="sum",
functions=[
FreshnessScoringFunction(
field_name="last_update",
boost=100,
parameters=FreshnessScoringParameters(boosting_duration="P2D"),
interpolation="linear",
)
],
)
index_name = "langchain-vector-demo-custom-scoring-profile"
vector_store: AzureSearch = AzureSearch(
azure_search_endpoint=vector_store_address,
azure_search_key=vector_store_password,
index_name=index_name,
embedding_function=embeddings.embed_query,
fields=fields,
scoring_profiles=[sc],
default_scoring_profile=sc_name,
)
# 添加具有不同 last_update 的相同数据以显示评分配置文件效果
from datetime import datetime, timedelta
today = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S-00:00")
yesterday = (datetime.utcnow() - timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%S-00:00")
one_month_ago = (datetime.utcnow() - timedelta(days=30)).strftime(
"%Y-%m-%dT%H:%M:%S-00:00"
)
vector_store.add_texts(
["Test 1", "Test 1", "Test 1"],
[
{
"title": "Title 1",
"source": "source1",
"random": "10290",
"last_update": today,
},
{
"title": "Title 1",
"source": "source1",
"random": "48392",
"last_update": yesterday,
},
{
"title": "Title 1",
"source": "source1",
"random": "32893",
"last_update": one_month_ago,
},
],
)
['NjUwNGQ5ZDUtMGVmMy00OGM4LWIxMGYtY2Y2MDFmMTQ0MjE5',
'NWFjN2YwY2UtOWQ4Yi00OTNhLTg2MGEtOWE0NGViZTVjOGRh',
'N2Y2NWUyZjctMDBjZC00OGY4LWJlZDEtNTcxYjQ1MmI1NjYx']
res = vector_store.similarity_search(query="Test 1", k=3, search_type="similarity")
res
[Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'source1', 'random': '32893', 'last_update': '2024-01-24T22:18:51-00:00'}),
Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'source1', 'random': '48392', 'last_update': '2024-02-22T22:18:51-00:00'}),
Document(page_content='Test 1', metadata={'title': 'Title 1', 'source': 'source1', 'random': '10290', 'last_update': '2024-02-23T22:18:51-00:00'})]
将这些文档连接到 Claude、VSCode 等,通过 MCP 获取实时答案。

