SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

PyCon JP主催メンバープレゼンツ Python最新トレンド

LLM時代のPython AI開発:GeminiとFast APIで学ぶ実践Web API入門

PyCon JP主催メンバープレゼンツ Python最新トレンド 第2回


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」がインストールされていることとします。

  1. 以下のファイルをダウンロードし、解凍(展開)する
  1. 展開したディレクトリでVS Codeを開く

 「フォルダを開く」から展開したフォルダを開きます。

  1. ターミナルを開き、以下のコマンドを入力して、プロジェクトに必要なパッケージをインストールする
$ 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",
]

 pyrightpytestruffなど、特定のパッケージが含まれています。これらのパッケージは開発時には必要ですが、本番環境ではなくても特に支障はありません。--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に定義されているクラスで、このプログラムでは返したいレスポンスの各項目を埋めて、そのままクライアントに返答するようになっています。

 実際に試すには、以下の手順が必要です。

  1. 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"
  1. APIサーバを起動する

 利用されるポートは、デフォルトでは8000番です。

$ uv run uvicorn main:app --reload
  1. 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
  }
}

次のページ
複数の問い合わせ要求を受け取り、回答を返す

この記事は参考になりましたか?

PyCon JP主催メンバープレゼンツ Python最新トレンド連載記事一覧
この記事の著者

鶴丸 倫琉(ツルマル ミチル)

 大学生。広島県在住。2005年、山口県生まれ。PyCon JP 2025 主催メンバーの一員として、Python コミュニティに関わる。2021年(高1)の時、地元で行われていたプログラミングコンテストに応募し、プログラミングの楽しさを知る。2023年、国際学生科学技術フェア(ISEF)に参加する...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/22169 2025/10/01 15:50

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー