Поделиться через


Индексирование BLOB-объектов Markdown и файлов в поиске искусственного интеллекта Azure

Замечание

Эта функция сейчас доступна в общедоступной предварительной версии. Этот предварительный просмотр предоставляется без соглашения об уровне обслуживания и не предназначается для производственных рабочих нагрузок. Некоторые функции могут не поддерживаться или их возможности могут быть ограничены. Для получения дополнительной информации см. Дополнительные условия использования для предварительных версий Microsoft Azure.

В поиске ИИ Azure индексаторы для хранилища BLOB-объектов Azure, файлов Azure и Microsoft OneLake поддерживают markdown режим синтаксического анализа файлов Markdown. Файлы Markdown можно индексировать двумя способами:

  • Режим разбора «один ко многим», создание нескольких документов поиска для каждого файла Markdown
  • Режим синтаксического анализа один к одному, создание одного документа поиска на файл Markdown

Подсказка

Перейдите к руководству: Поиск данных Markdown из хранилища BLOB Azure после просмотра этой статьи.

Предпосылки

  • Поддерживаемый источник данных: хранилище BLOB-объектов Azure, хранилище файлов Azure, Microsoft OneLake.

    Для OneLake убедитесь, что выполнены все требования индексатора OneLake.

    Служба хранилища Azure для индексаторов BLOB-объектов и индексаторов файлов — это стандартный экземпляр с высокой производительностью (версия 2 общего назначения), поддерживающий уровни горячего и холодного доступа.

Параметры режима синтаксического анализа Markdown

Параметры режима синтаксического анализа указываются в определении индексатора при создании или обновлении индексатора.

POST https://[service name].search.windows.net/indexers?api-version=2025-11-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 предоставляет следующие параметры подрежимов:

режим парсинга подрежим Поиск документа Description
markdown oneToMany Несколько на блок данных (по умолчанию) Разбивает Markdown на несколько документов поиска, каждый из которых представляет собой секцию содержимого (не заголовочную) файла Markdown. Вы можете опустить подмод, если только не хотите выполнять синтаксический анализ по одному.
markdown oneToOne Один на один блоб Анализирует Markdown в один документ поиска с разделами, сопоставленными с определенными заголовками в файле Markdown.

Для режима oneToMany вам следует ознакомиться с индексированием одного BLOB-объекта для создания множества документов поиска, чтобы понять, как индексатор BLOB-объектов обрабатывает разрешение неоднозначности ключа документа для нескольких документов поиска, созданных из одного и того же BLOB-объекта.

В последующих разделах подробно описан каждый подмод. Если вы не знакомы с клиентами и понятиями индексатора, см. статью "Создание индексатора поиска". Кроме того, следует ознакомиться с подробными сведениями о базовой конфигурации индексатора BLOB-объектов, которая здесь не приводится.

Необязательные параметры синтаксического анализа Markdown

Параметры чувствительны к регистру.

Имя параметра Допустимые значения Description
markdownHeaderDepth h1 h2 h3 h4 h5 h6(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, содержит строковые поля h1и h2h3. Эти поля индексируются путем зеркального отображения этой структуры в индексе или сопоставления полей в формате /sections/h1, sections/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.windows.net/indexers?api-version=2025-11-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, будет содержать следующие значения для content, sectionsh1иh2:

{
  {
    "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.String, h1_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: строка, указывающая уровень заголовка (h1, h2, h3и т. д.) в синтаксисе Markdown. Это поле помогает понять иерархию и структурировать содержимое.

    • header_name: строка, содержащая текст заголовка, как она отображается в документе Markdown. Это поле предоставляет метку или заголовок для раздела.

    • content: строка, содержащая текстовое содержимое, которое сразу же следует за заголовком, вплоть до следующего заголовка. Это поле записывает подробные сведения или описание, связанные с заголовком. Если под заголовком нет содержимого, значение является пустой строкой.

    • ordinal_position: целочисленное значение, указывающее положение раздела в иерархии документов. Это поле используется для упорядочивания разделов в их исходной последовательности, как они отображаются в документе, начиная с первой порядковой позиции и последовательно увеличивая нумерацию для каждого блока содержимого.

    • sections: массив, содержащий объекты, представляющие подразделы, вложенные в текущий раздел. Этот массив следует той же структуре, что и массив верхнего уровня sections , что позволяет представить несколько уровней вложенного содержимого. Каждый объект подраздела также включает свойства header_level, header_name, content и ordinal_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.windows.net/indexers?api-version=2025-11-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 ("##"), нам нужны поля, вложенные на глубину 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, а не обязательно содержат вложенные разделы для h1 последовательного выполнения h6 . Например, когда документ начинается с 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 имеют заголовок документа в первом, заголовок подраздела в первом h1h2и сводку в содержимом окончательного абзаца под окончательным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 индексируется как один документ поиска. При повторном индексировании файла:

  • Поведение перезаписи: существующий документ полностью заменен новой версией.
  • Нет устаревших разделов: когда файл переиндексирован, существующий документ заменяется на обновленную версию, и удаленное содержимое больше не включается. Единственное исключение состоит в том, если изменится путь к файлу или URI объекта BLOB, это может привести к созданию нового документа наряду со старым.

Варианты обходного решения

Чтобы убедиться, что индекс отражает текущее состояние файлов Markdown, рассмотрите один из следующих подходов:

Вариант 1. Обратимое удаление с метаданными

Этот метод использует мягкое удаление для удаления документов, связанных с конкретным блобом. Дополнительные сведения см. в статье об обнаружении изменений и удаления с помощью индексаторов для Azure Storage в Azure AI Search.

Шаги:

  1. Пометьте блоб как удаленный, установив поле метаданных.
  2. Пусть индексатор запускается. Он удаляет все документы в индексе, связанном с этим BLOB-объектом.
  3. Удалите маркер обратимого удаления и переиндексируйте файл.

Вариант 2. Использование API удаления

Перед повторной индексированием измененного файла Markdown явным образом удалите существующие документы, связанные с этим файлом, с помощью API удаления. Вы можете сделать одно из двух:

  • Вручную определите отдельные устаревшие документы, определив дубликаты в индексе, которые необходимо удалить. Это может быть выполнимо для небольших, хорошо понятных изменений, но это может занять много времени.
  • (Рекомендуется) Удалите все документы, созданные из одного родительского файла перед повторным индексированием, чтобы избежать несоответствий.

Шаги:

  1. Определите идентификатор документов, связанных с файлом. Используйте запрос, например следующий пример, чтобы получить идентификаторы ключей документа (например, , idи т. д.) для всех документов, chunk_idпривязанных к конкретному файлу. Замените metadata_storage_path на соответствующее поле в индексе, которое сопоставляется с путем к файлу или URI объекта BLOB. Это поле должно быть ключом.

    GET https://[service name].search.windows.net/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.windows.net/<container-name>/<file-name>.md'",
          "select": "id"
      }
    
  2. Отправьте запрос на удаление документов с указанными ключами.

    POST https://[service name].search.windows.net/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. Переиндексировать обновленный файл.

Дальнейшие шаги