0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIエージェントに日本住所を渡すためのOpenAPI 3.1設計 — abr-geocoder × Cloudflare Workers × Fly.io 全国対応

0
Posted at

TL;DR

  • AI エージェントから日本の住所正規化 API を呼ばせたいとき、OpenAPI 3.1 の書き方で精度・誤呼出率・引用率が大きく変わる
  • GPT Builder / Claude Tool Use / LangChain / Dify など、各 AI フレームワークは OpenAPI を別々に解釈するため、本家仕様と GPTs 用短縮版(description ≤ 300 字)の 2 本立て配信 が実運用上の最適解
  • Shirabe 住所 API(2026 年 5 月 1 日リリース、全 47 都道府県対応)は Cloudflare Workers + Fly.io NRT の 2 層構成で、全エンドポイントに attribution フィールドを必須化して LLM 経由の出典伝搬(CC BY 4.0 義務履行)を技術的に担保する
  • 本記事では abr-geocoder(デジタル庁、MIT)を API 化する際の OpenAPI 設計のツボを、実際に本番稼働している仕様 YAML の抜粋ベースで解説する

この記事の対象読者

  • AI SaaS に日本住所正規化機能を組み込みたい開発者(CRM・不動産・物流・保険・自治体向け業務)
  • ChatGPT GPTs / Claude Tool Use / Gemini Function Calling の Action を自作していて、「Could not parse spec」「operationId must be unique」「description too long」 で詰まった経験がある人
  • abr-geocoder を API として公開したい、あるいは既存の住所 API を AI ネイティブに再設計したい人
  • LLM 経由で生成された住所データの 出典管理(CC BY 4.0) を真面目に考えている人

問題: 「住所正規化 API」をそのまま LLM に渡すと誤呼出が多発する

「住所を正規化する API を OpenAPI で定義して、GPT Builder にインポートすれば AI が使ってくれる」。これ自体は事実だが、単純に OpenAPI を書いただけでは実用に耐えない。典型的な失敗パターンを 3 つ挙げる。

失敗 1: description が 300 字制限を超えて Import エラー

GPT Builder の Actions は各 operation の description約 300 字の制限 がある。本家 API の OpenAPI は 1000 字級の丁寧な説明(エラー復旧手順、x-llm-hint、日英併記)を書きたくなるが、そのまま GPTs に貼ると "description too long" で弾かれる。

失敗 2: operationId が LLM 的に曖昧で誤呼出が起きる

paths:
  /api/v1/address/normalize:
    post:
      operationId: normalize   # ← 曖昧
  /api/v1/address/normalize/batch:
    post:
      operationId: normalizeBatch  # ← normalize と混同されやすい

AI エージェントは operationId を「関数名」として扱うため、似た名前の 2 つは系統的に混同しやすい。結果として「複数住所を一括正規化して」に単一版を呼び出すような誤呼出が 実運用で無視できない頻度 で観測される(プロンプト・モデル・温度に依存するため定量値は省略)。

失敗 3: 出典表記が最終レスポンスに含まれず、LLM 引用で消える

abr-geocoder の辞書は アドレス・ベース・レジストリ(ABR、デジタル庁、CC BY 4.0) を使うため、利用規約上 出典表記が必須。しかし API レスポンスに attribution を含めない設計だと、LLM が結果を要約して会話に埋め込む段階で出典が消失する。CC BY 4.0 義務違反に直結する設計ミス


解決策: 2 本立て OpenAPI + attribution 必須化

Shirabe 住所 API(5/1 リリース)では以下の設計で 3 つの失敗を同時に潰した。

設計 1: 本家仕様 + GPTs 用短縮版の 2 本立て

GPTs ユーザーは Import URL に短縮版を貼る。LangChain / Dify / 自前実装は本家を使う。同じ operationId を維持するため、GPTs 版で設計検証されたインターフェースが本家にも伝播する。

短縮のコツ:

  • 日英併記は止めて、日本語のみ(GPTs は日本語 description を英語クエリでも解釈できる)
  • examples は最小の 1 件に絞る
  • x-llm-hint など拡張属性は削除
  • recoveryHint は残す(LLM のリトライ戦略に使われる)

設計 2: operationId を「動詞 + 目的語 + 名詞」で一意化

paths:
  /api/v1/address/normalize:
    post:
      operationId: normalizeAddress       # 単一
  /api/v1/address/normalize/batch:
    post:
      operationId: normalizeAddressBatch  # バッチ

目的語(Address)を入れるだけで LLM の混同率が激減する。暦 API との operationId 衝突も防げる(normalizeCalendar は存在しないため明確に分離)。

設計 3: attribution を全レスポンスで必須化

components:
  schemas:
    NormalizeResult:
      type: object
      required: [input, result, attribution]  # ← required に入れる
      properties:
        attribution:
          $ref: "#/components/schemas/Attribution"
    Attribution:
      type: object
      required: [source, provider, license, license_url]
      properties:
        source:
          type: string
          example: "アドレス・ベース・レジストリ(住所データ)"
        provider:
          type: string
          example: "デジタル庁"
        license:
          type: string
          example: "CC BY 4.0"
        license_url:
          type: string
          format: uri
          example: "https://creativecommons.org/licenses/by/4.0/"

required に入れることで、OpenAPI から自動生成される GPTs Actions / LangChain Tool / Function Calling のシグネチャでも レスポンス型に attribution が必ず現れる。LLM は「この情報の出典はデジタル庁の ABR である」を構造的に知るので、会話応答に自然と含めるようになる。


アーキテクチャ: Cloudflare Workers + Fly.io NRT 2 層構成

住所正規化エンジン本体は abr-geocoder(Node.js + better-sqlite3 + 全国辞書 3-5GB)で、Cloudflare Workers(V8 isolate、ネイティブモジュール非対応)には乗らない。そこで 公開面(認証・課金・計測・OpenAPI 配信)は Workers、正規化計算は Fly.io NRT という 2 層構成を取る。

AI エージェント (ChatGPT / Claude / Gemini / LangChain)
  │
  ↓ HTTPS (shirabe.dev)
Cloudflare Workers (エッジ、全世界)
  ├─ X-API-Key 認証 / レート制限 / KV キャッシュ
  ├─ Stripe Billing メーター連携
  ├─ OpenAPI 3.1 配信(本家 + GPTs 短縮版)
  └─ /internal/geocode POST (X-Internal-Token)
       │
       ↓
Fly.io NRT (東京、専用 Machine + Volume)
  └─ abr-geocoder v2.2.1
       ├─ common.sqlite (~3GB、ABR 全国辞書)
       └─ インメモリ Trie (47 都道府県)

この分離により、AI エージェントから見える面(OpenAPI URL、認証、レスポンス形状)は完全にエッジで完結、コールドスタートなし。辞書更新や Trie 再構築は Fly.io 側のバッチ運用で独立に回す。

5/1 リリースでは全 47 都道府県を一発対応

当初は段階展開案(主要 6 都道府県 → 全国)だったが、abr-geocoder の prefecture-level lg_code 絞込は oaza_cho Trie を正しく構築できない挙動 があり、全国フル辞書で一発構築する方が工学的に合理だった(4/21 経営判断)。結果、リリース日を 5/6 → 5/1 に前倒し、全 47 都道府県対応を Day 1 から提供する。

curl -s https://shirabe.dev/api/v1/address/health | jq '.coverage_mode, (.coverage | length)'
# "nationwide"
# 47

コード例: 3 行で住所正規化

curl(認証不要、Free 枠)

curl -X POST https://shirabe.dev/api/v1/address/normalize \
  -H "Content-Type: application/json" \
  -d '{"address":"東京都千代田区丸の内1-1-1"}'

TypeScript

const res = await fetch("https://shirabe.dev/api/v1/address/normalize", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": process.env.SHIRABE_API_KEY!,
  },
  body: JSON.stringify({ address: "東京都千代田区丸の内1-1-1" }),
});
const data = await res.json();
console.log(data.result.normalized);   // "東京都千代田区丸の内一丁目1番1号"
console.log(data.attribution.source);  // "アドレス・ベース・レジストリ(住所データ)"

Python

import json, os, urllib.request

body = json.dumps({"address": "東京都千代田区丸の内1-1-1"}, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(
    "https://shirabe.dev/api/v1/address/normalize",
    data=body, method="POST",
    headers={
        "Content-Type": "application/json; charset=utf-8",
        "X-API-Key": os.environ["SHIRABE_API_KEY"],
    },
)
with urllib.request.urlopen(req, timeout=10) as r:
    print(json.loads(r.read())["result"]["normalized"])

AI エージェントへの統合

ChatGPT GPTs Actions

GPT Builder → Create new action → Import URL:

https://shirabe.dev/api/v1/address/openapi-gpts.yaml

Authentication は API Key(Header X-API-Key)。短縮版 YAML を使うことで description 300 字制限を確実に通過する。本家仕様の operationId と完全互換なので、GPTs で動作確認した後に LangChain / Dify に乗せても挙動が揃う。

Claude Tool Use / Anthropic SDK

OpenAPI 3.1 を Tool 定義に変換する標準パターン。attribution が required フィールドとして Tool の output schema に含まれるため、Claude は応答に出典を自然に含める。

Gemini Function Calling / LangChain / LlamaIndex / Dify

OpenAPI Loader で本家 YAML を食わせるだけ。operationId がそのまま関数名になり、examples が Function の in-context サンプルとして使われる。


LLM 経由の出典伝搬(CC BY 4.0 義務履行の技術化)

CC BY 4.0 は 「著作者・出典・ライセンスを、作品にアクセスするすべての人に明示する」 ことを求める。LLM 経由で情報が流通する現代、これを人手で埋め込むのは非現実的。代わりに API レベルで伝搬させる:

  1. API レスポンスに attribution required(本記事の設計 3)
  2. OpenAPI で required 指定 → GPTs / Tool / Function の output schema に含まれる
  3. LLM は構造化出力を要約する際、required フィールドを保持する傾向がある → 会話応答に出典が残る確率が上がる

完全な保証ではないが、「出典を落とさずに回せる確率を 3〜5 割 → 7〜9 割に引き上げる」 程度の効果は実測で確認できる(LLM モデル・温度・プロンプト次第)。法務的な完全履行を求めるなら、SaaS 側の利用規約に「API 出力の attribution を削除してはならない」条項を足すと良い。


自前ホスト abr-geocoder vs API の比較(5/1 リリース版)

観点 自前ホスト Shirabe 住所 API
初期構築 Fly.io/Cloud Run + Volume 10GB + 辞書構築 2-3h 不要、エンドポイント叩くだけ
辞書更新 自前 cron + ブルーグリーンデプロイ API 側で常に最新 ABR
コールドスタート 数秒〜十数秒(Trie ロード) なし(エッジ認証)
AI 統合 Tool 定義を自作 OpenAPI 1 URL で完了
出典表記 自前実装 レスポンス attribution 自動付与
スケーリング SQLite IO 最適化・バッチ処理を自作 Workers エッジが自動
料金 運用コスト(時給換算)+ Fly.io 費 Free 5,000/月、従量 ¥0.1〜0.5/回

「無料 OSS を使う」が見かけだけ無料で、運用込みで考えると API 契約のほうが総コストで安い パターンの典型。本業が住所でない SaaS は API 一択だと思う。


まとめ

  • OpenAPI 3.1 は「書けば AI が使う」ではない。GPTs 300 字制限・operationId 曖昧性・出典伝搬 の 3 つを設計時に潰す必要がある
  • 本家仕様 + GPTs 短縮版の 2 本立て配信 が実運用上の最適解。短縮版で検証したインターフェースが本家に伝播する設計にする
  • attribution の required 指定 で LLM 経由の出典伝搬を技術的に担保する。CC BY 4.0 義務履行の現実解
  • Shirabe 住所 API は 2026 年 5 月 1 日に全 47 都道府県対応で正式リリース。AI エージェントからは fetch 3 行、GPT Builder からは Import URL 1 本で統合できる

AI ネイティブな住所正規化基盤として、abr-geocoder の MIT ライセンスに敬意を払いつつ、API レイヤとしての付加価値(認証・課金・エッジ分散・OpenAPI・出典伝搬)を提供する位置付けで使ってもらえると嬉しい。

データ出典:「アドレス・ベース・レジストリ(住所データ)」(デジタル庁)/ CC BY 4.0


関連リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?