プログラミング

LLM APIの呼び出しとAPIキー管理:Gemini・GPT・Claude

LLMを使うアプリ開発のためには、LLMのAPIを呼び出すことが必要になります。この記事では、APIキーを使って、PythonからLLMのAPIを呼び出す方法をまとめます。

まずhttpxで各社(Google、OpenAI、Anthropic)のAPIを直接叩いてHTTPでメッセージを取得するための流れを押さえ、次にLiteLLMという統一ライブラリで、Gemini・GPT・Claudeを同じ関数から呼べるようにします。両方を並べると、ライブラリが何を肩代わりしてくれるのかが具体的に見えてきます。

この記事はPythonの実行環境が用意できている前提で進めます。まだの場合は、当サイトの「Python Install ManagerでのPython環境構築」や「uvのインストールと使い方」を先にご覧ください。

動作確認

Python 3.12/httpx 0.28.1/litellm 1.91.0/python-dotenv 1.2.2で確認しています。モデル名・料金・APIの仕様は変化が速いため、本文の記述は2026年7月時点のものです。

目次

はじめに

この記事の目的は、LLMのAPIを、キーの安全に注意しつつ自分で呼べるようになることです。具体的には、次の3つになります。

  • APIキーを取得して環境変数で取り扱う。
  • httpxで各社のAPIを直接叩いて、リクエストとレスポンス・messages・キーの仕組みを理解する。
  • LiteLLMで同じ呼び出しを短く書き、プロバイダをまたいで使い回せる汎用関数を用意する。

扱うモデルはGemini(Google)・GPT(OpenAI)・Claude(Anthropic)の3社です。本記事のサンプルコードを動かすには以下のライブラリが必要です。

pip install httpx litellm python-dotenv

# uvの場合
uv add httpx litellm python-dotenv

APIキーの取得と管理

キーの取得

LLMのAPIを呼ぶには、各社のコンソールでAPIキーを発行します。3社とも「サインイン→キーを作成→表示された文字列をコピー」という流れで、個人利用の場合には次のとおりです (2026年7月時点。画面や課金の条件は変わることがあるので、最新は各リンク先で確認してください)。

プロバイダ取得方法
Gemini(Google)Google AI StudioにGoogleアカウントでサインインし、左メニューの「Get API key」からキーを作成します。無料枠があり、まず試すだけならクレジットカードは不要です(地域によっては課金設定が必要な場合があります)。作成後もこの画面でキーを再表示できます。
GPT(OpenAI)OpenAI Platformにサインインし、API keysの画面で「Create new secret key」を押します。キーは作成時に一度しか表示されないので、その場でコピーしてください。利用には支払い方法の登録が必要です。
Claude(Anthropic)Anthropic Consoleにサインインし、支払い方法を登録してから、API Keysの画面でキーを作成します(こちらも作成時に一度だけ表されます)。

※ 無料枠があるプロバイダ以外の場合は有料ですのでご注意ください。

企業向け基盤サービス

ここで説明したのは、各社と直接契約してキーを取得する方法です。同じモデルは、クラウド事業者の企業向け基盤からも使えます。この場合は契約先がクラウドアカウントになり、エンドポイントも認証も個人利用の場合とは変わります。OpenAI系ならMicrosoft AzureのAzure OpenAI、Gemini系ならGoogle Cloud、Claude系ならAWSのAmazon Bedrockになります。

プロジェクト作成・リージョン指定・モデルの利用申請といった準備も要ります。そのぶん、ネットワーク制御・監査ログ・権限管理といった企業向けの統制が得られます。個人や小規模で試すなら本記事の直接契約が簡単で、こうした統制が必要なら基盤経由を検討する、という使い分けです。

呼び出し方は、後述の「企業向けのクラウド基盤で呼ぶ」で簡単に扱います。

管理方法

APIキーは課金と結びついた「ユーザーの資格情報」なので、扱いには特に注意が必要です。

やってはいけないのは、APIキーをLLMのチャットやコードに直接書き込むこととそのままGitにコミットしてしまうことなどで第三者から見られるようにすることです。LLMや公開リポジトリに一度でも混入すると、第三者に使われて課金が発生する恐れがあります。

たとえ一瞬だったとしてもAPIキーを外部に出してしまった場合には、即座にAPIキーを無効化して再発行しなければなりません(botで自動的に回収されている可能性があります)。「エラーメッセージの中にAPIキーが含まれていないか」も要注意で、エラー解決のためにLLMに渡してしまうなども漏洩の原因になり得ます。

最も安全なのは、有料のシークレットサービスを使うことです。AWS Secrets ManagerやGoogle Secret Managerがあります。それ以外で、安全に扱うための定番の方法は、キーを環境変数として渡すことです。プロジェクト直下に.envファイルを作り、そこにキーを書きます。

# .env
OPENAI_API_KEY=...
ANTHROPIC_API_KEY=...
GEMINI_API_KEY=...

そして、gitリポジトリに間違って追加されないように、.gitignoreを作成してGitの追跡対象から外します。

# .gitignore
.env

Pythonからは、python-dotenvモジュールで.envを読み込むと、中身が環境変数として使えるようになります。

import os
from dotenv import load_dotenv

load_dotenv()  # カレントディレクトリの.envを読み込み、環境変数に載せる
print(os.environ["OPENAI_API_KEY"][:8], "...")  # 先頭だけ確認(全体は表示しない)

こうしておけば、コードには「キーの値」ではなく「環境変数の名前」しか登場しません。以降のコードもこの前提で書いていきます。

なお、load_dotenv()は既定でスクリプト付近の.envを自動で探しますが、別の場所に置いた場合はload_dotenv(dotenv_path="パス")のようにパスを指定できます。

LLM API呼び出しの基本

LLMのAPIは、こちらからリクエストを送り、モデルの応答をレスポンスとして受け取る、という流れで動きます。会話の内容は、主にシステムプロンプトユーザープロンプトモデルの応答から構成されます。

システムプロンプトは、モデルにどう振る舞ってほしいか(役割・口調・守ってほしいルール)をあらかじめ伝える指示です。ユーザープロンプトは、それを踏まえて実際に投げかける質問や依頼です。また、モデルの応答は送信したプロンプトに対してLLMのモデルから返される返事になります。

会話を続けるときは、この返事も含めて、やり取りをメッセージに積み重ねて毎回送ります。これはAPIは前回の会話を覚えていないためです。

送信するデータにはプロンプトのほかにも、呼び出し時に渡せるパラメータがあります。その代表が、「出力のばらつきを調整」するtemperatureです。低いほど毎回似た答えになり、高いほど表現が多様になります。ただし、このパラメータの扱いはモデルによって差があります。

プロンプトを送る際にどんなキー名・構造で表現されるかは、APIごとに少し異なります。たとえば、OpenAIの場合には次のようになります。

messages = [
    {
        "role": "system", 
        "content": "あなたは簡潔に答えるアシスタントです。日本語で答えてください。"
    },
    {
        "role": "user", 
        "content": "RAGとは何ですか。1文で説明してください。"
    },
]

上の例では、「簡潔に答えるアシスタントとして日本語で答える」がシステムプロンプト、「RAGとは何ですか」がユーザープロンプトにあたります。

料金も、一度に扱えるテキストの量の上限も、トークンという単位で決まります。トークンは、モデルが文章を処理するときの文字のかたまりの単位です。「何を1トークンとするか」はモデルによって異なり、同じ文でもトークン数は変わります。おおまかには、入力(送った文章)と出力(返ってきた文章)のトークン数に応じて料金がかかり、モデルごとに一度に扱えるトークン数の上限もあります。だから、無駄に長いプロンプトはコストにもテキスト量の上限にも効いてきます

トークン化(トークナイザ)

テキストをトークンに区切る処理と、その区切り方を決める部品をトークナイザと呼びます。多くの現代的なLLMは、単語より細かく文字より大きい単位に区切る「サブワード分割」という考え方を採用しています(代表的な方式にBPE・Unigram・WordPieceなどがあります)。そのため1トークンは「1文字」でも「1単語」でもなく、その中間の大きさになります。トークナイザや語彙はモデルごとに違うため、本文で触れたとおり同じ文でもトークン数が変わります。

httpxで各社のAPIを直接叩く

まずはライブラリに頼らず、httpxでHTTPリクエストを直接送ってみます。httpxはPythonのHTTPクライアントで、URL・ヘッダー・ボディを自分で組み立ててPOSTし、返ってきたJSONを自分で読み解きます。

同じ「LLMにmessagesを送って返事をもらう」という目的でも、各社でリクエストの形が異なります。違いは主に次の5点です(個人契約の場合)。

項目OpenAI(GPT)Anthropic(Claude)Google(Gemini)
エンドポイントhttps://api.openai.com/v1/chat/completionshttps://api.anthropic.com/v1/messageshttps://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent
認証ヘッダAuthorization: Bearer <キー>x-api-key: <キー>(+anthropic-versionx-goog-api-key: <キー>
会話の渡し方messages配列messages配列(systemは別枠)contents配列(本文はpartsの下)
出力上限の指定max_completion_tokensmax_tokensgenerationConfig.maxOutputTokens
応答本文の場所choices[0].message.contentcontent[0].textcandidates[0].content.parts[0].text

以下に、各社のAPIを個別にhttpxで呼ぶコードを示します。会社ごとに独立しているので、使う会社のぶんだけコピーして動かせます。どれも「環境変数からキーを読む → リクエストを組み立てる → POSTする → 返ってきたJSONから本文を取り出す」という同じ流れになっていて、違うのは上の表の5項目です。

# Google(Gemini)をhttpxで呼ぶ
import os
import httpx
from dotenv import load_dotenv

load_dotenv()  # .envからGEMINI_API_KEYを読み込む

model = "gemini-2.5-flash-lite"  # 使うモデル名
body = {
    "systemInstruction": {       # システムプロンプト
        "parts": [{"text": "あなたは簡潔に答えるアシスタントです。日本語で答えてください。"}]
    },
    "contents": [                # ユーザーのプロンプト
        {"role": "user", "parts": [{"text": "RAGとは何ですか。1文で説明してください。"}]},
    ],
    "generationConfig": {
        "maxOutputTokens": 1024,  # 出力するトークン数の上限
        "temperature": 0.7,       # 出力のばらつき(小さいほど安定、大きいほど多様)
    },
}

try:
    res = httpx.post(
        f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent",  # Geminiの送信先
        headers={"x-goog-api-key": os.environ["GEMINI_API_KEY"]},  # 認証(APIキー)
        json=body, timeout=60.0,  # bodyをJSONで送る/60秒でタイムアウト
    )
    res.raise_for_status()
    print(res.json()["candidates"][0]["content"]["parts"][0]["text"])  # 応答の本文
except httpx.HTTPStatusError as e:
    print(f"APIエラー: {e.response.status_code} {e.response.text[:200]}")
except httpx.RequestError as e:
    print(f"通信エラー: {e}")

テスト実行したところ、以下の結果が得られました(LLMであるため、結果は毎回変わる可能性があります)。

(Gemini)
RAG(Retrieval-Augmented Generation)とは、外部の知識ソースから関連情報を検索し、それを基に自然言語で回答を生成する技術です。

(GPT)
RAG(Retrieval-Augmented Generation)とは、外部知識を検索して取得した情報をもとに、生成AIがより正確で最新性のある回答を生成する手法です。

(Claude)
RAG(Retrieval-Augmented Generation)は、大規模言語モデルが外部の知識データベースから関連情報を取得して、その情報を基に回答を生成する技術です。

上記のサンプルコードの内容について、以下で補足します。

httpx.postjson=を渡すと、httpxが本文をJSONに変換し、Content-Type: application/jsonヘッダーも自動で付けてくれます。このため認証ヘッダーだけ指定すれば足ります。

また、外部APIは失敗する前提でエラーを捕まえています。res.raise_for_status()は、APIがエラーステータス(キーが無効・レート超過など)を返したときにhttpx.HTTPStatusErrorを送出します。接続自体に失敗した場合はhttpx.RequestErrorです。この2つを分けて捕まえておくと、原因の切り分けがしやすくなります。

なお、GPT-5系のように内部で推論を行うモデルでは、出力のトークン上限の一部が内部の推論に使われます。上限が小さすぎると本文が空になることがあるため、余裕をもたせて指定します。また、推論を重視する一部のモデルではtemperatureを使用するとレスポンスでエラーが返されます。GPTのサンプルコード(gpt-5.5モデルを使用)ではエラーがでるため、temperatureは設定していません。

LiteLLMで短く書く

LiteLLMは、100を超えるプロバイダのAPIを「OpenAIと同じ形」で呼べるようにするライブラリです。使うのはcompletion関数で、model引数の先頭にプロバイダ名(gemini/openai/anthropic/)を付けるだけで、呼び先が切り替わります。

import os
from dotenv import load_dotenv
from litellm import completion

load_dotenv()  # .envのキーを環境変数へ


def chat_litellm(model, messages, temperature=None, max_tokens=1024):
    kwargs = {"model": model, "messages": messages, "max_tokens": max_tokens}
    if temperature is not None:
        kwargs["temperature"] = temperature
    res = completion(**kwargs)  # キーは環境変数(OPENAI_API_KEY等)からLiteLLMが自動で読む
    return res.choices[0].message.content

.envに各社の決まった名前(OPENAI_API_KEYANTHROPIC_API_KEYGEMINI_API_KEY)で環境変数を置いておけば、modelの接頭辞に応じてLiteLLMが自動で読み込みます。また、環境変数を指定して渡すこともできます。

res = completion(
    model="openai/gpt-5.5",
    messages=[{"role": "user", "content": "こんにちは"}],
    api_key=os.environ["OPENAI_API_KEY"],  # 環境変数から読んだ値を渡す
)

いずれにせよ、このように環境変数を渡すこととし、APIキーを直書きしないようにします。

次に呼び出し部分です。メッセージの形はGPTの形式と同じものがそのまま使えます。

messages = [
    {"role": "system", "content": "あなたは簡潔に答えるアシスタントです。日本語で答えてください。"},
    {"role": "user", "content": "RAGとは何ですか。1文で説明してください。"},
]

print(chat_litellm("gemini/gemini-2.5-flash-lite", messages))
print(chat_litellm("openai/gpt-5.5", messages))
print(chat_litellm("anthropic/claude-haiku-4-5", messages))

httpx版と見比べると、LiteLLMが代替してくれるものが見えてきます。

項目ライブラリが代替するもの
認証各社で違うヘッダー(Authorizationx-api-keyx-goog-api-key)を、環境変数から読んで自動で付けてくれます。
リクエスト形式共通のmessagesを、各社の形へ変換してくれます。Geminiのcontentsへの変換や、Anthropicのsystem、さらに出力上限の指定名の違いまで、こちらは意識せずに済みます。
レスポンス形式どのプロバイダでもres.choices[0].message.contentで本文を取れます(OpenAI形式に統一されます)。
エラー各社バラバラのエラーを、litellm.AuthenticationError(キーが無効)やlitellm.RateLimitError(レート超過)といった共通の型にそろえてくれます。

つまり、httpx版では会社ごとにコードを分けて書いていた呼び出しを、LiteLLMなら1つの書き方にまとめられます。

特定のプロバイダに深く依存し、後から他社へ乗り換えにくくなる状態をベンダーロックインと呼びますが、LiteLLMのように呼び出しを共通化しておくと、モデルを差し替えて比較・移行しやすくなり、この依存を弱められます。

LiteLLMで用いるモデル名

モデル名はLiteLLMの公式ページのモデル一覧で確認できます。リンク先のページの下の方に一覧と検索窓があります。

gpt-claude-gemini-などで検索すると、使えるモデル名がまとめて出てきます。用途(フラグシップ・低コスト・バランス型など)は、各社の公式ページで確認してください。

LiteLLMで使うときは、model引数に「接頭辞+モデル名」を渡します。APIキーは、環境変数名を各社に決まったものにしておけばLiteLLMが自動で読み込みます。あるいは、既に述べた通りapi_key=によりキーを渡すこともできます。

プロバイダ接頭辞APIキー設定変数名(デフォルト)
OpenAIopenai/OPENAI_API_KEY
Anthropicanthropic/ANTHROPIC_API_KEY
Google Geminigemini/GEMINI_API_KEY

ストリーミング・非同期・リトライ

httpxで自前で実装すると手間がかかる処理も、LiteLLMなら引数ひとつで書けます。ストリーミング、非同期、リトライの3つの例について示します(コードの中のmodelを変えれば他社でも同様に使えます)。

回答を少しずつ受け取る(ストリーミング)には、stream=Trueを渡します。経過を見せることで、待ち時間に対するユーザーの体感を短くする効果があります。

from litellm import completion

response = completion(
    model="anthropic/claude-haiku-4-5",
    messages=[{"role": "user", "content": "RAGとは何ですか。"}],
    stream=True,  # 生成されたそばから少しずつ受け取る
)
for chunk in response:
    print(chunk.choices[0].delta.content or "", end="")

通常は完成した回答がmessage.contentに入りますが、ストリーミングでは増えた分がdelta.contentに少しずつ届くので、それをつなげて表示します。このときcompletionは回答の完成を待たず、チャンクが届くたびにresponseforループが回り、その場で逐次出力しています。

複数の呼び出しを並行して行う(非同期)には、acompletionを使います。acompletioncompletionの非同期版で、引数はcompletionと同じです。違いはawaitで呼ぶ点で、ストリーミングする場合はasync forで受け取ります。

import asyncio
from litellm import acompletion

async def main():
    response = await acompletion(
        model="anthropic/claude-haiku-4-5",
        messages=[{"role": "user", "content": "RAGとは何ですか。"}],
    )
    print(response.choices[0].message.content)

asyncio.run(main())

失敗したときに自動で再試行する(リトライ)には、num_retriesを渡します。

from litellm import completion

response = completion(
    model="anthropic/claude-haiku-4-5",
    messages=[{"role": "user", "content": "RAGとは何ですか。"}],
    num_retries=2,  # 失敗したら最大2回まで再試行する
)

どれも、httpxだと各社ごとに自分で書く必要がある部分ですが、LiteLLMなら共通の引数で済みます。

最終的なコード

ここまでの内容(モデルの切り替え・リトライ・ストリーミング・非同期)を1つにまとめると、次のようになります。同期版chatと非同期版achatを用意し、model(使うモデル)とnum_retries(再試行の回数)を引数で選べるようにしています。リトライは常に有効です。

# llm.py
import os
from litellm import completion, acompletion
from dotenv import load_dotenv

load_dotenv()  # .envから各社のAPIキーを読み込む

DEFAULT_MODEL = "anthropic/claude-haiku-4-5"  # 既定のモデル(引数で変更可)


def chat(messages, model=DEFAULT_MODEL, num_retries=2, stream=False, temperature=None, max_tokens=1024, **kwargs):
    """LLMを呼ぶ(同期版)。"""
    if temperature is not None:
        kwargs["temperature"] = temperature
    response = completion(
        model=model,
        messages=messages,
        num_retries=num_retries,  # 失敗したらこの回数まで自動で再試行
        stream=stream,
        max_tokens=max_tokens,    # 出力するトークン数の上限
        **kwargs,                 # temperatureなど、その他の引数もそのまま渡せる
    )
    if stream:
        return response           # ストリーミング時はチャンクを逐次返す(呼び出し側でforで回す)
    return response.choices[0].message.content


async def achat(messages, model=DEFAULT_MODEL, num_retries=2, stream=False, temperature=None, max_tokens=1024, **kwargs):
    """LLMを呼ぶ(非同期版)。"""
    if temperature is not None:
        kwargs["temperature"] = temperature
    response = await acompletion(
        model=model,
        messages=messages,
        num_retries=num_retries,
        stream=stream,
        max_tokens=max_tokens,
        **kwargs,
    )
    if stream:
        return response           # 呼び出し側でasync forで回す
    return response.choices[0].message.content

modelで使うプロバイダを切り替えられます。呼び出しが失敗したときは、num_retriesで指定した回数まで自動で再試行します。temperatureで出力のばらつきを、max_tokensで出力するトークン数の上限を指定できます。chatawaitなしで、achatawaitを付けて呼びます。

使い方は次のとおりです。

messages = [{"role": "user", "content": "RAGとは何ですか。1文で説明してください。"}]

# 基本
print(chat(messages))

# モデルを変える
print(chat(messages, model="gemini/gemini-2.5-flash-lite"))

# ストリーミング(少しずつ表示)
for chunk in chat(messages, stream=True):
    print(chunk.choices[0].delta.content or "", end="")

非同期版も同じように使えます。呼び出しをawaitし、非同期関数の中で動かします。

import asyncio

messages = [{"role": "user", "content": "RAGとは何ですか。1文で説明してください。"}]

async def main():
    # 基本
    print(await achat(messages))

    # モデルを変える
    print(await achat(messages, model="gemini/gemini-2.5-flash-lite"))

    # ストリーミング(少しずつ表示)
    stream = await achat(messages, stream=True)
    async for chunk in stream:
        print(chunk.choices[0].delta.content or "", end="")

asyncio.run(main())

補足:エラーメッセージ

"This model is currently experiencing high demand. Spikes in demand are usually temporary. Please try again later."

これは「このモデルは現在、需要が非常に高くなっています(アクセスが集中しています)。この混雑は通常一時的なものです。しばらくしてからもう一度お試しください。」という意味です。しばらく時間を空けるか、リトライ回数を増やす、モデルを変えるなどで、解決することがあります。

企業向けのクラウド基盤で呼ぶ

ここまでは各社と直接契約する方法でしたが、同じモデルは、クラウド事業者の企業向け基盤からも使えます。呼び出し方はほぼ同じです。変えるのは、modelの接頭辞と、認証情報の渡し方(環境変数)だけで、completion(...)にmessagesを渡してres.choices[0].message.contentで受け取る流れは変わりません。

基盤接頭辞環境変数
Azure OpenAIazure/AZURE_API_KEY(Azure OpenAIリソースのAPIキー)
AZURE_API_BASE(リソースのエンドポイントURL)
AZURE_API_VERSION(使うAPIのバージョン)
Vertex AIvertex_ai/VERTEXAI_PROJECT(Google CloudのプロジェクトID)
VERTEXAI_LOCATION(リージョン。例:us-central1global
Amazon Bedrockbedrock/AWS_ACCESS_KEY_ID(IAMのアクセスキーID)
AWS_SECRET_ACCESS_KEY(対応するシークレットアクセスキー)
AWS_REGION_NAME(AWSリージョン。例:us-east-1

Vertex AIは、Google Cloudの認証が前提です。コマンド(gcloud auth application-default login)によるADC、またはサービスアカウントの鍵を使います。追加のライブラリとして、pip install 'litellm[google]'またはpip install google-cloud-aiplatformが公式で案内されています。

Bedrockは、AWSの資格情報(アクセスキー+シークレット)で認証します。リージョンによって使えるモデルが変わる点に注意が必要です。追加のライブラリとして、pip install 'boto3>=1.28.57'が公式で案内されています。

モデル名は基盤ごとに異なり(特にBedrockはAWS独自の形式)、変化も速いので、正確なIDはLiteLLMのモデル一覧で確認します。azurevertex_aibedrockなどで検索するとモデル名が表示されます。

呼び出し方の詳細は、LiteLLM公式の各プロバイダのページを参照します(AzureVertex AIAmazon Bedrock)。

まとめ

LLMのAPIを呼ぶ流れを、キーの管理から確認しました。ポイントを以下にまとめました。

  • APIキーは.envと環境変数で扱い、コードやGitに混ぜない
  • 料金も、一度に扱えるテキストの量の上限も、トークンという単位で決まる
  • httpxで直接叩くと、リクエストとレスポンス・messages・認証というAPIの素の仕組みが見える
  • プロバイダごとにエンドポイント・認証・ボディ・レスポンスの形が異なるが、LiteLLMを使うとそれらの差を吸収して同じ書き方で呼べる

参考になりましたら幸いです。

参考資料

-プログラミング
-, , , , ,