8.2.4 統一 API インターフェース
- なぜマルチモデルシステムに統一 API 層が必要なのかを理解する
- 統一 API インターフェースが、実務上どこを楽にするのかを理解する
- 最小限の provider 抽象化の例を読めるようになる
- 統一 API は「すべてのモデルが完全に同じになる」ことではないと理解する
まずは全体像をつかもう
Section titled “まずは全体像をつかもう”ローカルモデルの実行や推論サービスを学んでいるなら、この節は自然な続きです。
- これまでに、モデルがどのように読み込まれ、サービス化されるかを学びました
- この節では、システムが複数のモデル / 複数の provider を受けるようになったとき、上位の業務コードをどう散らからせないかを考えます
なので、統一 API の節で本当に大事なのは「ただもう1枚ラッパーをかぶせること」ではなく、次の点です。
- マルチモデルシステムに安定した入口層を作ること
初心者にとって、統一 API は「もう1枚ラッパーをかぶせること」と捉えるより、まず次の図でイメージすると分かりやすいです。
flowchart LR A["複数の provider / モデル"] --> B["パラメータ名と戻り値の構造が違う"] B --> C["業務層のコードがどんどん乱れる"] C --> D["統一 API 層が差分を吸収する"] D --> E["上位の業務は安定したインターフェースだけを見る"]この節が本当に解決したいのは、次のようなことです。
- なぜマルチモデルシステムには、自然と抽象化の層が必要になるのか
- なぜ業務コードが provider ごとの差分をあちこちで知ってはいけないのか
初心者向けの、よりよいたとえ
Section titled “初心者向けの、よりよいたとえ”統一 API は次のように考えると理解しやすいです。
- いろいろなブランドのプラグに使う、共通の変換アダプター
この変換アダプターがなければ、
上位の業務コードは次のようになってしまいます。
- ここでは A 社向けの対応
- そこでは B 社向けの対応
- 別の場所ではローカルモデル向けの対応
最終的にシステムはどんどん分裂していきます。
統一 API の一番大きな価値は、こうした差分を1つの層に集めることです。
一、なぜ統一 API が重要になるのか?
Section titled “一、なぜ統一 API が重要になるのか?”モデルが1つだけなら、まだ目立たない
Section titled “モデルが1つだけなら、まだ目立たない”プロジェクトにモデルが1つしかないなら、シンプルな client だけでも十分なことが多いです。
ひとたびマルチモデル / マルチ provider になると
Section titled “ひとたびマルチモデル / マルチ provider になると”次のような問題に直面します。
- A モデルは
messagesを使う - B モデルは
promptを使う - あるものは
contentを返す - 別のものは
output_textを返す - token の集計項目も違う
こうなると、業務コードは一気に分かりにくくなります。
なので、統一 API の核心は、まず次のように覚えるとよいです。
provider の違いを1つの層に閉じ込め、業務層があちこちでその差分を知る必要をなくすこと。
最初に学ぶとき、何をつかむべきか?
Section titled “最初に学ぶとき、何をつかむべきか?”最初に押さえるべきなのは、「どれだけ美しい抽象化か」ではなく、次の一文です。
統一 API の核心は、モデル差分を切り離し、業務層が安定したインターフェースだけに触れられるようにすること。
この感覚がつかめると、あとで見る次のようなものも自然に理解できます。
- provider アダプタ
- ルーティング
- fallback
- 統一ログ
それらが、なぜこの層に載るのかも分かりやすくなります。
二、統一 API のよくある目的は何か?
Section titled “二、統一 API のよくある目的は何か?”通常、少なくとも次のものが含まれます。
- リクエスト構造の統一
- レスポンス構造の統一
- エラー処理の統一
- ログと トレース の統一
最小限の統一リクエスト構造
Section titled “最小限の統一リクエスト構造”request = { "provider": "demo_provider", "model": "demo-chat-model", "query": "返金ポリシーは何ですか?"}
print(request)期待される出力:
{'provider': 'demo_provider', 'model': 'demo-chat-model', 'query': '返金ポリシーは何ですか?'}最小限の統一レスポンス構造
Section titled “最小限の統一レスポンス構造”response = { "provider": "demo_provider", "model": "demo-chat-model", "answer": "コース購入後7日以内かつ学習進捗が20%未満であれば返金できます。", "usage": { "prompt_tokens": 24, "completion_tokens": 18 }}
print(response)期待される出力:
{'provider': 'demo_provider', 'model': 'demo-chat-model', 'answer': 'コース購入後7日以内かつ学習進捗が20%未満であれば返金できます。', 'usage': {'prompt_tokens': 24, 'completion_tokens': 18}}こうしておくと、次の利点があります。
- 上位の業務ロジックが、1つの安定した構造だけを見ればよい
初学者がまず覚えるのに向いている統一表
Section titled “初学者がまず覚えるのに向いている統一表”| 層 | 先に統一するもの |
|---|---|
| リクエスト | クエリ / model / provider / パラメータ形式 |
| レスポンス | answer / usage / error |
| ログ | トレース_id / provider / レイテンシ / token |
| エラー | error_code / message / retryable |
この表は初心者に向いています。
「統一 API」という抽象的な言葉を、見える対象のまとまりに戻してくれるからです。

三、最小限の provider 抽象化の例
Section titled “三、最小限の provider 抽象化の例”class ProviderA: def chat(self, query, model): return { "text": f"A-provider reply: {query}", "tokens": 30 }
class ProviderB: def generate(self, prompt, model_name): return { "output_text": f"B-provider reply: {prompt}", "usage": {"total_tokens": 28} }この2つを業務コードから直接それぞれ呼ぶようにすると、コードはどんどんバラバラになります。
四、統一アダプタ層は何をしているのか?
Section titled “四、統一アダプタ層は何をしているのか?”異なる provider を同じ構造に翻訳する
Section titled “異なる provider を同じ構造に翻訳する”class ProviderA: def chat(self, query, model): return { "text": f"A-provider reply: {query}", "tokens": 30 }
class ProviderB: def generate(self, prompt, model_name): return { "output_text": f"B-provider reply: {prompt}", "usage": {"total_tokens": 28} }
class UnifiedClient: def __init__(self): self.providers = { "provider_a": ProviderA(), "provider_b": ProviderB() }
def chat(self, provider, query, model): if provider == "provider_a": raw = self.providers[provider].chat(query=query, model=model) return { "provider": provider, "model": model, "answer": raw["text"], "usage": {"total_tokens": raw["tokens"]} }
if provider == "provider_b": raw = self.providers[provider].generate(prompt=query, model_name=model) return { "provider": provider, "model": model, "answer": raw["output_text"], "usage": raw["usage"] }
return {"error": "unknown_provider"}
client = UnifiedClient()print(client.chat("provider_a", "返金ポリシーは何ですか?", "demo-1"))print(client.chat("provider_b", "返金ポリシーは何ですか?", "demo-2"))期待される出力:
{'provider': 'provider_a', 'model': 'demo-1', 'answer': 'A-provider reply: 返金ポリシーは何ですか?', 'usage': {'total_tokens': 30}}{'provider': 'provider_b', 'model': 'demo-2', 'answer': 'B-provider reply: 返金ポリシーは何ですか?', 'usage': {'total_tokens': 28}}このコードで本当に大事なのは文法ではなく、層の分け方
Section titled “このコードで本当に大事なのは文法ではなく、層の分け方”ここで伝えたいのは、次のことです。
- provider ごとの差分は、できるだけ統一アダプタ層に閉じ込める
- 上位の業務コードは、できるだけ統一インターフェースだけを見る
これが、「統一 API」の実務上いちばん大きな価値です。
なぜこの層はログ・統計・ルーティングを担うのに向いているのか?
Section titled “なぜこの層はログ・統計・ルーティングを担うのに向いているのか?”この層は、すべてのリクエストが通る入口に位置しているからです。
そのため、次のような機能を置くのにとても向いています。
- token / コストの集計
- トレース とログ
- provider fallback
- モデルルーティング
最小限の「統一エラー構造」の例を見てみよう
Section titled “最小限の「統一エラー構造」の例を見てみよう”def normalize_error(provider, error_type, message): return { "provider": provider, "ok": False, "error": { "type": error_type, "message": message, "retryable": error_type in {"timeout", "rate_limit"}, }, }
print(normalize_error("provider_a", "timeout", "request timed out"))期待される出力:
{'provider': 'provider_a', 'ok': False, 'error': {'type': 'timeout', 'message': 'request timed out', 'retryable': True}}この例は初心者にとても役立ちます。
なぜなら、次のことに気づけるからです。
- 実は、保守が難しいのは成功レスポンスだけではない
- provider ごとにエラーの出方が違っても、上位に同じ契約を返す必要がある
五、なぜ統一 API は「すべてのモデルが完全に同じ」という意味ではないのか?
Section titled “五、なぜ統一 API は「すべてのモデルが完全に同じ」という意味ではないのか?”これは、とても誤解されやすい点です。
統一 API の目的は、すべてのモデルに違いがないふりをすることではありません。
目的は次の通りです。
共通部分を抜き出し、違いを限られた境界の中に収めること。
たとえば、異なるモデルには今でも次のような差があります。
- コンテキスト長
- ツール呼び出しの対応可否
- マルチモーダル能力
- 出力形式の制約能力
つまり、統一 API は次のようなものです。
- 入口を統一する
- 能力まで統一するわけではない
六、なぜ routing がこの層に自然に現れるのか?
Section titled “六、なぜ routing がこの層に自然に現れるのか?”統一 API 層ができると、次の疑問が自然に出てきます。
- どの種類のリクエストを、どのモデルに送るべきか?
- 安いモデルで十分か?
- リスクの高いリクエストは、より強いモデルに回すべきか?
シンプルなルーティング例
Section titled “シンプルなルーティング例”def route_model(query): if "要約" in query or "書き換え" in query: return "provider_a", "cheap-model" return "provider_b", "strong-model"
for q in ["この文章を要約して", "返金ポリシーは何ですか?"]: print(q, "->", route_model(q))期待される出力:
この文章を要約して -> ('provider_a', 'cheap-model')返金ポリシーは何ですか? -> ('provider_b', 'strong-model')統一 API 層は、このような「モデルルーティングの入口」としてとても適しています。
七、統一 API 層のよくある実務上のメリット
Section titled “七、統一 API 層のよくある実務上のメリット”モデル切り替えがしやすい
Section titled “モデル切り替えがしやすい”各業務モジュールを1つずつ変更しなくてよくなります。
ログ収集とコスト集計がしやすい
Section titled “ログ収集とコスト集計がしやすい”すべてのリクエストが同じ入口を通るからです。
グレーリリースや fallback がしやすい
Section titled “グレーリリースや fallback がしやすい”たとえば次のような運用です。
- メインモデルが失敗したら、予備モデルに切り替える
- 特定のリクエストだけ安いモデルに回す
これらは、統一入口があるととてもやりやすくなります。
初学者がまず覚えるのに向いた選択表
Section titled “初学者がまず覚えるのに向いた選択表”| システムで起きること | 先に整えるもの |
|---|---|
| provider が増えていく | リクエスト / レスポンスの統一 |
| ログが読みにくくなる | トレース と統一ログ |
| コストの把握が難しい | usage の統一 |
| モデル切り替えがつらい | routing と fallback |
この表は初心者に向いています。
「なぜ統一 API が必要なのか」を、実際の現場の悩みと直接つなげてくれるからです。
初心者が最初にマルチモデルシステムを作るときの、いちばん安定した順番
Section titled “初心者が最初にマルチモデルシステムを作るときの、いちばん安定した順番”より安定しやすい順番は、通常次の通りです。
- まずリクエスト構造を統一する
- 次にレスポンス構造を統一する
- その後でエラーとログを統一する
- 最後にモデルルーティングを考える
こうすると、最初から複雑な routing を作るより、インターフェース層が安定しやすくなります。
九、よくある誤解
Section titled “九、よくある誤解”統一 API を作れば、すべてのモデル差が消えると思う
Section titled “統一 API を作れば、すべてのモデル差が消えると思う”消えません。
違いは残ります。
ただし、その違いをより扱いやすく整理するのです。
早すぎる段階で、重すぎる設計をしてしまう
Section titled “早すぎる段階で、重すぎる設計をしてしまう”プロジェクトに provider が1つしかないなら、過剰な抽象化はかえって負担になります。
入出力だけ統一して、エラー構造とログを統一しない
Section titled “入出力だけ統一して、エラー構造とログを統一しない”これだと、あとで障害対応がやはりつらくなります。
このページを終えたら、この証拠カードを残します。
- ランタイム選択
- ローカルモデル、推論サーバー、または統合 API
- リクエスト契約
- エンドポイント、payload、出力形式、エラー形状
- レイテンシまたはコスト
- 1つの測定値または推定値
- 失敗確認
- タイムアウト、メモリ圧迫、モデル不一致、またはバージョンずれ
- ロールバック計画
- フォールバックモデル、リトライ方針、またはトラフィック切り替え
この節で最も大事なのは、UnifiedClient を書くことではなく、次を理解することです。
統一 API 層の核心は、複数 provider の差分を限られた境界に収め、上位の業務が安定した契約に触れられるようにすること。
この土台ができると、マルチモデル routing、fallback、コスト最適化のような実務的な機能も、ずっと作りやすくなります。
この節で持ち帰るべきこと
Section titled “この節で持ち帰るべきこと”- 統一 API は、コードの書き方ではなく、実務上のレイヤリング
- 価値は、違いを1つの層に圧縮すること
- 複数モデル・複数 provider が出てきたら、この層はほぼ自然に必要になる
これをプロジェクトやシステム設計として見せるなら、何を見せるとよいか
Section titled “これをプロジェクトやシステム設計として見せるなら、何を見せるとよいか”本当に見せる価値があるのは、たいてい次のようなものです。
- 統一前と統一後で、呼び出しがどう変わるか
- リクエスト / レスポンス / エラー構造がどう収束していくか
- routing と fallback が、なぜこの層に自然に載るのか
- この層が、コスト集計とログ管理にどう役立つのか
こうすると、見る人にも次のことが伝わりやすくなります。
- 単にクラスを包んだのではなく、入口層のシステム価値を理解している
- ということです
UnifiedClientに、さらに統一エラー構造を追加してみましょう。- どうして統一 API は「能力の統一」ではなく「入口の統一」だと言えるのでしょうか?
- システムがまだ1つのモデルしか使っていないなら、なぜ重い統一抽象化を早く作りすぎないほうがよいのでしょうか?
- 自分の言葉で説明してみましょう。なぜ統一 API 層は、モデルルーティングと fallback を受け持つのに向いているのでしょうか?
参考実装と解説
- 例として
{ok: false, error: {code, message, retryable, provider, request_id}}のような構造を返し、provider 固有の例外を業務コードへ直接漏らさないようにします。 - 統一 API がそろえるのは呼び出し入口と結果処理です。provider ごとの能力、コンテキスト長、tool、コスト、レイテンシはまだ違います。
- 早すぎる重い抽象化は provider 固有の便利な機能を隠し、実際の差分が少ない段階で保守コストを増やします。
- この層は provider の状態、モデルのコスト/レイテンシ、リクエスト形状、共通エラー意味論を見られるため、routing と fallback を置きやすいです。