0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【実践】無駄なLLM呼び出しを減らす!Agent Development Kit(ADK)のcallbackでエージェント出力を賢く制御する方法

Posted at

はじめに

LLMを呼び出すと便利ですが、毎回呼んでいてはコストがかさみ、レスポンスも不安定になります。
ここで役立つのが、Google ADK(Agent Development Kit)callbackです。

callbackで出力を制御することで、指定したレスポンスを返したり、使い方によっては不要なLLM呼び出しを防ぐこともできます!
本記事では、そんなcallbackの使い方について紹介します。

callbackとは

LLMの前後やツール呼び出しの直前など、エージェント実行の節目で処理を差し込むことができる機能。
これにより、エージェントの出力そのものを制御することが可能です。

出力制御の実践

callbackを使用した例をいくつか紹介します。

1. ガードレール

まずはベーシックな利用方法として、「必ず所定の形式で返す」「禁止ワードを含んでいれば差し替える」といったガードレール的な使い方をご紹介します。

以下の例では、「Paris」というワードが入っていたらLLMを実行せずcallbackがブロックします。

from google.adk.agents import LlmAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.models import LlmResponse, LlmRequest

def block_paris_guardrail(
    callback_context: CallbackContext, llm_request: LlmRequest
) -> Optional[LlmResponse]:
    """
    LLMリクエストに'Paris'が含まれているかチェックします。
    もしそうなら、LLM実行をブロックし、特定のエラー文を返します。
    それ以外の場合は、Noneを返してLLM呼び出しを続行させます。
    """

     # 直近のユーザーメッセージを取得
    last_user_message = ""
    if llm_request.contents and llm_request.contents[-1].role == 'user':
         if llm_request.contents[-1].parts:
            last_user_message = llm_request.contents[-1].parts[0].text

    # メッセージに"Paris"が含まれるかチェック
    if "Paris" in last_user_message:
        # LLM呼び出しを中止 → callbackから直接textを返す
        return LlmResponse(
            content=types.Content(
                role="model",
                parts=[types.Part(text="LLM呼び出しはブロックされました。")],
            )
        )
    else:
        # Noneを返す場合、その後のLLMは通常通り動く
        return None

root_agent = LlmAgent(
    name="RootAgent",
    model="gemini-2.5-pro",
    description="ガードレール確認エージェント",
    instruction="""とりあえず「こんにちは」と挨拶してください""",
    before_model_callback=block_paris_guardrail, # LLMモデル実行前にcallback関数を実行
)

callbackの特徴として、None以外を返すと直接返り値が出力される仕様になっています。
そのため、LLMの前後で処理を行いたいけど、特に出力に影響を与える必要がない場合はNoneを返せばOKです。

2. tool使用後の強制終了

エージェント内でtoolを実行したらそのまま処理を終了したい場合があると思います。
(例えば、メールを送信してそのまま終了するなど)

その場合は、tool, state, callbackを組み合わせればOKです。

  • tool:AIエージェントに提供される特定の機能(アクション実行や外部連携が可能)
  • state:エージェントが会話や処理を覚えておくためのメモリのような機能

tool実行後そのまま強制終了するので、無駄なLLM呼び出しを防ぐことが可能です。

例として、以下のような構成を考えてみましょう。
最後に呼び出されるサブエージェント(Agent_2)でtool実行後、処理をそのまま終了する流れです。

エージェント構造
import os
 
from typing import Optional, Dict, Any, List
from google.adk.agents import LlmAgent, SequentialAgent
from google.adk.tools import FunctionTool
from google.adk.tools.tool_context import ToolContext
from google.adk.agents.callback_context import CallbackContext
from google.adk.models.llm_request import LlmRequest
from google.adk.models.llm_response import LlmResponse
from google.adk.tools.base_tool import BaseTool
from google.genai import types

def Tool_1(tool_context: ToolContext) -> None:
    """
    このツールの機能を説明します。
    """
    tool_context.state["is_finish"] = True

Tool_1_tool = FunctionTool(func=Tool_1)
 
def Callback_1(callback_context: CallbackContext, llm_request: LlmRequest):
    """
    State: is_finishを確認
    """
 
    if callback_context.state.get("is_finish") == True:  
        return LlmResponse(  
            content=types.Content(  
                role="model",  
                parts=[types.Part(text='処理を終了します')]  
            )  
        )  
 
Agent_1 = LlmAgent(
    name="Agent_1",
    model="gemini-2.5-pro",
    description="""これはtestですと出力して""",
    instruction="""これはtestですと出力して""",
    output_key=None
)
 
Agent_2 = LlmAgent(
    name="Agent_2",
    model="gemini-2.5-pro",
    description="""tool :test_toolを実行する""",
    instruction="""最初にtool :test_toolを1回だけ、必ず実行する""",
    tools=[Tool_1_tool],
    before_model_callback=Callback_1,
    output_key=None
)
 
RootAgent = SequentialAgent(
    name="SeqAgent",
    description="パイプラインエージェント",
    sub_agents=[Agent_1, Agent_2],
)

root_agent = RootAgent

ポイントとしては、以下あたりです。

  • 最後に呼び出されるAgent_2にてbefore_model_callbackを設定
  • before_model_callbackでは、state is_finishをチェック
    Trueであればcallbackが直接レスポンスを返す
  • Tool_1を呼び出した際にis_finishをTrueに設定

要は、毎回エージェントを呼び出す前に必ずcallbackを実行してフラグのチェックを行い、Trueだったらそのまま指定したテキストを返す、ということをしています。

実際にadk webコマンドを使用してADK web UIを立ち上げ、動作を確認してみます。

image.png

Agent_2がTool_1を呼び出して実行した後、call_llmが実行されずに指定した文章がちゃんと返ってきていますね。

3. (おまけ) callbackを使わずに強制終了する

先ほどのやり方と別で、tool内でそのまま処理を強制終了するには、もう1つ方法があります。
こちらはもう少しシンプルに実装することができます。

import os

from typing import Optional, Dict, Any, List
from google.adk.agents import LlmAgent
from google.adk.tools import FunctionTool
from google.adk.tools.tool_context import ToolContext

def TestTool(query: str, tool_context: ToolContext) -> str:
    """
    ユーザーからの質問内容を検索するTool
    """
    tool_context._invocation_context.end_invocation = True 
    return "処理を終了します"

TestTool_tool = FunctionTool(func=TestTool)

root_agent = LlmAgent(
    name="RootAgent",
    model="gemini-2.5-pro",
    description="""とりあえずtool: TestToolを呼び出して回答して""",
    instruction="""とりあえずtool: TestToolを呼び出して回答して""",
    tools=[TestTool_tool],
    output_key=None
)

ポイントとしてはtool_contextend_invocationをTrueに設定するところです。
end_invocationは、ツール実行中に現在の要求-応答のサイクルを終了するためのフラグで、エージェント内でこのフラグをチェックする仕組みになっています。

tool強制終了のcallbackありなしの使い分け

では、なぜわざわざcallbackを使用して強制終了する必要があるのか?こちらの方がシンプルなのでは?という意見が出てくるかと思います。
実はこのend_invocationは、SequentialAgentやLoopAgentなどのサブエージェント内では機能しません。

理由としては、SequentialAgentやLoopAgentは、サブエージェントを実行する場合end_invacationフラグをチェックしない仕組みだからです。

そのため、LlmAgentであればend_invocationを使用し、SequentialAgentなどの場合はcallbackとstateを使用する、みたいに使い分ける必要があります。

おわりに

callbackを使用してエージェントの出力を制御し、不要なLLM呼び出しを防ぐ方法をご紹介しました。
意外とLLMで考えずに定型文で返せばOKみたいなことも多いと思います。
また、最後にLLMが呼び出されることで処理の終了を伝えたいだけなのに返答に時間がかかる、みたいなこともあるかと思います。
そういった場面で、本記事で紹介した内容は活用できるかと思いますので、ぜひ参考にしてもらえればと思います!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?