共用方式為

在 Azure AI 搜索中为 Markdown Blob 和文件编制索引

注释

此功能目前处于公开预览状态。 此预览版未随附服务级别协议,建议不要用于生产工作负载。 某些功能可能不受支持或者受限。 有关详细信息,请参阅适用于 Azure 预览版的补充使用条款

在 Azure AI 搜索中,适用于 Azure Blob 存储、Azure 文件存储和 OneLake 的索引器均支持 Markdown 文件的 markdown 分析模式。 Markdown 文件可以通过两种方式编制索引:

  • 一对多解析模式,为每个 Markdown 文件创建多个搜索文档。
  • 一对一分析模式,每个 Markdown 文件创建一个搜索文档

先决条件

  • 支持的数据源:Azure Blob 存储、Azure Fabric 中的 Azure 文件存储。

    用于 Blob 索引器文件索引器的 Azure 存储是一种标准性能(常规用途 v2)实例,支持热访问层和冷访问层。

Markdown 解析模式参数

在创建或更新索引器时,可以在索引器定义中指定分析模式参数。

POST https://[service name].search.azure.cn/indexers?api-version=2025-08-01-preview
Content-Type: application/json
api-key: [admin key]

{
  "name": "my-markdown-indexer",
  "dataSourceName": "my-blob-datasource",
  "targetIndexName": "my-target-index",
  "parameters": {
    "configuration": {
      "parsingMode": "markdown",
      "markdownParsingSubmode": "oneToMany",
      "markdownHeaderDepth": "h6"
    }
  },
}

Blob 索引器提供了一个 submode 参数,用于确定搜索文档的结构输出。 Markdown 分析模式提供以下子模式选项:

parsingMode 子模式 搜索文档 DESCRIPTION
markdown oneToMany 每个 Blob 有多个 (默认)将 Markdown 拆分成多个搜索文档,每个文档表示 Markdown 文件中的一个内容(非标题)节。 除非你想要进行一对一分析,否则可以省略子模式。
markdown oneToOne 每个 Blob 各有一个 将 Markdown 分析为一个搜索文档,其中的各节映射到 Markdown 文件中的特定标题。

对于 oneToMany 子模式,应查阅为一个 Blob 编制索引以生成多个搜索文档,了解对于从同一 Blob 生成的多个搜索文档,Blob 索引器如何消除文档键的歧义。

后面的各个部分更详细地介绍了每个子模式。 如果你不熟悉索引器客户端和概念,请参阅创建搜索索引器。 还应熟悉基本 blob 索引器配置的详细信息,这在此不予重复。

可选的 Markdown 分析参数

参数区分大小写。

参数名称 允许的值 DESCRIPTION
markdownHeaderDepth h1h2h3h4h5h6(default) 此参数决定了分析时考虑的最深层级标题,以灵活处理文档结构(例如,当 markdownHeaderDepth 设置为 h1 时,分析程序仅识别以“#”开头的顶级标题,所有较低级别的标题都被视为纯文本)。 如果未指定,则默认为 h6

此设置可以在初始创建索引器后进行更改,但根据 Markdown 内容的不同,生成的搜索文档结构可能会发生变化。

支持的 Markdown 元素

Markdown 分析仅基于标头拆分内容。 所有其他元素(如列表、代码块、表等)都被视为纯文本,并传入内容字段。

Markdown 内容示例

以下是本页面示例中使用的 Markdown 内容:

# Section 1
Content for section 1.

## Subsection 1.1
Content for subsection 1.1.

# Section 2
Content for section 2.

使用一对多解析模式

一对多解析模式将 Markdown 文件解析为多个搜索文档,每个文档对应 Markdown 文件中的某个特定内容部分,根据该点在文档中的标头元数据来确定。 Markdown 通过对标头的解析生成搜索文档,其中包括以下内容:

  • content:一个字符串,其中包含基于文档中该点处的标题元数据在特定位置找到的原始 Markdown。

  • sections:一个对象,其中包含标题元数据的子字段,最高为所需的标题级别。 例如,当 markdownHeaderDepth 设置为 h3 时,包含字符串字段 h1h2h3。 这些字段通过镜像索引中的此结构或通过格式为 /sections/h1sections/h2 等的字段映射编制索引。有关上下文中的示例,请参阅以下示例中的索引和索引器配置。 包含的子字段有:

    • h1 - 一个包含 h1 标题值的字符串。 如果在文档中此点处未设置,则为空字符串。
    • (可选)h2 - 一个包含 h2 标题值的字符串。 如果在文档中此点处未设置,则为空字符串。
    • (可选)h3 - 一个包含 h3 标题值的字符串。 如果在文档中此点处未设置,则为空字符串。
    • (可选)h4 - 一个包含 h4 标题值的字符串。 如果在文档中此点处未设置,则为空字符串。
    • (可选)h5 - 一个包含 h5 标题值的字符串。 如果在文档中此点处未设置,则为空字符串。
    • (可选)h6 - 一个包含 h6 标题值的字符串。 如果在文档中此点处未设置,则为空字符串。
  • ordinal_position:一个整数值,指示该节在文档层次结构中的位置。 此字段用于按原始顺序对文档中的各节进行排序,从序号位置 1 开始,每个标题的序号依次递增。

用于一对多分析的索引架构

示例索引配置可能如下所示:

{
  "name": "my-markdown-index",
  "fields": [
  {
    "name": "id",
    "type": "Edm.String",
    "key": true
  },
  {
    "name": "content",
    "type": "Edm.String",
  },
  {
    "name": "ordinal_position",
    "type": "Edm.Int32"
  },
  {
    "name": "sections",
    "type": "Edm.ComplexType",
    "fields": [
    {
      "name": "h1",
      "type": "Edm.String"
    },
    {
      "name": "h2",
      "type": "Edm.String"
    }]
  }]
}

用于一对多分析的索引器定义

如果字段名称和数据类型一致,Blob 索引器可以在请求中没有显式字段映射的情况下推断映射,因此,与提供的索引配置对应的索引器配置可能如下所示:

POST https://[service name].search.azure.cn/indexers?api-version=2025-08-01-preview
Content-Type: application/json
api-key: [admin key]

{
  "name": "my-markdown-indexer",
  "dataSourceName": "my-blob-datasource",
  "targetIndexName": "my-target-index",
  "parameters": {
    "configuration": { "parsingMode": "markdown" }
  },
}

注释

不需要在此处显式设置 submode,因为 oneToMany 是默认值。

一对多解析的索引器输出

由于有三个内容节,此 Markdown 文件在编制索引后将生成三个搜索文档。 从提供的 Markdown 文档的第一个内容节生成的搜索文档将包含以下 contentsectionsh1h2 的值:

{
  {
    "content": "Content for section 1.\r\n",
    "sections": {
      "h1": "Section 1",
      "h2": ""
    },
    "ordinal_position": 1
  },
  {
    "content": "Content for subsection 1.1.\r\n",
    "sections": {
      "h1": "Section 1",
      "h2": "Subsection 1.1"
    },
    "ordinal_position": 2
  },
  {
    "content": "Content for section 2.\r\n",
    "sections": {
      "h1": "Section 2",
      "h2": ""
    },
    "ordinal_position": 3
  }
}   

在搜索索引中映射一对多字段

如果字段名称和类型不相同,则字段映射将源字段与目标字段相关联。 不过,字段映射还可用于匹配 Markdown 文档的各个部分,并将这些部分“提升”到搜索文档的顶级字段。

以下示例对此情况进行了说明。 有关一般字段映射的详细信息,请参阅“字段映射”。

假设某个搜索索引包含以下字段:raw_content 类型的 Edm.Stringh1_header 类型的 Edm.String,以及 h2_header 类型的 Edm.String。 要将 Markdown 映射到所需的形状,请使用以下字段映射:

"fieldMappings" : [
    { "sourceFieldName" : "/content", "targetFieldName" : "raw_content" },
    { "sourceFieldName" : "/sections/h1", "targetFieldName" : "h1_header" },
    { "sourceFieldName" : "/sections/h2", "targetFieldName" : "h2_header" },
  ]

索引中生成的搜索文档将如下所示:

{
  {
    "raw_content": "Content for section 1.\r\n",
    "h1_header": "Section 1",
    "h2_header": "",
  },
  {
    "raw_content": "Content for section 1.1.\r\n",
    "h1_header": "Section 1",
    "h2_header": "Subsection 1.1",
  },
  {
    "raw_content": "Content for section 2.\r\n",
    "h1_header": "Section 2",
    "h2_header": "",
  }
}

使用一对一分析模式

在一对一分析模式中,整个 Markdown 文档作为单个搜索文档编制索引,保留原始内容的层次结构和结构。 当要编制索引的文件共享相同的结构时,此模式最有用,可以利用这种通用结构在索引中使相关字段可搜索。

在索引器定义中,将 parsingMode 设置为 "markdown",并使用可选的 markdownHeaderDepth 参数定义用于分块的最大标题深度。 如果未指定,则默认为 h6,捕获所有可能的标题深度。

Markdown 通过对标头的解析生成搜索文档,其中包括以下内容:

  • document_content:包含完整 Markdown 文本作为单个字符串。 此字段作为输入文档的原始表示形式。

  • sections:一个对象数组,其中包含 Markdown 文档中各节的分层表示形式。 每节表示为此数组中的一个对象,并以与标题及其相应内容对应的嵌套方式捕获文档的结构。 通过引用路径(例如 /sections/content),可以通过字段映射访问这些字段。 此数组中的对象具有以下属性:

    • header_level:一个字符串,指示 Markdown 语法中的标题级别(h1h2h3 等)。 此字段有助于理解内容的层次结构和组织方式。

    • header_name:一个字符串,其中包含在 Markdown 文档中显示的标题的文本。 此字段提供相应节的标签或标题。

    • content:一个字符串,其中包含紧跟标题之后的文本内容,直到下一个标题为止。 此字段捕获与标题关联的详细信息或描述。 如果没有直接在标头下的内容,则值为空字符串。

    • ordinal_position:一个整数值,指示该节在文档层次结构中的位置。 此字段用于按原始顺序对文档中的各节进行排序,从序号位置 1 开始,每个内容块的序号依次递增。

    • sections:一个数组,其中包含表示嵌套在当前节下的小节的对象。 此数组遵循与顶级 sections 数组相同的结构,允许表示多级嵌套内容。 每个子节对象也包括 header_levelheader_namecontentordinal_position 属性,实现递归结构,展现 Markdown 内容的层级关系。

下面是用于解释围绕每个分析模式设计的索引架构的示例 Markdown。

# Section 1
Content for section 1.

## Subsection 1.1
Content for subsection 1.1.

# Section 2
Content for section 2.

用于一对一分析的索引架构

如果不利用字段映射,索引的形状应反映 Markdown 内容的形状。 鉴于示例 Markdown 的结构,其中包含两个节和单个小节,索引应类似于以下示例:

{
  "name": "my-markdown-index",
  "fields": [
  {
    "name": "id",
    "type": "Edm.String",
    "key": true
  },
  {
    "name": "document_content",
    "type": "Edm.String"
  },
  {
    "name": "sections",
    "type": "Collection(Edm.ComplexType)",
    "fields": [
    {
      "name": "header_level",
      "type": "Edm.String"
    },
    {
      "name": "header_name",
      "type": "Edm.String"
    },
    {
      "name": "content",
      "type": "Edm.String"
    },
    {
      "name": "ordinal_position",
      "type": "Edm.Int32"
    },
    {
      "name": "sections",
      "type": "Collection(Edm.ComplexType)",
      "fields": [
      {
        "name": "header_level",
        "type": "Edm.String"
      },
      {
        "name": "header_name",
        "type": "Edm.String"
      },
      {
        "name": "content",
        "type": "Edm.String"
      },
      {
        "name": "ordinal_position",
        "type": "Edm.Int32"
      }]
    }]
  }]
}

用于一对一分析的索引器定义

POST https://[service name].search.azure.cn/indexers?api-version=2025-08-01-preview
Content-Type: application/json
api-key: [admin key]

{
  "name": "my-markdown-indexer",
  "dataSourceName": "my-blob-datasource",
  "targetIndexName": "my-target-index",
  "parameters": {
    "configuration": {
      "parsingMode": "markdown",
      "markdownParsingSubmode": "oneToOne",
    }
  }
}

用于一对一分析的索引器输出

由于要编制索引的 Markdown 只到达深度 h2(“##”),因此需要将 sections 字段嵌套到深度 2 以与之匹配。 此配置将导致索引中的数据如下所示:

  "document_content": "# Section 1\r\nContent for section 1.\r\n## Subsection 1.1\r\nContent for subsection 1.1.\r\n# Section 2\r\nContent for section 2.\r\n",
  "sections": [
    {
      "header_level": "h1",
      "header_name": "Section 1",
      "content": "Content for section 1.",
      "ordinal_position": 1,
      "sections": [
        {
          "header_level": "h2",
          "header_name": "Subsection 1.1",
          "content": "Content for subsection 1.1.",
          "ordinal_position": 2,
        }]
    }],
    {
      "header_level": "h1",
      "header_name": "Section 2",
      "content": "Content for section 2.",
      "ordinal_position": 3,
      "sections": []
    }]
  }

如你所见,序号位置根据内容在文档中的位置递增。

还应注意,如果内容中跳过了标头级别,则生成的文档结构反映的是 Markdown 内容中存在的标头,而不一定包含连续的 h1h6 的嵌套部分。 例如,当文档从 h2 开始时,顶级节数组中的第一个元素是 h2

在搜索索引中映射一对一字段

如果要从文档中提取具有自定义名称的字段,可以使用字段映射来实现。 以之前的 Markdown 示例为例,考虑以下索引配置:

{
  "name": "my-markdown-index",
  "fields": [
    {
      "name": "document_content",
      "type": "Edm.String",
    },
    {
      "name": "document_title",
      "type": "Edm.String",
    },
    {
      "name": "opening_subsection_title"
      "type": "Edm.String",
    }
    {
      "name": "summary_content",
      "type": "Edm.String",
    }
  ]
}

从经过分析的 Markdown 中提取特定字段的过程与文档路径在 outputFieldMappings 中的处理方式类似,区别在于路径以 /sections 开始,而不是 /document。 例如,/sections/0/content 将映射到节数组中位置为 0 的项下的内容。

一个强有力的用例示例可能是:所有 Markdown 文件在第一个 h1 中都有文档标题、在第一个 h2 中有小节标题,最终段落中的总结内容位于最后一个 h1 下。 可以使用以下字段映射来仅为该内容编制索引:

"fieldMappings" : [
  { "sourceFieldName" : "/content", "targetFieldName" : "raw_content" },
  { "sourceFieldName" : "/sections/0/header_name", "targetFieldName" : "document_title" },
  { "sourceFieldName" : "/sections/0/sections/header_name", "targetFieldName" : "opening_subsection_title" },
  { "sourceFieldName" : "/sections/1/content", "targetFieldName" : "summary_content" },
]

在这种情况下,只能从该文档中提取相关部分。 为了更有效地使用此功能,你计划编制索引的文档应共享相同的分层标题结构。

索引中生成的搜索文档将如下所示:

{
  "content": "Content for section 1.\r\n",
  "document_title": "Section 1",
  "opening_subsection_title": "Subsection 1.1",
  "summary_content": "Content for section 2."
}

注释

这些示例详细说明了如何完全在有或没有字段映射的情况下使用这些解析模式,但如果适合您的需求,您也可以在同一方案中同时应用这两种模式。

管理陈旧文档的 Markdown 重新索引过程

在使用一对多解析模式时,如果删除了部分内容,重新为修改后的 Markdown 文件编制索引可能会导致文档过时或重复。 此行为特定于一对多模式,不适用于一对一分析。

行为概述

一对多分析模式

oneToMany 模式下,每个 Markdown 节(基于标题)都作为单独的搜索文档编制索引。 重新编制文件索引时:

  • 无自动删除:索引器使用新文档覆盖现有文档,但不会删除不再对应于更新文件中的任何内容的文档。
  • 重复项的可能性:此问题仅在索引运行之间删除的节多于插入的节时出现。 在这种情况下,旧版本中的剩余文档仍保留在索引中,导致不再反映源文件的当前状态的过时条目。

一对一分析模式

oneToOne 模式下,整个 Markdown 文件作为单个搜索文档编制索引。 重新编制文件索引时:

  • 覆盖行为:现有文档完全替换为新版本。
  • 没有过时的部分:重新编制文件索引后,现有文档将替换为更新的版本,并且不再包含已删除的内容。 唯一的例外是文件路径或 Blob URI 发生更改,这可能会导致新文档与旧文档一起创建。

解决方法选项

若要确保索引反映 Markdown 文件的当前状态,请考虑以下方法之一:

选项 1. 使用元数据进行软删除

此方法使用软删除来删除与特定 Blob 关联的文档。 有关详细信息,请参阅 在 Azure AI 搜索中使用 Azure 存储索引器更改和删除检测

步骤:

  1. 通过设置元数据字段将 Blob 标记为已删除。
  2. 让索引器运行。 它会删除与该 Blob 关联的索引中的所有文档。
  3. 删除软删除标记并重新为文件编制索引。

选项 2. 使用 API 删除

在重新为修改后的 Markdown 文件编制索引之前,请使用 删除 API 显式删除与该文件关联的现有文档。 您可以选择:

  • 手动识别各个过时文档,通过标记索引中需要删除的重复项。 对于一些小的且易于理解的改动,这可能可行,但可能会很耗时。
  • 推荐)在重新编制索引之前删除从同一父文件生成的所有文档,确保避免不一致。

步骤:

  1. 标识与文件关联的文档的 ID。 使用类似于以下示例的查询检索绑定到特定文件的所有文档的文档密钥 ID(例如 idchunk_id等)。 将 metadata_storage_path 替换为索引中映射到文件路径或 Blob URI 的相应字段。 此字段必须是键。

    GET https://[service name].search.azure.cn/indexes/[index name]/docs?api-version=2025-05-01-preview
    Content-Type: application/json
    api-key: [admin key]
    
      {  
          "filter": "metadata_storage_path eq 'https://storage-account>.blob.core.chinacloudapi.cn/<container-name>/<file-name>'",
          "select": "id"
      }
    
  2. 对具有已标识密钥的文档发出删除请求。

    POST https://[service name].search.azure.cn/indexes/[index name]/docs/index?api-version=2025-05-01-preview
    Content-Type: application/json
    api-key: [admin key]
    
    {  
      "value": [  
        {  
          "@search.action": "delete",  
          "id": "aHR0c...jI1"  
        },
        {  
          "@search.action": "delete",  
          "id": "aHR0...MQ2"  
        }  
      ]  
    }
    
  3. 重新为更新的文件编制索引。

后续步骤