使用 Azure 认知服务启用文档处理

利用 Azure 认知服务

Azure 认知服务是一组基于云的 API,可在 AI 应用程序和数据流中使用。 它提供可在应用程序中使用的预先训练的模型,无需数据,也无需进行模型训练。 它们可以通过 HTTP REST 接口轻松集成到应用程序中。

“使用 Webhook”教程中,你已了解如何将 Webhook 与应用程序配合使用,以在更新现有文件或上传新文件时获取通知。 本教程介绍如何将其与 Azure 认知服务连接以从发票中提取数据。

若要在容器发生更改时使用当前 SharePoint 应用程序设置自动 AI 处理,需要遵循 使用 Webhook, 然后:

  1. 获取容器的增量更改。 你当前可以在容器中发生任何更改时收到通知,并且现在将获取添加或更新的文件。
  2. 调用 Azure 认知服务的文档智能服务 API。 需要创建一个 Azure AI 资源,以使用该 API 从图像中提取字段并获取提取的文件。 可以按照本教程中所示存储它们,也可以根据需要处理它们。 文档处理架构

提示

若要详细了解本教程中使用的 Microsoft Graph API,请参阅 跟踪驱动器的更改获取 DriveItem 资源以及 上传或替换 DriveItem 的内容

获取容器的增量更改

打开 GraphProvider.ts 并实现 方法 getDriveChanges 以获取已更改项的列表:

public static async getDriveChanges(driveId: string): Promise<any[]> {
  let changedItems: any[] = [];
  const driveDeltaBasePath: string = `/drives/${driveId}/items/root/delta`;
  let driveDeltaTokenParams: string = "";
  let hasMoreChanges: boolean = true;
  try{
    do {
      if (this.changeTokens.has(driveId)) {
        driveDeltaTokenParams = `?token=${this.changeTokens.get(driveId)}`
      }
      const response = await this.graphClient.api(driveDeltaBasePath + driveDeltaTokenParams).get();
      changedItems.push(...response.value);
      if (response['@odata.nextLink']) {
        const token = new URL(response['@odata.nextLink']).searchParams.get('token');
        this.changeTokens.set(driveId, token);
      } else {
        hasMoreChanges = false;
        const token = new URL(response['@odata.deltaLink']).searchParams.get('token');
        this.changeTokens.set(driveId, token);
      }
      console.log(this.changeTokens.get(driveId));
    } while (hasMoreChanges);
  }
  catch(err){
    console.log(err);
  }
  return changedItems;
}

实现 方法 getDriveItem 以从容器中提取文件:

public static async getDriveItem(driveId: string, itemId: string): Promise<any> {
  return await this.graphClient.api(`/drives/${driveId}/items/${itemId}`).get();
}

ReceiptProcessor.ts创建一个新文件,并实现 方法 processDrive

export abstract class ReceiptProcessor {

  public static async processDrive(driveId: string): Promise<void> {
    const changedItems = await GraphProvider.getDriveChanges(driveId);
    for (const changedItem of changedItems) {
      try {
        const item = await GraphProvider.getDriveItem(driveId, changedItem.id);
        const extension = this.getFileExtension(item.name);
        if (this.SUPPORTED_FILE_EXTENSIONS.includes(extension.toLowerCase())) {
          console.log(item.name);
          const url = item["@microsoft.graph.downloadUrl"];
          const receipt = await this.analyzeReceiptStream(await this.getDriveItemStream(url));
          const receiptString = JSON.stringify(receipt, null, 2)
          const fileName = this.getFileDisplayName(item.name) + "-extracted-fields.json";
          const parentId = item.parentReference.id;
          await GraphProvider.addDriveItem(driveId, parentId, fileName, receiptString);
        }
      } catch (error) {
        console.log(error);
      }
    }
  }
}

此时,如果重启应用以及隧道和订阅,则应会看到控制台中列出的最近添加/更新的文件。

调用 Azure 认知服务的文档智能服务 API

若要使用 Azure 认知服务文档智能 API,需要为 Azure AI 服务创建多服务或文档智能资源。 请参阅以下教程来创建资源:

完成此步骤后,应有一个终结点和一个可供使用的密钥。

现在打开 ReceiptProcessor.ts ,创建用于存储 Azure 认知服务凭据的方法 dac

private static dac = new DocumentAnalysisClient(
  `${process.env["DAC_RESOURCE_ENDPOINT"]}`,
  new AzureKeyCredential(`${process.env["DAC_RESOURCE_KEY"]}`)
);

Create 方法 getDriveItemStream

private static async getDriveItemStream(url: string): Promise<Readable> {
  const token = GraphProvider.graphAccessToken;
  const config: AxiosRequestConfig = {
    method: "get",
    url: url,
    headers: {
      "Authorization": `Bearer ${token}`
    },
    responseType: 'stream'
  };
  const response = await axios.get<Readable>(url, config);
  return response.data;
}

创建方法 analyzeReceiptStream 以通过 Azure 认知服务处理获取 OCR 字段。 此处我们采用模型 prebuilt-invoice ,但可以选择其他模型:

private static async analyzeReceiptStream(stream: Readable): Promise<any> {
  const poller = await this.dac.beginAnalyzeDocument("prebuilt-invoice", stream, {
    onProgress: ({ status }) => {
      console.log(`status: ${status}`);
    },
  });

  const {
    documents: [result] = [],
  } = await poller.pollUntilDone();

  const fields = result?.fields;
  this.removeUnwantedFields(fields);
  return fields;
}

创建方法 removeUnwantedFields 以删除 Azure 认知服务响应中不需要的字段:

private static removeUnwantedFields(fields: any) {
  for (const prop in fields) {
    if (prop === 'boundingRegions' || prop === 'content' || prop === 'spans') {
      delete fields[prop];
    }
    if (typeof fields[prop] === 'object') {
      this.removeUnwantedFields(fields[prop]);
    }
  }
}

最后,打开GraphProvider.ts,在 类的GraphProvider末尾添加 addDriveItem 方法。

public static async addDriveItem(driveId: string, parentId: any, fileName: string, receiptString: string) {
  await this.graphClient.api(`/drives/${driveId}/items/${parentId}:/${fileName}:/content`).put(receiptString);
}

现在,重启演示应用,并在容器上使用 ngrok 和增量更改订阅再次设置隧道。

如果在此容器中添加/更新 (支持格式的任何文件:JPEG、JPG、PNG、BMP、TIFF、PDF) ,应会看到创建并包含从文件中提取的字段的新 JSON 文件。