見出し画像

RAGの限界を超える第一歩──Azure AI Document Intelligenceによる文書解析の実践

RAG(Retrieval-Augmented Generation)は、ユーザーが求める情報が「どこかに存在する」場合に、それを的確に探し出すのが得意な仕組みです。しかし一方で、複数の資料群から全体像を把握したり、資料の目的や変遷をたどるような理解を行うのは、苦手とされています。その理由のひとつが、「一度に処理できる文字数の制約」にあります。

本記事では、こうした課題に対する一つのアプローチとして、Azure AI Document Intelligence を取り上げます。



Azure AI Document Intelligenceとは

Azure AI Document Intelligence は、PDFファイルなどから文字を高精度で抽出できるサービスです。
Azure AI Search の Free プランにも PDF の読み取り機能はありますが、Document Intelligence の特長は以下の点にあります:

  • 手書き文字やデザイン文字にも対応

  • 文字サイズやレイアウトに左右されず、構造的にテキストを抽出

  • レイアウト情報を保持したまま解析が可能


システム概要:PDFアップロードをトリガーに文字抽出を自動実行

今回は、Blob ストレージに PDF ファイルがアップロードされたことをトリガーとして Azure Functions を起動し、Document Intelligence を使って文字抽出を行う仕組みを構築します。
システムのフロー概要は以下の通りです:

図1.システム概要図

実装手順

# リソースグループ作成
az group create --name rg-kraft12345 --location japaneast

# ストレージアカウント作成
az storage account create --name sakraft12345 --location japaneast --resource-group rg-kraft12345 --sku Standard_LRS

# ストレージアカウントキー取得
az storage account keys list \
    --resource-group rg-kraft12345 \
    --account-name sakraft12345 \
    --query "[0].value" \
    --output tsv

# コンテナ作成
az storage container create \
    --name user-data \
    --account-name sakraft12345 \
    --account-key "<ストレージアカウントキー>"

# Azure AI Document Intelligence リソース作成
az cognitiveservices account create \
  --name docint-kraft12345 \
  --resource-group rg-kraft12345 \
  --kind FormRecognizer \
  --sku S0 \
  --location japaneast \
  --yes \
  --output table

# エンドポイント取得
az cognitiveservices account show \
  --name docint-kraft12345 \
  --resource-group rg-kraft12345 \
  --query "properties.endpoint" \
  --output tsv

# APIキー取得
az cognitiveservices account keys list \
  --name docint-kraft12345 \
  --resource-group rg-kraft12345

# トークン取得(expiryは数日後などに設定)
az storage container generate-sas \
  --account-name sakraft12345 \
  --name user-data \
  --permissions r \
  --expiry 2025-07-09T00:00Z \
  --output tsv

# Azure Functions 作成
az functionapp create \
    --resource-group rg-kraft12345 \
    --consumption-plan-location japaneast \
    --runtime python \
    --runtime-version 3.12 \
    --functions-version 4 \
    --name pf-kraft-12345 \
    --storage-account sakraft12345 \
    --os-type Linux

# 環境変数設定
az functionapp config appsettings set \
  --name pf-kraft-12345 \
  --resource-group rg-kraft12345 \
  --settings \
  DOCUMENT_INTELLIGENCE_ENDPOINT="<エンドポイント>" \
  DOCUMENT_INTELLIGENCE_KEY="<APIキー>" \
  DOCUMENT_INTELLIGENCE_TOKEN="<トークン>" \
  STORAGE_ACCOUNT_NAME="sakraft12345" \
  BLOB_CONTAINER_NAME="user-data"

# プロジェクトフォルダでデプロイ
func azure functionapp publish pf-kraft-12345 --python

Azure Functions の構成

以下のようなフォルダ構成で実装します。

project-folder/
├── function_app/
│   ├── __init__.py
│   └── function.json
└── requirements.txt

トリガー設定(function.json)

Blob ストレージへのファイルアップロードをトリガーとするため、function.json の内容は以下のようになります。

function.json

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "name": "blob",
      "type": "blobTrigger",
      "direction": "in",
      "path": "user-data/{name}",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

処理内容(init.py)

今回は、Document Intelligence で抽出した文字列を ログに出力するシンプルな実装としています。

__init__.py

import logging
import os
import requests
import azure.functions as func
from urllib.parse import quote
import time

def main(blob: func.InputStream):
    logging.info(f"[START] Blob trigger function processing: {blob.name}, size: {blob.length} bytes")

    # 環境変数からDocument Intelligenceの設定を取得
    endpoint = os.environ["DOCUMENT_INTELLIGENCE_ENDPOINT"]
    api_key = os.environ["DOCUMENT_INTELLIGENCE_KEY"]
    sas_token = os.environ["DOCUMENT_INTELLIGENCE_TOKEN"]
    storage_account = os.environ["STORAGE_ACCOUNT_NAME"]
    container = os.environ["BLOB_CONTAINER_NAME"]

    # ?がなければ付加
    if not sas_token.startswith("?"):
        sas_token = f"?{sas_token}"

    # Blob URL構築
    blob_file = quote(os.path.basename(blob.name))
    blob_url = f"https://{storage_account}.blob.core.windows.net/{container}/{blob_file}{sas_token}"

    # APIエンドポイント
    if not endpoint.endswith("/"):
        endpoint += "/"
    analyze_url = f"{endpoint}formrecognizer/documentModels/prebuilt-layout:analyze?api-version=2023-07-31"

    headers = {
        "Content-Type": "application/json",
        "Ocp-Apim-Subscription-Key": api_key
    }

    body = {
        "urlSource": blob_url
    }

    try:
        logging.info(f"[INFO] Sending request to Document Intelligence: {analyze_url}")
        response = requests.post(analyze_url, headers=headers, json=body)
        logging.info(f"Response status: {response.status_code}")
        logging.info(f"Response body: {response.text}")

        response.raise_for_status()

        operation_location = response.headers["operation-location"]
        logging.info(f"[INFO] Operation-Location: {operation_location}")

        # ポーリング(最大10回)
        for _ in range(10):
            result = requests.get(operation_location, headers={"Ocp-Apim-Subscription-Key": api_key})
            result_json = result.json()
            status = result_json.get("status")
            if status == "succeeded":
                logging.info("[SUCCESS] Document analysis completed")
                logging.info(result_json)
                break
            elif status == "failed":
                logging.error("[FAILED] Document analysis failed")
                break
            time.sleep(2)

    except Exception as e:
        logging.error(f"[ERROR] Failed to process Document Intelligence API: {e}")

動作確認:PDFの取り込みと解析結果

アプリケーション Insights のログを確認するために、以下の KQL クエリを実行します。

traces
| where message has "Blob trigger function processing" or message has "SUCCESS"
| order by timestamp desc
画面1.結果画面

PDFファイルがトリガーとなり、Document Intelligence による文字抽出が成功したことが確認できました。


実例:スーパーのチラシを解析してみた

今回はテストケースとして、「スーパーのチラシ画像を読み込んでテキスト化する」というシナリオで試してみました。

画面2.解析画面

解析結果を確認すると、たとえば「AEON」のロゴは「/EON」と読み取られていました。これはロゴデザインの影響と考えられます。
また、レイアウトの影響により「本体 98 2点 円」など、数字や単位がやや混ざった形で読み取られている箇所もありました。

つまり、Document Intelligence は画像のレイアウトを忠実に追いながら文字を抽出する一方で、意味的な補正や再構成までは行わないという特性があると分かりました。


結論:どう活用するかは目的次第

今回の検証を通じて、Document Intelligence の出力結果をそのまま保存して活用するのか、あるいは OpenAI Chat などを用いてテキストの整形や意味づけを行うのかは、用途や目的に応じて選択する必要があると分かりました。

AIサービスはそれぞれ得意分野が異なります。
だからこそ、目的に応じてサービスを組み合わせ、柔軟に設計していくことが、AI活用の鍵となります。


おわりに

今回は、ファイルアップロードをトリガーにした Azure Functions の起動から、Document Intelligence による解析までの一連の流れをご紹介しました。
RAGの高度化に向けて、データ前処理や解析手法にはさまざまなアプローチがあり、その一歩として参考になれば幸いです。

いいなと思ったら応援しよう!