はじめに
Google が提供している ADK (Agent Development Kit) を使うと、簡単にチ
ャットエージェントを作れます。
ただし、ADK Web のサンプルコードをそのまま使うと、Excel や Word ファイルを添付したときにエラーになることがあります。
本記事では、次の流れで問題を解決します。
- 現象の再現(どんなエラーになるか)
- 原因(なぜエラーになるのか)
- コールバックの基礎(コールバックって何?)
- 修正方法(コールバックで Markdown に変換する)
1. 現象の再現(どんなエラーになるか)
参考:初期サンプル(公式ドキュメント)
- Google ADK Quickstart の agent.py(公式)
https://google.github.io/adk-docs/get-started/quickstart/#agentpy
注記
この記事内で掲載する「初期サンプル」は、上記の公式サンプルをベースに、説明に不要な部分を一部省略しています(可読性のため)。処理の流れや意図が変わらない範囲で簡略化しています。
まずは ADK Web のサンプルを(上記をベースに)そのまま動かします。
import datetime
from zoneinfo import ZoneInfo
from google.adk.agents import Agent
def get_weather(city: str) -> dict:
if city.lower() == "new york":
return {"status": "success", "report": "The weather in New York is sunny."}
else:
return {"status": "error", "error_message": f"No data for {city}"}
def get_current_time(city: str) -> dict:
if city.lower() == "new york":
tz = ZoneInfo("America/New_York")
now = datetime.datetime.now(tz)
return {"status": "success", "report": now.strftime("%Y-%m-%d %H:%M:%S")}
else:
return {"status": "error", "error_message": f"No timezone for {city}"}
root_agent = Agent(
name="weather_time_agent",
model="gemini-2.0-flash",
description="Agent to answer time and weather.",
instruction="You are a helpful agent.",
tools=[get_weather, get_current_time],
)
この状態でチャットにテキスト文を直接打つと、正しく挙動しますが、同じテキスト文を記載した Excel ファイル をチャットに添付すると、次のようなエラーが出ます。
400 INVALID_ARGUMENT.
Unable to submit request because it has a mimeType parameter with value
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, which is not supported.
2. 原因(なぜエラーになるのか)
Gemini などの LLM は バイナリの Excel/Word ファイルを直接理解できません。
添付ファイルは inline_data
として mimeType
がそのまま LLM に渡されるのですが、Gemini は application/vnd.openxmlformats-officedocument.*
系を受け付けないため、400 エラー になります。
つまり:
- サンプルコードには 「添付ファイルをテキストに変換する処理」 が入っていない
- そのため バイナリがそのまま投げられてエラー になる
コールバックの基礎(コールバックって何?)
一言でいうと
「フレームワークの処理の前後で、開発者が差し込みたい関数を“呼び戻してもらう”仕組み」です。
- 例:ボタンをクリックしたら実行される関数 → クリックイベントのコールバック
- ADK では、Agent / Model / Tools それぞれの 前後 にコールバックを差し込めます
コールバックの全体像(図とリファレンス)
図1: ADK におけるコールバック呼び出しポイント(Agent/Model/Tools の前後)
出典: Google ADK Docs — Callbacks: Introduction
- before_agent_callback:LLM 呼び出し直前。入力の整形/権限制御/添付のテキスト化(今回)
- after_agent_callback:最終応答直後。監査ログ/要約保存/メトリクス送信
- before/after_model_callback:モデル入出力の最終整形や安全フィルタ
- before/after_tool_callback:ツール引数の検証、結果整形、キャッシュ 等
関数のシグネチャ(ADK 1.13 以降)
キーワード引数で呼ばれるため、引数名に注意します。
from google.adk.agents.callback_context import CallbackContext
def before_agent_callback(*, callback_context: CallbackContext) -> None:
...
補足:
callback_context
という 引数名が要求されます。違う名前にすると
unexpected keyword argument 'callback_context'
で落ちるため注意。
4. 修正方法(コールバックで Markdown に変換する)
LLM に渡す前に添付ファイルをテキスト(Markdown)へ変換すれば OK です。
ADK の before_agent_callback
フックで、DOCX/XLSX を Markdown に変換し、バイナリは捨ててテキストだけ残すようにします。
依存の導入(重要):
markitdown
は拡張子ごとに追加依存が必要です。
Word/Excel を扱う場合は以下をインストールしてください。pip install --upgrade pip pip install "markitdown[docx]" "markitdown[xlsx]" # まとめて入れる場合は # pip install "markitdown[all]"
コールバック実装
import io
from google.adk.agents.callback_context import CallbackContext
from google.genai import types
from markitdown import MarkItDown # バイナリ→Markdown 変換ライブラリ
# 変換対象の MIME タイプ(Word/Excel の OpenXML 形式)
DOC_MIMES = {
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", # .docx
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # .xlsx
}
def before_agent_callback(*, callback_context: CallbackContext) -> None:
"""LLM 実行前のフック。DOCX/XLSX を Markdown に変換し、バイナリは渡さない。"""
# 1) ユーザーの発話のみを対象にする(system/assistant には介入しない)
uc = getattr(callback_context, "user_content", None)
if not uc or getattr(uc, "role", "") != "user":
return None
# 2) メッセージが Part(複数要素)で構成されていなければ何もしない
parts = getattr(uc, "parts", None)
if not parts:
return None
# 3) 変換器を用意(バイナリ→Markdown)
converter = MarkItDown()
# 4) 新しい Part 配列を作成(対象だけ置換、その他はそのまま)
new_parts = []
for part in parts:
inline = getattr(part, "inline_data", None) # バイナリを持つ Part か
mime = getattr(inline, "mime_type", "") if inline else "" # MIME を取得
data = getattr(inline, "data", None) if inline else None # バイナリ(bytes 前提)
# 4-1) DOCX/XLSX かつバイナリあり → 変換
if inline and mime in DOC_MIMES and data:
try:
md_text = converter.convert(io.BytesIO(data)).text_content
# バイナリ Part は捨てて、Markdown テキスト Part に置換
new_parts.append(types.Part(text=md_text))
except Exception:
# 失敗してもバイナリは渡さず、失敗メッセージのテキストに置換
new_parts.append(types.Part(text="[変換失敗: 添付ファイルを読み取れませんでした]"))
else:
# 4-2) 対象外(通常テキストや画像など)はそのまま通す
new_parts.append(part)
# 5) 差し替えた Part を反映(以降、LLM にはテキストのみが渡る)
callback_context.user_content.parts = new_parts
return None
Agent へ組み込み
root_agent = Agent(
name="weather_time_agent",
model="gemini-2.0-flash",
description="Agent to answer time and weather.",
instruction="You are a helpful agent.",
tools=[get_weather, get_current_time],
before_agent_callback=before_agent_callback, # ← 追加
)
実行結果
この修正版を使うと、Excel や Word を添付してもエラーにならず、
ファイルの中身が Markdown テキストとして LLM に渡されます。
まとめ
- ADK Web のサンプルをそのまま使うと、Word/Excel 添付で
mimeType
が LLM に渡りエラーになる -
before_agent_callback
を使って Markdown に変換し、バイナリは渡さないようにすれば解決 - 依存は
markitdown[docx]
/markitdown[xlsx]
(またはmarkitdown[all]
)を導入
参考リンク
- Google ADK Quickstart(公式): agent.py の初期サンプル
https://google.github.io/adk-docs/get-started/quickstart/#agentpy
本記事のサンプルは、上記の公式コードをベースに 一部を省略 しています(可読性のため)。