Skip to content
Published on

LiteLLM 実践ガイド — 100以上の LLM を一つのインターフェースで

シェア
Authors

概要

LLM アプリを作り続けると、すぐ同じ壁に当たる。プロバイダーごとに SDK が違い、リクエストのパラメータが違い、レスポンスの形が違う。OpenAI 向けのコードで始めて Anthropic に移そうとすると、クライアントの初期化からメッセージのパースまで全部を書き直す羽目になる。LiteLLM はまさにこの摩擦を取り除く。

LiteLLM(BerriAI 製)は二つの部品からなる。一つは Python SDK、もう一つは Proxy Server(AI ゲートウェイ)だ。両者には共通の目標がある。100 以上の LLM API を OpenAI のリクエスト/レスポンス形式 で呼び出すことだ。その均一な表面の上に、コスト追跡、リトライ、ロードバランシング、フォールバック、ガードレール、ロギングといった運用機能が乗る。

この記事は、すばやく生産性を得るための 実践クイックスタート だ。インストールして最初の呼び出しを投げ、プロバイダーを差し替え、ストリーミングと Router、Proxy までを最短経路でたどる。LiteLLM の内部構造や本番運用の詳細設定を深く扱った LiteLLM 完全ガイド がすでにこのブログにあるので、仕組みや本番の細部が必要ならそちらを併せて読むとよい。この記事は意図的に軽く保つ。

まず一つの見方を先に固めておこう。LiteLLM は「もう一つのプロバイダー」ではなく、複数のプロバイダーの上に置く 単一の統合レイヤー だ。そしてその下には、OpenRouter のようにさらに数百のモデルを束ねるルーターを入れることもできる。つまり一つの LiteLLM 構成で、直接つないだプロバイダーと OpenRouter 経由のプロバイダーの両方を、同じコードで呼べる。

1. インストールと最初の呼び出し

インストールは一行だ。pip でも uv でも違いはない。

pip install litellm
# uv を使う場合
uv add litellm

最小の呼び出しは次のとおり。completion 関数一つに modelmessages を渡すだけで、レスポンスは OpenAI の形そのままで返る。

from litellm import completion
import os

os.environ["OPENAI_API_KEY"] = "sk-..."

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

注目したいのはアクセスの仕方だ。resp.choices[0].message.content は、OpenAI SDK を使ったことがあれば見慣れたあの構造だ。どのプロバイダーを呼んでもこの形が保たれるので、レスポンスを扱うコードは一度だけ書けばよい。

SDK の段階でも、簡単な安定性オプションをそのまま渡せる。num_retries でリトライ回数を、timeout で応答の待ち時間を指定すれば、一時的なエラーに少し強くなる。これらのオプションは Router なしの単一呼び出しでも動く。

from litellm import completion

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    num_retries=2,
    timeout=30,
)
print(resp.choices[0].message.content)

2. 複数プロバイダーを同じコードで呼ぶ

プロバイダーを切り替える方法は二段階だけだ。model 文字列の 接頭辞(prefix) を変え、そのプロバイダーの 環境変数 を設定する。それで全部だ。メッセージの構造も、レスポンスのパースもそのままでよい。

プロバイダーmodel 接頭辞の例環境変数
OpenAIopenai/gpt-4oOPENAI_API_KEY
Anthropicanthropic/claude-3-5-sonnet-20241022ANTHROPIC_API_KEY
Gemini (AI Studio)gemini/gemini-1.5-proGEMINI_API_KEY
Vertex AIvertex_ai/gemini-1.5-proGCP 認証情報
AWS Bedrockbedrock/...AWS 認証情報
Azure OpenAIazure/...Azure 設定
Ollama (ローカル)ollama/llama3なし(ローカル実行)

たとえば Anthropic に移すとこうなる。

from litellm import completion
import os

os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..."

resp = completion(
    model="anthropic/claude-3-5-sonnet-20241022",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

ローカルモデルで試したいなら、Ollama を立ち上げて接頭辞を ollama/ に変えるだけだ。API キーもいらない。

from litellm import completion

resp = completion(
    model="ollama/llama3",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

この構造のおかげで、「開発はローカル Ollama、ステージングは Gemini、本番は Bedrock」といった切り替えを、コード変更なしに環境変数とモデル文字列だけで扱える。

3. LiteLLM で OpenRouter を使う

ここがこの記事でとくに強調したい組み合わせだ。OpenRouter は、それ自体で 300 以上のモデルを一つの API に束ねるサービスだ。LiteLLM をその上に重ねると、OpenRouter の広いモデルカバレッジに、LiteLLM の均一なインターフェースと運用機能(リトライ、フォールバック、コスト追跡)を合わせて得られる。

方法はこれまでと同じパターンだ。OPENROUTER_API_KEY を設定し、モデル文字列を openrouter/ で始めて、その後ろに OpenRouter のモデル id を付ける。

from litellm import completion
import os

os.environ["OPENROUTER_API_KEY"] = "sk-or-..."

resp = completion(
    model="openrouter/openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

接頭辞を分解すると分かりやすい。先頭の openrouter/ は LiteLLM に「OpenRouter 経由で送れ」と指示し、後ろの openai/gpt-4o は OpenRouter が認識するモデル id だ。つまり openrouter/ の後ろには、OpenRouter が対応するどのモデル id でもそのまま置ける。

この組み合わせの実用的な利点は明快だ。直接契約したいくつかのプロバイダーは LiteLLM に直結し、それ以外の実験的で多様なモデルは OpenRouter 経由でつなぎつつ、アプリのコードは全部同じ completion 呼び出しに保てる。モデルを増やす際の統合コストが実質ゼロに近づく。

4. ストリーミングと非同期

トークンをリアルタイムで流すには、stream=True を足して戻り値を反復するだけだ。デルタ(delta)から断片を取り出してつなげる形になる。

from litellm import completion

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Write a haiku about the sea"}],
    stream=True,
)

for chunk in resp:
    print(chunk.choices[0].delta.content or "", end="")

delta.contentNone で届くチャンクもあるので、or "" で守るのが慣例だ。こうすれば空の断片で例外が起きない。

非同期コードでは acompletion を使う。シグネチャは completion と同じで、await を付けるだけでよい。

import asyncio
from litellm import acompletion

async def main():
    resp = await acompletion(
        model="anthropic/claude-3-5-sonnet-20241022",
        messages=[{"role": "user", "content": "Hello"}],
    )
    print(resp.choices[0].message.content)

asyncio.run(main())

ストリーミングと非同期は組み合わせて使える。FastAPI のような非同期 Web フレームワークでトークンを Server-Sent Events として流すとき、この二つを一緒に使うことになる。

5. Router — ロードバランシングとフォールバック

呼び出し量が増えて安定性が重要になると、SDK の Router クラスが登場する。Router は複数のデプロイ(deployment)を一つのエイリアス(alias)にまとめ、リクエストを分散し、失敗を自動で吸収する。

まず model_list を定義する。各エントリは外向きの名前 model_name と、実際の呼び出しパラメータ litellm_params を持つ。同じ model_name を複数のデプロイに付けると、Router はそのエイリアス宛のリクエストを複数のデプロイに振り分ける。

from litellm import Router

model_list = [
    {
        "model_name": "my-gpt",
        "litellm_params": {
            "model": "openai/gpt-4o",
            "api_key": "sk-...",
        },
    },
    {
        "model_name": "my-gpt",
        "litellm_params": {
            "model": "azure/gpt-4o-deployment",
            "api_key": "...",
            "api_base": "https://your-resource.openai.azure.com",
        },
    },
]

router = Router(model_list=model_list)

resp = router.completion(
    model="my-gpt",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

Router の本当の価値はフォールバック(fallback)にある。失敗の種類に応じて三つのフォールバックを分けて設定できる。

フォールバックの種類トリガー備考
fallbacks429/500 系のエラー別のモデルへ回す
context_window_fallbacksコンテキスト超過enable_pre_call_checks=True が必要
content_policy_fallbacksコンテンツポリシー拒否別のモデルへ迂回

三つをまとめて設定した例は次のとおり。コンテキストのフォールバックを使うには事前チェックを有効にする必要がある、という点だけ注意すればよい。

from litellm import Router

router = Router(
    model_list=model_list,
    fallbacks=[{"my-gpt": ["claude"]}],
    context_window_fallbacks=[{"my-gpt": ["claude-long"]}],
    content_policy_fallbacks=[{"my-gpt": ["safe-model"]}],
    enable_pre_call_checks=True,
)

このほかにも Router はリトライ(retries)、クールダウン(cooldowns)、タイムアウト(timeouts)を合わせて管理する。あるデプロイが失敗し続けると、しばらくクールダウンのリストに入れてトラフィックを送らず、一定時間後に復帰させる、という具合だ。アプリのコードは相変わらず router.completion(model="my-gpt", ...) の一行でよい。

6. Proxy Server (AI ゲートウェイ)

複数のチームやアプリが一つのゲートウェイを共有する必要があるなら、Proxy Server が答えだ。Proxy は別プロセスとして立ち上がり、どんな OpenAI 互換クライアントも受け付けるゲートウェイとして働く。

まず config.yamlmodel_list を書く。API キーは os.environ/ で環境変数を参照させるのが安全だ。

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY
  - model_name: claude
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022
      api_key: os.environ/ANTHROPIC_API_KEY

次に設定ファイルを指定して proxy を起動する。デフォルトでは http://0.0.0.0:4000 で待ち受ける。

litellm --config config.yaml

あとは、どんな OpenAI 互換クライアントでも base URL を proxy に向け、ダミー/ベースのキーを渡せばそのまま動く。実際のプロバイダーキーは proxy が config から保持しているので、クライアントには露出しない。

from openai import OpenAI

client = OpenAI(
    base_url="http://0.0.0.0:4000",
    api_key="anything",  # proxy が管理する仮想キー/ダミーキー
)

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

Proxy レイヤーが加えるものは、単なるプロキシを超える。チームごとの仮想キー(virtual keys)の発行、予算(budgets)の上限、コスト追跡のダッシュボード、リクエストのロギングが、すべてゲートウェイで中央管理される。アプリは実際のプロバイダーキーを知らなくてよく、運用チームは誰がどれだけ使ったかを一箇所で見られる。

7. SDK と Proxy の使い分け、そして実践のヒント

どちらを使うかは規模で分かれる。

  • SDK を使うとき: 単一のアプリやスクリプト。ライブラリを import してコードの中で直接呼べばよく、別のインフラは要らない。個人プロジェクト、バッチ処理、単一サービスに向く。
  • Proxy を使うとき: 複数のチームやアプリが一つのゲートウェイを共有する必要があるとき。中央集約のキー管理、予算、コストダッシュボードが要るなら Proxy が正解だ。組織規模の LLM プラットフォームを作るときは自然とこの方向に向かう。

実践で役立つ点をいくつかまとめる。

  1. レスポンスコードは一度だけ: どのプロバイダーを呼んでも resp.choices[0].message.content の形が保たれるので、レスポンスのパースをプロバイダーごとに書き直す必要はない。
  2. キーは環境変数で: SDK でも proxy の config でも、キーをコードに埋め込まず環境変数から注入する。proxy では os.environ/ 参照を使う。
  3. モデル文字列がスイッチ: プロバイダーの切り替えは接頭辞の差し替えと環境変数の設定、この二つで全部だ。デプロイ環境ごとにモデル文字列だけを変える戦略がよく効く。
  4. OpenRouter を隣に置く: よく使うプロバイダーは直結し、実験的で多様なモデルは openrouter/ 接頭辞で付ければ、統合コストなしに選択肢を広げられる。
  5. 小さく始めて育てる: 最初は SDK で始め、チームが増えて中央管理が必要になった時点で、同じ model_list の考え方をそのまま Proxy の config に移せばよい。

まとめると、LiteLLM はプロバイダーの分断をコードの外へ押し出す。最初の呼び出しは数行で済み、必要になった瞬間に Router で安定性を、Proxy で組織レベルの統制を足せる。より深い内部動作と本番の詳細設定は、LiteLLM 完全ガイド で続けて読むとよい。

参考資料