🤖

Gemini APIのCode Executionを使ってみた

に公開

この記事は、ラクスパートナーズ Advent Calendar 2025の8日目の記事です🎄

https://qiita.com/advent-calendar/2025/rakus-partners

はじめに

直近の業務で、Gemini APIのCode Executionを使った処理を実装するタスクを担当しました。
ただ実際に触ってみると、気にしないといけない挙動があり、割と実装に詰まりがちでした。
そこで今回は、Code Executionの概要や使い方、実際にどう動くのかをまとめてみました!

Code Executionって何?

Code Executionを使うと、AIがユーザーのリクエストに応じて自動的にPythonコードを生成・実行し、その結果をもとに回答してくれます。
コード実行は、ツールとしての利用やFunction Calling経由で呼び出すことができ、ツールとして使う場合はAIが必要に応じて実行タイミングを判断します。
また、NumPyやMatplotlibなどのライブラリがあらかじめ内部環境に含まれているため、より専門的なコード実行も可能です。

利用料金

Gemini APIでCode Executionを使っても、追加料金は発生しません。あくまで利用しているモデルに基づいて、入力トークンと出力トークンの現在のレートで課金されます。
主な注意事項は以下です。

  • 課金対象は入力トークン(元プロンプト + 中間ステップ)、最終出力トークン
  • 回答に加えて、生成されたコード、実行されたコードの結果も出力トークンに含まれる

制限事項

Code Executionにはいくつかの制限事項があります。

  • メディアファイルなどの出力には非対応
    モデルはコードの生成と実行は可能ですが、画像・音声・動画などの「ファイルとしての出力」はできません。

  • ファイルパスやURIは使えないが、インラインバイトならOK
    ファイルパスやURIを直接入出力に使うことはできません。
    ただ、インラインバイト(バイナリをテキスト内に埋め込む形式)は使用可能です。
    そのため、ファイルをアップロードして内容の質問や指示をしたり、コード実行結果としてグラフを生成したりできます。
    インラインバイトでサポートされているファイル拡張子は以下の通りです:
    .cpp.csv.java.jpeg.js.png.py.ts.xml

  • コード実行の最大時間は30秒
    30秒を超える場合はタイムアウトになります。

  • モデル出力の他領域で回帰が発生する可能性あり
    Code Execution を有効化すると、ストーリー生成など他の生成タスクで回帰が発生する場合があります。

実際に動かしてみる

実際に挙動を確認するために、Gemini APIとCode Executionを組み合わせて、Next.js製の簡単なチャットデモを作ってみました。
https://github.com/uraaaa24/gemini-code-exec-demo

①セットアップ

Next.jsの新規プロジェクトを作成して、必要なライブラリをインストールします。

// Next.js プロジェクト作成
pnpm create next-app gemini-code-exec-demo \
  --ts --tailwind --app --eslint

cd gemini-code-exec-demo

// Google Gemini API SDK をインストール
pnpm add @google/genai

②環境変数を設定(APIキー)

プロジェクト直下に env.localを作成します。

env.local
NEXT_PUBLIC_GEMINI_API_KEY=<自身のAPIキー>

③チャット画面を作成

app/page.tsxでチャットを利用できるようにします。
このページではストリーム処理に対応した処理にしています。

app/page.tsx
app/page.tsx
'use client'

import {
  type GenerateContentConfig,
  GoogleGenAI,
  type Part,
} from '@google/genai'
import { useState } from 'react'

const apiKey = process.env.NEXT_PUBLIC_GEMINI_API_KEY
if (!apiKey) {
  throw new Error('Missing NEXT_PUBLIC_GEMINI_API_KEY is not set')
}

const ai = new GoogleGenAI({ apiKey })

const DEFAULT_PROMPT =
  'ここに任意のテキストを書いてください。\n' +
  '例: 1〜200までの素数を列挙して合計を求めてください。Python のコードを生成し、Code Execution で実行して結果を使って説明してください。'

export default function Home() {
  const [prompt, setPrompt] = useState<string>('')
  const [useCodeExecution, setUseCodeExecution] = useState<boolean>(true)

  const [streamText, setStreamText] = useState<string>('')
  const [lastCode, setLastCode] = useState<string>('')
  const [execOutput, setExecOutput] = useState<string>('')

  const [isRunning, setIsRunning] = useState<boolean>(false)
  const [error, setError] = useState<string | null>(null)

  const handleRun = async () => {
    if (!prompt.trim()) {
      setError('Prompt is empty')
      return
    }

    setIsRunning(true)
    setStreamText('')
    setLastCode('')
    setExecOutput('')
    setError(null)

    try {
      const config: GenerateContentConfig = {}
      if (useCodeExecution) {
        config.tools = [{ codeExecution: {} }]
      }

      const stream = await ai.models.generateContentStream({
        model: 'gemini-2.5-flash',
        contents: [prompt],
        config,
      })

      // Process the streaming response
      for await (const chunk of stream) {
        if (chunk.text) {
          setStreamText((prev) => prev + chunk.text)
        }

        const parts = chunk?.candidates?.[0]?.content?.parts || []
        parts.forEach((part: Part) => {
          // Capture generated executable code
          if (part.executableCode?.code) {
            setLastCode(part.executableCode.code)
          }
          // Capture code execution results
          if (part.codeExecutionResult?.output) {
            setExecOutput(
              (prev) => `${prev + (part.codeExecutionResult?.output ?? '')}\n`,
            )
          }
        })
      }
    } catch (err) {
      console.error('Error during generation:', err)
      if (err instanceof Error) {
        setError(err.message || 'An error occurred')
      } else {
        setError('An unknown error occurred')
      }
    } finally {
      setIsRunning(false)
    }
  }

  return (
    <main className="min-h-screen bg-neutral-100 text-neutral-900 flex justify-center p-6">
      <div className="w-full max-w-5xl space-y-6">
        <header className="space-y-2">
          <h1 className="text-2xl font-semibold tracking-tight">
            Gemini Code Execution Streaming Demo
          </h1>
          <p className="text-sm text-neutral-500">
            任意のテキストを入力して、Code
            Executionのあり/なしを切り替えながら、ストリーミング出力をブラウザ上で確認するためのシンプルなデモです。
          </p>
        </header>

        {/* Input section */}
        <section className="space-y-3">
          <div className="flex items-center justify-between gap-2">
            <label
              htmlFor="prompt"
              className="block text-sm font-medium text-neutral-800"
            >
              Prompt
            </label>

            {/* Code Execution toggle */}
            <button
              type="button"
              onClick={() => setUseCodeExecution((prev) => !prev)}
              className={`cursor-pointer inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium transition-colors ${
                useCodeExecution
                  ? 'border-emerald-400 bg-emerald-50 text-emerald-700'
                  : 'border-neutral-300 bg-white text-neutral-600'
              }`}
            >
              <span
                className={`inline-block h-2 w-2 rounded-full ${
                  useCodeExecution ? 'bg-emerald-500' : 'bg-neutral-400'
                }`}
              />
              {useCodeExecution ? 'Code Execution: ON' : 'Code Execution: OFF'}
            </button>
          </div>

          {/* Prompt input */}
          <textarea
            id="prompt"
            className="w-full h-32 rounded-xl border border-neutral-300 bg-white px-3 py-2 text-sm outline-none ring-0 focus:border-sky-500 focus:ring-1 focus:ring-sky-500 resize-none"
            value={prompt}
            onChange={(e) => setPrompt(e.target.value)}
            placeholder={DEFAULT_PROMPT}
          />

          {/* Run button */}
          <button
            type="button"
            onClick={handleRun}
            disabled={prompt.trim() === '' || isRunning}
            className="cursor-pointer disabled:cursor-not-allowed inline-flex items-center justify-center rounded-full bg-sky-600 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-sky-700 disabled:bg-neutral-400"
          >
            {isRunning ? '実行中...' : 'チャットを送信する'}
          </button>

          {error && <p className="text-xs text-red-500 mt-1">{error}</p>}
        </section>

        {/* Output section */}
        <section className="grid grid-cols-1 md:grid-cols-3 gap-4">
          {/* Streaming Text */}
          <div className="rounded-xl border border-neutral-200 bg-white p-3 flex flex-col shadow-sm">
            <h2 className="text-xs font-semibold text-neutral-700 mb-1">
              Streaming Text
            </h2>
            <div className="flex-1 overflow-auto text-xs whitespace-pre-wrap font-mono text-neutral-800">
              {streamText ||
                '(ここにモデルの回答がストリーミング表示されます)'}
            </div>
          </div>

          {/* Generated Code */}
          {useCodeExecution && (
            <div className="rounded-xl border border-neutral-200 bg-white p-3 flex flex-col shadow-sm">
              <h2 className="text-xs font-semibold text-neutral-700 mb-1">
                Generated Code
              </h2>
              <div className="flex-1 overflow-auto text-xs whitespace-pre font-mono text-emerald-800">
                {lastCode ||
                  '# ここに Code Execution 用に生成された Python コードが表示されます'}
              </div>
            </div>
          )}

          {/* Code Execution Result */}
          {useCodeExecution && (
            <div className="rounded-xl border border-neutral-200 bg-white p-3 flex flex-col shadow-sm">
              <h2 className="text-xs font-semibold text-neutral-700 mb-1">
                Code Execution Result
              </h2>
              <div className="flex-1 overflow-auto text-xs whitespace-pre-wrap font-mono text-amber-800">
                {execOutput ||
                  '(ここに Python 実行結果の標準出力などが表示されます)'}
              </div>
            </div>
          )}
        </section>
      </div>
    </main>
  )
}

④チャットしてみる

実際に試してみます。
フィボナッチ数列を計算し、その一部を抜き出して説明させるようなプロンプトを投げて、Code Executionがしっかり動作するか検証してみました。

バッチリ回答が返ってきました!
Pythonコードの生成〜実行結果の取得まで問題なく動き、その内容を踏まえた説明をしてくれています。

業務でハマったポイント

ここからは実際に業務でCode Executionを実装した時に、悩んだポイントです。
主にファイル周りが強敵でした。。

対応していないファイルも生成できてしまう

先ほど制限事項のところで、以下のファイル拡張子がツールに対応していると書きました。

.cpp.csv.java.jpeg.js.png.py.ts.xml

ただ実際に試してみたところ、出力としては対応していない拡張子のファイルも生成できることが分かりました。
実際にGoogle AI Studioでも、PDFファイル等の生成ができてしまいます。
動画の画面右下らへんでCode Executionのトグルがあるので、そこに注目してみてください。

色々挙動を見てみたところ、Google AI Studioでは以下のような制御を入れていることがわかりました。

  • Code Executionが未選択の状態で、対応していないファイル(例: PDF)を添付すると、それ以降Code Executionを選択できなくなる
  • Code Executionを選択した状態で、非対応ファイル(例: PDF)を生成するようなプロンプトを入力すると、ファイル生成後にCode Executionの選択が無効化され、以降選択できなくなる

最初試した時は、Code Executionがしれっと無効化されており、一瞬なんで使えないのか分からなかったです😇
そのため、業務で実装した際は、Code Executionが使えなくなった理由がユーザーにわかるように、アイコンやツールチップで補足してあげるようなUIの工夫を入れることにしました。

File APIが使えない

https://ai.google.dev/gemini-api/docs/code-execution?hl=ja#input-output-details
こちらのドキュメントを読むと、以下のような記述がありました。

入力ファイルは part.inlineData または part.fileData(Files API を介してアップロード)で渡すことができ、出力ファイルは常に part.inlineData として返されます。

なるほど、Files APIというのも使えるのか。
え!こっちだと48時間アップロードしたファイルを保存してくれるから、モデル内で生成したファイルも楽に使えるじゃん!

そう思っていたのですが、よくよくSDKのドキュメントを調べると以下の文言がありました。

Files are only supported in Gemini Developer API. See the ‘Create a client’ section above to initialize a client.

どうやらVertexAIを使っている場合、Files APIが使えないとのこと😭
結局、諦めてインラインデータでファイルは受け渡すようにしました。

まとめ

今回Code Executionを業務で実装するにあたって、思ったより苦戦してしまったので、自分自身のメモ書きも兼ねて記事にしました。
特にファイル周りは、今後の仕様変更で改善される可能性もありますが、現段階では少し工夫や注意が必要だなあという印象です。

この記事が今後Code Executionを実装する際の参考になってくれれば幸いです!

参考

https://ai.google.dev/gemini-api/docs/code-execution?hl=ja
https://googleapis.github.io/python-genai/

Discussion