FastAPIとは何か
さて、先ほどまでで、Web APIがどんなものであるか、そして、Gemini APIのroleについての機能がどのようなものであるかがわかったでしょう。しかしこの方法では、一つのクエリと一つのロールにしか対応できません。つまり、「同じ質問に対して複数の役割で答えを並べて比較したい」といった使い方は、そのままでは実現できないのです。
そこで、Gemini APIとユーザの間に、FastAPIを用いて代わりに複数の役割をスイッチングして代わりに尋ねに行ってくれるAPIサーバを立ててみましょう。

FastAPIは、PythonでAPIを構築するための、高速なWebフレームワークです。現代のWeb開発で広く使われているAPIの形式 「OpenAPI(旧 Swagger)」 や 「JSON Schema」 を標準でサポートしており、ソースコード上で適切に書くべきものを書くことでAPIドキュメントを生成する機能があります。また、非同期での処理を念頭に置いて開発されており、大変扱いやすいのが特徴です。
とはいえ、すべてのコードを書くのは大変ですから、今回は用意されたテンプレートを用いて、Web APIに関わる部分だけ、実際に書いてみましょう。
プロジェクトを準備する
このプロジェクトでは、前提として、「uv」、「Visual Studio Code」がインストールされていることとします。
- 以下のファイルをダウンロードし、解凍(展開)する
- 展開したディレクトリでVS Codeを開く
「フォルダを開く」から展開したフォルダを開きます。
- ターミナルを開き、以下のコマンドを入力して、プロジェクトに必要なパッケージをインストールする
$ uv sync --dev --extra 'dev'
さて、前回の解説記事では、共同開発のために使用されているパッケージを同期するときuv syncをすると言いました。それでは、この--extra 'dev'とは何でしょうか。pyproject.tomlには、現在の依存関係がすべて書かれています。以下の行に注目してください。
[project.optional-dependencies]
dev = [
"httpx>=0.28.1",
"pyright>=1.1.401",
"pytest>=8.3.5",
"pytest-asyncio>=1.0.0",
"ruff>=0.11.12",
]
pyright、pytest、ruffなど、特定のパッケージが含まれています。これらのパッケージは開発時には必要ですが、本番環境ではなくても特に支障はありません。--extra 'dev'と記載することで、[project.optional-dependencies]セクションに定義された"dev"グループに含まれる依存関係も一緒にインストールする、という意味になります。"dev"グループには、開発時に必要だと開発者が考えたパッケージを列挙しています。つまり、逆に言えば、完成したプロダクトをサーバにデプロイするときは、このパッケージを追加する必要はない、ということですね。
単一の質問(クエリ)を受け取り、回答を返す
FastAPIの書き方
それでは、いよいよFastAPIの部分であるmain.pyを覗いてみましょう。
@app.get("/")
def index(name: str = "匿名"):
"""
... (略) ...
"""
return f"こんにちは、{name}さん"
これは、エンドポイントに与えられたクエリ文字列に合わせて、「こんにちは、{名前}さん」と返すだけのプログラムです。
@app.get("/")はindex関数のデコレータです。デコレータとは、関数やクラス・メソッドに新たな機能を追加するための仕組みです。main.pyでは、最初に変数appとして、FastAPIクラスのインスタンスが作成されています。
app = FastAPI(
title="Google Gemini Multi Access API",
description="Google Gemini に対して複数の問い合わせを行う API サーバ",
version="0.1.0",
)
この関数のパス/にHTTP GETメソッドで問い合わせがあった時、以下の関数を実行しますよ、と宣言しているわけです。
このエンドポイントに、例えば以下のようにアクセスすると、次のように返答されます。
$ curl http://127.0.0.1:8000/?name=CodeZine > こんにちは、CodeZineさん
それでは、Geminiに単一のクエリを送信するエンドポイントを実際に作ってみます。
Geminiに問い合わせる関数を紐解く
Gemini APIに問い合わせる部分は、このリポジトリではすでに完成しています。
searchapi.pyを以下の部分を見てみましょう。このコードはGemini APIに1回クエリを投げて応答を返す関数を定義している部分です。
def query_gemini(
q: str,
role: str,
model_name: AVAILABLE_MODELS,
temperature: float,
max_tokens: int | None = None,
) -> tuple[str, QueryArgs]:
"""
Gemini APIに単一の問い合わせを行う関数
"""
if not HAS_API_KEY:
raise ValueError("GOOGLE_API_KEY 環境変数が設定されていません。")
chat = ChatGoogleGenerativeAI(
model=model_name, temperature=temperature, max_tokens=max_tokens
)
messages = [SystemMessage(content=role), HumanMessage(content=q)]
result = chat.invoke(messages)
args_dict: QueryArgs = {
"query": q,
"role": role,
"model_name": model_name,
"temperature": temperature,
"max_tokens": max_tokens,
}
# result.contentをstr型に確実に変換
content_str = str(result.content)
return content_str, args_dict
引数として、質問(q)とシステム指示(role)、また使用するモデルなどをGeminiに送って返答を得る関数です。また、返答の形はタプル型で返され、文字列型の回答の本文と、どのような条件の要求で生成された回答かが返ってきます。
ところで、model_nameには型として、以下のようにAVAILABLE_MODELSが指定されています。
def query_gemini(
...(略)...
model_name: AVAILABLE_MODELS,
...(略)...
) -> tuple[str, QueryArgs]:
このAVAILABLE_MODELSとはなんでしょうか。これ自体は、Pythonのクラスです。models.pyには、次のように定義されています。
class AVAILABLE_MODELS(str, enum.Enum):
"""利用可能なモデルの列挙型"""
GEMINI_2_0_FLASH = "gemini-2.0-flash"
GEMINI_1_5_FLASH = "gemini-1.5-flash"
GEMINI_2_5_FLASH = "gemini-2.5-flash-preview-05-20"
これらは、Gemini APIで現在利用できるモデルの列挙がされています。今回はその仕組みについて詳しく述べませんが、ここで、例えば、gemini-100-flashなどの存在しないモデルをリクエストされたとしましょう。すると、以下のようにエラーが発生したことをユーザに示してくれます。
"Input should be 'gemini-2.0-flash', 'gemini-1.5-flash' or 'gemini-2.5-flash-preview-05-20'"
このように、開発者の意図しない入力をバリデーションしてくれました。なお、今後APIに新しいモデルが出てきて追加したい時は、先ほどのクラスを適切に書き換えると良いです。
これを使って、まずAPIの処理の部分を書いてみましょう。大枠は、以下の部分です。
@app.post("/single", response_model=ApiResponse)
def single(data: SingleRequest):
"""
単一の問い合わせを行うエンドポイント
"""
# ここにコードを書く
return ApiResponse(
data=QueryResponse(result="Not Implemented", args=None), meta={"duration": 0}
)
この「# ここにコードを書く」と書かれている部分から下を、修正します。さて、どんな流れが必要でしょうか。
まず、オプションとしてモデル名を引数に取ります。ユーザから指定されていればそのモデルを、されていなければ、デフォルトのモデルを自動的に適用します。
以下は、single関数から部分的に抜粋したコードです。
options = data.options
if options is None:
model_name = AVAILABLE_MODELS.GEMINI_2_0_FLASH
max_tokens = 1024
else:
model_name = options.model
max_tokens = options.max_tokens
次に、いよいよGemini APIに問い合わせます。先ほど出てきた関数をうまく使うことができます。
start_time = time.time()
try:
# Gemini APIに問い合わせ
result, args = query_gemini(
q=data.q,
role="あなたは親切なアシスタントです。",
model_name=model_name,
temperature=0.7,
max_tokens=max_tokens,
)
except ValueError as e:
raise HTTPException(status_code=500, detail=str(e))
else:
end_time = time.time()
duration = end_time - start_time
# 応答を作成
response = ApiResponse(
data=QueryResponse(
result=result,
args=args,
),
meta={"duration": duration},
)
return response
try-except文を使っていることがわかります。これは、Geminiにアクセスできないなどの事由を適切に捌く必要があるからです。
また、応答を作成する部分ではApiResponseというクラスが登場しています。これは、models.pyに定義されているクラスで、このプログラムでは返したいレスポンスの各項目を埋めて、そのままクライアントに返答するようになっています。
実際に試すには、以下の手順が必要です。
- Google Gemini APIの環境変数を設定する
先ほど、Google AI Studioで取得したAPIキーを環境変数GOOGLE_API_KEYに設定してください。なお、この設定は、ターミナルを開いている間だけ有効です。ターミナルを閉じると、設定は失われます。
- bash、zsh(macOS/Linux)の場合
$ export GOOGLE_API_KEY="your_api_key"
- PowerShell(Windows)の場合
$ env:GOOGLE_API_KEY="your_api_key"
- APIサーバを起動する
利用されるポートは、デフォルトでは8000番です。
$ uv run uvicorn main:app --reload
- curlでアクセスしてみる
$ curl -X POST "http://127.0.0.1:8000/single" \
-H "Content-Type: application/json" \
-d '{
"q": "広島の観光名所を教えてください",
"options": {
"model": "gemini-2.0-flash",
"max_tokens": 1024
}
}'
すると、一つの回答が返ってきます。整形したものがこちらです。うまく動いたでしょうか。
{
"data": {
"result": "広島には魅力的な観光名所がたくさんありますね!いくつかご紹介します",
"model_name": "gemini-2.0-flash",
"temperature": 0.7,
"max_tokens": 1024
},
"meta": {
"duration": 3.5292320251464844
}
}
