你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
为父子索引编制定义索引投影
对于包含分块文档的索引,索引投影指定如何将父子内容映射到搜索索引中的字段,以便进行一对多索引编制。 通过索引投影,可以将内容发送到:
单个索引,其中父字段针对每个区块重复,但索引的粒度为区块级别。 RAG 教程是此方法的一个示例。
两个或更多索引,其中父索引具有与父文档相关的字段,子索引围绕区块进行组织。 子索引是主搜索语料库,但当希望检索特定区块的父字段或独立查询时,可以将父索引用于查找查询。
大多数实现都是围绕区块组织的单个索引,具有针对每个区块重复的父字段(例如文档文件名)。 但是,如果您有相关需求,请注意该系统设计为支持单独索引和多个子索引。 Azure AI 搜索不支持索引联接,因此必须由应用程序代码来处理要使用哪个索引。
索引投影是在技能组中定义的。 它负责协调将内容区块以及与每个区块关联的父内容发送到搜索索引的索引编制过程。 它通过提供更多用于控制父子内容索引编制方式的选项,改进了原生数据分块的工作方式。
本文解释了如何为一对多索引编制创建索引架构和索引器投影模式。
先决条件
该技能组包含索引器投影,投影对数据进行整形以便进行一对多索引编制。 如果你的场景包含集成矢量化,则技能组还可以包含其他技能,例如嵌入技能(如 AzureOpenAIEmbedding)。
对索引器处理的依赖性
一对多索引编制依赖于技能组和基于索引器的索引编制,其中包括以下四个组件:
- 数据源
- 可搜索内容的一个或多个索引
- 包含索引投影的技能组*
- 索引器
你的数据可以来自任何受支持的数据源,但前提是内容足够大,你想对其进行分块,分块的原因是你正在实施一个为聊天模型提供基础数据 RAG 模式。 或者,你正在实施矢量搜索,并且需要满足嵌入模型需要较小输入的要求。
索引器将具有索引的数据加载到预定义索引中。 如何定义架构以及是使用一个还是多个索引是一对多索引编制场景中要做出的第一个决策。 下一部分介绍了索引设计。
为一对多索引编制创建索引
无论是为重复父值的区块创建一个索引,还是为父子字段布局创建单独的索引,用于搜索的主索引都是围绕数据区块设计的。 它必须具有以下字段:
唯一标识每个文档的文档键字段。 它必须随
keyword
分析器定义为类型Edm.String
。一个将每个区块与其父级关联的字段。 该对象的类型必须是
Edm.String
。 它不能是文档键字段,并且必须将filterable
设置为 true。 它在示例中被称为 parent_id,在本文中称为投影键值。内容的其他字段,例如文本或矢量化区块字段。
在创建技能组或运行索引器之前,搜索服务上必须存在索引。
包含父字段和子字段的单索引架构
单一索引围绕区块设计,每个块都有重复的父内容,这是 RAG 和矢量搜索场景的主要模式。 通过索引投影,可以将正确的父内容与每个区块相关联。
以下架构是满足索引投影要求的一个示例。 在此示例中,父字段是 parent_id 和 title。 子字段是矢量和非矢量区块。 chunk_id 是此索引的文档 ID。 parent_id 和 title 针对索引中的每个块重复。
可以使用 Azure 门户、REST API 或 Azure SDK 来创建索引。
{
"name": "my_consolidated_index",
"fields": [
{"name": "chunk_id", "type": "Edm.String", "key": true, "filterable": true, "analyzer": "keyword"},
{"name": "parent_id", "type": "Edm.String", "filterable": true},
{"name": "title", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "retrievable": true},
{"name": "chunk", "type": "Edm.String","searchable": true,"retrievable": true},
{"name": "chunk_vector", "type": "Collection(Edm.Single)", "searchable": true, "retrievable": false, "stored": false, "dimensions": 1536, "vectorSearchProfile": "hnsw"}
],
"vectorSearch": {
"algorithms": [{"name": "hsnw", "kind": "hnsw", "hnswParameters": {}}],
"profiles": [{"name": "hsnw", "algorithm": "hnsw"}]
}
}
向技能组中添加索引投影
索引投影是在技能组定义中定义的,主要定义为 selectors
的数组,其中每个选择器对应于搜索服务上的一个不同的目标索引。 本部分首先提供了上下文的语法和示例,然后提供了参数参考信息。
请选择与各种 API 语法对应的选项卡。 除了编辑技能组 JSON 定义之外,目前门户不支持对投影进行任何其他设置。 请参阅 JSON 的 REST 示例。
索引投影已正式发布。 建议使用最新的稳定 API:
下面是索引投影定义的示例有效负载,你可以使用它将文本拆分技能的各个页面输出投影为搜索索引中其自己的文档。
"indexProjections": {
"selectors": [
{
"targetIndexName": "my_consolidated_index",
"parentKeyFieldName": "parent_id",
"sourceContext": "/document/pages/*",
"mappings": [
{
"name": "chunk",
"source": "/document/pages/*",
"sourceContext": null,
"inputs": []
},
{
"name": "chunk_vector",
"source": "/document/pages/*/chunk_vector",
"sourceContext": null,
"inputs": []
},
{
"name": "title",
"source": "/document/title",
"sourceContext": null,
"inputs": []
}
]
}
],
"parameters": {
"projectionMode": "skipIndexingParentDocuments"
}
}
参数参考
索引投影参数 | 定义 |
---|---|
selectors |
用于主要搜索语料库(通常是围绕区块设计的语料库)的参数。 |
projectionMode |
一个可选参数,用于向索引器提供指令。 此参数的唯一有效值是 skipIndexingParentDocuments ,当块索引是主搜索语料库并且你需要指定父字段是否作为分块索引中的额外搜索文档编制索引时,请使用此参数。 如果不设置 skipIndexingParentDocuments ,则会在索引中得到多余的搜索文档,这些文档的块为空,但仅填充了父字段。 例如,如果五个文档为索引贡献了 100 个区块,则索引中的文档数为 105。 创建的五个文档或父字段的区块(子)字段为空,这与索引中的大部分文档显著不同。 建议将 projectionMode 设置为 skipIndexingParentDocument 。 |
选择器的定义中必须包含以下参数。
选择器参数 | 定义 |
---|---|
targetIndexName |
索引数据投影到的索引的名称。 它是具有重复父字段的单个分块索引,如果为父子内容使用单独的索引,则它是子索引。 |
parentKeyFieldName |
提供父文档的键的字段名称。 |
sourceContext |
定义将数据映射到各个搜索文档的粒度的扩充注释。 有关详细信息,请参阅技能上下文和输入注释语言。 |
mappings |
扩充数据到搜索索引中的字段的映射数组。 每个映射都包含:name :应在其中对数据编制索引的搜索索引中的字段的名称。 source :应从中拉取数据的扩充注释路径。 每个 mapping 还可以递归方式使用可选的 sourceContext 和 inputs 字段定义数据,类似于知识存储或整形程序技能。 根据你的应用程序,这些参数允许你将数据整形为搜索索引中的 Edm.ComplexType 类型的字段。 某些 LLM 不接受搜索结果中的复杂类型,因此你使用的 LLM 决定了复杂类型映射是否有用。 |
mappings
参数非常重要。 你必须显式映射子索引中的每个字段,但 ID 字段除外,例如文档键和父 ID。
此要求与 Azure AI 搜索中的其他字段映射约定形成鲜明对比。 对于某些数据源类型,索引器可以基于相似名称或已知特征来隐式映射字段(例如,blob 索引器使用唯一的元数据存储路径作为默认文档键)。 但是,对于索引器投影,你必须在关系的“多”端显式指定每个字段映射。
请勿为父键字段创建字段映射。 这样做会中断更改跟踪和同步的数据刷新。
处理父文档
现在你已了解了一对多索引的几种模式,接下来让我们比较一下每个选项的主要差异。 索引投影可有效地为在整个技能组中运行的每个“父”文档生成“子”文档。 有多个选项可用于处理“父”文档。
若要将父文档和子文档发送到单独的索引,请将索引器定义的
targetIndexName
设置为父索引,并将索引投影选择器中的targetIndexName
设置为子索引。若要将父文档和子文档保留在同一索引中,请将索引器
targetIndexName
和索引投影targetIndexName
设置为同一索引。若要避免创建父搜索文档并确保索引仅包含统一粒度的子文档,请将索引器定义和选择器
targetIndexName
设置为同一索引,但在selectors
后添加额外的parameters
对象,并将projectionMode
键设置为skipIndexingParentDocuments
,如下所示:"indexProjections": { "selectors": [ ... ], "parameters": { "projectionMode": "skipIndexingParentDocuments" } }
检查字段映射
索引器与三种不同类型的字段映射相关联。 在运行索引器之前,请检查你的字段映射并了解何时使用每种类型。
字段映射是在索引器中定义的,用于将源字段映射到索引字段。 字段映射用作数据路径,它们从源中提取数据并将其传入以进行索引编制,无需使用中间技能处理步骤。 通常,索引器可以自动映射具有相同名称和类型的字段。 仅当存在差异时,才需要使用显式字段映射。 在目前为止讨论的一对多索引编制和模式中,可能不需要字段映射。
输出字段映射是在索引器中定义的,用于将技能组生成的扩充内容映射到主索引中。 在本文介绍的一对多模式中,这是双索引解决方案中的父索引。 在本文中所示的示例中,父索引是稀疏的,只有一个 title 字段,并且该字段中未填充来自技能组处理的内容,因此我们没有使用输出字段映射。
索引器投影字段映射用于将技能组生成的内容映射到子索引中的字段。 如果子索引还包括父字段(例如在合并索引解决方案中),则应为每个包含内容的字段设置字段映射,包括父级 title 字段(假设你希望 title 显示在每个分块文档中)。 如果你使用单独的父索引和子索引,则索引器投影应仅包含子级字段的字段映射。
注意
输出字段映射和索引器投影字段映射都接受扩充的文档树节点作为源输入。 了解如何指定每个节点的路径对于设置数据路径至关重要。 若要了解有关路径语法的详细信息,请参考引用扩充节点的路径和技能组定义作为示例。
运行索引器
创建数据源、索引和技能组后,即可创建并运行索引器。 此步骤将执行管道。
你可以在处理结束后查询你的搜索索引以测试你的解决方案。
内容生命周期
根据底层数据源的不同,索引器通常可以提供持续的更改跟踪和删除检测。 本部分解释了一对多索引的内容生命周期,因为它与数据刷新有关。
对于提供更改跟踪和删除检测的数据源,索引器进程可以获取源数据中的更改。 每次运行索引器和技能组时,如果技能组或基础源数据已更改,索引投影就会更新。 索引器选取的任何更改都将通过扩充过程传播到索引中的投影,从而确保投影数据是原始数据源中内容的当前表示形式。 数据刷新活动被捕获在每个区块的投影键值中。 当基础数据发生更改时,此值将更新。
注意
虽然可以使用索引推送 API 手动编辑投影文档中的数据,但应避免这样做。 假设源数据中的文档已更新,并且数据源已启用更改跟踪或删除检测,则对索引的手动更新将在下一次管道调用时被覆盖。
更新的内容
如果你向数据源中添加新内容,则新区块或子文档将在下次运行索引器时添加到索引中。
如果你修改了数据源中的现有内容,并且你使用的数据源支持更改跟踪和删除检测,则区块将在搜索索引中以增量方式更新。 例如,如果文档中的单词或句子发生了变化,则目标索引中包含该单词或句子的块将在下次运行索引器时更新。 现有字段不支持其他类型的更新,例如更改字段类型和某些属性。 有关允许的更新的详细信息,请参阅更改索引架构。
某些数据源(例如 Azure 存储)默认支持基于时间戳的更改和删除跟踪。 其他数据源(例如 OneLake、Azure SQL 或 Azure Cosmos DB)必须进行配置才能支持更改跟踪。
删除的内容
如果源内容不再存在(例如,如果文本被缩短为具有更少的区块),则搜索索引中的相应子文档将被删除。 其余子文档还会更新其键以包括新的哈希值,即使其内容不更改也是如此。
如果父文档从数据源中完全删除,则仅当数据源定义上定义的 dataDeletionDetectionPolicy
检测到删除时,才会删除相应的子文档。 如果未配置 dataDeletionDetectionPolicy
,但需要从数据源中删除父文档,应在不再需要它时手动删除子文档。
投影的键值
为了确保已更新和删除的内容的数据完整性,数据刷新在一对多索引中依赖于“多”端的投影键值。 如果你使用集成矢量化或导入和矢量化数据向导,则投影键值是索引的分块端或“多”端的 parent_id
字段。
投影键值是索引器为每个文档生成的唯一标识符。 它确保唯一性并使得更改和删除跟踪正常工作。 该键包含以下各段:
- 用于保证唯一性的随机哈希。 如果在后续的索引器运行之间更新了父文档,则此哈希会更改。
- 父文档的键。
- 标识从中生成文档的上下文的扩充注释路径。
例如,如果将具有键值“aa1b22c33”的父文档拆分为四页,则会通过索引投影将其中每个页面投影为其自己的文档:
- aa1b22c33
- aa1b22c33_pages_0
- aa1b22c33_pages_1
- aa1b22c33_pages_2
如果源数据中的父文档被更新,可能会导致更多的分块页面,随机哈希值会发生更改,会添加更多的页面,并且每个区块的内容都会被更新以匹配源文档中的任何内容。
单独的父子索引的示例
本部分展示了单独的父索引和子索引的示例。 这是一种不常见的模式,但使用这种方法可能最能满足你的应用程序需求。 在此场景中,你将父子内容投影到两个单独的索引中。
每个架构都有其特定粒度的字段,父 ID 字段是两个索引通用的,用于查找查询中。 主搜索语料库是子索引,但随后会发出查找查询,以检索结果中每个匹配项的父字段。 Azure AI 搜索在查询时不支持联接,因此你的应用程序代码或业务流程层需要合并或整理可传递给应用或进程的结果。
父索引具有 parent_id 字段和 title。 parent_id 是文档键。 除非你希望在父文档级别对字段进行矢量化,否则不需要对搜索配置进行矢量化。
{
"name": "my-parent-index",
"fields": [
{"name": "parent_id", "type": "Edm.String", "filterable": true},
{"name": "title", "type": "Edm.String", "searchable": true, "filterable": true, "sortable": true, "retrievable": true},
]
}
子索引具有分块字段,以及 parent_id 字段。 如果你使用集成矢量化、计分配置文件、语义排序器或分析器,则你将在子索引中设置它们。
{
"name": "my-child-index",
"fields": [
{"name": "chunk_id", "type": "Edm.String", "key": true, "filterable": true, "analyzer": "keyword"},
{"name": "parent_id", "type": "Edm.String", "filterable": true},
{"name": "chunk", "type": "Edm.String","searchable": true,"retrievable": true},
{"name": "chunk_vector", "type": "Collection(Edm.Single)", "searchable": true, "retrievable": false, "stored": false, "dimensions": 1536, "vectorSearchProfile": "hnsw"}
],
"vectorSearch": {
"algorithms": [{"name": "hsnw", "kind": "hnsw", "hnswParameters": {}}],
"profiles": [{"name": "hsnw", "algorithm": "hnsw"}]
},
"scoringProfiles": [],
"semanticConfiguration": [],
"analyzers": []
}
下面是索引投影定义的一个示例,它指定索引器用来为内容编制索引的数据路径。 它在索引投影定义中指定子索引名称,并指定每个子级或区块级字段的映射。 这是指定子索引名称的唯一位置。
"indexProjections": {
"selectors": [
{
"targetIndexName": "my-child-index",
"parentKeyFieldName": "parent_id",
"sourceContext": "/document/pages/*",
"mappings": [
{
"name": "chunk",
"source": "/document/pages/*",
"sourceContext": null,
"inputs": []
},
{
"name": "chunk_vector",
"source": "/document/pages/*/chunk_vector",
"sourceContext": null,
"inputs": []
}
]
}
]
}
索引器定义指定管道的组件。 在索引器定义中,要提供的索引名称是父索引。 如果你需要父级字段的字段映射,请在 outputFieldMappings 中定义它们。 对于使用单独索引的一对多索引编制,索引器定义可能如以下示例所示。
{
"name": "my-indexer",
"dataSourceName": "my-ds",
"targetIndexName": "my-parent-index",
"skillsetName" : "my-skillset"
"parameters": { },
"fieldMappings": (optional) Maps fields in the underlying data source to fields in an index,
"outputFieldMappings" : (required) Maps skill outputs to fields in an index,
}
下一步
数据分块和一对多索引编制是 Azure AI 搜索中 RAG 模式的一部分。 继续学习以下教程和代码示例,了解有关它的详细信息。