索引包含可搜索的文本和矢量内容以及配置。 在使用聊天模型进行响应的 RAG 模式中,需要一个包含可在查询时传递给 LLM 的内容块的索引。
在本教程中,你将了解:
- 了解为 RAG 生成的索引架构的特征
- 创建可容纳矢量和混合查询的索引
- 添加矢量配置文件和配置
- 添加结构化数据
- 添加筛选
包含 Python 扩展和 Jupyter 包的 Visual Studio Code。 有关详细信息,请参阅 Visual Studio Code 中的 Python。
本练习的输出是 JSON 格式的索引定义。 此时,它尚未上传到 Azure AI 搜索,因此本练习对云服务或权限没有任何要求。
在对话搜索中,LLM(而不是搜索引擎)编写用户看到的响应,因此不需要考虑在搜索结果中显示哪些字段,以及各个搜索文档的表示是否与用户一致。 根据问题的不同,LLM 可能会从索引中返回逐字内容,或者更有可能重新打包内容以获得更好的答案。
当 LLM 生成响应时,它们会对消息输入的内容块进行操作,虽然它们需要知道块的源以便于引用,但最重要的是消息输入的质量及其与用户问题的相关性。 无论这些内容块来自一个文档还是一千个文档,LLM 都会引入信息或基础数据,并使用系统提示中提供的说明来制定响应。
块是架构的焦点,每个块是 RAG 模式中搜索文档的定义元素。 可以将索引视为大量块的集合,这与可能具有更多结构的传统搜索文档不同,例如包含名称、描述、类别和地址的统一内容的字段。
在本教程中,示例数据包括 PDF 和来自 NASA Earth Book 的内容。 此内容描述性强,信息量丰富,多次提及了全球的地理、国家和地区。 所有文本内容都以块的形式捕获,但重复出现的地名实例为向索引添加结构创造了机会。 使用技能,可以识别文本中的实体,并在索引中捕获实体,以便在查询和筛选器中使用。 在本教程中,我们包括了一个实体识别技能,用于识别和提取位置实体,将其加载到可搜索和可筛选的 locations
字段中。 向索引中添加结构化内容可提供更多用于筛选、相关性改进和更具针对性的答案的选项。
分块内容通常源自较大的文档。 尽管架构是围绕块组织的,但你也可以在父级别捕获属性和内容。 这些属性的示例包括父文件路径、标题、作者、出版日期或摘要。
架构设计中的一个转折点是,是要为父内容和子内容/分块内容创建两个索引,还是创建为每个块重复父元素的单个索引。
在本教程中,由于所有文本块都源自单个父级 (NASA Earth Book),因此不需要专用于提升父字段级别的单独索引。 但是,如果从多个父 PDF 编制索引,则可能需要父子索引对来捕获特定于级别的字段,然后向父索引发送查找查询以检索与每个块相关的字段。
在 Azure AI 搜索中,最适合 RAG 工作负载的索引具有以下特征:
返回与查询相关且可供 LLM 读取的块。 LLM 可以处理一定程度的块状脏数据,例如标记、冗余和不完整的字符串。 虽然块需要可读且与问题相关,但它们不需要是完美的。
维护文档块与父文档的属性(例如文件名、文件类型、标题、作者等)之间的父子关系。 为了回答查询,可以从索引中的任何位置拉取块。 与提供块的父文档的关联对于上下文、引述和后续查询很有用。
满足你要创建的查询。 应有用于矢量和混合内容的字段,并且这些字段应该归因于支持特定的查询行为,例如可搜索或可筛选。 一次只能查询一个索引(无联接),因此字段集合应该定义所有可搜索的内容。
LLM 的最小索引被设计用于存储内容块。 如果你要执行相似性搜索以获取高度相关的结果,它通常包括矢量字段。 它还包括非矢量字段,以便向 LLM 提供人类可读的输入进行对话式搜索。 搜索结果中的非矢量分块内容成为发送到 LLM 的基础数据。
打开 Visual Studio Code 并创建一个新文件。 对于本练习而言,它不必是 Python 文件类型。
下面是支持矢量和混合搜索的 RAG 解决方案的最小索引定义。 请查看它以了解所需元素的简介:索引名称、字段和矢量字段的配置部分。
{ "name": "example-minimal-index", "fields": [ { "name": "id", "type": "Edm.String", "key": true }, { "name": "chunked_content", "type": "Edm.String", "searchable": true, "retrievable": true }, { "name": "chunked_content_vectorized", "type": "Edm.Single", "dimensions": 1536, "vectorSearchProfile": "my-vector-profile", "searchable": true, "retrievable": false, "stored": false }, { "name": "metadata", "type": "Edm.String", "retrievable": true, "searchable": true, "filterable": true } ], "vectorSearch": { "algorithms": [ { "name": "my-algo-config", "kind": "hnsw", "hnswParameters": { } } ], "profiles": [ { "name": "my-vector-profile", "algorithm": "my-algo-config" } ] } }
字段必须包含关键字段(在本例中为
"id"
),并且应包含用于相似性搜索的矢量块和用于 LLM 输入的非矢量块。矢量字段与在查询时确定搜索路径的算法相关联。 索引有一个 vectorSearch 节,用于指定多个算法配置。 矢量字段还具有特定类型和附加属性,用于嵌入模型维度。
Edm.Single
是一种适用于常用 LLM 的数据类型。 有关矢量字段的详细信息,请参阅创建矢量索引。元数据字段可能是父文件路径、创建日期或内容类型,对于筛选器很有用。
下面是教程源代码和 Earth Book 内容的索引架构。
与基本架构一样,它是围绕块进行组织的。
chunk_id
唯一标识每个块。text_vector
字段是块的嵌入。 非矢量chunk
字段是可读的字符串。title
映射到 Blob 的唯一元数据存储路径。parent_id
是仅有的父级字段,它是父文件 URI 的 base64 编码版本。该架构还包括一个
locations
字段,用于存储由索引管道创建的生成内容。from azure.identity import DefaultAzureCredential from azure.identity import get_bearer_token_provider from azure.search.documents.indexes import SearchIndexClient from azure.search.documents.indexes.models import ( SearchField, SearchFieldDataType, VectorSearch, HnswAlgorithmConfiguration, VectorSearchProfile, AzureOpenAIVectorizer, AzureOpenAIVectorizerParameters, SearchIndex ) credential = DefaultAzureCredential() # Create a search index index_name = "py-rag-tutorial-idx" index_client = SearchIndexClient(endpoint=AZURE_SEARCH_SERVICE, credential=credential) fields = [ SearchField(name="parent_id", type=SearchFieldDataType.String), SearchField(name="title", type=SearchFieldDataType.String), SearchField(name="locations", type=SearchFieldDataType.Collection(SearchFieldDataType.String), filterable=True), SearchField(name="chunk_id", type=SearchFieldDataType.String, key=True, sortable=True, filterable=True, facetable=True, analyzer_name="keyword"), SearchField(name="chunk", type=SearchFieldDataType.String, sortable=False, filterable=False, facetable=False), SearchField(name="text_vector", type=SearchFieldDataType.Collection(SearchFieldDataType.Single), vector_search_dimensions=1024, vector_search_profile_name="myHnswProfile") ] # Configure the vector search configuration vector_search = VectorSearch( algorithms=[ HnswAlgorithmConfiguration(name="myHnsw"), ], profiles=[ VectorSearchProfile( name="myHnswProfile", algorithm_configuration_name="myHnsw", vectorizer_name="myOpenAI", ) ], vectorizers=[ AzureOpenAIVectorizer( vectorizer_name="myOpenAI", kind="azureOpenAI", parameters=AzureOpenAIVectorizerParameters( resource_url=AZURE_OPENAI_ACCOUNT, deployment_name="text-embedding-3-large", model_name="text-embedding-3-large" ), ), ], ) # Create the search index index = SearchIndex(name=index_name, fields=fields, vector_search=vector_search) result = index_client.create_or_update_index(index) print(f"{result.name} created")
对于更近似地模拟结构化内容的索引架构,可为父和子(分块)字段创建单独的索引。 需要使用索引投影来同时协调两个索引的索引编制。 查询针对子索引执行。 查询逻辑包括一个查找查询,它使用 parent_id 从父索引检索内容。
子索引中的字段:
- ID
- chunk
- chunkVectcor
- parent_id
父索引中的字段(你希望是“其中之一”的所有内容):
- parent_id
- 父级字段(名称、标题、类别)