nikkie-ftnextの日記

イベントレポートや読書メモを発信

Agent Development Kit (1.10.0) のWeb UIやAPIサーバは DEBUG レベルでロギングして送られているプロンプトを確認できますが、その実装には伸びしろがあります(Workaroundを添えて)

はじめに

七尾百合子さん、お誕生日 145日目 おめでとうございます! nikkieです。

あのGoogleが作っているAgent Development Kit (ADK)でもまた、現実世界のロギングに苦しめられています...

目次

ドキュメント「Logging in the Agent Development Kit (ADK)」

Observabilityのセクションの1ページ

google.github.io

ADK標準ライブラリのloggingを使ってロギングしています1
ロガーの命名ではgoogle_adk.というプレフィックスを付けています2

デバッグ目的にはログレベル DEBUG を指定することになります3
DEBUGレベルでロギングされるものは4

  • Full LLM Prompts: The complete request sent to the language model, including system instructions, history, and tools.
  • Detailed API responses from services.

ADKCLIを使うとき、DEBUGレベルでのロギングは以下のように指定できます5

% adk web --log_level DEBUG
% # または
% adk web -v

--log_level DEBUGのショートカットが-v(--verbose)です。

adk webだけでなく、adk api_serverについても当てはまります。

DEBUGレベルでロギングしてみる

サンプルの YouTube Shorts エージェントを動かします。

% uvx --from google-adk adk web --log_level DEBUG

「write me a script on how to build AI agents」

LLM RequestやLLM Responseが見えて、どのように動いているかを知るのにとても助かります!

2025-08-09 13:06:45,169 - DEBUG - google_llm.py:117 - 
LLM Request:
-----------------------------------------------------------
System Instruction:
You are the Shorts Content Orchestrator. <https://github.com/google/adk-docs/blob/main/examples/python/agent-samples/youtube-shorts-assistant/shorts_agent_instruction.txt の内容が続く>

You are an agent. Your internal name is "youtube_shorts_agent".

 The description about you is "You are an agent that can write scripts, visuals and format youtube short videos. You have subagents that can do this"
-----------------------------------------------------------
Contents:
{"parts":[{"text":"write me a script on how to build AI agents"}],"role":"user"}
<ここまでの履歴 略>
-----------------------------------------------------------
Functions:
ShortsScriptwriter: {'request': {'type': <Type.STRING: 'STRING'>}} 
ShortsVisualizer: {'request': {'type': <Type.STRING: 'STRING'>}} 
ConceptFormatter: {'request': {'type': <Type.STRING: 'STRING'>}} 
-----------------------------------------------------------
2025-08-09 13:06:53,241 - DEBUG - google_llm.py:204 - 
LLM Response:
-----------------------------------------------------------
Text:
Of course! Here is a complete script and visual plan for a YouTube Short on how to build an AI agent.

<省略>
-----------------------------------------------------------
Function calls:

-----------------------------------------------------------
Raw response:
<省略>
-----------------------------------------------------------

その一方で、LLM RequestでもLLM Responseでもない、関係ないログも出力されてノイジーです。

2025-08-09 13:06:45,169 - INFO - models.py:7804 - AFC is enabled with max remote calls: 10.

2025-08-09 13:06:45,192 - DEBUG - _trace.py:87 - start_tls.started ssl_context=<ssl.SSLContext object at 0x12acbccb0> server_hostname='generativelanguage.googleapis.com' timeout=None
2025-08-09 13:06:45,247 - DEBUG - _trace.py:87 - start_tls.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x12aea6a50>
2025-08-09 13:06:45,247 - DEBUG - _trace.py:87 - send_request_body.started request=<Request [b'POST']>
2025-08-09 13:06:45,247 - DEBUG - _trace.py:87 - send_request_body.complete

2025-08-09 13:06:53,238 - INFO - _client.py:1740 - HTTP Request: POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent "HTTP/1.1 200 OK"

これはADKのロギングの実装の伸びしろです

伸びしろ:ADKはルートロガーを設定する実装をしている

adk webadk api_serverlogs.setup_adk_logger()関数を呼び出しています。

logs.setup_adk_logger()の実装に伸びしろがあります。
https://github.com/google/adk-python/blob/v1.10.0/src/google/adk/cli/utils/logs.py#L27-L32

def setup_adk_logger(level=logging.INFO):
  logging.basicConfig(level=level, format=LOGGING_FORMAT)

  adk_logger = logging.getLogger('google_adk')
  adk_logger.setLevel(level)
  • basicConfig()でルートロガーを設定
    • 渡したログレベル(先の例ではDEBUG
    • 標準出力へのハンドラ + フォーマッタ6
  • adk_loggerを渡したログレベルに設定
    • ハンドラは設定しない7

まず、ロガーには階層関係(=親子関係)があります。

  • foo.barロガーの親はfooロガー
  • fooロガーの親はルートロガー

つまり、google_adkロガーの親はルートロガーです。
ロガーが(自身のレベル以上の呼び出しによって)ログレコードを作る時、ログレコードは親のロガーに渡ります(propagate
https://docs.python.org/ja/3/library/logging.html#logging.Logger.propagate

単にlogging.getLogger()すると、そのロガーはNOTSETレベルです。
NOTSETレベルとは、ロガーを親へ親へと辿っていき、最初に見つかったNOTSET以外のレベルとして働きます。
つまり、ルートロガーがDEBUGレベルのとき、google_adkロガーはDEBUGレベルになります
adk_logger.setLevel(level)はあってもなくても変わらないでしょう。

一番の伸びしろは、ライブラリがbasicConfig()でルートロガーを設定してしまっていることです。
https://docs.python.org/ja/3/library/logging.html#logging.basicConfig

ルートロガーはすべてのロガーの親です。
そしてADKの依存するライブラリの中に、ルートロガーの子に当たるロガーがNOTSETレベルでたくさんあります。
ルートロガーがDEBUGレベルの時、google_adkロガーやライブラリの中のロガーはDEBUGレベルになります。

google_adkロガーやライブラリの中のロガーが作ったログレコードは、propagateによりルートロガーにわたります。
basicConfig()により、ルートロガーには標準出力へのハンドラが設定されています。
よって、全ロガーのDEBUGレベル(以上)のログが標準出力に出力されているわけです!

つまり伸びしろは、ユーザはADKのDEBUGレベルのログだけを見たいのに、実装はADKが依存するライブラリのDEBUGレベルも含めてすべてロギングしてしまっていることにあります。

Workaround:google_adkロガーのログだけフィルタする

adk webを叩くとルートロガーが設定されてしまうので(それはリポジトリ側に働きかけるとして)、ルートロガーのハンドラにフィルタを設定してgoogle_adkロガーのログだけ出力するようにします。
https://docs.python.org/ja/3/library/logging.html#filter-objects

root_logger = logging.getLogger()
root_logger.handlers[0].addFilter(logging.Filter("google_adk"))

Logging Flowに図示されているのですが、

  • 子のロガー(DEBUGレベル)でログレコード作成
  • -> 親のルートロガーにpropagate
  • -> ルートロガーのハンドラのフィルタをチェック
    • google_adkロガーのログだけハンドラで出力するように設定
    • (フィルタがない場合はすべてのログがハンドラで出力され、ノイジーになります)

これにより、LLM RequestとLLM Responseが中心で(一部ADKのロギングが混じる)出力となりました8

終わりに

ADKCLIでは--log_level DEBUGまたは-v/--verboseの指定で、LLMへのリクエストやLLM からのレスポンスをロギングして確認できます。
ただし、v1.10.0時点のロギングの実装には、logging.basicConfig()でルートロガーを設定してしまっているという伸びしろがあり、ログ出力はノイジーです。
エージェントの実装の中で、ルートロガーにフィルタを設定するというWorkaroundを紹介しました。

結びに、Python公式ドキュメント内の「ライブラリのためのロギングの設定」より

ルートロガーに直接ログを記録することにより、あなたのライブラリを利用するアプリケーション開発者が、ロギングの詳細度 (verbosity) やハンドラを望みのとおりに設定することを困難にしたり、不可能にしてしまいます。

ライブラリでルートロガーを触ってはいけません


  1. 「Standard Library」 https://google.github.io/adk-docs/observability/logging/#logging-philosophy
  2. 「Hierarchical Loggers」 https://google.github.io/adk-docs/observability/logging/#logging-philosophy
  3. Crucial for debugging https://google.github.io/adk-docs/observability/logging/#log-levels
  4. 全容は https://google.github.io/adk-docs/observability/logging/#what-is-logged
  5. https://google.github.io/adk-docs/observability/logging/#configuring-logging-with-the-adk-cli
  6. ファイル名(ファイルパスではない9)と行数が出るフォーマットです https://github.com/google/adk-python/blob/v1.10.0/src/google/adk/cli/utils/logs.py#L22-L24
  7. ルートロガーへのpropagateを使って、ルートロガーのハンドラで出力するためです
  8. LLM RequestとLLM Responseが作られるファイルは https://github.com/google/adk-python/blob/v1.10.0/src/google/adk/models/google_llm.py なので、フィルタでもっときつく絞ることもできます
  9. 個人的にはログレコードのnamefuncNameを出力したいです https://docs.python.org/ja/3/library/logging.html#logrecord-attributes (ライブラリ側でbasicConfigされていると、この差し替えも大変)