教程:使用 REST 和 AI 从 Azure Blob 生成可搜索的内容

如果在 Azure Blob 存储中有非结构化文本或图像,则可使用 AI 扩充管道提取信息,并创建可用于全文搜索或知识挖掘方案的新内容。

本 REST 教程介绍以下知识:

  • 设置开发环境。
  • 定义使用 OCR、语言检测、实体识别和关键短语提取的管道。
  • 执行管道调用转换,以及创建和加载搜索索引。
  • 使用全文搜索和丰富的查询语法浏览结果。

如果没有 Azure 订阅,请在开始前创建一个试用版订阅

概述

本教程使用 Postman 和 Azure 认知搜索 REST API 来创建数据源、索引、索引器和技能组。

索引器连接到 Azure Blob 存储并检索必须提前加载的内容。 然后,该索引器调用 技能组进行专用处理,并将扩充的内容引入搜索索引

技能组已附加到索引器。 它使用 Azure 提供的内置技能来查找和提取信息。 管道中的步骤包括对图像的光学字符识别 (OCR)、语言检测、关键短语提取和实体识别(组织、位置、人员)。 管道创建的新信息存储在索引的新字段中。 填充索引后,可在查询、facet 和筛选器中使用这些字段。

先决条件

注意

可在本教程中使用免费服务。 免费搜索服务限制为三个索引、三个索引器和三个数据源。 本教程每样创建一个。 在开始之前,请确保服务中有足够的空间可接受新资源。

下载文件

示例数据包含 14 个混合内容类型的文件,这些文件将在后面的步骤中上传到 Azure Blob 存储。

  1. azure-search-sample-data/ai-enrichment-mixed-media/ 获取文件,并复制到本地计算机。

  2. 接下来,获取本教程的源代码(Postman 集合文件)。 可以在 azure-search-postman-samples/tree/master/Tutorial 中找到源代码。

1 - 创建服务

本教程使用 Azure 认知搜索编制索引和进行查询、使用后端的 Azure AI 服务进行 AI 扩充,并使用 Azure Blob 存储提供数据。 本教程使用的 Azure AI 服务不超过每日为每个索引器免费分配 20 个事务这一限制,因此,只需要创建搜索和存储服务。

如果可能,请在同一区域和资源组中创建这两个服务,使它们相互靠近并易于管理。 在实践中,Azure 存储帐户可位于任意区域。

从 Azure 存储开始

  1. 登录到 Azure 门户,然后选择“+ 创建资源”。

  2. 搜索存储帐户,并选择“Azure 的存储帐户”产品/服务。

    Create Storage account

  3. 在“基本信息”选项卡中,必须填写以下项。 对于其他任何字段,请接受默认设置。

    • 资源组。 选择现有的资源组或创建新资源组,但对于所有服务请使用相同的组,以便可以统一管理这些服务。

    • 存储帐户名称。 如果你认为将来可能会用到相同类型的多个资源,请使用名称来区分类型和区域,例如 blobstoragechinanorth。

    • Location。 如果可能,请选择 Azure 认知搜索和 Azure AI 服务所用的相同位置。 使用一个位置可以避免带宽费用。

    • 帐户类型。 选择默认设置“StorageV2 (常规用途 v2)” 。

  4. 选择“查看 + 创建”以创建服务。

  5. 创建后,选择“转到资源”打开“概述”页。

  6. 选择“Blob”服务。

  7. 选择“+ 容器”创建容器,并将其命名为 cog-search-demo

  8. 选择“cog-search-demo”,然后单击“上传”打开下载文件所保存到的文件夹。 选择所有文件。 选择“上传”。

    Screenshot of the files in File Explorer.

  9. 在退出 Azure 存储之前获取一个连接字符串,以便可以在 Azure 认知搜索中构建连接。

    1. 向后浏览到存储帐户的“概览”页(我们使用了 blobstragechinanorth 作为示例)。

    2. 在左侧导航窗格中,选择“访问密钥”并复制其中一个连接字符串。

    连接字符串是类似于以下示例的 URL:

    DefaultEndpointsProtocol=https;AccountName=cogsrchdemostorage;AccountKey=<your account key>;EndpointSuffix=core.chinacloudapi.cn
    
  10. 将连接字符串保存到记事本中。 稍后在设置数据源连接时需要用到它。

Azure AI 服务

AI 扩充由 Azure AI 服务(包括用于自然语言和图像处理的语言服务与 Azure AI 视觉)提供支持。 如果你的目标是完成实际原型或项目,则此时应预配 Azure AI 服务(在 Azure 认知搜索所在的同一区域中),以便可将其附加到技能组

但是,对于本练习,可以跳过资源预配,因为 Azure 认知搜索可以连接到 Azure AI 服务,并为每个索引器运行免费执行 20 个事务。 由于本教程使用 14 个事务,因此免费的分配已足够。 对于大型项目,请计划在标准预付费套餐 S0 层预配 Azure AI 服务。

第三个组件是 Azure 认知搜索,可以在门户中创建搜索服务,或在订阅中找到现有的搜索服务

可使用免费层完成本演练。

要与 Azure 认知搜索服务交互,需要该服务 URL 和访问密钥。

  1. 登录到 Azure 门户,在搜索服务的概述页面中获取搜索服务的名称。 可以通过查看终结点 URL 来确认服务名称。 如果终结点 URL 为 https://mydemo.search.azure.cn,则服务名称为 mydemo

  2. 在“设置”>“密钥”中,获取有关该服务的完全权限的管理员密钥 。 可以复制主密钥或辅助密钥。

    Get the service name and admin key

对搜索服务的所有 HTTP 请求都需要 API 密钥。 具有有效的密钥可以在发送请求的应用程序与处理请求的服务之间建立信任关系,这种信任关系以每个请求为基础。

2 - 设置 Postman

  1. 启动 Postman,导入集合,然后设置环境变量。 如果不熟悉此工具,请参阅探索 Azure 认知搜索 REST API

  2. 需要提供搜索服务名称、管理 API 密钥、索引名称、Azure 存储帐户的连接字符串,以及容器名称。

    Screenshot of the Variables page in Postman.

本集合中使用的请求方法是 PUT 和 GET 。 你将使用这两种方法创建数据源、技能组、索引和索引器。

3 - 创建管道

在 Azure 认知搜索中,扩充是在索引编制(或数据引入)期间发生的。 本演练部分将创建四个对象:数据源、索引定义、技能集和索引器。

步骤 1:创建数据源

调用创建数据源为包含示例数据文件的 Blob 容器提供连接字符串。

  1. 选择“创建数据源”请求。

  2. 请求正文为 JSON,并包含索引器数据源对象的属性。 连接字符串包含用于访问服务的凭据。

    {
        "description" : "Demo files to demonstrate cognitive search capabilities.",
        "type" : "azureblob",
        "credentials" : {
           "connectionString": "{{azure_storage_connection_string}}"
        },
      "container" : {
        "name" : "{{container_name}}"
      }
    }
    
  3. 发送请求。 应会看到状态代码 201(确认成功)。

如果收到 403 或 404 错误,请检查搜索管理 API 密钥和 Azure 存储连接字符串。

步骤 2:创建技能集

调用创建技能组指定将哪些扩充步骤应用于内容。

  1. 选择“创建技能组”请求。

  2. 请求的正文指定以下内置技能:

    技能 说明
    光学字符识别 识别图像文件中的文本和数字。
    文本合并 创建“合并内容”,该内容重新合并以前分隔的内容,对于嵌入了图像的文档(PDF、DOCX 等)很有用。 图像和文本在文档分解阶段被分离。 合并技能通过将在扩充期间创建的任何已识别的文本、图像描述文字或标记插入到文档中从其中提取图像的同一位置来重新合并它们。

    在技能组中处理合并的内容时,此节点将包含文档中的所有文本,包括从未经过 OCR 或图像分析的纯文本文档。
    语言检测 检测语言并输出语言名称或代码。 在多语言数据集中,语言字段可用于筛选器。
    实体识别 从合并的内容中提取人员、组织和位置的名称。
    文本拆分 将大段合并内容拆分为较小区块,然后调用关键短语提取技能。 关键短语提取接受不超过 50,000 个字符的输入。 有几个示例文件需要拆分才能保留在此限制范围内。
    关键短语提取 提取出最相关的关键短语。

    每个技能会针对文档的内容执行。 在处理期间,Azure 认知搜索会解码每个文档,以从不同的文件格式读取内容。 从源文件中找到的文本将放入一个生成的 content 字段(每个文档对应一个字段)。 因此,输入将变为 "/document/content"

    对于关键短语提取,由于我们使用了文本拆分器技能将较大文件分解成多个页面,因此关键短语提取技能的上下文是 "document/pages/*"(适用于文档中的每个页面),而不是 "/document/content"

    {
      "description": "Apply OCR, detect language, extract entities, and extract key-phrases.",
      "cognitiveServices": null,
      "skills":
      [
        {
          "@odata.type": "#Microsoft.Skills.Vision.OcrSkill",
          "context": "/document/normalized_images/*",
          "defaultLanguageCode": "en",
          "detectOrientation": true,
          "inputs": [
            {
              "name": "image",
              "source": "/document/normalized_images/*"
            }
          ],
          "outputs": [
            {
              "name": "text"
            }
          ]
        },
        {
          "@odata.type": "#Microsoft.Skills.Text.MergeSkill",
          "description": "Create merged_text, which includes all the textual representation of each image inserted at the right location in the content field. This is useful for PDF and other file formats that supported embedded images.",
          "context": "/document",
          "insertPreTag": " ",
          "insertPostTag": " ",
          "inputs": [
            {
              "name":"text",
              "source": "/document/content"
            },
            {
              "name": "itemsToInsert",
              "source": "/document/normalized_images/*/text"
            },
            {
              "name":"offsets",
              "source": "/document/normalized_images/*/contentOffset"
            }
          ],
          "outputs": [
            {
              "name": "mergedText",
              "targetName" : "merged_text"
            }
          ]
        },
        {
          "@odata.type": "#Microsoft.Skills.Text.SplitSkill",
          "textSplitMode": "pages",
          "maximumPageLength": 4000,
          "defaultLanguageCode": "en",
          "context": "/document",
          "inputs": [
            {
              "name": "text",
              "source": "/document/merged_text"
            }
          ],
          "outputs": [
            {
              "name": "textItems",
              "targetName": "pages"
            }
          ]
        },
        {
          "@odata.type": "#Microsoft.Skills.Text.LanguageDetectionSkill",
          "description": "If you have multilingual content, adding a language code is useful for filtering",
          "context": "/document",
          "inputs": [
            {
              "name": "text",
              "source": "/document/merged_text"
            }
          ],
          "outputs": [
            {
              "name": "languageName",
              "targetName": "language"
            }
          ]
        },
        {
          "@odata.type": "#Microsoft.Skills.Text.KeyPhraseExtractionSkill",
          "context": "/document/pages/*",
          "inputs": [
            {
              "name": "text",
              "source": "/document/pages/*"
            }
          ],
          "outputs": [
            {
              "name": "keyPhrases",
              "targetName": "keyPhrases"
            }
          ]
        },
        {
          "@odata.type": "#Microsoft.Skills.Text.V3.EntityRecognitionSkill",
          "categories": ["Organization"],
          "context": "/document",
          "inputs": [
            {
              "name": "text",
              "source": "/document/merged_text"
            }
          ],
          "outputs": [
            {
              "name": "organizations",
              "targetName": "organizations"
            }
          ]
        },
        {
          "@odata.type": "#Microsoft.Skills.Text.V3.EntityRecognitionSkill",
          "categories": ["Location"],
          "context": "/document",
          "inputs": [
            {
              "name": "text",
              "source": "/document/merged_text"
            }
          ],
          "outputs": [
            {
              "name": "locations",
              "targetName": "locations"
            }
          ]
        },
        {
          "@odata.type": "#Microsoft.Skills.Text.V3.EntityRecognitionSkill",
          "categories": ["Person"],
          "context": "/document",
          "inputs": [
            {
              "name": "text",
              "source": "/document/merged_text"
            }
          ],
          "outputs": [
            {
              "name": "persons",
              "targetName": "persons"
            }
          ]
        }
      ]
    }
    

    技能集的一部分的图形表示形式如下所示。

    Understand a skillset

  3. 发送请求。 Postman 应返回状态代码 201(确认成功)。

注意

输出可以映射到索引、用作下游技能的输入,或者既映射到索引又用作输入(在语言代码中就是这样)。 在索引中,语言代码可用于筛选。 若要详细了解技能集的基础知识,请参阅如何定义技能集

步骤 3:创建索引

调用创建索引来提供在 Azure 认知搜索中创建反向索引和其他结构的模式。 索引的最大组件是字段集合,其中的数据类型和属性确定了 Azure 认知搜索中的内容和行为。

  1. 选择“创建索引”请求。

  2. 请求的主体定义搜索索引的架构。 字段集合需要将一个字段指定为键。 对于 Blob 内容,此字段通常是唯一标识容器中每个 blob 的“metadata_storage_path”。

    在此架构中,“文本”字段接收 OCR 输出,“内容”接收合并的输出,“语言”接收语言检测输出。 从 Blob 存储中提取的关键短语、实体和多个字段构成了其余条目。

    {
      "fields": [
        {
          "name": "text",
          "type": "Collection(Edm.String)",
          "searchable": true,
          "sortable": false,
          "filterable": true,
          "facetable": false
        },
        {
          "name": "content",
          "type": "Edm.String",
          "searchable": true,
          "sortable": false,
          "filterable": false,
          "facetable": false
        },
        {
          "name": "language",
          "type": "Edm.String",
          "searchable": false,
          "sortable": true,
          "filterable": true,
          "facetable": false
        },
        {
          "name": "keyPhrases",
          "type": "Collection(Edm.String)",
          "searchable": true,
          "sortable": false,
          "filterable": true,
          "facetable": true
        },
        {
          "name": "organizations",
          "type": "Collection(Edm.String)",
          "searchable": true,
          "sortable": false,
          "filterable": true,
          "facetable": true
        },
        {
          "name": "persons",
          "type": "Collection(Edm.String)",
          "searchable": true,
          "sortable": false,
          "filterable": true,
          "facetable": true
        },
        {
          "name": "locations",
          "type": "Collection(Edm.String)",
          "searchable": true,
          "sortable": false,
          "filterable": true,
          "facetable": true
        },
        {
          "name": "metadata_storage_path",
          "type": "Edm.String",
          "key": true,
          "searchable": true,
          "sortable": false,
          "filterable": false,
          "facetable": false
        },
        {
          "name": "metadata_storage_name",
          "type": "Edm.String",
          "searchable": true,
          "sortable": false,
          "filterable": false,
          "facetable": false
        }
      ]
    }
    
  3. 发送请求。 Postman 应返回状态代码 201(确认成功)。

步骤 4:创建并运行索引器

调用创建索引器来驱动管道。 到目前为止创建的三个组件(数据源、技能集、索引)是索引器的输入。 在 Azure 认知搜索中创建索引器是运转整个管道的事件。

  1. 选择“创建索引器”请求。

  2. 请求的正文包括对以前对象的引用、图像处理所需的配置属性以及两种类型的字段映射。

    "fieldMappings" 在技能组之前处理,它将数据源中的内容发送到索引中的目标字段。 稍后要使用字段映射将未修改的现有内容发送到索引。 如果两端的字段名称和类型相同,则无需映射。

    "outputFieldMappings" 用于技能组执行后由技能创建的字段。 对 outputFieldMappingssourceFieldName 的引用在文档破解或扩充创建它们之前并不存在。 targetFieldName 是索引中的字段,在索引架构中定义。

    {
      "dataSourceName" : "{{index_name}}-datasource",
      "targetIndexName" : "{{index_name}}",
      "skillsetName" : "{{index_name}}-skillset",
      "fieldMappings" : [
            {
              "sourceFieldName" : "metadata_storage_path",
              "targetFieldName" : "metadata_storage_path",
              "mappingFunction" : { "name" : "base64Encode" }
            },
            {
              "sourceFieldName": "metadata_storage_name",
              "targetFieldName": "metadata_storage_name"
            }
       ],
      "outputFieldMappings" :
      [
        {
              "sourceFieldName": "/document/merged_text",
              "targetFieldName": "content"
            },
            {
                "sourceFieldName" : "/document/normalized_images/*/text",
                "targetFieldName" : "text"
            },
          {
              "sourceFieldName" : "/document/organizations",
              "targetFieldName" : "organizations"
            },
            {
              "sourceFieldName": "/document/language",
              "targetFieldName": "language"
            },
          {
              "sourceFieldName" : "/document/persons",
              "targetFieldName" : "persons"
            },
          {
              "sourceFieldName" : "/document/locations",
              "targetFieldName" : "locations"
            },
            {
              "sourceFieldName" : "/document/pages/*/keyPhrases/*",
              "targetFieldName" : "keyPhrases"
            }
        ],
      "parameters":
      {
      "batchSize": 1,
        "maxFailedItems":-1,
        "maxFailedItemsPerBatch":-1,
        "configuration":
      {
          "dataToExtract": "contentAndMetadata",
          "imageAction": "generateNormalizedImages"
      }
      }
    }
    
  3. 发送请求。 Postman 应返回状态代码 201(确认处理成功)。

    此步骤预期需要几分钟时间才能完成。 即使数据集较小,分析技能也会消耗大量的计算资源。

注意

创建索引器会调用管道。 如果访问数据、映射输入和输出或操作顺序出现问题,此阶段会显示这些问题。 若要结合代码或脚本更改重新运行管道,可能需要先删除对象。 有关详细信息,请参阅重置并重新运行

关于索引器参数

脚本将 "maxFailedItems" 设置为 -1,指示索引引擎在数据导入期间忽略错误。 这是可接受的,因为演示数据源中的文档很少。 对于更大的数据源,请将值设置为大于 0。

"dataToExtract":"contentAndMetadata" 语句告知索引器自动从 blob 的内容属性以及每个对象的元数据中提取值。

提取内容后,可以设置 imageAction,以从数据源中的图像提取文本。 "imageAction":"generateNormalizedImages" 配置与 OCR 技能和文本合并技能相结合,告知索引器从图像中提取文本(例如,禁行交通标志中的单词“stop”),并将其嵌入到内容字段中。 此行为既适用于嵌入的图像(如 PDF 中的图像),也适用于独立图像文件(如 JPG 文件)。

4 - 监视索引

提交“创建索引器”请求后,索引编制和扩充立即开始。 根据定义的认知技能,索引编制可能需要花费一段时间。

若要确定索引器是否仍在运行,请调用获取索引器状态来检查索引器状态。

  1. 选择并发送“检查索引器状态”请求。

  2. 检查响应,以了解索引器是否正在运行,或者查看错误和警告信息。

在某些情况下经常出现警告,但不一定意味着存在问题。 例如,如果某个 Blob 容器包含图像文件,而管道不处理图像,则会出现一条警告,指出图像未处理。

此示例中有一个不包含任何文本的 PNG 文件。 无法对此文件执行所有五个基于文本的技能(语言检测、位置的实体识别、组织、人员和关键短语提取)。 生成的通知将显示在执行历史记录中。

创建包含 AI 生成的内容的索引后,请调用搜索文档,以运行一些查询来查看结果。

回顾前文,我们是从 Blob 内容着手的,整个文档已打包到一个 content 字段中。 可以搜索此字段并查找查询的匹配项。

  1. 打开“搜索”请求并运行它来初步了解索引内容。 此请求为空搜索 ("search=*"),因此它将返回 14 个文档中每个文档的内容。 $select 参数将结果限制为文件名、语言名称和识别的实体之一。

     GET /indexes//{{index_name}}/docs?search=*&$select=metadata_storage_name,language,organizations&$count=true&api-version=2020-06-30
    
  2. 修改上一个查询,搜索“创建无边界机会”。 此短语是通过对 PDF 文档中的嵌入图像文件进行 OCR 获得的。 包含“突出显示”可对密集填充字段中的匹配字词应用格式设置。

     GET /indexes//{{index_name}}/docs?search=creating boundaryless opportunities&$select=content&highlight=content&$count=true&api-version=2020-06-30
    
  3. 对于下一个查询,应用筛选器。 请记住,语言字段和所有实体字段都是可筛选的。

     GET /indexes/{{index_name}}/docs?search=*&$filter=organizations/any(organizations: organizations eq 'NASDAQ')&$select=metadata_storage_name,organizations&$count=true&api-version=2020-06-30
    

这些查询演示了对认知搜索创建的新字段使用查询语法和筛选器的多种方式。 有关更多查询示例,请参阅搜索文档 REST API 中的示例简单语法查询示例完整 Lucene 查询示例

重置并重新运行

在开发的早期阶段,对设计的迭代很常见。 你很可能经常删除和重新生成相同的对象。

如果使用门户进行删除,并首先删除索引器,门户会提示你删除关联的对象。

Delete search objects

或者,可以使用 DELETE 并提供每个对象的 URL。 以下命令删除一个索引器。

DELETE https://[YOUR-SERVICE-NAME].search.azure.cn/indexers/cog-search-demo-idxr?api-version=2020-06-30

成功删除后会返回状态代码 204。

要点

本教程演示了通过创建组件部件(数据源、技能集、索引和索引器)生成扩充索引管道的基本步骤。

其中介绍了内置技能组、技能集定义,以及通过输入和输出将技能链接在一起的机制。 此外,还提到需要使用索引器定义中的 outputFieldMappings,将管道中的扩充值路由到 Azure 认知搜索服务中的可搜索索引。

最后,介绍了如何测试结果并重置系统以进一步迭代。 本教程提到,针对索引发出查询会返回扩充的索引管道创建的输出。

清理资源

在自己的订阅中操作时,最好在项目结束时删除不再需要的资源。 持续运行资源可能会产生费用。 可以逐个删除资源,也可以删除资源组以删除整个资源集。

可以使用左侧导航窗格中的“所有资源”或“资源组”链接 ,在门户中查找和管理资源。

后续步骤

熟悉 AI 扩充管道中的所有对象后,接下来让我们更详细地了解技能集定义和各项技能。