2026年6月3日

BigQueryの新AI関数 AI.PARSE_DOCUMENT でPDF請求書を構造化データに変換してみた!


Content

こんにちは!みっちーです!


これまで構築してきたデータサイエンスエージェントは、BigQueryに格納された構造化データをSQLで分析するのが得意です。ところが実務では「Cloud Storage に溜まっている請求書PDFをまとめて分析したい」「契約書から条件を一括で抽出したい」という場合があります。これまでは PDF → テキスト抽出 → BigQuery取り込みという前処理パイプラインを別途組む必要があり、手間がかかっていました。


2026年5月のアップデートで、BigQueryに新しいAI関数 AI.PARSE_DOCUMENT がプレビューで登場しました。Cloud Storage 上のPDFや画像ドキュメントに対して OCR・レイアウト解析・テキストチャンキングを SQL だけで実行できるテーブル値関数(TVF)です。この関数で取り出したテキストをさらに AI.GENERATE に渡すことで、請求書番号や金額といった構造化フィールドを抽出するパイプラインをすべて BigQuery SQL 上で完結させることができます。早速試してみました!

試した環境

  • BigQuery:US リージョンのデータセットを使用
  • Document AI:Layout Parser プロセッサーを US リージョンで作成(事前に Document AI コンソールから作成が必要)
  • テストデータ:Cloud Storage バケットに格納したPDF請求書(50件)。取引先・請求番号・請求金額・発行日・支払期限の5フィールドが含まれる一般的な書式
  • 外部テーブル:Cloud Storage のPDFをBigQueryから参照するためのオブジェクトテーブルを作成

やってみた

①:BigQuery 接続を作成してIAM権限を設定する

AI.PARSE_DOCUMENT は内部で Document AI と Gemini Enterprise Agent Platform(旧 Vertex AI)を呼び出すため、専用の BigQuery 接続を作成し、接続に紐づくサービスアカウントに必要な権限を付与します。今回はCloud Shellを使用して設定してみます。

# 接続を作成(US リージョン)
bq mk --connection \
--connection_type=CLOUD_RESOURCE \
--location=US \
<<任意の接続名>>
# サービスアカウント ID を確認
bq show --connection <<プロジェクトID>>.US.<<作成した接続名>>

表示されたサービスアカウント ID に対して、以下の3つの権限を付与します。

# Gemini Enterprise Agent Platform 呼び出し権限(AI.GENERATE で使用)
gcloud projects add-iam-policy-binding <<プロジェクトID>> \
--member="serviceAccount:<<サービスアカウントID>>" \
--role="roles/aiplatform.user"

# Document AI プロセッサー参照権限
gcloud projects add-iam-policy-binding <<プロジェクトID>> \
--member="serviceAccount:<<サービスアカウントID>>" \
--role="roles/documentai.viewer"

# Document AI プロセス実行権限(AI.PARSE_DOCUMENT で使用)
gcloud projects add-iam-policy-binding <<プロジェクトID>> \
--member="serviceAccount:<<サービスアカウントID>>" \
--role="roles/documentai.apiUser"

また、Cloud Storage バケットへの読み取り権限も必要です。

gcloud storage buckets add-iam-policy-binding gs://<<バケット名>> \
--member="serviceAccount:<<サービスアカウントID>>" \
--role="roles/storage.objectViewer"


以上でCloudShell上での操作は終了です。

②:Cloud Storage 上のPDFをオブジェクトテーブルとして参照する

ここからは、BigQuery上で操作を行います。
Cloud Storage バケット上のPDFファイル群をBigQueryのオブジェクトテーブルとして定義します。オブジェクトテーブルはBigQueryの外部テーブルの一種で、Cloud Storage 上のファイルをBigQuery SQL から参照できるようにするものです。今回は外部テーブルとしてinvoice_filesというテーブルを作成しています。PDF格納場所を示すuriの部分は適宜書き換えてください。

CREATE OR REPLACE EXTERNAL TABLE `<<プロジェクトID>>.<<データセット名>>.invoice_files`
WITH CONNECTION `<<プロジェクトID>>.US.<<作成した接続名>>`
OPTIONS (
object_metadata = 'SIMPLE',
uris = ['gs://<<バケット名>>/*.pdf']
);

作成後に SELECT * FROM `<<プロジェクトID>>.<<データセット名>>.invoice_files` LIMIT 5 を実行すると、uricontent_typesizeupdatedref 等のメタデータ列が返ってきます。AI.PARSE_DOCUMENT にはこのテーブルをそのまま渡します。



③:AI.PARSE_DOCUMENT でPDFをテキストチャンクに変換する

いよいよ本題です。AI.PARSE_DOCUMENT は FROM 句で呼び出すテーブル値関数(TVF)です。Document AI の Layout Parser プロセッサーを使って PDF に OCR とレイアウト解析を行い、テキストをチャンク単位で返します。まずは3件だけ試してみます。<<プロセッサID>>の部分は、Document AIで作成したLayout ParserプロセッサのIDを入力してください。下の画像の白塗りされた部分に記載されているものがプロセッサIDになります。



-- AI.PARSE_DOCUMENT でPDFをテキストチャンクに変換(プレビュー)
SELECT
uri,
chunk_id,
start_page,
end_page,
content
FROM AI.PARSE_DOCUMENT(
(
SELECT *
FROM `<<プロジェクトID>>.<<データセット名>>.invoice_files`
LIMIT 3
),
endpoint => 'projects/<<プロジェクト番号>>/locations/us/processors/<<プロセッサーID>>',
connection_id => '<<プロジェクトID>>.US.<<作成した接続名>>'
);

返ってきた結果は次のような列構成です:

  • chunk_id:チャンク番号(1ページ1チャンクが基本)
  • start_page / end_page:チャンクが対応するページ範囲
  • content:OCR・レイアウト解析されたテキスト全文(テーブルはMarkdown形式で埋め込まれる)

実際の出力結果は以下の画像のようなものになっています。請求書のテーブル部分もMarkdown形式で忠実に再現されており、後段で構造化抽出しやすい形になっています:

④:AI.GENERATE で構造化フィールドを抽出する

AI.PARSE_DOCUMENT が返す content(テキスト)を AI.GENERATE に渡し、必要なフィールドを JSON 形式で抽出します。プロンプトに出力フォーマットを明示することで、日付フォーマットの揺れ(「令和X年X月X日」「2025/05/01」など)も統一して返してもらえます。

-- AI.GENERATE で構造化フィールドを抽出
SELECT
uri,
AI.GENERATE(
CONCAT(
'以下の請求書テキストから下記フィールドをJSON形式で返してください。\n',
'- invoice_number: 請求書番号\n',
'- vendor_name: 請求先の会社名\n',
'- total_amount: 請求金額の合計(数値のみ、カンマなし)\n',
'- issue_date: 発行日(YYYY-MM-DD形式)\n',
'- payment_due_date: 支払期限(YYYY-MM-DD形式)\n\n',
content
),
endpoint => 'gemini-2.5-flash',
connection_id => '<<プロジェクトID>>.US.<<作成した接続名>>'
) AS gen
FROM AI.PARSE_DOCUMENT(
(
SELECT *
FROM `<<プロジェクトID>>.<<データセット名>>.invoice_files`
LIMIT 5
),
endpoint => 'projects/<<プロジェクト番号>>/locations/us/processors/<<プロセッサーID>>',
connection_id => '<<プロジェクトID>>.US.<<作成した接続名>>'
);

返ってきた gen 列は STRUCT 型(result STRINGfull_response JSONstatus STRING)です。result フィールドには JSON が Markdown コードブロック(“`json“`)で包まれて返されます。REGEXP_EXTRACT で JSON を取り出し、PARSE_JSON を通した上で JSON_VALUE で個別の値を取り出せます:

-- JSONから各フィールドを列として展開する
SELECT
uri,
JSON_VALUE(fields, '$.invoice_number') AS invoice_number,
JSON_VALUE(fields, '$.vendor_name') AS vendor_name,
SAFE_CAST(JSON_VALUE(fields, '$.total_amount') AS INT64) AS total_amount,
SAFE_CAST(JSON_VALUE(fields, '$.issue_date') AS DATE) AS issue_date,
SAFE_CAST(JSON_VALUE(fields, '$.payment_due_date') AS DATE) AS payment_due_date
FROM (
SELECT
uri,
PARSE_JSON(
REGEXP_EXTRACT(gen.result, r'```json\n([\s\S]*?)\n```')
) AS fields
FROM (
-- 上記のAI.GENERATE クエリを gen AS エイリアスで入れる
...
)
);


上記のクエリ結果は以下のようなものになります



⑤:抽出結果をBigQueryテーブルに書き込んでデータサイエンスエージェントで分析する

50件全件を処理してBigQueryの通常テーブルに書き込みます。一時テーブルに格納しておくことで、毎回 AI.PARSE_DOCUMENT・AI.GENERATE を実行するコストを避けられます。今回は、parsed_invoicesという一時テーブルを作成しています。

-- 全件を処理して構造化テーブルに保存
CREATE OR REPLACE TABLE `<<プロジェクトID>>.<<データセット名>>.parsed_invoices` AS
SELECT
uri,
JSON_VALUE(fields, '$.invoice_number') AS invoice_number,
JSON_VALUE(fields, '$.vendor_name') AS vendor_name,
SAFE_CAST(JSON_VALUE(fields, '$.total_amount') AS INT64) AS total_amount,
SAFE_CAST(JSON_VALUE(fields, '$.issue_date') AS DATE) AS issue_date,
SAFE_CAST(JSON_VALUE(fields, '$.payment_due_date') AS DATE) AS payment_due_date
FROM (
SELECT
uri,
PARSE_JSON(
REGEXP_EXTRACT(gen.result, r'```json\n([\s\S]*?)\n```')
) AS fields
FROM (
SELECT
uri,
AI.GENERATE(
CONCAT(
'以下の請求書テキストから下記フィールドをJSON形式で返してください。\n',
'- invoice_number: 請求書番号\n',
'- vendor_name: 請求先の会社名\n',
'- total_amount: 請求金額の合計(数値のみ、カンマなし)\n',
'- issue_date: 発行日(YYYY-MM-DD形式)\n',
'- payment_due_date: 支払期限(YYYY-MM-DD形式)\n\n',
content
),
endpoint => 'gemini-2.5-flash',
connection_id => '<<プロジェクトID>>.US.<<作成した接続名>>'
) AS gen
FROM AI.PARSE_DOCUMENT(
TABLE `<<プロジェクトID>>.<<データセット名>>.invoice_files`,
endpoint => 'projects/<<プロジェクト番号>>/locations/us/processors/<<プロセッサーID>>',
connection_id => '<<プロジェクトID>>.US.<<作成した接続名>>'
)
)
);

処理が完了すると parsed_invoices テーブルに構造化データが格納され、以降は通常のBigQueryテーブルとして扱えます。あとはデータサイエンスエージェントに「請求書データから取引先ごとの月次支払い傾向を分析してください」と投げるだけで、db_agentが parsed_invoices テーブルに対してSQLを生成し、ds_agentがグラフを描いてくれます。

つまずいたポイント・気づいたこと

① AI.PARSE_DOCUMENT はフィールド指定ではなくテキストチャンクを返す

最初、JSON スキーマを渡して直接フィールドを抽出できると思っていましたが、AI.PARSE_DOCUMENT の役割は OCR とテキストチャンキングです。構造化フィールドの抽出は後段の AI.GENERATE に任せる2ステップ構成が正しいアプローチです。AI.PARSE_DOCUMENT → AI.GENERATE のパイプラインと理解しておくと迷いません。

② Document AI Layout Parser プロセッサーが事前に必要

AI.PARSE_DOCUMENT を実行するには、Document AI コンソールで Layout Parser プロセッサーをあらかじめ作成しておく必要があります。プロセッサー ID を endpoint 引数に指定する形です。接続のサービスアカウントには roles/documentai.viewer(プロセッサー参照)と roles/documentai.apiUser(プロセス実行)の両方が必要です。roles/documentai.viewer だけでは “Permission denied” エラーになります。

③ 件数を絞るにはサブクエリで TABLE を渡す

AI.PARSE_DOCUMENT はテーブル値関数(TVF)なので、LIMIT を外側に書いても全件処理が走ります。件数を絞りたい場合はサブクエリ (SELECT * FROM table LIMIT N) を引数として渡すのが正しいアプローチです。これは AlloyDBのAI関数と共通の注意点です。

④ SAFE_CAST で型変換エラーに備える

AI.GENERATE が返す JSON の値は文字列なので、INT64 や DATE への変換が必要です。スキャン品質が低い PDF では null や変換できない文字列が返ることがあります。SAFE_CAST を使っておくと変換エラーでクエリ全体が落ちるのを防げます。

⑤ 処理コストとレイテンシはドキュメント数に比例する

AI.PARSE_DOCUMENT(Document AI 呼び出し)と AI.GENERATE(Gemini Enterprise Agent Platform 呼び出し)の2段階を経るため、毎回全件に対して実行するのは現実的ではありません。差分処理(前回処理日時以降に追加されたファイルのみ対象)や Cloud Workflows での定期バッチ実行と組み合わせるのが実用的な運用パターンです。

まとめ

BigQuery の AI.PARSE_DOCUMENT と AI.GENERATE を組み合わせることで、Cloud Storage 上のPDF請求書をSQLだけで構造化データに変換できました。

  • AI.PARSE_DOCUMENT は OCR・レイアウト解析・チャンキングを行うTVF。Document AI Layout Parser プロセッサーの事前作成が必要(プレビュー)
  • 構造化フィールドの抽出は後段の AI.GENERATE に任せる2ステップ構成が正しいアプローチ
  • AI.GENERATE のプロンプトに出力フォーマット(日付は YYYY-MM-DD、金額は数値のみ)を明示すると後処理が楽になる
  • 件数を絞るにはサブクエリで渡す・SAFE_CAST を使う——これはAlloyDB AI関数と共通の注意点
  • 全件一括処理はコストとレイテンシが大きくなるため、差分バッチ処理との組み合わせが現実的
  • 抽出済みのテーブルをデータサイエンスエージェントのdb_agentに渡せば、PDF → BigQuery → 自然言語分析のパイプラインが完成する

「PDFをBigQueryに取り込む前処理が面倒で非構造化ドキュメントの分析を後回しにしていた」という方には、AI.PARSE_DOCUMENT + AI.GENERATE のパイプラインはかなり有力な選択肢になりそうです。現在プレビュー段階のため本番適用にはもう少し待つ必要がありますが、データサイエンスエージェントと組み合わせた「PDF → 構造化 → 自然言語分析」の流れを今のうちに検証しておく価値は十分あります。ぜひ試してみてください!

システムサポートでは、Google Cloudの導入や活用を支援しております。

Google Cloudを導入したい・導入したけど使いこなせていない…という方は、お気軽にご相談ください!


Google Cloud 導入・活動支援に関するご相談はこちら

2026年6月3日 BigQueryの新AI関数 AI.PARSE_DOCUMENT でPDF請求書を構造化データに変換してみた!

Category Google Cloud

ご意見・ご相談・料金のお見積もりなど、
お気軽にお問い合わせください。

お問い合わせはこちら