コンテンツにスキップ

関数ツール

ADKでサポートPython v0.1.0Go v0.1.0Java v0.1.0

事前に構築されたADKツールが要件を満たさない場合は、カスタムの関数ツールを作成できます。関数ツールを構築すると、独自のデータベースへの接続や独自のアルゴリズムの実装など、カスタマイズされた機能を作成できます。 たとえば、関数ツールmyfinancetoolは、特定の財務指標を計算する関数である可能性があります。ADKは長時間実行される関数もサポートしているため、その計算に時間がかかる場合でも、エージェントは他のタスクを続行できます。

ADKは、それぞれ異なるレベルの複雑さと制御に適した、関数ツールを作成するためのいくつかの方法を提供します。

関数ツール

Python関数をツールに変換することは、カスタムロジックをエージェントに統合する簡単な方法です。エージェントのtoolsリストに関数を割り当てると、フレームワークは自動的にそれをFunctionToolとしてラップします。

仕組み

ADKフレームワークは、名前、docstring、パラメーター、型ヒント、デフォルト値など、Python関数のシグネチャを自動的に検査してスキーマを生成します。このスキーマは、LLMがツールの目的、使用時期、および必要な引数を理解するために使用するものです。

関数シグネチャの定義

LLMがツールを正しく使用するには、明確に定義された関数シグネチャが重要です。

パラメーター

必須パラメーター

パラメーターは、型ヒントがあるがデフォルト値がない場合に必須と見なされます。LLMは、ツールを呼び出すときにこの引数の値を指定する必要があります。パラメーターの説明は、関数のdocstringから取得されます。

例:必須パラメーター
def get_weather(city: str, unit: str):
    """
    指定された単位で都市の天気を取得します。

    引数:
        city (str): 都市名。
        unit (str): 温度単位。「摂氏」または「華氏」のいずれか。
    """
    # ... 関数のロジック ...
    return {"status": "success", "report": f"{city}の天気は晴れです。"}

この例では、cityunitの両方が必須です。LLMがどちらか一方なしでget_weatherを呼び出そうとすると、ADKはLLMにエラーを返し、呼び出しを修正するように促します。

Goでは、構造体タグを使用してJSONスキーマを制御します。2つの主要なタグはjsonjsonschemaです。

パラメーターは、構造体フィールドのjsonタグにomitemptyまたはomitzeroオプションがない場合に必須と見なされます。

jsonschemaタグは、引数の説明を提供するために使用されます。これは、LLMが引数が何であるかを理解するために重要です。

例:必須パラメーター
// GetWeatherParamsは、getWeatherツールの引数を定義します。
type GetWeatherParams struct {
    // このフィールドは必須です(「omitempty」なし)。
    // jsonschemaタグは説明を提供します。
    Location string `json:"location" jsonschema:"都市と州、例:カリフォルニア州サンフランシスコ"`

    // このフィールドも必須です。
    Unit     string `json:"unit" jsonschema:"温度単位。「摂氏」または「華氏」のいずれか"`
}

この例では、locationunitの両方が必須です。

オプションのパラメーター

パラメーターは、デフォルト値を指定した場合にオプションと見なされます。これは、オプションの引数を定義するための標準的なPythonの方法です。typing.Optional[SomeType]または| None構文(Python 3.10以降)を使用して、パラメーターをオプションとしてマークすることもできます。

例:オプションのパラメーター
def search_flights(destination: str, departure_date: str, flexible_days: int = 0):
    """
    フライトを検索します。

    引数:
        destination (str): 目的地の都市。
        departure_date (str): 希望する出発日。
        flexible_days (int, optional): 検索の柔軟な日数。デフォルトは0です。
    """
    # ... 関数のロジック ...
    if flexible_days > 0:
        return {"status": "success", "report": f"{destination}への柔軟なフライトが見つかりました。"}
    return {"status": "success", "report": f"{departure_date}{destination}へのフライトが見つかりました。"}

ここでは、flexible_daysはオプションです。LLMはそれを提供することを選択できますが、必須ではありません。

パラメーターは、構造体フィールドのjsonタグにomitemptyまたはomitzeroオプションがある場合にオプションと見なされます。

例:オプションのパラメーター
// GetWeatherParamsは、getWeatherツールの引数を定義します。
type GetWeatherParams struct {
    // 場所は必須です。
    Location string `json:"location" jsonschema:"都市と州、例:カリフォルニア州サンフランシスコ"`

    // 単位はオプションです。
    Unit string `json:"unit,omitempty" jsonschema:"温度単位。「摂氏」または「華氏」のいずれか"`

    // 日数はオプションです。
    Days int `json:"days,omitzero" jsonschema:"返す予報日数(デフォルトは1)"`
}

ここでは、unitdaysはオプションです。LLMはそれらを提供することを選択できますが、必須ではありません。

typing.Optionalを使用したオプションのパラメーター

typing.Optional[SomeType]または| None構文(Python 3.10以降)を使用して、パラメーターをオプションとしてマークすることもできます。これは、パラメーターがNoneになる可能性があることを示します。Noneのデフォルト値と組み合わせると、標準のオプションパラメーターのように動作します。

例:typing.Optional
from typing import Optional

def create_user_profile(username: str, bio: Optional[str] = None):
    """
    新しいユーザープロファイルを作成します。

    引数:
        username (str): ユーザーの一意のユーザー名。
        bio (str, optional): ユーザーの短い経歴。デフォルトはNoneです。
    """
    # ... 関数のロジック ...
    if bio:
        return {"status": "success", "message": f"{username}のプロファイルが経歴付きで作成されました。"}
    return {"status": "success", "message": f"{username}のプロファイルが作成されました。"}
可変長引数(*argsおよび**kwargs

他の目的で関数シグネチャに*args(可変長位置引数)および**kwargs(可変長キーワード引数)を含めることはできますが、LLMのツールスキーマを生成する際にADKフレームワークによって無視されます。LLMはそれらを認識せず、引数を渡すことはできません。LLMから期待するすべてのデータについては、明示的に定義されたパラメーターに依存するのが最善です。

戻り値の型

関数ツールの推奨される戻り値の型は、Pythonでは辞書、JavaではMapです。これにより、キーと値のペアで応答を構造化し、LLMにコンテキストと明確さを提供できます。関数が辞書以外の型を返す場合、フレームワークは自動的にそれを"result"という名前の単一のキーを持つ辞書にラップします。

戻り値をできるだけ説明的にするように努めてください。たとえば、数値のエラーコードを返す代わりに、人間が読める説明を含む「error_message」キーを持つ辞書を返します。コードではなくLLMが結果を理解する必要があることを忘れないでください。ベストプラクティスとして、戻り値の辞書に「status」キーを含めて、全体的な結果(「success」、「error」、「pending」など)を示し、LLMに操作の状態に関する明確なシグナルを提供します。

Docstrings

関数のdocstringは、ツールの説明として機能し、LLMに送信されます。したがって、LLMがツールを効果的に使用する方法を理解するには、よく書かれた包括的なdocstringが不可欠です。関数の目的、パラメーターの意味、および期待される戻り値を明確に説明してください。

ツール間のデータ受け渡し

エージェントが複数のツールを順番に呼び出す場合、あるツールから別のツールにデータを渡す必要がある場合があります。推奨される方法は、セッション状態でtemp:プレフィックスを使用することです。

ツールはtemp:変数にデータを書き込むことができ、後続のツールはそれを読み取ることができます。このデータは現在の呼び出しでのみ使用でき、その後は破棄されます。

共有呼び出しコンテキスト

単一のエージェントターン内のすべてのツール呼び出しは、同じInvocationContextを共有します。つまり、同じ一時的な(temp:)状態も共有するため、ツール間でデータを渡すことができます。

このツールは、指定された株式ティッカー/シンボルの株価を取得するPython関数です。

:このツールを使用する前に、pip install yfinanceライブラリをインストールする必要があります。

# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

import yfinance as yf


APP_NAME = "stock_app"
USER_ID = "1234"
SESSION_ID = "session1234"

def get_stock_price(symbol: str):
    """
    Retrieves the current stock price for a given symbol.

    Args:
        symbol (str): The stock symbol (e.g., "AAPL", "GOOG").

    Returns:
        float: The current stock price, or None if an error occurs.
    """
    try:
        stock = yf.Ticker(symbol)
        historical_data = stock.history(period="1d")
        if not historical_data.empty:
            current_price = historical_data['Close'].iloc[-1]
            return current_price
        else:
            return None
    except Exception as e:
        print(f"Error retrieving stock price for {symbol}: {e}")
        return None


stock_price_agent = Agent(
    model='gemini-2.0-flash',
    name='stock_agent',
    instruction= 'You are an agent who retrieves stock prices. If a ticker symbol is provided, fetch the current price. If only a company name is given, first perform a Google search to find the correct ticker symbol before retrieving the stock price. If the provided ticker symbol is invalid or data cannot be retrieved, inform the user that the stock price could not be found.',
    description='This agent specializes in retrieving real-time stock prices. Given a stock ticker symbol (e.g., AAPL, GOOG, MSFT) or the stock name, use the tools and reliable data sources to provide the most up-to-date price.',
    tools=[get_stock_price], # You can add Python functions directly to the tools list; they will be automatically wrapped as FunctionTools.
)


# Session and Runner
async def setup_session_and_runner():
    session_service = InMemorySessionService()
    session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
    runner = Runner(agent=stock_price_agent, app_name=APP_NAME, session_service=session_service)
    return session, runner

# Agent Interaction
async def call_agent_async(query):
    content = types.Content(role='user', parts=[types.Part(text=query)])
    session, runner = await setup_session_and_runner()
    events = runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=content)

    async for event in events:
        if event.is_final_response():
            final_response = event.content.parts[0].text
            print("Agent Response: ", final_response)


# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
await call_agent_async("stock price of GOOG")

このツールからの戻り値は辞書にラップされます。

{"result": "$123"}

このツールは、株価のモック値を取得します。

import (
    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/functiontool"
    "google.golang.org/genai"
)

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
    "context"
    "fmt"
    "log"
    "strings"

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/agenttool"
    "google.golang.org/adk/tool/functiontool"

    "google.golang.org/genai"
)

// mockStockPrices provides a simple in-memory database of stock prices
// to simulate a real-world stock data API. This allows the example to
// demonstrate tool functionality without making external network calls.
var mockStockPrices = map[string]float64{
    "GOOG": 300.6,
    "AAPL": 123.4,
    "MSFT": 234.5,
}

// getStockPriceArgs defines the schema for the arguments passed to the getStockPrice tool.
// Using a struct is the recommended approach in the Go ADK as it provides strong
// typing and clear validation for the expected inputs.
type getStockPriceArgs struct {
    Symbol string `json:"symbol" jsonschema:"The stock ticker symbol, e.g., GOOG"`
}

// getStockPriceResults defines the output schema for the getStockPrice tool.
type getStockPriceResults struct {
    Symbol string  `json:"symbol"`
    Price  float64 `json:"price,omitempty"`
    Error  string  `json:"error,omitempty"`
}

// getStockPrice is a tool that retrieves the stock price for a given ticker symbol
// from the mockStockPrices map. It demonstrates how a function can be used as a
// tool by an agent. If the symbol is found, it returns a struct containing the
// symbol and its price. Otherwise, it returns a struct with an error message.
func getStockPrice(ctx tool.Context, input getStockPriceArgs) (getStockPriceResults, error) {
    symbolUpper := strings.ToUpper(input.Symbol)
    if price, ok := mockStockPrices[symbolUpper]; ok {
        fmt.Printf("Tool: Found price for %s: %f\n", input.Symbol, price)
        return getStockPriceResults{Symbol: input.Symbol, Price: price}, nil
    }
    return getStockPriceResults{}, fmt.Errorf("no data found for symbol")
}

// createStockAgent initializes and configures an LlmAgent.
// This agent is equipped with the getStockPrice tool and is instructed
// on how to respond to user queries about stock prices. It uses the
// Gemini model to understand user intent and decide when to use its tools.
func createStockAgent(ctx context.Context) (agent.Agent, error) {
    stockPriceTool, err := functiontool.New(
        functiontool.Config{
            Name:        "get_stock_price",
            Description: "Retrieves the current stock price for a given symbol.",
        },
        getStockPrice)
    if err != nil {
        return nil, err
    }

    model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})

    if err != nil {
        log.Fatalf("Failed to create model: %v", err)
    }

    return llmagent.New(llmagent.Config{
        Name:        "stock_agent",
        Model:       model,
        Instruction: "You are an agent who retrieves stock prices. If a ticker symbol is provided, fetch the current price. If only a company name is given, first perform a Google search to find the correct ticker symbol before retrieving the stock price. If the provided ticker symbol is invalid or data cannot be retrieved, inform the user that the stock price could not be found.",
        Description: "This agent specializes in retrieving real-time stock prices. Given a stock ticker symbol (e.g., AAPL, GOOG, MSFT) or the stock name, use the tools and reliable data sources to provide the most up-to-date price.",
        Tools: []tool.Tool{
            stockPriceTool,
        },
    })
}

// userID and appName are constants used to identify the user and application
// throughout the session. These values are important for logging, tracking,
// and managing state across different agent interactions.
const (
    userID  = "example_user_id"
    appName = "example_app"
)

// callAgent orchestrates the execution of the agent for a given prompt.
// It sets up the necessary services, creates a session, and uses a runner
// to manage the agent's lifecycle. It streams the agent's responses and
// prints them to the console, handling any potential errors during the run.
func callAgent(ctx context.Context, a agent.Agent, prompt string) {
    sessionService := session.InMemoryService()
    // Create a new session for the agent interactions.
    session, err := sessionService.Create(ctx, &session.CreateRequest{
        AppName: appName,
        UserID:  userID,
    })
    if err != nil {
        log.Fatalf("Failed to create the session service: %v", err)
    }
    config := runner.Config{
        AppName:        appName,
        Agent:          a,
        SessionService: sessionService,
    }

    // Create the runner to manage the agent execution.
    r, err := runner.New(config)

    if err != nil {
        log.Fatalf("Failed to create the runner: %v", err)
    }

    sessionID := session.Session.ID()

    userMsg := &genai.Content{
        Parts: []*genai.Part{
            genai.NewPartFromText(prompt),
        },
        Role: string(genai.RoleUser),
    }

    for event, err := range r.Run(ctx, userID, sessionID, userMsg, agent.RunConfig{
        StreamingMode: agent.StreamingModeNone,
    }) {
        if err != nil {
            fmt.Printf("\nAGENT_ERROR: %v\n", err)
        } else {
            for _, p := range event.Content.Parts {
                fmt.Print(p.Text)
            }
        }
    }
}

// RunAgentSimulation serves as the entry point for this example.
// It creates the stock agent and then simulates a series of user interactions
// by sending different prompts to the agent. This function showcases how the
// agent responds to various queries, including both successful and unsuccessful
// attempts to retrieve stock prices.
func RunAgentSimulation() {
    // Create the stock agent
    agent, err := createStockAgent(context.Background())
    if err != nil {
        panic(err)
    }

    fmt.Println("Agent created:", agent.Name())

    prompts := []string{
        "stock price of GOOG",
        "What's the price of MSFT?",
        "Can you find the stock price for an unknown company XYZ?",
    }

    // Simulate running the agent with different prompts
    for _, prompt := range prompts {
        fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
        callAgent(context.Background(), agent, prompt)
        fmt.Println("\n---")
    }
}

// createSummarizerAgent creates an agent whose sole purpose is to summarize text.
func createSummarizerAgent(ctx context.Context) (agent.Agent, error) {
    model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
    if err != nil {
        return nil, err
    }
    return llmagent.New(llmagent.Config{
        Name:        "SummarizerAgent",
        Model:       model,
        Instruction: "You are an expert at summarizing text. Take the user's input and provide a concise summary.",
        Description: "An agent that summarizes text.",
    })
}

// createMainAgent creates the primary agent that will use the summarizer agent as a tool.
func createMainAgent(ctx context.Context, tools ...tool.Tool) (agent.Agent, error) {
    model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
    if err != nil {
        return nil, err
    }
    return llmagent.New(llmagent.Config{
        Name:  "MainAgent",
        Model: model,
        Instruction: "You are a helpful assistant. If you are asked to summarize a long text, use the 'summarize' tool. " +
            "After getting the summary, present it to the user by saying 'Here is a summary of the text:'.",
        Description: "The main agent that can delegate tasks.",
        Tools:       tools,
    })
}

func RunAgentAsToolSimulation() {
    ctx := context.Background()

    // 1. Create the Tool Agent (Summarizer)
    summarizerAgent, err := createSummarizerAgent(ctx)
    if err != nil {
        log.Fatalf("Failed to create summarizer agent: %v", err)
    }

    // 2. Wrap the Tool Agent in an AgentTool
    summarizeTool := agenttool.New(summarizerAgent, &agenttool.Config{
        SkipSummarization: true,
    })

    // 3. Create the Main Agent and provide it with the AgentTool
    mainAgent, err := createMainAgent(ctx, summarizeTool)
    if err != nil {
        log.Fatalf("Failed to create main agent: %v", err)
    }

    // 4. Run the main agent
    prompt := `
        Please summarize this text for me:
        Quantum computing represents a fundamentally different approach to computation,
        leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
        that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
        being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
        meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
        interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
        as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
        faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.
    `
    fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
    callAgent(context.Background(), mainAgent, prompt)
    fmt.Println("\n---")
}


func main() {
    fmt.Println("Attempting to run the agent simulation...")
    RunAgentSimulation()
    fmt.Println("\nAttempting to run the agent-as-a-tool simulation...")
    RunAgentAsToolSimulation()
}

このツールからの戻り値はgetStockPriceResultsインスタンスになります。

入力`{"symbol": "GOOG"}`の場合:{"price":300.6,"symbol":"GOOG"}

このツールは、株価のモック値を取得します。

import com.google.adk.agents.LlmAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;
import java.util.HashMap;
import java.util.Map;

public class StockPriceAgent {

  private static final String APP_NAME = "stock_agent";
  private static final String USER_ID = "user1234";

  // Mock data for various stocks functionality
  // NOTE: This is a MOCK implementation. In a real Java application,
  // you would use a financial data API or library.
  private static final Map<String, Double> mockStockPrices = new HashMap<>();

  static {
    mockStockPrices.put("GOOG", 1.0);
    mockStockPrices.put("AAPL", 1.0);
    mockStockPrices.put("MSFT", 1.0);
  }

  @Schema(description = "Retrieves the current stock price for a given symbol.")
  public static Map<String, Object> getStockPrice(
      @Schema(description = "The stock symbol (e.g., \"AAPL\", \"GOOG\")",
        name = "symbol")
      String symbol) {

    try {
      if (mockStockPrices.containsKey(symbol.toUpperCase())) {
        double currentPrice = mockStockPrices.get(symbol.toUpperCase());
        System.out.println("Tool: Found price for " + symbol + ": " + currentPrice);
        return Map.of("symbol", symbol, "price", currentPrice);
      } else {
        return Map.of("symbol", symbol, "error", "No data found for symbol");
      }
    } catch (Exception e) {
      return Map.of("symbol", symbol, "error", e.getMessage());
    }
  }

  public static void callAgent(String prompt) {
    // Create the FunctionTool from the Java method
    FunctionTool getStockPriceTool = FunctionTool.create(StockPriceAgent.class, "getStockPrice");

    LlmAgent stockPriceAgent =
        LlmAgent.builder()
            .model("gemini-2.0-flash")
            .name("stock_agent")
            .instruction(
                "You are an agent who retrieves stock prices. If a ticker symbol is provided, fetch the current price. If only a company name is given, first perform a Google search to find the correct ticker symbol before retrieving the stock price. If the provided ticker symbol is invalid or data cannot be retrieved, inform the user that the stock price could not be found.")
            .description(
                "This agent specializes in retrieving real-time stock prices. Given a stock ticker symbol (e.g., AAPL, GOOG, MSFT) or the stock name, use the tools and reliable data sources to provide the most up-to-date price.")
            .tools(getStockPriceTool) // Add the Java FunctionTool
            .build();

    // Create an InMemoryRunner
    InMemoryRunner runner = new InMemoryRunner(stockPriceAgent, APP_NAME);
    // InMemoryRunner automatically creates a session service. Create a session using the service
    Session session = runner.sessionService().createSession(APP_NAME, USER_ID).blockingGet();
    Content userMessage = Content.fromParts(Part.fromText(prompt));

    // Run the agent
    Flowable<Event> eventStream = runner.runAsync(USER_ID, session.id(), userMessage);

    // Stream event response
    eventStream.blockingForEach(
        event -> {
          if (event.finalResponse()) {
            System.out.println(event.stringifyContent());
          }
        });
  }

  public static void main(String[] args) {
    callAgent("stock price of GOOG");
    callAgent("What's the price of MSFT?");
    callAgent("Can you find the stock price for an unknown company XYZ?");
  }
}

このツールからの戻り値はMapにラップされます。

入力`GOOG`の場合:{"symbol": "GOOG", "price": "1.0"}

ベストプラクティス

関数を定義する際にはかなりの柔軟性がありますが、シンプルさがLLMの使いやすさを向上させることを忘れないでください。次のガイドラインを検討してください。

  • パラメーターは少ない方が良い: 複雑さを減らすためにパラメーターの数を最小限に抑えます。
  • 単純なデータ型: 可能な場合は、カスタムクラスよりもstrintなどのプリミティブデータ型を優先します。
  • 意味のある名前: 関数の名前とパラメーター名は、LLMがツールを解釈して利用する方法に大きく影響します。関数の目的と入力の意味を明確に反映する名前を選択してください。do_stuff()beAgent()などの一般的な名前は避けてください。
  • 並列実行用に構築: 複数のツールが実行されるときに非同期操作用に構築することで、関数呼び出しのパフォーマンスを向上させます。ツールの並列実行を有効にする方法については、 並列実行によるツールパフォーマンスの向上を参照してください。

長時間実行関数ツール

このツールは、エージェントの実行をブロックすることなく、エージェントワークフローの操作の外部で処理され、かなりの処理時間を必要とするタスクを開始および管理するのに役立つように設計されています。このツールはFunctionToolのサブクラスです。

LongRunningFunctionToolを使用する場合、関数は長時間実行操作を開始し、オプションで長時間実行操作IDなどの初期結果を返すことができます。長時間実行関数ツールが呼び出されると、エージェントランナーはエージェントの実行を一時停止し、エージェントクライアントが長時間実行操作が終了するまで続行するか待機するかを決定できるようにします。エージェントクライアントは、長時間実行操作の進行状況をクエリし、中間または最終応答を返信できます。その後、エージェントは他のタスクを続行できます。例としては、エージェントがタスクを続行する前に人間の承認が必要なヒューマンインザループシナリオがあります。

警告:実行処理

長時間実行関数ツールは、エージェントワークフローの一部として長時間実行タスクを開始および管理するのに役立つように設計されていますが、実際の長時間タスクを実行するわけではありません。 完了にかなりの時間が必要なタスクの場合は、タスクを実行する別のサーバーを実装する必要があります。

ヒント:並列実行

構築しているツールの種類によっては、非同期操作用に設計する方が、長時間実行ツールを作成するよりも優れたソリューションになる場合があります。 詳細については、 並列実行によるツールパフォーマンスの向上を参照してください。

仕組み

Pythonでは、関数をLongRunningFunctionToolでラップします。Javaでは、メソッド名をLongRunningFunctionTool.create()に渡します。

  1. 開始: LLMがツールを呼び出すと、関数は長時間実行操作を開始します。

  2. 初期更新: 関数はオプションで初期結果(長時間実行操作IDなど)を返す必要があります。ADKフレームワークは結果を受け取り、FunctionResponse内にパッケージ化してLLMに返します。これにより、LLMはユーザーに通知できます(ステータス、完了率、メッセージなど)。その後、エージェントの実行は終了/一時停止されます。

  3. 続行または待機: 各エージェントの実行が完了した後。エージェントクライアントは、長時間実行操作の進行状況をクエリし、中間応答でエージェントの実行を続行するか(進行状況を更新するため)、最終応答が取得されるまで待機するかを決定できます。エージェントクライアントは、次回の実行のために中間または最終応答をエージェントに返信する必要があります。

  4. フレームワーク処理: ADKフレームワークは実行を管理します。エージェントクライアントから送信された中間または最終FunctionResponseをLLMに送信して、ユーザーフレンドリーなメッセージを生成します。

ツールの作成

ツール関数を定義し、LongRunningFunctionToolクラスを使用してラップします。

# 1. Define the long running function
def ask_for_approval(
    purpose: str, amount: float
) -> dict[str, Any]:
    """Ask for approval for the reimbursement."""
    # create a ticket for the approval
    # Send a notification to the approver with the link of the ticket
    return {'status': 'pending', 'approver': 'Sean Zhou', 'purpose' : purpose, 'amount': amount, 'ticket-id': 'approval-ticket-1'}

def reimburse(purpose: str, amount: float) -> str:
    """Reimburse the amount of money to the employee."""
    # send the reimbrusement request to payment vendor
    return {'status': 'ok'}

# 2. Wrap the function with LongRunningFunctionTool
long_running_tool = LongRunningFunctionTool(func=ask_for_approval)
import (
    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/functiontool"
    "google.golang.org/genai"
)

// CreateTicketArgs defines the arguments for our long-running tool.
type CreateTicketArgs struct {
    Urgency string `json:"urgency" jsonschema:"The urgency level of the ticket."`
}

// CreateTicketResults defines the *initial* output of our long-running tool.
type CreateTicketResults struct {
    Status   string `json:"status"`
    TicketId string `json:"ticket_id"`
}

// createTicketAsync simulates the *initiation* of a long-running ticket creation task.
func createTicketAsync(ctx tool.Context, args CreateTicketArgs) (CreateTicketResults, error) {
    log.Printf("TOOL_EXEC: 'create_ticket_long_running' called with urgency: %s (Call ID: %s)\n", args.Urgency, ctx.FunctionCallID())

    // "Generate" a ticket ID and return it in the initial response.
    ticketID := "TICKET-ABC-123"
    log.Printf("ACTION: Generated Ticket ID: %s for Call ID: %s\n", ticketID, ctx.FunctionCallID())

    // In a real application, you would save the association between the
    // FunctionCallID and the ticketID to handle the async response later.
    return CreateTicketResults{
        Status:   "started",
        TicketId: ticketID,
    }, nil
}

func createTicketAgent(ctx context.Context) (agent.Agent, error) {
    ticketTool, err := functiontool.New(
        functiontool.Config{
            Name:        "create_ticket_long_running",
            Description: "Creates a new support ticket with a specified urgency level.",
        },
        createTicketAsync,
    )
    if err != nil {
        return nil, fmt.Errorf("failed to create long running tool: %w", err)
    }

    model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
    if err != nil {
        return nil, fmt.Errorf("failed to create model: %v", err)
    }

    return llmagent.New(llmagent.Config{
        Name:        "ticket_agent",
        Model:       model,
        Instruction: "You are a helpful assistant for creating support tickets. Provide the status of the ticket at each interaction.",
        Tools:       []tool.Tool{ticketTool},
    })
}
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.LongRunningFunctionTool;
import java.util.HashMap;
import java.util.Map;

public class ExampleLongRunningFunction {

  // 長時間実行関数を定義します。
  // 払い戻しの承認を求めます。
  public static Map<String, Object> askForApproval(String purpose, double amount) {
    // チケットの作成と通知の送信をシミュレートします
    System.out.println(
        "目的のためのチケット作成をシミュレートしています: " + purpose + ", 金額: " + amount);

    // チケットのリンクを付けて承認者に通知を送信します
    Map<String, Object> result = new HashMap<>();
    result.put("status", "pending");
    result.put("approver", "Sean Zhou");
    result.put("purpose", purpose);
    result.put("amount", amount);
    result.put("ticket-id", "approval-ticket-1");
    return result;
  }

  public static void main(String[] args) throws NoSuchMethodException {
    // メソッドをLongRunningFunctionTool.createに渡します
    LongRunningFunctionTool approveTool =
        LongRunningFunctionTool.create(ExampleLongRunningFunction.class, "askForApproval");

    // エージェントにツールを含めます
    LlmAgent approverAgent =
        LlmAgent.builder()
            // ...
            .tools(approveTool)
            .build();
  }
}

中間/最終結果の更新

エージェントクライアントは、長時間実行関数呼び出しでイベントを受信し、チケットのステータスを確認します。その後、エージェントクライアントは中間または最終応答を返信して進行状況を更新できます。フレームワークは、この値(Noneであっても)をLLMに返されるFunctionResponseのコンテンツにパッケージ化します。

注:再開機能付きの長時間実行関数応答

ADKエージェントワークフローが 再開機能で構成されている場合は、長時間実行 関数応答とともに呼び出しID(invocation_id)パラメーターも指定する必要があります。 指定する呼び出しIDは、長時間実行関数リクエストを生成した呼び出しと同じでなければなりません。 そうしないと、システムは応答で新しい呼び出しを開始します。 エージェントが再開機能を使用する場合は、応答に含めることができるように、 長時間実行関数リクエストとともに呼び出しIDをパラメーターとして含めることを検討してください。 再開機能の使用方法の詳細については、 停止したエージェントの再開を参照してください。

Java ADKにのみ適用

関数ツールでToolContextを渡す場合は、次のいずれかが当てはまることを確認してください。

  • スキーマが関数シグネチャのToolContextパラメーターとともに渡されます。例:

    @com.google.adk.tools.Annotations.Schema(name = "toolContext") ToolContext toolContext
    
    または

  • 次の-parametersフラグがmvnコンパイラプラグインに設定されています

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.14.0</version> <!-- またはそれ以降 -->
            <configuration>
                <compilerArgs>
                    <arg>-parameters</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>
この制約は一時的なものであり、削除されます。

# Agent Interaction
async def call_agent_async(query):

    def get_long_running_function_call(event: Event) -> types.FunctionCall:
        # Get the long running function call from the event
        if not event.long_running_tool_ids or not event.content or not event.content.parts:
            return
        for part in event.content.parts:
            if (
                part
                and part.function_call
                and event.long_running_tool_ids
                and part.function_call.id in event.long_running_tool_ids
            ):
                return part.function_call

    def get_function_response(event: Event, function_call_id: str) -> types.FunctionResponse:
        # Get the function response for the fuction call with specified id.
        if not event.content or not event.content.parts:
            return
        for part in event.content.parts:
            if (
                part
                and part.function_response
                and part.function_response.id == function_call_id
            ):
                return part.function_response

    content = types.Content(role='user', parts=[types.Part(text=query)])
    session, runner = await setup_session_and_runner()

    print("\nRunning agent...")
    events_async = runner.run_async(
        session_id=session.id, user_id=USER_ID, new_message=content
    )


    long_running_function_call, long_running_function_response, ticket_id = None, None, None
    async for event in events_async:
        # Use helper to check for the specific auth request event
        if not long_running_function_call:
            long_running_function_call = get_long_running_function_call(event)
        else:
            _potential_response = get_function_response(event, long_running_function_call.id)
            if _potential_response: # Only update if we get a non-None response
                long_running_function_response = _potential_response
                ticket_id = long_running_function_response.response['ticket-id']
        if event.content and event.content.parts:
            if text := ''.join(part.text or '' for part in event.content.parts):
                print(f'[{event.author}]: {text}')


    if long_running_function_response:
        # query the status of the correpsonding ticket via tciket_id
        # send back an intermediate / final response
        updated_response = long_running_function_response.model_copy(deep=True)
        updated_response.response = {'status': 'approved'}
        async for event in runner.run_async(
          session_id=session.id, user_id=USER_ID, new_message=types.Content(parts=[types.Part(function_response = updated_response)], role='user')
        ):
            if event.content and event.content.parts:
                if text := ''.join(part.text or '' for part in event.content.parts):
                    print(f'[{event.author}]: {text}')

次の例は、マルチターンワークフローを示しています。まず、ユーザーはエージェントにチケットを作成するように依頼します。エージェントは長時間実行ツールを呼び出し、クライアントはFunctionCall IDをキャプチャします。次に、クライアントは、チケットIDと最終ステータスを提供するために後続のFunctionResponseメッセージをエージェントに返信することで、非同期作業の完了をシミュレートします。

// runTurn executes a single turn with the agent and returns the captured function call ID.
func runTurn(ctx context.Context, r *runner.Runner, sessionID, turnLabel string, content *genai.Content) string {
    var funcCallID atomic.Value // Safely store the found ID.

    fmt.Printf("\n--- %s ---\n", turnLabel)
    for event, err := range r.Run(ctx, userID, sessionID, content, agent.RunConfig{
        StreamingMode: agent.StreamingModeNone,
    }) {
        if err != nil {
            fmt.Printf("\nAGENT_ERROR: %v\n", err)
            continue
        }
        // Print a summary of the event for clarity.
        printEventSummary(event, turnLabel)

        // Capture the function call ID from the event.
        for _, part := range event.Content.Parts {
            if fc := part.FunctionCall; fc != nil {
                if fc.Name == "create_ticket_long_running" {
                    funcCallID.Store(fc.ID)
                }
            }
        }
    }

    if id, ok := funcCallID.Load().(string); ok {
        return id
    }
    return ""
}

func main() {
    ctx := context.Background()
    ticketAgent, err := createTicketAgent(ctx)
    if err != nil {
        log.Fatalf("Failed to create agent: %v", err)
    }

    // Setup the runner and session.
    sessionService := session.InMemoryService()
    session, err := sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID})
    if err != nil {
        log.Fatalf("Failed to create session: %v", err)
    }
    r, err := runner.New(runner.Config{AppName: appName, Agent: ticketAgent, SessionService: sessionService})
    if err != nil {
        log.Fatalf("Failed to create runner: %v", err)
    }

    // --- Turn 1: User requests to create a ticket. ---
    initialUserMessage := genai.NewContentFromText("Create a high urgency ticket for me.", genai.RoleUser)
    funcCallID := runTurn(ctx, r, session.Session.ID(), "Turn 1: User Request", initialUserMessage)
    if funcCallID == "" {
        log.Fatal("ERROR: Tool 'create_ticket_long_running' not called in Turn 1.")
    }
    fmt.Printf("ACTION: Captured FunctionCall ID: %s\n", funcCallID)

    // --- Turn 2: App provides the final status of the ticket. ---
    // In a real application, the ticketID would be retrieved from a database
    // using the funcCallID. For this example, we'll use the same ID.
    ticketID := "TICKET-ABC-123"
    willContinue := false // Signal that this is the final response.
    ticketStatusResponse := &genai.FunctionResponse{
        Name: "create_ticket_long_running",
        ID:   funcCallID,
        Response: map[string]any{
            "status":    "approved",
            "ticket_id": ticketID,
        },
        WillContinue: &willContinue,
    }
    appResponseWithStatus := &genai.Content{
        Role:  string(genai.RoleUser),
        Parts: []*genai.Part{{FunctionResponse: ticketStatusResponse}},
    }
    runTurn(ctx, r, session.Session.ID(), "Turn 2: App provides ticket status", appResponseWithStatus)
    fmt.Println("Long running function completed successfully.")
}

// printEventSummary provides a readable log of agent and LLM interactions.
func printEventSummary(event *session.Event, turnLabel string) {
    for _, part := range event.Content.Parts {
        // Check for a text part.
        if part.Text != "" {
            fmt.Printf("[%s][%s_TEXT]: %s\n", turnLabel, event.Author, part.Text)
        }
        // Check for a function call part.
        if fc := part.FunctionCall; fc != nil {
            fmt.Printf("[%s][%s_CALL]: %s(%v) ID: %s\n", turnLabel, event.Author, fc.Name, fc.Args, fc.ID)
        }
    }
}
import com.google.adk.agents.LlmAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.LongRunningFunctionTool;
import com.google.adk.tools.ToolContext;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.genai.types.Content;
import com.google.genai.types.FunctionCall;
import com.google.genai.types.FunctionResponse;
import com.google.genai.types.Part;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

public class LongRunningFunctionExample {

  private static String USER_ID = "user123";

  @Schema(
      name = "create_ticket_long_running",
      description = """
          Creates a new support ticket with a specified urgency level.
          Examples of urgency are 'high', 'medium', or 'low'.
          The ticket creation is a long-running process, and its ID will be provided when ready.
      """)
  public static void createTicketAsync(
      @Schema(
              name = "urgency",
              description =
                  "The urgency level for the new ticket, such as 'high', 'medium', or 'low'.")
          String urgency,
      @Schema(name = "toolContext") // Ensures ADK injection
          ToolContext toolContext) {
    System.out.printf(
        "TOOL_EXEC: 'create_ticket_long_running' called with urgency: %s (Call ID: %s)%n",
        urgency, toolContext.functionCallId().orElse("N/A"));
  }

  public static void main(String[] args) {
    LlmAgent agent =
        LlmAgent.builder()
            .name("ticket_agent")
            .description("Agent for creating tickets via a long-running task.")
            .model("gemini-2.0-flash")
            .tools(
                ImmutableList.of(
                    LongRunningFunctionTool.create(
                        LongRunningFunctionExample.class, "createTicketAsync")))
            .build();

    Runner runner = new InMemoryRunner(agent);
    Session session =
        runner.sessionService().createSession(agent.name(), USER_ID, null, null).blockingGet();

    // --- Turn 1: User requests ticket ---
    System.out.println("\n--- Turn 1: User Request ---");
    Content initialUserMessage =
        Content.fromParts(Part.fromText("Create a high urgency ticket for me."));

    AtomicReference<String> funcCallIdRef = new AtomicReference<>();
    runner
        .runAsync(USER_ID, session.id(), initialUserMessage)
        .blockingForEach(
            event -> {
              printEventSummary(event, "T1");
              if (funcCallIdRef.get() == null) { // Capture the first relevant function call ID
                event.content().flatMap(Content::parts).orElse(ImmutableList.of()).stream()
                    .map(Part::functionCall)
                    .flatMap(Optional::stream)
                    .filter(fc -> "create_ticket_long_running".equals(fc.name().orElse("")))
                    .findFirst()
                    .flatMap(FunctionCall::id)
                    .ifPresent(funcCallIdRef::set);
              }
            });

    if (funcCallIdRef.get() == null) {
      System.out.println("ERROR: Tool 'create_ticket_long_running' not called in Turn 1.");
      return;
    }
    System.out.println("ACTION: Captured FunctionCall ID: " + funcCallIdRef.get());

    // --- Turn 2: App provides initial ticket_id (simulating async tool completion) ---
    System.out.println("\n--- Turn 2: App provides ticket_id ---");
    String ticketId = "TICKET-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    FunctionResponse ticketCreatedFuncResponse =
        FunctionResponse.builder()
            .name("create_ticket_long_running")
            .id(funcCallIdRef.get())
            .response(ImmutableMap.of("ticket_id", ticketId))
            .build();
    Content appResponseWithTicketId =
        Content.builder()
            .parts(
                ImmutableList.of(
                    Part.builder().functionResponse(ticketCreatedFuncResponse).build()))
            .role("user")
            .build();

    runner
        .runAsync(USER_ID, session.id(), appResponseWithTicketId)
        .blockingForEach(event -> printEventSummary(event, "T2"));
    System.out.println("ACTION: Sent ticket_id " + ticketId + " to agent.");

    // --- Turn 3: App provides ticket status update ---
    System.out.println("\n--- Turn 3: App provides ticket status ---");
    FunctionResponse ticketStatusFuncResponse =
        FunctionResponse.builder()
            .name("create_ticket_long_running")
            .id(funcCallIdRef.get())
            .response(ImmutableMap.of("status", "approved", "ticket_id", ticketId))
            .build();
    Content appResponseWithStatus =
        Content.builder()
            .parts(
                ImmutableList.of(Part.builder().functionResponse(ticketStatusFuncResponse).build()))
            .role("user")
            .build();

    runner
        .runAsync(USER_ID, session.id(), appResponseWithStatus)
        .blockingForEach(event -> printEventSummary(event, "T3_FINAL"));
    System.out.println("Long running function completed successfully.");
  }

  private static void printEventSummary(Event event, String turnLabel) {
    event
        .content()
        .ifPresent(
            content -> {
              String text =
                  content.parts().orElse(ImmutableList.of()).stream()
                      .map(part -> part.text().orElse(""))
                      .filter(s -> !s.isEmpty())
                      .collect(Collectors.joining(" "));
              if (!text.isEmpty()) {
                System.out.printf("[%s][%s_TEXT]: %s%n", turnLabel, event.author(), text);
              }
              content.parts().orElse(ImmutableList.of()).stream()
                  .map(Part::functionCall)
                  .flatMap(Optional::stream)
                  .findFirst() // Assuming one function call per relevant event for simplicity
                  .ifPresent(
                      fc ->
                          System.out.printf(
                              "[%s][%s_CALL]: %s(%s) ID: %s%n",
                              turnLabel,
                              event.author(),
                              fc.name().orElse("N/A"),
                              fc.args().orElse(ImmutableMap.of()),
                              fc.id().orElse("N/A")));
            });
  }
}
Pythonの完全な例:ファイル処理シミュレーション
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import asyncio
from typing import Any
from google.adk.agents import Agent
from google.adk.events import Event
from google.adk.runners import Runner
from google.adk.tools import LongRunningFunctionTool
from google.adk.sessions import InMemorySessionService
from google.genai import types


# 1. Define the long running function
def ask_for_approval(
    purpose: str, amount: float
) -> dict[str, Any]:
    """Ask for approval for the reimbursement."""
    # create a ticket for the approval
    # Send a notification to the approver with the link of the ticket
    return {'status': 'pending', 'approver': 'Sean Zhou', 'purpose' : purpose, 'amount': amount, 'ticket-id': 'approval-ticket-1'}

def reimburse(purpose: str, amount: float) -> str:
    """Reimburse the amount of money to the employee."""
    # send the reimbrusement request to payment vendor
    return {'status': 'ok'}

# 2. Wrap the function with LongRunningFunctionTool
long_running_tool = LongRunningFunctionTool(func=ask_for_approval)


# 3. Use the tool in an Agent
file_processor_agent = Agent(
    # Use a model compatible with function calling
    model="gemini-2.0-flash",
    name='reimbursement_agent',
    instruction="""
      You are an agent whose job is to handle the reimbursement process for
      the employees. If the amount is less than $100, you will automatically
      approve the reimbursement.

      If the amount is greater than $100, you will
      ask for approval from the manager. If the manager approves, you will
      call reimburse() to reimburse the amount to the employee. If the manager
      rejects, you will inform the employee of the rejection.
    """,
    tools=[reimburse, long_running_tool]
)


APP_NAME = "human_in_the_loop"
USER_ID = "1234"
SESSION_ID = "session1234"

# Session and Runner
async def setup_session_and_runner():
    session_service = InMemorySessionService()
    session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
    runner = Runner(agent=file_processor_agent, app_name=APP_NAME, session_service=session_service)
    return session, runner


# Agent Interaction
async def call_agent_async(query):

    def get_long_running_function_call(event: Event) -> types.FunctionCall:
        # Get the long running function call from the event
        if not event.long_running_tool_ids or not event.content or not event.content.parts:
            return
        for part in event.content.parts:
            if (
                part
                and part.function_call
                and event.long_running_tool_ids
                and part.function_call.id in event.long_running_tool_ids
            ):
                return part.function_call

    def get_function_response(event: Event, function_call_id: str) -> types.FunctionResponse:
        # Get the function response for the fuction call with specified id.
        if not event.content or not event.content.parts:
            return
        for part in event.content.parts:
            if (
                part
                and part.function_response
                and part.function_response.id == function_call_id
            ):
                return part.function_response

    content = types.Content(role='user', parts=[types.Part(text=query)])
    session, runner = await setup_session_and_runner()

    print("\nRunning agent...")
    events_async = runner.run_async(
        session_id=session.id, user_id=USER_ID, new_message=content
    )


    long_running_function_call, long_running_function_response, ticket_id = None, None, None
    async for event in events_async:
        # Use helper to check for the specific auth request event
        if not long_running_function_call:
            long_running_function_call = get_long_running_function_call(event)
        else:
            _potential_response = get_function_response(event, long_running_function_call.id)
            if _potential_response: # Only update if we get a non-None response
                long_running_function_response = _potential_response
                ticket_id = long_running_function_response.response['ticket-id']
        if event.content and event.content.parts:
            if text := ''.join(part.text or '' for part in event.content.parts):
                print(f'[{event.author}]: {text}')


    if long_running_function_response:
        # query the status of the correpsonding ticket via tciket_id
        # send back an intermediate / final response
        updated_response = long_running_function_response.model_copy(deep=True)
        updated_response.response = {'status': 'approved'}
        async for event in runner.run_async(
          session_id=session.id, user_id=USER_ID, new_message=types.Content(parts=[types.Part(function_response = updated_response)], role='user')
        ):
            if event.content and event.content.parts:
                if text := ''.join(part.text or '' for part in event.content.parts):
                    print(f'[{event.author}]: {text}')


# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.

# reimbursement that doesn't require approval
# asyncio.run(call_agent_async("Please reimburse 50$ for meals"))
await call_agent_async("Please reimburse 50$ for meals") # For Notebooks, uncomment this line and comment the above line
# reimbursement that requires approval
# asyncio.run(call_agent_async("Please reimburse 200$ for meals"))
await call_agent_async("Please reimburse 200$ for meals") # For Notebooks, uncomment this line and comment the above line

この例の主な側面

  • LongRunningFunctionTool: 提供されたメソッド/関数をラップします。フレームワークは、生成された更新と最終的な戻り値を連続したFunctionResponsesとして送信することを処理します。

  • エージェントの指示: LLMにツールを使用し、ユーザーの更新のために受信するFunctionResponseストリーム(進行状況と完了)を理解するように指示します。

  • 最終的な戻り値: 関数は最終的な結果の辞書を返し、完了を示すために最後のFunctionResponseで送信されます。

ツールとしてのエージェント

この強力な機能により、システム内の他のエージェントの機能をツールとして呼び出すことで活用できます。ツールとしてのエージェントを使用すると、別のエージェントを呼び出して特定のタスクを実行させ、効果的に責任を委任できます。これは、概念的には、別のエージェントを呼び出し、エージェントの応答を関数の戻り値として使用するPython関数を作成することに似ています。

サブエージェントとの主な違い

ツールとしてのエージェントとサブエージェントを区別することが重要です。

  • ツールとしてのエージェント: エージェントAがエージェントBをツールとして呼び出すと(ツールとしてのエージェントを使用)、エージェントBの回答はエージェントAに返され、エージェントAは回答を要約してユーザーに応答を生成します。エージェントAは制御を維持し、将来のユーザー入力を引き続き処理します。

  • サブエージェント: エージェントAがエージェントBをサブエージェントとして呼び出すと、ユーザーに応答する責任は完全にエージェントBに移管されます。エージェントAは事実上ループから外れます。後続のすべてのユーザー入力はエージェントBによって応答されます。

使用法

エージェントをツールとして使用するには、エージェントをAgentToolクラスでラップします。

tools=[AgentTool(agent=agent_b)]
agenttool.New(agent, &agenttool.Config{...})
AgentTool.create(agent)

カスタマイズ

AgentToolクラスは、その動作をカスタマイズするための次の属性を提供します。

  • skip_summarization: bool: Trueに設定すると、フレームワークはツールエージェントの応答のLLMベースの要約をバイパスします。これは、ツールの応答がすでに適切にフォーマットされており、それ以上の処理が必要ない場合に役立ちます。
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.agent_tool import AgentTool
from google.genai import types

APP_NAME="summary_agent"
USER_ID="user1234"
SESSION_ID="1234"

summary_agent = Agent(
    model="gemini-2.0-flash",
    name="summary_agent",
    instruction="""You are an expert summarizer. Please read the following text and provide a concise summary.""",
    description="Agent to summarize text",
)

root_agent = Agent(
    model='gemini-2.0-flash',
    name='root_agent',
    instruction="""You are a helpful assistant. When the user provides a text, use the 'summarize' tool to generate a summary. Always forward the user's message exactly as received to the 'summarize' tool, without modifying or summarizing it yourself. Present the response from the tool to the user.""",
    tools=[AgentTool(agent=summary_agent, skip_summarization=True)]
)

# Session and Runner
async def setup_session_and_runner():
    session_service = InMemorySessionService()
    session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
    runner = Runner(agent=root_agent, app_name=APP_NAME, session_service=session_service)
    return session, runner


# Agent Interaction
async def call_agent_async(query):
    content = types.Content(role='user', parts=[types.Part(text=query)])
    session, runner = await setup_session_and_runner()
    events = runner.run_async(user_id=USER_ID, session_id=SESSION_ID, new_message=content)

    async for event in events:
        if event.is_final_response():
            final_response = event.content.parts[0].text
            print("Agent Response: ", final_response)


long_text = """Quantum computing represents a fundamentally different approach to computation, 
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers 
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively 
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled, 
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and 
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such 
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far 
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages."""


# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
await call_agent_async(long_text)
import (
    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/agenttool"
    "google.golang.org/genai"
)

// createSummarizerAgent creates an agent whose sole purpose is to summarize text.
func createSummarizerAgent(ctx context.Context) (agent.Agent, error) {
    model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
    if err != nil {
        return nil, err
    }
    return llmagent.New(llmagent.Config{
        Name:        "SummarizerAgent",
        Model:       model,
        Instruction: "You are an expert at summarizing text. Take the user's input and provide a concise summary.",
        Description: "An agent that summarizes text.",
    })
}

// createMainAgent creates the primary agent that will use the summarizer agent as a tool.
func createMainAgent(ctx context.Context, tools ...tool.Tool) (agent.Agent, error) {
    model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
    if err != nil {
        return nil, err
    }
    return llmagent.New(llmagent.Config{
        Name:  "MainAgent",
        Model: model,
        Instruction: "You are a helpful assistant. If you are asked to summarize a long text, use the 'summarize' tool. " +
            "After getting the summary, present it to the user by saying 'Here is a summary of the text:'.",
        Description: "The main agent that can delegate tasks.",
        Tools:       tools,
    })
}

func RunAgentAsToolSimulation() {
    ctx := context.Background()

    // 1. Create the Tool Agent (Summarizer)
    summarizerAgent, err := createSummarizerAgent(ctx)
    if err != nil {
        log.Fatalf("Failed to create summarizer agent: %v", err)
    }

    // 2. Wrap the Tool Agent in an AgentTool
    summarizeTool := agenttool.New(summarizerAgent, &agenttool.Config{
        SkipSummarization: true,
    })

    // 3. Create the Main Agent and provide it with the AgentTool
    mainAgent, err := createMainAgent(ctx, summarizeTool)
    if err != nil {
        log.Fatalf("Failed to create main agent: %v", err)
    }

    // 4. Run the main agent
    prompt := `
        Please summarize this text for me:
        Quantum computing represents a fundamentally different approach to computation,
        leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
        that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
        being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
        meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
        interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
        as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
        faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.
    `
    fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
    callAgent(context.Background(), mainAgent, prompt)
    fmt.Println("\n---")
}
import com.google.adk.agents.LlmAgent;
import com.google.adk.events.Event;
import com.google.adk.runner.InMemoryRunner;
import com.google.adk.sessions.Session;
import com.google.adk.tools.AgentTool;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import io.reactivex.rxjava3.core.Flowable;

public class AgentToolCustomization {

  private static final String APP_NAME = "summary_agent";
  private static final String USER_ID = "user1234";

  public static void initAgentAndRun(String prompt) {

    LlmAgent summaryAgent =
        LlmAgent.builder()
            .model("gemini-2.0-flash")
            .name("summaryAgent")
            .instruction(
                "You are an expert summarizer. Please read the following text and provide a concise summary.")
            .description("Agent to summarize text")
            .build();

    // Define root_agent
    LlmAgent rootAgent =
        LlmAgent.builder()
            .model("gemini-2.0-flash")
            .name("rootAgent")
            .instruction(
                "You are a helpful assistant. When the user provides a text, always use the 'summaryAgent' tool to generate a summary. Always forward the user's message exactly as received to the 'summaryAgent' tool, without modifying or summarizing it yourself. Present the response from the tool to the user.")
            .description("Assistant agent")
            .tools(AgentTool.create(summaryAgent, true)) // Set skipSummarization to true
            .build();

    // Create an InMemoryRunner
    InMemoryRunner runner = new InMemoryRunner(rootAgent, APP_NAME);
    // InMemoryRunner automatically creates a session service. Create a session using the service
    Session session = runner.sessionService().createSession(APP_NAME, USER_ID).blockingGet();
    Content userMessage = Content.fromParts(Part.fromText(prompt));

    // Run the agent
    Flowable<Event> eventStream = runner.runAsync(USER_ID, session.id(), userMessage);

    // Stream event response
    eventStream.blockingForEach(
        event -> {
          if (event.finalResponse()) {
            System.out.println(event.stringifyContent());
          }
        });
  }

  public static void main(String[] args) {
    String longText =
        """
            Quantum computing represents a fundamentally different approach to computation,
            leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
            that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
            being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
            meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
            interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
            as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
            faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.""";

    initAgentAndRun(longText);
  }
}

仕組み

  1. main_agentが長いテキストを受信すると、その指示により、長いテキストには「summarize」ツールを使用するように指示されます。
  2. フレームワークは、「summarize」をsummary_agentをラップするAgentToolとして認識します。
  3. 舞台裏では、main_agentは長いテキストを入力としてsummary_agentを呼び出します。
  4. summary_agentは、その指示に従ってテキストを処理し、要約を生成します。
  5. summary_agentからの応答は、main_agentに返されます。
  6. main_agentは、要約を取得して、ユーザーへの最終的な応答を作成できます(例:「テキストの要約は次のとおりです...」)