コンテンツにスキップ

ADK用カスタムツール

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

ADKエージェントワークフローでは、ツールはADKエージェントがアクションを実行するために呼び出すことができる、構造化された入力と出力を持つプログラミング関数です。ADKツールは、Geminiやその他の生成AIモデルで関数呼び出しを使用する方法と同様に機能します。ADKツールを使用して、次のようなさまざまなアクションやプログラミング関数を実行できます。

  • データベースのクエリ
  • APIリクエストの作成:天気データの取得、予約システム
  • ウェブ検索
  • コードスニペットの実行
  • ドキュメントからの情報取得(RAG)
  • 他のソフトウェアやサービスとの対話

ADKツールリスト

ADK用の独自のツールを構築する前に、ADKエージェントで使用できる事前に構築されたツールについては、 ADKツールリスト を確認してください。

ツールとは?

ADKのコンテキストでは、ツールはAIエージェントに提供される特定の機能を表し、エージェントがコアのテキスト生成と推論能力を超えてアクションを実行し、世界と対話できるようにします。有能なエージェントを基本的な言語モデルと区別するのは、多くの場合、ツールの効果的な使用です。

技術的には、ツールは通常、Python/Java関数、クラスメソッド、さらには別の特殊なエージェントのようなモジュール式のコードコンポーネントであり、明確に定義されたタスクを実行するように設計されています。これらのタスクには、多くの場合、外部システムまたはデータとの対話が含まれます。

エージェントツールコール

主な特徴

アクション指向: ツールは、情報検索、API呼び出し、計算実行など、エージェントの特定のアクションを実行します。

エージェント機能の拡張: エージェントがリアルタイム情報にアクセスし、外部システムに影響を与え、トレーニングデータに固有の知識の制限を克服できるようにします。

事前定義されたロジックの実行: 重要なことに、ツールは開発者が定義した特定のロジックを実行します。エージェントのコアである大規模言語モデル(LLM)のような独立した推論機能は備えていません。LLMは、どのツールをいつ、どの入力で使用するかを推論しますが、ツール自体は指定された関数を実行するだけです。

エージェントによるツールの使用方法

エージェントは、多くの場合、関数呼び出しを含むメカニズムを介して動的にツールを活用します。このプロセスは通常、次の手順に従います。

  1. 推論: エージェントのLLMは、システム命令、会話履歴、およびユーザーリクエストを分析します。
  2. 選択: 分析に基づいて、LLMは、エージェントで利用可能なツールと各ツールを説明するdocstringに基づいて、実行するツール(もしあれば)を決定します。
  3. 呼び出し: LLMは、選択したツールに必要な引数(入力)を生成し、その実行をトリガーします。
  4. 観察: エージェントは、ツールによって返された出力(結果)を受け取ります。
  5. 最終処理: エージェントは、ツールの出力を進行中の推論プロセスに組み込んで、次の応答を作成し、後続のステップを決定するか、目標が達成されたかどうかを判断します。

ツールは、エージェントのインテリジェントコア(LLM)が複雑なタスクを達成するために必要に応じてアクセスして利用できる特殊なツールキットと考えてください。

ADKのツールタイプ

ADKは、いくつかのタイプのツールをサポートすることで柔軟性を提供します。

  1. 関数ツール: 特定のアプリケーションのニーズに合わせて作成したツール。
  2. 組み込みツール: 一般的なタスクのためにフレームワークが提供するすぐに使えるツール。 例:Google検索、コード実行、検索拡張生成(RAG)。
  3. サードパーティツール: 人気のある外部ライブラリのツールをシームレスに統合します。

各ツールタイプの詳細情報と例については、上記のリンク先の各ドキュメントページに移動してください。

エージェントの指示におけるツールの参照

エージェントの指示内で、関数名を使用してツールを直接参照できます。ツールの関数名とdocstringが十分に説明的であれば、指示は主に大規模言語モデル(LLM)がツールをいつ利用すべきかに焦点を当てることができます。これにより、明確さが促進され、モデルが各ツールの意図された使用法を理解するのに役立ちます。

ツールが生成する可能性のあるさまざまな戻り値を処理する方法をエージェントに明確に指示することが重要です。たとえば、ツールがエラーメッセージを返す場合、指示では、エージェントが操作を再試行するか、タスクをあきらめるか、ユーザーに追加情報を要求するかを指定する必要があります。

さらに、ADKは、あるツールの出力が別のツールの入力として機能するツールの順次使用をサポートしています。このようなワークフローを実装する場合、必要な手順をモデルにガイドするために、エージェントの指示内で意図されたツール使用シーケンスを記述することが重要です。

例

次の例は、エージェントが指示で関数名を参照してツールを使用する方法を示しています。また、成功またはエラーメッセージなど、ツールからのさまざまな戻り値を処理するようにエージェントをガイドする方法、およびタスクを達成するために複数のツールの順次使用を調整する方法も示しています。

# 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 google.adk.agents import Agent
from google.adk.tools import FunctionTool
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types

APP_NAME="weather_sentiment_agent"
USER_ID="user1234"
SESSION_ID="1234"
MODEL_ID="gemini-2.0-flash"

# Tool 1
def get_weather_report(city: str) -> dict:
    """Retrieves the current weather report for a specified city.

    Returns:
        dict: A dictionary containing the weather information with a 'status' key ('success' or 'error') and a 'report' key with the weather details if successful, or an 'error_message' if an error occurred.
    """
    if city.lower() == "london":
        return {"status": "success", "report": "The current weather in London is cloudy with a temperature of 18 degrees Celsius and a chance of rain."}
    elif city.lower() == "paris":
        return {"status": "success", "report": "The weather in Paris is sunny with a temperature of 25 degrees Celsius."}
    else:
        return {"status": "error", "error_message": f"Weather information for '{city}' is not available."}

weather_tool = FunctionTool(func=get_weather_report)


# Tool 2
def analyze_sentiment(text: str) -> dict:
    """Analyzes the sentiment of the given text.

    Returns:
        dict: A dictionary with 'sentiment' ('positive', 'negative', or 'neutral') and a 'confidence' score.
    """
    if "good" in text.lower() or "sunny" in text.lower():
        return {"sentiment": "positive", "confidence": 0.8}
    elif "rain" in text.lower() or "bad" in text.lower():
        return {"sentiment": "negative", "confidence": 0.7}
    else:
        return {"sentiment": "neutral", "confidence": 0.6}

sentiment_tool = FunctionTool(func=analyze_sentiment)


# Agent
weather_sentiment_agent = Agent(
    model=MODEL_ID,
    name='weather_sentiment_agent',
    instruction="""You are a helpful assistant that provides weather information and analyzes the sentiment of user feedback.
**If the user asks about the weather in a specific city, use the 'get_weather_report' tool to retrieve the weather details.**
**If the 'get_weather_report' tool returns a 'success' status, provide the weather report to the user.**
**If the 'get_weather_report' tool returns an 'error' status, inform the user that the weather information for the specified city is not available and ask if they have another city in mind.**
**After providing a weather report, if the user gives feedback on the weather (e.g., 'That's good' or 'I don't like rain'), use the 'analyze_sentiment' tool to understand their sentiment.** Then, briefly acknowledge their sentiment.
You can handle these tasks sequentially if needed.""",
    tools=[weather_tool, sentiment_tool]
)

async def main():
    """Main function to run the agent asynchronously."""
    # Session and Runner Setup
    session_service = InMemorySessionService()
    # Use 'await' to correctly create the session
    await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)

    runner = Runner(agent=weather_sentiment_agent, app_name=APP_NAME, session_service=session_service)

    # Agent Interaction
    query = "weather in london?"
    print(f"User Query: {query}")
    content = types.Content(role='user', parts=[types.Part(text=query)])

    # The runner's run method handles the async loop internally
    events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)

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

# Standard way to run the main async function
if __name__ == "__main__":
    asyncio.run(main())
/**
 * 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 { LlmAgent, FunctionTool, InMemoryRunner, isFinalResponse, stringifyContent } from '@google/adk';
import { z } from "zod";
import { Content, createUserContent } from "@google/genai";

/**
 * Retrieves the current weather report for a specified city.
 */
function getWeatherReport(params: { city: string }): Record<string, any> {
    if (params.city.toLowerCase().includes("london")) {
        return {
            "status": "success",
            "report": "The current weather in London is cloudy with a " +
                "temperature of 18 degrees Celsius and a chance of rain.",
        };
    }
    if (params.city.toLowerCase().includes("paris")) {
        return {
            "status": "success",
            "report": "The weather in Paris is sunny with a temperature of 25 " +
                "degrees Celsius.",
        };
    }
    return {
        "status": "error",
        "error_message": `Weather information for '${params.city}' is not available.`,
    };
}

/**
 * Analyzes the sentiment of a given text.
 */
function analyzeSentiment(params: { text: string }): Record<string, any> {
    if (params.text.includes("cloudy") || params.text.includes("rain")) {
        return { "status": "success", "sentiment": "negative" };
    }
    if (params.text.includes("sunny")) {
        return { "status": "success", "sentiment": "positive" };
    }
    return { "status": "success", "sentiment": "neutral" };
}

const weatherTool = new FunctionTool({
    name: "get_weather_report",
    description: "Retrieves the current weather report for a specified city.",
    parameters: z.object({
        city: z.string().describe("The city to get the weather for."),
    }),
    execute: getWeatherReport,
});

const sentimentTool = new FunctionTool({
    name: "analyze_sentiment",
    description: "Analyzes the sentiment of a given text.",
    parameters: z.object({
        text: z.string().describe("The text to analyze the sentiment of."),
    }),
    execute: analyzeSentiment,
});

const instruction = `
    You are a helpful assistant that first checks the weather and then analyzes
    its sentiment.

    Follow these steps:
    1. Use the 'get_weather_report' tool to get the weather for the requested
       city.
    2. If the 'get_weather_report' tool returns an error, inform the user about
       the error and stop.
    3. If the weather report is available, use the 'analyze_sentiment' tool to
       determine the sentiment of the weather report.
    4. Finally, provide a summary to the user, including the weather report and
       its sentiment.
    `;

const agent = new LlmAgent({
    name: "weather_sentiment_agent",
    instruction: instruction,
    tools: [weatherTool, sentimentTool],
    model: "gemini-2.5-flash"
});

async function main() {

    const runner = new InMemoryRunner({ agent: agent, appName: "weather_sentiment_app" });

    await runner.sessionService.createSession({
        appName: "weather_sentiment_app",
        userId: "user1",
        sessionId: "session1"
    });

    const newMessage: Content = createUserContent("What is the weather in London?");

    for await (const event of runner.runAsync({
        userId: "user1",
        sessionId: "session1",
        newMessage: newMessage,
    })) {
        if (isFinalResponse(event) && event.content?.parts?.length) {
            const text = stringifyContent(event).trim();
            if (text) {
                console.log(text);
            }
        }
    }
}

main();
// 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/v2/agent"
    "google.golang.org/adk/v2/agent/llmagent"
    "google.golang.org/adk/v2/model/gemini"
    "google.golang.org/adk/v2/runner"
    "google.golang.org/adk/v2/session"
    "google.golang.org/adk/v2/tool"
    "google.golang.org/adk/v2/tool/functiontool"
    "google.golang.org/genai"
)

type getWeatherReportArgs struct {
    City string `json:"city" jsonschema:"The city for which to get the weather report."`
}

type getWeatherReportResult struct {
    Status string `json:"status"`
    Report string `json:"report,omitempty"`
}

func getWeatherReport(ctx agent.Context, args getWeatherReportArgs) (getWeatherReportResult, error) {
    if strings.ToLower(args.City) == "london" {
        return getWeatherReportResult{Status: "success", Report: "The current weather in London is cloudy with a temperature of 18 degrees Celsius and a chance of rain."}, nil
    }
    if strings.ToLower(args.City) == "paris" {
        return getWeatherReportResult{Status: "success", Report: "The weather in Paris is sunny with a temperature of 25 degrees Celsius."}, nil
    }
    return getWeatherReportResult{}, fmt.Errorf("weather information for '%s' is not available.", args.City)
}

type analyzeSentimentArgs struct {
    Text string `json:"text" jsonschema:"The text to analyze for sentiment."`
}

type analyzeSentimentResult struct {
    Sentiment  string  `json:"sentiment"`
    Confidence float64 `json:"confidence"`
}

func analyzeSentiment(ctx agent.Context, args analyzeSentimentArgs) (analyzeSentimentResult, error) {
    if strings.Contains(strings.ToLower(args.Text), "good") || strings.Contains(strings.ToLower(args.Text), "sunny") {
        return analyzeSentimentResult{Sentiment: "positive", Confidence: 0.8}, nil
    }
    if strings.Contains(strings.ToLower(args.Text), "rain") || strings.Contains(strings.ToLower(args.Text), "bad") {
        return analyzeSentimentResult{Sentiment: "negative", Confidence: 0.7}, nil
    }
    return analyzeSentimentResult{Sentiment: "neutral", Confidence: 0.6}, nil
}

func main() {
    ctx := context.Background()
    model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{})
    if err != nil {
        log.Fatal(err)
    }

    weatherTool, err := functiontool.New(
        functiontool.Config{
            Name:        "get_weather_report",
            Description: "Retrieves the current weather report for a specified city.",
        },
        getWeatherReport,
    )
    if err != nil {
        log.Fatal(err)
    }

    sentimentTool, err := functiontool.New(
        functiontool.Config{
            Name:        "analyze_sentiment",
            Description: "Analyzes the sentiment of the given text.",
        },
        analyzeSentiment,
    )
    if err != nil {
        log.Fatal(err)
    }

    weatherSentimentAgent, err := llmagent.New(llmagent.Config{
        Name:        "weather_sentiment_agent",
        Model:       model,
        Instruction: "You are a helpful assistant that provides weather information and analyzes the sentiment of user feedback. **If the user asks about the weather in a specific city, use the 'get_weather_report' tool to retrieve the weather details.** **If the 'get_weather_report' tool returns a 'success' status, provide the weather report to the user.** **If the 'get_weather_report' tool returns an 'error' status, inform the user that the weather information for the specified city is not available and ask if they have another city in mind.** **After providing a weather report, if the user gives feedback on the weather (e.g., 'That's good' or 'I don't like rain'), use the 'analyze_sentiment' tool to understand their sentiment.** Then, briefly acknowledge their sentiment. You can handle these tasks sequentially if needed.",
        Tools:       []tool.Tool{weatherTool, sentimentTool},
    })
    if err != nil {
        log.Fatal(err)
    }

    sessionService := session.InMemoryService()
    runner, err := runner.New(runner.Config{
        AppName:        "weather_sentiment_agent",
        Agent:          weatherSentimentAgent,
        SessionService: sessionService,
    })
    if err != nil {
        log.Fatal(err)
    }

    session, err := sessionService.Create(ctx, &session.CreateRequest{
        AppName: "weather_sentiment_agent",
        UserID:  "user1234",
    })
    if err != nil {
        log.Fatal(err)
    }

    run(ctx, runner, session.Session.ID(), "weather in london?")
    run(ctx, runner, session.Session.ID(), "I don't like rain.")
}

func run(ctx context.Context, r *runner.Runner, sessionID string, prompt string) {
    fmt.Printf("\n> %s\n", prompt)
    events := r.Run(
        ctx,
        "user1234",
        sessionID,
        genai.NewContentFromText(prompt, genai.RoleUser),
        agent.RunConfig{
            StreamingMode: agent.StreamingModeNone,
        },
    )
    for event, err := range events {
        if err != nil {
            log.Fatalf("ERROR during agent execution: %v", err)
        }

        if event.Content.Parts[0].Text != "" {
            fmt.Printf("Agent Response: %s\n", event.Content.Parts[0].Text)
        }
    }
}
import com.google.adk.agents.BaseAgent;
import com.google.adk.agents.LlmAgent;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext; // Ensure this import is correct
import com.google.common.collect.ImmutableList;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class WeatherSentimentAgentApp {

  private static final String APP_NAME = "weather_sentiment_agent";
  private static final String USER_ID = "user1234";
  private static final String SESSION_ID = "1234";
  private static final String MODEL_ID = "gemini-2.0-flash";

  /**
   * Retrieves the current weather report for a specified city.
   *
   * @param city The city for which to retrieve the weather report.
   * @param toolContext The context for the tool.
   * @return A dictionary containing the weather information.
   */
  public static Map<String, Object> getWeatherReport(
      @Schema(name = "city")
      String city,
      @Schema(name = "toolContext")
      ToolContext toolContext) {
    Map<String, Object> response = new HashMap<>();

    if (city.toLowerCase(Locale.ROOT).equals("london")) {
      response.put("status", "success");
      response.put(
          "report",
          "The current weather in London is cloudy with a temperature of 18 degrees Celsius and a"
              + " chance of rain.");
    } else if (city.toLowerCase(Locale.ROOT).equals("paris")) {
      response.put("status", "success");
      response.put(
          "report", "The weather in Paris is sunny with a temperature of 25 degrees Celsius.");
    } else {
      response.put("status", "error");
      response.put(
          "error_message", String.format("Weather information for '%s' is not available.", city));
    }
    return response;
  }

  /**
   * Analyzes the sentiment of the given text.
   *
   * @param text The text to analyze.
   * @param toolContext The context for the tool.
   * @return A dictionary with sentiment and confidence score.
   */
  public static Map<String, Object> analyzeSentiment(
      @Schema(name = "text")
      String text,
      @Schema(name = "toolContext")
      ToolContext toolContext) {
    Map<String, Object> response = new HashMap<>();
    String lowerText = text.toLowerCase(Locale.ROOT);
    if (lowerText.contains("good") || lowerText.contains("sunny")) {
      response.put("sentiment", "positive");
      response.put("confidence", 0.8);
    } else if (lowerText.contains("rain") || lowerText.contains("bad")) {
      response.put("sentiment", "negative");
      response.put("confidence", 0.7);
    } else {
      response.put("sentiment", "neutral");
      response.put("confidence", 0.6);
    }
    return response;
  }

  /**
   * Calls the agent with the given query and prints the final response.
   *
   * @param runner The runner to use.
   * @param query The query to send to the agent.
   */
  public static void callAgent(Runner runner, String query) {
    Content content = Content.fromParts(Part.fromText(query));

    InMemorySessionService sessionService = (InMemorySessionService) runner.sessionService();
    Session session =
        sessionService
            .createSession(APP_NAME, USER_ID, /* state= */ null, SESSION_ID)
            .blockingGet();

    runner
        .runAsync(session.userId(), session.id(), content)
        .forEach(
            event -> {
              if (event.finalResponse()
                  && event.content().isPresent()
                  && event.content().get().parts().isPresent()
                  && !event.content().get().parts().get().isEmpty()
                  && event.content().get().parts().get().get(0).text().isPresent()) {
                String finalResponse = event.content().get().parts().get().get(0).text().get();
                System.out.println("Agent Response: " + finalResponse);
              }
            });
  }

  public static void main(String[] args) throws NoSuchMethodException {
    FunctionTool weatherTool =
        FunctionTool.create(
            WeatherSentimentAgentApp.class.getMethod(
                "getWeatherReport", String.class, ToolContext.class));
    FunctionTool sentimentTool =
        FunctionTool.create(
            WeatherSentimentAgentApp.class.getMethod(
                "analyzeSentiment", String.class, ToolContext.class));

    BaseAgent weatherSentimentAgent =
        LlmAgent.builder()
            .model(MODEL_ID)
            .name("weather_sentiment_agent")
            .description("Weather Sentiment Agent")
            .instruction("""
                    You are a helpful assistant that provides weather information and analyzes the
                    sentiment of user feedback
                    **If the user asks about the weather in a specific city, use the
                    'get_weather_report' tool to retrieve the weather details.**
                    **If the 'get_weather_report' tool returns a 'success' status, provide the
                    weather report to the user.**
                    **If the 'get_weather_report' tool returns an 'error' status, inform the
                    user that the weather information for the specified city is not available
                    and ask if they have another city in mind.**
                    **After providing a weather report, if the user gives feedback on the
                    weather (e.g., 'That's good' or 'I don't like rain'), use the
                    'analyze_sentiment' tool to understand their sentiment.** Then, briefly
                    acknowledge their sentiment.
                    You can handle these tasks sequentially if needed.
                    """)
            .tools(ImmutableList.of(weatherTool, sentimentTool))
            .build();

    InMemorySessionService sessionService = new InMemorySessionService();
    Runner runner = new Runner(weatherSentimentAgent, APP_NAME, null, sessionService);

    // Change the query to ensure the tool is called with a valid city that triggers a "success"
    // response from the tool, like "london" (without the question mark).
    callAgent(runner, "weather in paris");
  }
}
import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.annotations.Param
import com.google.adk.kt.annotations.Tool
import com.google.adk.kt.models.Gemini
import com.google.adk.kt.runners.InMemoryRunner
import com.google.adk.kt.sessions.InMemorySessionService
import com.google.adk.kt.sessions.SessionKey
import com.google.adk.kt.types.Content
import com.google.adk.kt.types.Part
import com.google.adk.kt.types.Role
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking

private const val APP_NAME = "weather_sentiment_agent"
private const val USER_ID = "user1234"
private const val SESSION_ID = "1234"

class WeatherSentimentTools {
    /**
     * Retrieves the current weather report for a specified city.
     *
     * Returns a map with a "status" key ("success" or "error"), plus a "report"
     * with the weather details on success or an "error_message" on failure.
     */
    @Tool
    fun getWeatherReport(
        @Param("The city to retrieve the weather report for.") city: String,
    ): Map<String, String> =
        when (city.lowercase()) {
            "london" ->
                mapOf(
                    "status" to "success",
                    "report" to
                        "The current weather in London is cloudy with a temperature " +
                        "of 18 degrees Celsius and a chance of rain.",
                )
            "paris" ->
                mapOf(
                    "status" to "success",
                    "report" to
                        "The weather in Paris is sunny with a temperature of " +
                        "25 degrees Celsius.",
                )
            else ->
                mapOf(
                    "status" to "error",
                    "error_message" to "Weather information for '$city' is not available.",
                )
        }

    /**
     * Analyzes the sentiment of the given text.
     *
     * Returns a map with a "sentiment" ("positive", "negative" or "neutral") and
     * a "confidence" score.
     */
    @Tool
    fun analyzeSentiment(
        @Param("The text to analyze.") text: String,
    ): Map<String, Any> {
        val lowered = text.lowercase()
        return when {
            "good" in lowered || "sunny" in lowered ->
                mapOf("sentiment" to "positive", "confidence" to 0.8)
            "rain" in lowered || "bad" in lowered ->
                mapOf("sentiment" to "negative", "confidence" to 0.7)
            else -> mapOf("sentiment" to "neutral", "confidence" to 0.6)
        }
    }
}

fun main() =
    runBlocking {
        // The instruction names each tool and says how to handle its return values,
        // including chaining one tool's output into the next.
        val weatherSentimentAgent =
            LlmAgent(
                name = "weather_sentiment_agent",
                model = Gemini(name = "gemini-flash-latest"),
                instruction =
                    Instruction(
                        """
                        You are a helpful assistant that provides weather information
                        and analyzes the sentiment of user feedback.

                        If the user asks about the weather in a specific city, use the
                        getWeatherReport tool. If it returns a "success" status, provide
                        the report to the user. If it returns an "error" status, tell the
                        user the information is unavailable and ask for another city.

                        After providing a weather report, if the user gives feedback on
                        the weather, use the analyzeSentiment tool to understand their
                        sentiment, then briefly acknowledge it.
                        """.trimIndent(),
                    ),
                tools = WeatherSentimentTools().generatedTools(),
            )

        val sessionService = InMemorySessionService()
        val runner =
            InMemoryRunner(
                agent = weatherSentimentAgent,
                appName = APP_NAME,
                sessionService = sessionService,
            )
        sessionService.createSession(SessionKey(APP_NAME, USER_ID, SESSION_ID))

        val query = "weather in london?"
        println("User Query: $query")
        val userContent = Content(role = Role.USER, parts = listOf(Part(text = query)))

        val events =
            runner.runAsync(
                userId = USER_ID,
                sessionId = SESSION_ID,
                newMessage = userContent,
            ).toList()

        for (event in events) {
            if (event.isFinalResponse) {
                println("Agent Response: ${event.content?.parts?.firstOrNull()?.text}")
            }
        }
    }

ツールコンテキスト

より高度なシナリオでは、ADKを使用すると、特別なパラメーターtool_context: ToolContextを含めることで、ツール関数内から追加のコンテキスト情報にアクセスできます。これを関数シグネチャに含めることで、ADKはエージェントの実行中にツールが呼び出されたときに自動的にToolContextクラスのインスタンスを提供します。

ToolContextは、いくつかの重要な情報と制御レバーへのアクセスを提供します。

  • state: State: 現在のセッションの状態を読み書きします。ここで行われた変更は追跡され、永続化されます。

  • actions: EventActions: ツールが実行された後のエージェントの後続のアクションに影響を与えます(例:要約をスキップする、別のエージェントに転送する)。

  • function_call_id: str: この特定のツール呼び出しにフレームワークによって割り当てられた一意の識別子。認証応答の追跡と関連付けに役立ちます。これは、単一のモデル応答内で複数のツールが呼び出された場合にも役立ちます。

  • function_call_event_id: str: この属性は、現在のツール呼び出しをトリガーしたイベントの一意の識別子を提供します。これは、追跡およびロギングの目的で役立ちます。

  • auth_response: Any: ツールが呼び出される前にフレームワークによって認証がすでに処理されていた場合、認証応答/資格情報が含まれます(RestApiToolおよびOpenAPIセキュリティスキームで一般的です)。

  • サービスへのアクセス:アーティファクトやメモリなどの構成済みサービスと対話するためのメソッド。

ツール関数のdocstringにtool_contextパラメーターを含めないでください。ToolContextは、LLMがツール関数を呼び出すことを決定した後にADKフレームワークによって自動的に挿入されるため、LLMの意思決定には関係なく、含めるとLLMを混乱させる可能性があります。

状態管理

tool_context.state属性は、現在のセッションに関連付けられた状態への直接の読み取りおよび書き込みアクセスを提供します。辞書のように動作しますが、変更がデルタとして追跡され、セッションサービスによって永続化されることを保証します。これにより、ツールはさまざまな対話やエージェントのステップ間で情報を維持および共有できます。

  • 状態の読み取り: 標準の辞書アクセス(tool_context.state['my_key'])または.get()メソッド(tool_context.state.get('my_key', default_value))を使用します。

  • 状態の書き込み: 値を直接割り当てます(tool_context.state['new_key'] = 'new_value')。これらの変更は、結果のイベントのstate_deltaに記録されます。

  • 状態プレフィックス: 標準の状態プレフィックスを覚えておいてください。

    • app:*: アプリケーションのすべてのユーザー間で共有されます。

    • user:*: すべてのセッションで現在のユーザーに固有です。

    • (プレフィックスなし): 現在のセッションに固有です。

    • temp:*: 一時的で、呼び出し間で永続化されません(単一の実行呼び出し内でデータを渡すのに役立ちますが、通常、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.tools import ToolContext, FunctionTool

def update_user_preference(preference: str, value: str, tool_context: ToolContext):
    """Updates a user-specific preference."""
    user_prefs_key = "user:preferences"
    # Get current preferences or initialize if none exist
    preferences = tool_context.state.get(user_prefs_key, {})
    preferences[preference] = value
    # Write the updated dictionary back to the state
    tool_context.state[user_prefs_key] = preferences
    print(f"Tool: Updated user preference '{preference}' to '{value}'")
    return {"status": "success", "updated_preference": preference}

pref_tool = FunctionTool(func=update_user_preference)

# In an Agent:
# my_agent = Agent(..., tools=[pref_tool])

# When the LLM calls update_user_preference(preference='theme', value='dark', ...):
# The tool_context.state will be updated, and the change will be part of the
# resulting tool response event's actions.state_delta.
import { Context } from '@google/adk';

// Updates a user-specific preference.
export function updateUserThemePreference(
  value: string,
  context: Context
): Record<string, any> {
  const userPrefsKey = "user:preferences";

  // Get current preferences or initialize if none exist
  const preferences = context.state.get(userPrefsKey, {}) as Record<string, any>;
  preferences["theme"] = value;

  // Write the updated dictionary back to the state
  context.state.set(userPrefsKey, preferences);
  console.log(
    `Tool: Updated user preference ${userPrefsKey} to ${JSON.stringify(context.state.get(userPrefsKey))}`
  );

  return {
    status: "success",
    updated_preference: context.state.get(userPrefsKey),
  };
  // When the LLM calls updateUserThemePreference("dark"):
  // The context.state will be updated, and the change will be part of the
  // resulting tool response event's actions.stateDelta.
}
import (
    "fmt"

    "google.golang.org/adk/v2/agent"
)

type updateUserPreferenceArgs struct {
    Preference string `json:"preference" jsonschema:"The name of the preference to set."`
    Value      string `json:"value" jsonschema:"The value to set for the preference."`
}

type updateUserPreferenceResult struct {
    UpdatedPreference string `json:"updated_preference"`
}

func updateUserPreference(ctx agent.Context, args updateUserPreferenceArgs) (*updateUserPreferenceResult, error) {
    userPrefsKey := "user:preferences"
    val, err := ctx.State().Get(userPrefsKey)
    if err != nil {
        val = make(map[string]any)
    }

    preferencesMap, ok := val.(map[string]any)
    if !ok {
        preferencesMap = make(map[string]any)
    }

    preferencesMap[args.Preference] = args.Value

    if err := ctx.State().Set(userPrefsKey, preferencesMap); err != nil {
        return nil, err
    }

    fmt.Printf("Tool: Updated user preference '%s' to '%s'\n", args.Preference, args.Value)
    return &updateUserPreferenceResult{
        UpdatedPreference: args.Preference,
    }, nil
}
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext;

// ユーザー固有のテーマ設定を更新します。
public Map<String, String> updateUserThemePreference(String value, ToolContext toolContext) {
  String userPrefsKey = "user:preferences:theme";

  // 現在の設定を取得するか、存在しない場合は初期化します
  String preference = toolContext.state().getOrDefault(userPrefsKey, "").toString();
  if (preference.isEmpty()) {
    preference = value;
  }

  // 更新された辞書を状態に書き戻します
  toolContext.state().put("user:preferences", preference);
  System.out.printf("ツール:ユーザー設定 %s を %s に更新しました", userPrefsKey, preference);

  return Map.of("status", "success", "updated_preference", toolContext.state().get(userPrefsKey).toString());
  // LLMがupdateUserThemePreference("dark")を呼び出すと:
  // toolContext.stateが更新され、変更は
  // 結果のツール応答イベントのactions.stateDeltaの一部になります。
}
import com.google.adk.kt.annotations.Param
import com.google.adk.kt.annotations.Tool
import com.google.adk.kt.tools.ToolContext

class UserPreferenceTools {
    /**
     * Updates a user-specific preference.
     */
    @Tool
    fun updateUserPreference(
        @Param("The name of the preference to update.") preference: String,
        @Param("The value to set the preference to.") value: String,
        context: ToolContext,
    ): Map<String, String> {
        // One key per preference, so each write stands on its own. The "user:"
        // prefix scopes the value to this user across all their sessions.
        val userPrefsKey = "user:preferences:$preference"

        // Read through the readonly view of the context.
        val previous = context.context.state[userPrefsKey] as? String

        // Kotlin has no mutable `state` on ToolContext. Writing through
        // actions.stateDelta is what puts the change on the resulting event, which
        // is the same effect as assigning to `tool_context.state` in Python.
        context.actions.stateDelta[userPrefsKey] = value

        println("Tool: Updated user preference '$preference' from '$previous' to '$value'")
        return mapOf("status" to "success", "updated_preference" to preference)
    }
}

// In an agent:
//   LlmAgent(..., tools = UserPreferenceTools().generatedTools())
//
// When the LLM calls updateUserPreference(preference = "theme", value = "dark"),
// the delta is written to the session state and travels on the resulting tool
// response event as actions.stateDelta.

エージェントフローの制御

tool_context.actions属性(JavaではToolContext.actions()、Goではagent.Context.Actions())は、EventActionsオブジェクトを保持します。このオブジェクトの属性を変更すると、ツールが実行を完了した後にエージェントまたはフレームワークが何をするかに影響を与えることができます。

  • skip_summarization: bool: (デフォルト:False)Trueに設定すると、ADKに通常ツールの出力を要約するLLM呼び出しをバイパスするように指示します。これは、ツールの戻り値がすでにユーザーがすぐに使えるメッセージである場合に役立ちます。

  • transfer_to_agent: str: これを別のエージェントの名前に設定します。フレームワークは現在のエージェントの実行を停止し、会話の制御を指定されたエージェントに転送します。これにより、ツールはタスクをより専門的なエージェントに動的に引き渡すことができます。

  • escalate: bool: (デフォルト:False)これをTrueに設定すると、現在のエージェントがリクエストを処理できず、制御を親エージェント(階層内にある場合)に渡す必要があることを示します。LoopAgentでは、サブエージェントのツールでescalate=Trueを設定するとループが終了します。

例

# 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.tools import FunctionTool
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools import ToolContext
from google.genai import types

APP_NAME="customer_support_agent"
USER_ID="user1234"
SESSION_ID="1234"


def check_and_transfer(query: str, tool_context: ToolContext) -> str:
    """Checks if the query requires escalation and transfers to another agent if needed."""
    if "urgent" in query.lower():
        print("Tool: Detected urgency, transferring to the support agent.")
        tool_context.actions.transfer_to_agent = "support_agent"
        return "Transferring to the support agent..."
    else:
        return f"Processed query: '{query}'. No further action needed."

escalation_tool = FunctionTool(func=check_and_transfer)

main_agent = Agent(
    model='gemini-2.0-flash',
    name='main_agent',
    instruction="""You are the first point of contact for customer support of an analytics tool. Answer general queries. If the user indicates urgency, use the 'escalation_tool' tool.""",
    tools=[escalation_tool]
)

support_agent = Agent(
    model='gemini-2.0-flash',
    name='support_agent',
    instruction="""You are the dedicated support agent. Mentioned you are a support handler and please help the user with their urgent issue."""
)

main_agent.sub_agents = [support_agent]

# 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=main_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("this is urgent, i cant login")
/**
 * 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 { LlmAgent, FunctionTool, Context, InMemoryRunner, isFinalResponse, stringifyContent } from '@google/adk';
import { z } from "zod";
import { Content, createUserContent } from "@google/genai";

function checkAndTransfer(
  params: { query: string },
  context?: Context
): Record<string, any> {
  if (!context) {
    // This should not happen in a normal ADK flow where the tool is called by an agent.
    throw new Error("Context is required to transfer agents.");
  }
  if (params.query.toLowerCase().includes("urgent")) {
    console.log("Tool: Urgent query detected, transferring to support_agent.");
    context.actions.transferToAgent = "support_agent";
    return { status: "success", message: "Transferring to support agent." };
  }

  console.log("Tool: Query is not urgent, handling normally.");
  return { status: "success", message: "Query will be handled by the main agent." };
}

const transferTool = new FunctionTool({
  name: "check_and_transfer",
  description: "Checks the user's query and transfers to a support agent if urgent.",
  parameters: z.object({
    query: z.string().describe("The user query to analyze."),
  }),
  execute: checkAndTransfer,
});

const supportAgent = new LlmAgent({
  name: "support_agent",
  description: "Handles urgent user requests about accounts.",
  instruction: "You are the support agent. Handle the user's urgent request.",
  model: "gemini-2.5-flash"
});

const mainAgent = new LlmAgent({
  name: "main_agent",
  description: "The main agent that routes non-urgent queries.",
  instruction: "You are the main agent. Use the check_and_transfer tool to analyze the user query. If the query is not urgent, handle it yourself.",
  tools: [transferTool],
  subAgents: [supportAgent],
  model: "gemini-2.5-flash"
});

async function main() {
  const runner = new InMemoryRunner({ agent: mainAgent, appName: "customer_support_app" });

  console.log("--- Running with a non-urgent query ---");
  await runner.sessionService.createSession({ appName: "customer_support_app", userId: "user1", sessionId: "session1" });
  const nonUrgentMessage: Content = createUserContent("I have a general question about my account.");
  for await (const event of runner.runAsync({ userId: "user1", sessionId: "session1", newMessage: nonUrgentMessage })) {
    if (isFinalResponse(event) && event.content?.parts?.length) {
      const text = stringifyContent(event).trim();
      if (text) {
        console.log(`Final Response: ${text}`);
      }
    }
  }

  console.log("\n--- Running with an urgent query ---");
  await runner.sessionService.createSession({ appName: "customer_support_app", userId: "user1", sessionId: "session2" });
  const urgentMessage: Content = createUserContent("My account is locked and this is urgent!");
  for await (const event of runner.runAsync({ userId: "user1", sessionId: "session2", newMessage: urgentMessage })) {
    if (isFinalResponse(event) && event.content?.parts?.length) {
      const text = stringifyContent(event).trim();
      if (text) {
        console.log(`Final Response: ${text}`);
      }
    }
  }
}

main();
// 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/v2/agent"
    "google.golang.org/adk/v2/agent/llmagent"
    "google.golang.org/adk/v2/model/gemini"
    "google.golang.org/adk/v2/runner"
    "google.golang.org/adk/v2/session"
    "google.golang.org/adk/v2/tool"
    "google.golang.org/adk/v2/tool/functiontool"
    "google.golang.org/genai"
)

type checkAndTransferArgs struct {
    Query string `json:"query" jsonschema:"The user's query to check for urgency."`
}

type checkAndTransferResult struct {
    Status string `json:"status"`
}

func checkAndTransfer(ctx agent.Context, args checkAndTransferArgs) (checkAndTransferResult, error) {
    if strings.Contains(strings.ToLower(args.Query), "urgent") {
        fmt.Println("Tool: Detected urgency, transferring to the support agent.")
        ctx.Actions().TransferToAgent = "support_agent"
        return checkAndTransferResult{Status: "Transferring to the support agent..."}, nil
    }
    return checkAndTransferResult{Status: fmt.Sprintf("Processed query: '%s'. No further action needed.", args.Query)}, nil
}

func main() {
    ctx := context.Background()
    model, err := gemini.NewModel(ctx, "gemini-flash-latest", &genai.ClientConfig{})
    if err != nil {
        log.Fatal(err)
    }

    supportAgent, err := llmagent.New(llmagent.Config{
        Name:        "support_agent",
        Model:       model,
        Instruction: "You are the dedicated support agent. Mentioned you are a support handler and please help the user with their urgent issue.",
    })
    if err != nil {
        log.Fatal(err)
    }

    checkAndTransferTool, err := functiontool.New(
        functiontool.Config{
            Name:        "check_and_transfer",
            Description: "Checks if the query requires escalation and transfers to another agent if needed.",
        },
        checkAndTransfer,
    )
    if err != nil {
        log.Fatal(err)
    }

    mainAgent, err := llmagent.New(llmagent.Config{
        Name:        "main_agent",
        Model:       model,
        Instruction: "You are the first point of contact for customer support of an analytics tool. Answer general queries. If the user indicates urgency, use the 'check_and_transfer' tool.",
        Tools:       []tool.Tool{checkAndTransferTool},
        SubAgents:   []agent.Agent{supportAgent},
    })
    if err != nil {
        log.Fatal(err)
    }

    sessionService := session.InMemoryService()
    runner, err := runner.New(runner.Config{
        AppName:        "customer_support_agent",
        Agent:          mainAgent,
        SessionService: sessionService,
    })
    if err != nil {
        log.Fatal(err)
    }

    session, err := sessionService.Create(ctx, &session.CreateRequest{
        AppName: "customer_support_agent",
        UserID:  "user1234",
    })
    if err != nil {
        log.Fatal(err)
    }

    run(ctx, runner, session.Session.ID(), "this is urgent, i cant login")
}

func run(ctx context.Context, r *runner.Runner, sessionID string, prompt string) {
    fmt.Printf("\n> %s\n", prompt)
    events := r.Run(
        ctx,
        "user1234",
        sessionID,
        genai.NewContentFromText(prompt, genai.RoleUser),
        agent.RunConfig{
            StreamingMode: agent.StreamingModeNone,
        },
    )
    for event, err := range events {
        if err != nil {
            log.Fatalf("ERROR during agent execution: %v", err)
        }

        if event.Content.Parts[0].Text != "" {
            fmt.Printf("Agent Response: %s\n", event.Content.Parts[0].Text)
        }
    }
}
import com.google.adk.agents.LlmAgent;
import com.google.adk.runner.Runner;
import com.google.adk.sessions.InMemorySessionService;
import com.google.adk.sessions.Session;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext;
import com.google.common.collect.ImmutableList;
import com.google.genai.types.Content;
import com.google.genai.types.Part;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

public class CustomerSupportAgentApp {

  private static final String APP_NAME = "customer_support_agent";
  private static final String USER_ID = "user1234";
  private static final String SESSION_ID = "1234";
  private static final String MODEL_ID = "gemini-2.0-flash";

  /**
   * Checks if the query requires escalation and transfers to another agent if needed.
   *
   * @param query The user's query.
   * @param toolContext The context for the tool.
   * @return A map indicating the result of the check and transfer.
   */
  public static Map<String, Object> checkAndTransfer(
      @Schema(name = "query", description = "the user query")
      String query,
      @Schema(name = "toolContext", description = "the tool context")
      ToolContext toolContext) {
    Map<String, Object> response = new HashMap<>();
    if (query.toLowerCase(Locale.ROOT).contains("urgent")) {
      System.out.println("Tool: Detected urgency, transferring to the support agent.");
      toolContext.actions().setTransferToAgent("support_agent");
      response.put("status", "transferring");
      response.put("message", "Transferring to the support agent...");
    } else {
      response.put("status", "processed");
      response.put(
          "message", String.format("Processed query: '%s'. No further action needed.", query));
    }
    return response;
  }

  /**
   * Calls the agent with the given query and prints the final response.
   *
   * @param runner The runner to use.
   * @param query The query to send to the agent.
   */
  public static void callAgent(Runner runner, String query) {
    Content content =
        Content.fromParts(Part.fromText(query));

    InMemorySessionService sessionService = (InMemorySessionService) runner.sessionService();
    // Fixed: session ID does not need to be an optional.
    Session session =
        sessionService
            .createSession(APP_NAME, USER_ID, /* state= */ null, SESSION_ID)
            .blockingGet();

    runner
        .runAsync(session.userId(), session.id(), content)
        .forEach(
            event -> {
              if (event.finalResponse()
                  && event.content().isPresent()
                  && event.content().get().parts().isPresent()
                  && !event.content().get().parts().get().isEmpty()
                  && event.content().get().parts().get().get(0).text().isPresent()) {
                String finalResponse = event.content().get().parts().get().get(0).text().get();
                System.out.println("Agent Response: " + finalResponse);
              }
            });
  }

  public static void main(String[] args) throws NoSuchMethodException {
    FunctionTool escalationTool =
        FunctionTool.create(
            CustomerSupportAgentApp.class.getMethod(
                "checkAndTransfer", String.class, ToolContext.class));

    LlmAgent supportAgent =
        LlmAgent.builder()
            .model(MODEL_ID)
            .name("support_agent")
            .description("""
                The dedicated support agent.
                Mentions it is a support handler and helps the user with their urgent issue.
            """)
            .instruction("""
                You are the dedicated support agent.
                Mentioned you are a support handler and please help the user with their urgent issue.
            """)
            .build();

    LlmAgent mainAgent =
        LlmAgent.builder()
            .model(MODEL_ID)
            .name("main_agent")
            .description("""
                The first point of contact for customer support of an analytics tool.
                Answers general queries.
                If the user indicates urgency, uses the 'check_and_transfer' tool.
                """)
            .instruction("""
                You are the first point of contact for customer support of an analytics tool.
                Answer general queries.
                If the user indicates urgency, use the 'check_and_transfer' tool.
                """)
            .tools(ImmutableList.of(escalationTool))
            .subAgents(supportAgent)
            .build();
    // Fixed: LlmAgent.subAgents() expects 0 arguments.
    // Sub-agents are now added to the main agent via its builder,
    // as `subAgents` is a property that should be set during agent construction
    // if it's not dynamically managed.

    InMemorySessionService sessionService = new InMemorySessionService();
    Runner runner = new Runner(mainAgent, APP_NAME, null, sessionService);

    // Agent Interaction
    callAgent(runner, "this is urgent, i cant login");
  }
}
import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.annotations.Param
import com.google.adk.kt.annotations.Tool
import com.google.adk.kt.models.Gemini
import com.google.adk.kt.runners.InMemoryRunner
import com.google.adk.kt.sessions.InMemorySessionService
import com.google.adk.kt.sessions.SessionKey
import com.google.adk.kt.tools.ToolContext
import com.google.adk.kt.types.Content
import com.google.adk.kt.types.Part
import com.google.adk.kt.types.Role
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking

private const val APP_NAME = "customer_support_agent"
private const val USER_ID = "user1234"
private const val SESSION_ID = "1234"

class EscalationTools {
    /**
     * Checks if the query requires escalation and transfers to another agent if needed.
     */
    @Tool
    fun checkAndTransfer(
        @Param("The user query to triage.") query: String,
        context: ToolContext,
    ): String =
        if ("urgent" in query.lowercase()) {
            println("Tool: Detected urgency, transferring to the support agent.")
            // Setting transferToAgent on the actions hands control to the named
            // agent once this tool returns.
            context.actions.transferToAgent = "support_agent"
            "Transferring to the support agent..."
        } else {
            "Processed query: '$query'. No further action needed."
        }
}

fun main() =
    runBlocking {
        val supportAgent =
            LlmAgent(
                name = "support_agent",
                model = Gemini(name = "gemini-flash-latest"),
                instruction =
                    Instruction(
                        "You are the dedicated support agent. Mention that you are a " +
                            "support handler and help the user with their urgent issue.",
                    ),
            )

        val mainAgent =
            LlmAgent(
                name = "main_agent",
                model = Gemini(name = "gemini-flash-latest"),
                instruction =
                    Instruction(
                        "You are the first point of contact for customer support of an " +
                            "analytics tool. Answer general queries. If the user indicates " +
                            "urgency, use the checkAndTransfer tool.",
                    ),
                tools = EscalationTools().generatedTools(),
                subAgents = listOf(supportAgent),
            )

        val sessionService = InMemorySessionService()
        val runner =
            InMemoryRunner(
                agent = mainAgent,
                appName = APP_NAME,
                sessionService = sessionService,
            )
        sessionService.createSession(
            SessionKey(APP_NAME, USER_ID, SESSION_ID),
        )

        val query = "this is urgent, i cant login"
        val userContent = Content(role = Role.USER, parts = listOf(Part(text = query)))

        val events =
            runner.runAsync(
                userId = USER_ID,
                sessionId = SESSION_ID,
                newMessage = userContent,
            ).toList()

        for (event in events) {
            if (event.isFinalResponse) {
                println("Agent Response: ${event.content?.parts?.firstOrNull()?.text}")
            }
        }
    }
説明
  • main_agentとsupport_agentの2つのエージェントを定義します。main_agentは、最初の連絡先となるように設計されています。
  • check_and_transferツールは、main_agentによって呼び出されると、ユーザーのクエリを調べます。
  • クエリに「urgent」という単語が含まれている場合、ツールはtool_context、具体的にはtool_context.actionsにアクセスし、transfer_to_agent属性をsupport_agentに設定します。
  • このアクションは、フレームワークに会話の制御をsupport_agentという名前のエージェントに転送するように通知します。
  • main_agentが緊急クエリを処理すると、check_and_transferツールが転送をトリガーします。後続の応答は、理想的にはsupport_agentから返されます。
  • 緊急性のない通常のクエリの場合、ツールは転送をトリガーせずに単に処理します。

この例は、ツールがToolContextのEventActionsを介して、別の特殊なエージェントに制御を転送することで、会話の流れに動的に影響を与えることができる方法を示しています。

認証

ADKでサポートPython v0.1.0

ToolContextは、認証済みAPIと対話するツールにメカニズムを提供します。ツールが認証を処理する必要がある場合は、次を使用できます。

  • auth_response: ツールが呼び出される前にフレームワークによって認証がすでに処理されていた場合、資格情報(トークンなど)が含まれます(RestApiToolおよびOpenAPIセキュリティスキームで一般的です)。

  • request_credential(auth_config: dict): ツールが認証が必要であると判断したが、資格情報が利用できない場合にこのメソッドを呼び出します。これにより、提供されたauth_configに基づいて認証フローを開始するようにフレームワークに通知されます。

  • get_auth_response(): 後続の呼び出しで(request_credentialが正常に処理された後)、ユーザーが提供した資格情報を取得するためにこれを呼び出します。

認証フロー、構成、および例の詳細については、専用のツール認証ドキュメントページを参照してください。

コンテキスト対応データアクセス方法

これらのメソッドは、ツールが構成済みサービスによって管理されるセッションまたはユーザーに関連付けられた永続データと対話するための便利な方法を提供します。

  • list_artifacts()(またはJavaではlistArtifacts()):artifact_serviceを介して現在セッションに保存されているすべてのアーティファクトのファイル名(またはキー)のリストを返します。アーティファクトは通常、ユーザーがアップロードした、またはツール/エージェントが生成したファイル(画像、ドキュメントなど)です。

  • load_artifact(filename: str): artifact_serviceからファイル名で特定のアーティファクトを取得します。オプションでバージョンを指定できます。省略すると、最新バージョンが返されます。アーティファクトデータとMIMEタイプを含むgoogle.genai.types.Partオブジェクトを返します。見つからない場合はNoneを返します。

  • save_artifact(filename: str, artifact: types.Part): artifact_serviceにアーティファactの新しいバージョンを保存します。新しいバージョン番号(0から始まる)を返します。

  • search_memory(query: str): (ADK PythonおよびGoでサポート) 構成済みのmemory_serviceを使用してユーザーの長期記憶をクエリします。これは、過去の対話または保存された知識から関連情報を取得するのに役立ちます。SearchMemoryResponseの構造は、特定のメモリサービスの実装によって異なりますが、通常、関連するテキストスニペットまたは会話の抜粋が含まれます。

例

# 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.tools import ToolContext, FunctionTool
from google.genai import types


async def process_document(
    document_name: str, analysis_query: str, tool_context: ToolContext
) -> dict:
    """Analyzes a document using context from memory."""

    # 1. Load the artifact
    print(f"Tool: Attempting to load artifact: {document_name}")
    document_part = await tool_context.load_artifact(document_name)

    if not document_part:
        return {"status": "error", "message": f"Document '{document_name}' not found."}

    document_text = document_part.text  # Assuming it's text for simplicity
    print(f"Tool: Loaded document '{document_name}' ({len(document_text)} chars).")

    # 2. Search memory for related context
    print(f"Tool: Searching memory for context related to: '{analysis_query}'")
    memory_response = await tool_context.search_memory(
        f"Context for analyzing document about {analysis_query}"
    )
    memory_context = "\n".join(
        [
            m.events[0].content.parts[0].text
            for m in memory_response.memories
            if m.events and m.events[0].content
        ]
    )  # Simplified extraction
    print(f"Tool: Found memory context: {memory_context[:100]}...")

    # 3. Perform analysis (placeholder)
    analysis_result = f"Analysis of '{document_name}' regarding '{analysis_query}' using memory context: [Placeholder Analysis Result]"
    print("Tool: Performed analysis.")

    # 4. Save the analysis result as a new artifact
    analysis_part = types.Part.from_text(text=analysis_result)
    new_artifact_name = f"analysis_{document_name}"
    version = await tool_context.save_artifact(new_artifact_name, analysis_part)
    print(f"Tool: Saved analysis result as '{new_artifact_name}' version {version}.")

    return {
        "status": "success",
        "analysis_artifact": new_artifact_name,
        "version": version,
    }


doc_analysis_tool = FunctionTool(func=process_document)

# In an Agent:
# Assume artifact 'report.txt' was previously saved.
# Assume memory service is configured and has relevant past data.
# my_agent = Agent(..., tools=[doc_analysis_tool], artifact_service=..., memory_service=...)
import { Part } from "@google/genai";
import { Context } from '@google/adk';

// Analyzes a document using context from memory.
export async function processDocument(
  params: { documentName: string; analysisQuery: string },
  context?: Context
): Promise<Record<string, any>> {
  if (!context) {
    throw new Error("Context is required for this tool.");
  }

  // 1. List all available artifacts
  const artifacts = await context.listArtifacts();
  console.log(`Listing all available artifacts: ${artifacts}`);

  // 2. Load an artifact
  console.log(`Tool: Attempting to load artifact: ${params.documentName}`);
  const documentPart = await context.loadArtifact(params.documentName);
  if (!documentPart) {
    console.log(`Tool: Document '${params.documentName}' not found.`);
    return {
      status: "error",
      message: `Document '${params.documentName}' not found.`, 
    };
  }

  const documentText = documentPart.text ?? "";
  console.log(
    `Tool: Loaded document '${params.documentName}' (${documentText.length} chars).`
  );

  // 3. Search memory for related context
  console.log(`Tool: Searching memory for context related to '${params.analysisQuery}'`);
  const memory_results = await context.searchMemory(params.analysisQuery);
  console.log(`Tool: Found ${memory_results.memories.length} relevant memories.`);
  const context_from_memory = memory_results.memories
    .map((m) => m.content.parts[0].text)
    .join("\n");

  // 4. Perform analysis (placeholder)
  const analysisResult =
    `Analysis of '${params.documentName}' regarding '${params.analysisQuery}':\n` +
    `Context from Memory:\n${context_from_memory}\n` +
    `[Placeholder Analysis Result]`;
  console.log("Tool: Performed analysis.");

  // 5. Save the analysis result as a new artifact
  const analysisPart: Part = { text: analysisResult };
  const newArtifactName = `analysis_${params.documentName}`;
  await context.saveArtifact(newArtifactName, analysisPart);
  console.log(`Tool: Saved analysis result to '${newArtifactName}'.`);

  return {
    status: "success",
    analysis_artifact: newArtifactName,
  };
}
// 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 (
    "fmt"

    "google.golang.org/adk/v2/agent"
    "google.golang.org/genai"
)

type processDocumentArgs struct {
    DocumentName  string `json:"document_name" jsonschema:"The name of the document to be processed."`
    AnalysisQuery string `json:"analysis_query" jsonschema:"The query for the analysis."`
}

type processDocumentResult struct {
    Status           string `json:"status"`
    AnalysisArtifact string `json:"analysis_artifact,omitempty"`
    Version          int64  `json:"version,omitempty"`
    Message          string `json:"message,omitempty"`
}

func processDocument(ctx agent.Context, args processDocumentArgs) (*processDocumentResult, error) {
    fmt.Printf("Tool: Attempting to load artifact: %s\n", args.DocumentName)

    // List all artifacts
    listResponse, err := ctx.Artifacts().List(ctx)
    if err != nil {
        return nil, fmt.Errorf("failed to list artifacts")
    }

    fmt.Println("Tool: Available artifacts:")
    for _, file := range listResponse.FileNames {
        fmt.Printf(" - %s\n", file)
    }

    documentPart, err := ctx.Artifacts().Load(ctx, args.DocumentName)
    if err != nil {
        return nil, fmt.Errorf("document '%s' not found", args.DocumentName)
    }

    fmt.Printf("Tool: Loaded document '%s' of size %d bytes.\n", args.DocumentName, len(documentPart.Part.InlineData.Data))

    // 3. Search memory for related context
    fmt.Printf("Tool: Searching memory for context related to: '%s'\n", args.AnalysisQuery)
    memoryResp, err := ctx.SearchMemory(ctx, args.AnalysisQuery)
    if err != nil {
        fmt.Printf("Tool: Error searching memory: %v\n", err)
    }
    memoryResultCount := 0
    if memoryResp != nil {
        memoryResultCount = len(memoryResp.Memories)
    }
    fmt.Printf("Tool: Found %d memory results.\n", memoryResultCount)

    analysisResult := fmt.Sprintf("Analysis of '%s' regarding '%s' using memory context: [Placeholder Analysis Result]", args.DocumentName, args.AnalysisQuery)
    fmt.Println("Tool: Performed analysis.")

    analysisPart := genai.NewPartFromText(analysisResult)
    newArtifactName := fmt.Sprintf("analysis_%s", args.DocumentName)
    version, err := ctx.Artifacts().Save(ctx, newArtifactName, analysisPart)
    if err != nil {
        return nil, fmt.Errorf("failed to save artifact")
    }
    fmt.Printf("Tool: Saved analysis result as '%s' version %d.\n", newArtifactName, version.Version)

    return &processDocumentResult{
        Status:           "success",
        AnalysisArtifact: newArtifactName,
        Version:          version.Version,
    }, nil
}
// メモリのコンテキストを使用してドキュメントを分析します。
// コールバックコンテキストまたはLoadArtifactsツールを使用して、アーティファクトを一覧表示、ロード、保存することもできます。
public static @NonNull Maybe<ImmutableMap<String, Object>> processDocument(
    @Annotations.Schema(description = "分析するドキュメントの名前。") String documentName,
    @Annotations.Schema(description = "分析のクエリ。") String analysisQuery,
    ToolContext toolContext) {

  // 1. 利用可能なすべてのアーティファクトを一覧表示します
  System.out.printf(
      "利用可能なすべてのアーティファクトを一覧表示しています %s:", toolContext.listArtifacts().blockingGet());

  // 2. アーティファクトをメモリにロードします
  System.out.println("ツール:アーティファクトのロードを試みています: " + documentName);
  Part documentPart = toolContext.loadArtifact(documentName, Optional.empty()).blockingGet();
  if (documentPart == null) {
    System.out.println("ツール:ドキュメント '" + documentName + "' が見つかりません。");
    return Maybe.just(
        ImmutableMap.<String, Object>of(
            "status", "error", "message", "ドキュメント '" + documentName + "' が見つかりません。"));
  }
  String documentText = documentPart.text().orElse("");
  System.out.println(
      "ツール:ドキュメント '" + documentName + "' (" + documentText.length() + " 文字) をロードしました。");

  // 3. 分析を実行します(プレースホルダー)
  String analysisResult =
      "'"
          + documentName
          + "' に関する '"
          + analysisQuery
          + " [プレースホルダー分析結果]";
  System.out.println("ツール:分析を実行しました。");

  // 4. 分析結果を新しいアーティファクトとして保存します
  Part analysisPart = Part.fromText(analysisResult);
  String newArtifactName = "analysis_" + documentName;

  toolContext.saveArtifact(newArtifactName, analysisPart);

  return Maybe.just(
      ImmutableMap.<String, Object>builder()
          .put("status", "success")
          .put("analysis_artifact", newArtifactName)
          .build());
}
// FunctionTool processDocumentTool =
//      FunctionTool.create(ToolContextArtifactExample.class, "processDocument");
// エージェントに、この関数ツールを含めます。
// LlmAgent agent = LlmAgent().builder().tools(processDocumentTool).build();
import com.google.adk.kt.annotations.Param
import com.google.adk.kt.annotations.Tool
import com.google.adk.kt.tools.ToolContext
import com.google.adk.kt.types.Part

class DocAnalysisTools {
    /**
     * Analyzes a document held in the session's artifacts.
     *
     * Artifact access is suspending in Kotlin, so the tool is a `suspend fun`.
     * `searchMemory` is not available on the Kotlin ToolContext; to bring
     * long-term memory into a turn, add LoadMemoryTool or PreloadMemoryTool to
     * the agent instead.
     */
    @Tool
    suspend fun processDocument(
        @Param("The name of the document to analyze.") documentName: String,
        @Param("The query for the analysis.") analysisQuery: String,
        context: ToolContext,
    ): Map<String, String> {
        // 1. List all available artifacts.
        println("Tool: Available artifacts: ${context.listArtifacts()}")

        // 2. Load the requested artifact.
        println("Tool: Attempting to load artifact: $documentName")
        val documentPart = context.loadArtifact(documentName)
        if (documentPart == null) {
            println("Tool: Document '$documentName' not found.")
            return mapOf(
                "status" to "error",
                "message" to "Document '$documentName' not found.",
            )
        }

        val documentText = documentPart.text.orEmpty()
        println("Tool: Loaded '$documentName' (${documentText.length} chars).")

        // 3. Perform the analysis (placeholder).
        val analysisResult =
            "Analysis of '$documentName' regarding '$analysisQuery' " +
                "[Placeholder Analysis Result]"
        println("Tool: Performed analysis.")

        // 4. Save the analysis back as a new artifact. saveArtifact returns the
        // new version number and records the change in actions.artifactDelta.
        val newArtifactName = "analysis_$documentName"
        context.saveArtifact(newArtifactName, Part(text = analysisResult))

        return mapOf(
            "status" to "success",
            "analysis_artifact" to newArtifactName,
        )
    }
}

ToolContextを活用することで、開発者はADKのアーキテクチャとシームレスに統合し、エージェントの全体的な機能を強化する、より高度でコンテキストを意識したカスタムツールを作成できます。

効果的なツール関数の定義

メソッドまたは関数をADKツールとして使用する場合、その定義方法がエージェントの正しい使用能力に大きく影響します。エージェントの大規模言語モデル(LLM)は、関数の名前、パラメーター(引数)、型ヒント、およびdocstring / ソースコードコメントに大きく依存して、その目的を理解し、正しい呼び出しを生成します。

効果的なツール関数を定義するための主なガイドラインは次のとおりです。

  • 関数名:

    • アクションを明確に示す、説明的な動詞-名詞ベースの名前を使用します(例:get_weather、searchDocuments、schedule_meeting)。
    • run、process、handle_dataなどの一般的な名前や、doStuffなどの過度に曖昧な名前は避けてください。優れた説明があっても、do_stuffのような名前は、モデルがツールをいつ使用するか、たとえばcancelFlightと比較して混乱させる可能性があります。
    • LLMは、ツール選択中に主要な識別子として関数名を使用します。
  • パラメーター(引数):

    • 関数には任意の数のパラメーターを含めることができます。
    • 明確で説明的な名前を使用します(例:cの代わりにcity、qの代わりにsearch_query)。
    • Pythonですべてのパラメーターに型ヒントを提供します(例:city: str、user_id: int、items: list[str])。これは、ADKがLLMの正しいスキーマを生成するために不可欠です。
    • すべてのパラメータータイプがJSONシリアル化可能であることを確認します。str、int、float、bool、list、dictなどのすべてのJavaプリミティブおよび標準Pythonタイプとその組み合わせは、一般的に安全です。明確なJSON表現がない限り、複雑なカスタムクラスインスタンスを直接パラメーターとして使用しないでください。
    • モデルが提供すべき情報にはデフォルト値を避けてください。 たとえば、目的地がユーザーまたは会話コンテキストから来るべき場合は def my_func(destination: str = "Paris") のような形を避けます。デフォルト値は本当にオプションの調整パラメータには適切な場合がありますが、ツールスキーマから必須のビジネス入力を隠す目的では使わないでください。
    • self / clsは自動的に処理されます: self(インスタンスメソッドの場合)やcls(クラスメソッドの場合)などの暗黙的なパラメーターは、ADKによって自動的に処理され、LLMに表示されるスキーマから除外されます。ツールがLLMに提供する必要がある論理パラメーターの型ヒントと説明を定義するだけで済みます。
  • 戻り値の型:

    • 関数の戻り値は、Pythonでは辞書(dict)、JavaではMapでなければなりません。
    • 関数が辞書以外の型(文字列、数値、リストなど)を返す場合、ADKフレームワークは、結果をモデルに返す前に、自動的に{'result': your_original_return_value}のような辞書/Mapにラップします。
    • 辞書/Mapのキーと値を、LLMが簡単に理解できるように説明的に設計します。モデルはこの出力を読み取って次のステップを決定することを忘れないでください。
    • 意味のあるキーを含めます。たとえば、500のようなエラーコードだけを返すのではなく、{'status': 'error', 'error_message': 'データベース接続に失敗しました'}を返します。
    • モデルのツール実行の結果を明確に示すために、statusキー('success'、'error'、'pending'、'ambiguous'など)を含めることは強く推奨されるプラクティスです。
  • Docstring / ソースコードコメント:

    • これは重要です。 docstringは、LLMの主要な説明情報源です。
    • ツールが何をするかを明確に記述します。 その目的と制限について具体的に記述します。
    • ツールをいつ使用すべきかを説明します。 LLMの意思決定をガイドするために、コンテキストまたはシナリオ例を提供します。
    • 各パラメーターを明確に説明します。 LLMがその引数に提供する必要がある情報を説明します。
    • 予想されるdict戻り値の構造と意味、特にさまざまなstatus値と関連するデータキーを説明します。
    • 挿入されたToolContextパラメーターを説明しないでください。LLMが知る必要のあるパラメーターではないため、docstringの説明内でオプションのtool_context: ToolContextパラメーターに言及しないでください。ToolContextは、LLMがそれを呼び出すことを決定した後にADKによって挿入されます。

    良い定義の例:

def lookup_order_status(order_id: str) -> dict:
  """顧客の注文の現在のステータスをIDを使用して取得します。

  ユーザーが特定の注文のステータスを明示的に尋ね、注文IDを提供した場合にのみ、このツールを使用してください。
  一般的な問い合わせには使用しないでください。

  引数:
      order_id:検索する注文の一意の識別子。

  戻り値:
      結果を示す辞書。
      成功した場合、ステータスは「success」で、「order」辞書が含まれます。
      失敗した場合、ステータスは「error」で、「error_message」が含まれます。
      成功例:{'status': 'success', 'order': {'state': 'shipped', 'tracking_number': '1Z9...'}}
      エラー例:{'status': 'error', 'error_message': '注文IDが見つかりません。'}
  """
  # ...ステータスを取得するための関数実装...
  if status_details := fetch_status_from_backend(order_id):
    return {
        "status": "success",
        "order": {
            "state": status_details.state,
            "tracking_number": status_details.tracking,
        },
    }
  else:
    return {"status": "error", "error_message": f"注文ID {order_id} が見つかりません。"}
import (
    "fmt"

    "google.golang.org/adk/v2/agent"
)

type lookupOrderStatusArgs struct {
    OrderID string `json:"order_id" jsonschema:"The ID of the order to look up."`
}

type order struct {
    State          string `json:"state"`
    TrackingNumber string `json:"tracking_number"`
}

type lookupOrderStatusResult struct {
    Status string `json:"status"`
    Order  order  `json:"order,omitempty"`
}

func lookupOrderStatus(ctx agent.Context, args lookupOrderStatusArgs) (*lookupOrderStatusResult, error) {
    // ... function implementation to fetch status ...
    statusDetails, ok := fetchStatusFromBackend(args.OrderID)
    if !ok {
        return nil, fmt.Errorf("order ID %s not found", args.OrderID)
    }
    return &lookupOrderStatusResult{
        Status: "success",
        Order: order{
            State:          statusDetails.State,
            TrackingNumber: statusDetails.Tracking,
        },
    }, nil
}
/**
 * 指定された都市の現在の天気予報を取得します。
 *
 * @param city 天気予報を取得する都市。
 * @param toolContext ツールのコンテキスト。
 * @return 天気情報を含む辞書。
 */
public static Map<String, Object> getWeatherReport(String city, ToolContext toolContext) {
    Map<String, Object> response = new HashMap<>();
    if (city.toLowerCase(Locale.ROOT).equals("london")) {
        response.put("status", "success");
        response.put(
                "report",
                "ロンドンの現在の天気は曇りで、気温は18℃、雨の可能性があります。");
    } else if (city.toLowerCase(Locale.ROOT).equals("paris")) {
        response.put("status", "success");
        response.put("report", "パリの天気は晴れで、気温は25℃です。");
    } else {
        response.put("status", "error");
        response.put("error_message", String.format("'%s'の天気情報は利用できません。", city));
    }
    return response;
}
import com.google.adk.kt.annotations.Param
import com.google.adk.kt.annotations.Tool

class OrderTools {
    /**
     * Fetches the current status of a customer's order using its ID.
     *
     * Use this tool ONLY when a user explicitly asks for the status of a specific
     * order and provides the order ID. Do not use it for general inquiries.
     *
     * Returns a map indicating the outcome. On success, "status" is "success" and
     * an "order" map holds the "state" and "tracking_number". On failure, "status"
     * is "error" and "error_message" explains why.
     */
    @Tool
    fun lookupOrderStatus(
        @Param("The unique identifier of the order to look up.") orderId: String,
    ): Map<String, Any> {
        val statusDetails = fetchStatusFromBackend(orderId)
        return if (statusDetails != null) {
            mapOf(
                "status" to "success",
                "order" to
                    mapOf(
                        "state" to statusDetails.state,
                        "tracking_number" to statusDetails.tracking,
                    ),
            )
        } else {
            mapOf(
                "status" to "error",
                "error_message" to "Order ID $orderId not found.",
            )
        }
    }
}

private data class OrderStatusDetails(val state: String, val tracking: String)

private fun fetchStatusFromBackend(orderId: String): OrderStatusDetails? =
    if (orderId == "1Z9") OrderStatusDetails(state = "shipped", tracking = "1Z9...") else null
  • シンプルさと焦点:
    • ツールを集中させる: 各ツールは、理想的には1つの明確に定義されたタスクを実行する必要があります。
    • パラメーターが少ない方が良い: モデルは一般的に、多くのオプションまたは複雑なパラメーターを持つツールよりも、明確に定義されたパラメーターが少ないツールをより確実に処理します。
    • 単純なデータ型を使用する: 可能な場合は、複雑なカスタムクラスや深くネストされた構造よりも、基本的な型(Pythonではstr、int、bool、float、List[str]、Javaではint、byte、short、long、float、double、boolean、char)を優先します。
    • 複雑なタスクを分解する: 複数の異なる論理ステップを実行する関数を、より小さく、より焦点を絞ったツールに分割します。たとえば、単一のupdate_user_profile(profile: ProfileObject)ツールの代わりに、update_user_name(name: str)、update_user_address(address: str)、update_user_preferences(preferences: list[str])などの個別のツールを検討してください。これにより、LLMが正しい機能を選択して使用しやすくなります。

これらのガイドラインに従うことで、LLMにカスタム関数ツールを効果的に活用するために必要な明確さと構造を提供し、より有能で信頼性の高いエージェントの動作につながります。

ツールセット:ツールのグループ化と動的な提供

ADKでサポートPython v0.5.0TypeScript v0.2.0Java v0.3.0Kotlin v0.1.0

個々のツールを超えて、ADKはBaseToolsetインターフェイス(google.adk.tools.base_toolsetで定義)を介してツールセットの概念を導入します。ツールセットを使用すると、BaseToolインスタンスのコレクションを管理し、多くの場合動的にエージェントに提供できます。

このアプローチは、次の場合に有益です。

  • 関連ツールの整理: 共通の目的を果たすツール(数学演算用のすべてのツール、または特定のAPIと対話するすべてのツールなど)をグループ化します。
  • 動的なツールの可用性: エージェントが現在のコンテキスト(ユーザー権限、セッション状態、またはその他のランタイム条件など)に基づいて異なるツールを利用できるようにします。ツールセットのget_toolsメソッドは、公開するツールを決定できます。
  • 外部ツールプロバイダーの統合: ツールセットは、OpenAPI仕様やMCPサーバーなどの外部システムから提供されるツールのアダプターとして機能し、ADK互換のBaseToolオブジェクトに変換できます。

BaseToolsetインターフェイス

ADKでツールセットとして機能するクラスは、BaseToolset抽象基本クラスを実装する必要があります。このインターフェイスは、主に2つのメソッドを定義します。

  • async def get_tools(...) -> list[BaseTool]: これはツールセットのコアメソッドです。ADKエージェントが利用可能なツールを知る必要がある場合、toolsリストで提供される各BaseToolsetインスタンスでget_tools()を呼び出します。

    • オプションのreadonly_context(ReadonlyContextのインスタンス)を受け取ります。このコンテキストは、現在のセッション状態(readonly_context.state)、エージェント名、呼び出しIDなどの情報への読み取り専用アクセスを提供します。ツールセットは、このコンテキストを使用して、返すツールを動的に決定できます。
    • BaseToolインスタンス(FunctionTool、RestApiToolなど)のlistを必ず返す必要があります。
  • async def close(self) -> None: この非同期メソッドは、エージェントサーバーがシャットダウンしているときやRunnerが閉じられているときなど、ツールセットが不要になったときにADKフレームワークによって呼び出されます。このメソッドを実装して、ネットワーク接続のクローズ、ファイルハンドルの解放、ツールセットによって管理されるその他のリソースのクリーンアップなど、必要なクリーンアップを実行します。

エージェントでのツールセットの使用

BaseToolset実装のインスタンスを、個々のBaseToolインスタンスとともにLlmAgentのtoolsリストに直接含めることができます。

エージェントが初期化されるか、利用可能な機能を決定する必要がある場合、ADKフレームワークはtoolsリストを反復処理します。

  • アイテムがBaseToolインスタンスの場合、直接使用されます。
  • アイテムがBaseToolsetインスタンスの場合、そのget_tools()メソッドが呼び出され(現在のReadonlyContextとともに)、返されたBaseToolのリストがエージェントの利用可能なツールに追加されます。

例:シンプルな数学ツールセット

簡単な算術演算を提供するツールセットの基本的な例を作成しましょう。

import asyncio
from typing import Optional, List, Dict, Any

from google.adk.agents import LlmAgent
from google.adk.agents.readonly_context import ReadonlyContext
from google.adk.tools import BaseTool, FunctionTool
from google.adk.tools.base_toolset import BaseToolset
from google.adk.tools.tool_context import ToolContext
from google.adk.runners import InMemoryRunner


# 1. Define the individual tool functions
def add_numbers(a: int, b: int, tool_context: ToolContext) -> Dict[str, Any]:
    """Adds two integer numbers.
    Args:
        a: The first number.
        b: The second number.
    Returns:
        A dictionary with the sum, e.g., {'status': 'success', 'result': 5}
    """
    print(f"Tool: add_numbers called with a={a}, b={b}")
    result = a + b
    # Example: Storing something in tool_context state
    tool_context.state["last_math_operation"] = "addition"
    return {"status": "success", "result": result}


def subtract_numbers(a: int, b: int) -> Dict[str, Any]:
    """Subtracts the second number from the first.
    Args:
        a: The first number.
        b: The second number.
    Returns:
        A dictionary with the difference, e.g., {'status': 'success', 'result': 1}
    """
    print(f"Tool: subtract_numbers called with a={a}, b={b}")
    return {"status": "success", "result": a - b}


# 2. Create the Toolset by implementing BaseToolset
class SimpleMathToolset(BaseToolset):
    def __init__(self, prefix: str = "math"):
        self.prefix = prefix
        super().__init__(tool_name_prefix=self.prefix) # Toolset can customize names by passing a prefix

        # Create FunctionTool instances once
        self._add_tool = FunctionTool(
            func=add_numbers,
        )
        self._subtract_tool = FunctionTool(
            func=subtract_numbers,
        )
        print(f"SimpleMathToolset initialized with prefix '{self.prefix}'")

    async def get_tools(
        self, readonly_context: Optional[ReadonlyContext] = None
    ) -> List[BaseTool]:
        print("SimpleMathToolset.get_tools() called.")
        # Example of dynamic behavior:
        # Could use readonly_context.state to decide which tools to return
        # For instance, if readonly_context.state.get("enable_advanced_math"):
        #    return [self._add_tool, self._subtract_tool, self._multiply_tool]

        # For this simple example, always return both tools
        tools_to_return = [self._add_tool, self._subtract_tool]
        print(f"SimpleMathToolset providing tools: {[t.name for t in tools_to_return]}")
        return tools_to_return

    async def close(self) -> None:
        # No resources to clean up in this simple example
        print(f"SimpleMathToolset.close() called for prefix '{self.prefix}'.")
        await asyncio.sleep(0)  # Placeholder for async cleanup if needed


# 3. Define an individual tool (not part of the toolset)
def greet_user(name: str = "User") -> Dict[str, str]:
    """Greets the user."""
    print(f"Tool: greet_user called with name={name}")
    return {"greeting": f"Hello, {name}!"}


greet_tool = FunctionTool(func=greet_user)

# 4. Instantiate the toolset
math_toolset_instance = SimpleMathToolset(prefix="calculator")

# 5. Define an agent that uses both the individual tool and the toolset
calculator_agent = LlmAgent(
    name="CalculatorAgent",
    model="gemini-flash-latest",  # Replace with your desired model
    instruction="You are a helpful calculator and greeter. "
    "Use 'greet_user' for greetings. "
    "Use 'calculator_add_numbers' to add and 'calculator_subtract_numbers' to subtract. "
    "Announce the state of 'last_math_operation' if it's set.",
    tools=[greet_tool, math_toolset_instance],  # Individual tool  # Toolset instance
)

# 6. Run the agent
runner = InMemoryRunner(agent=calculator_agent, app_name="toolset_example_app")

async def main():
    print("\n--- Query 1: Greeting ---")
    await runner.run_debug("Hi there!")
    print("\n--- Query 2: Addition ---")
    await runner.run_debug("What is 5 plus 3?")
    await math_toolset_instance.close()

if __name__ == "__main__":
    asyncio.run(main())
/**
 * 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 { LlmAgent, FunctionTool, Context, BaseToolset, InMemoryRunner, isFinalResponse, BaseTool, stringifyContent } from '@google/adk';
import { z } from "zod";
import { Content, createUserContent } from "@google/genai";

function addNumbers(params: { a: number; b: number }, context?: Context): Record<string, any> {
  if (!context) {
    throw new Error("Context is required for this tool.");
  }
  const result = params.a + params.b;
  context.state.set("last_math_result", result);
  return { result: result };
}

function subtractNumbers(params: { a: number; b: number }): Record<string, any> {
  return { result: params.a - params.b };
}

function greetUser(params: { name: string }): Record<string, any> {
  return { greeting: `Hello, ${params.name}!` };
}

class SimpleMathToolset extends BaseToolset {
  private readonly tools: BaseTool[];

  constructor(prefix = "") {
    super([]); // No filter
    this.tools = [
      new FunctionTool({
        name: `${prefix}add_numbers`,
        description: "Adds two numbers and stores the result in the session state.",
        parameters: z.object({ a: z.number(), b: z.number() }),
        execute: addNumbers,
      }),
      new FunctionTool({
        name: `${prefix}subtract_numbers`,
        description: "Subtracts the second number from the first.",
        parameters: z.object({ a: z.number(), b: z.number() }),
        execute: subtractNumbers,
      }),
    ];
  }

  async getTools(): Promise<BaseTool[]> {
    return this.tools;
  }

  async close(): Promise<void> {
    console.log("SimpleMathToolset closed.");
  }
}

async function main() {
  const mathToolset = new SimpleMathToolset("calculator_");
  const greetTool = new FunctionTool({
    name: "greet_user",
    description: "Greets the user.",
    parameters: z.object({ name: z.string() }),
    execute: greetUser,
  });

  const instruction =
    `You are a calculator and a greeter.
    If the user asks for a math operation, use the calculator tools.
    If the user asks for a greeting, use the greet_user tool.
    The result of the last math operation is stored in the 'last_math_result' state variable.`;

  const calculatorAgent = new LlmAgent({
    name: "calculator_agent",
    instruction: instruction,
    tools: [greetTool, mathToolset],
    model: "gemini-2.5-flash",
  });

  const runner = new InMemoryRunner({ agent: calculatorAgent, appName: "toolset_app" });
  await runner.sessionService.createSession({ appName: "toolset_app", userId: "user1", sessionId: "session1" });

  const message: Content = createUserContent("What is 5 + 3?");

  for await (const event of runner.runAsync({ userId: "user1", sessionId: "session1", newMessage: message })) {
    if (isFinalResponse(event) && event.content?.parts?.length) {
      const text = stringifyContent(event).trim();
      if (text) {
        console.log(`Response from agent: ${text}`);
      }
    }
  }

  await mathToolset.close();
}

main();
import com.google.adk.agents.LlmAgent;
import com.google.adk.agents.ReadonlyContext;
import com.google.adk.tools.Annotations.Schema;
import com.google.adk.tools.BaseTool;
import com.google.adk.tools.BaseToolset;
import com.google.adk.tools.FunctionTool;
import com.google.adk.tools.ToolContext;
import io.reactivex.rxjava3.core.Flowable;
import java.util.HashMap;
import java.util.Map;

public class SimpleMathToolsetApp {

  // 1. Define the individual tool functions

  /**
   * Adds two integer numbers.
   *
   * @param a The first number.
   * @param b The second number.
   * @param toolContext The tool context.
   * @return A map with the sum.
   */
  public static Map<String, Object> addNumbers(
      @Schema(name = "a", description = "The first number") int a,
      @Schema(name = "b", description = "The second number") int b,
      ToolContext toolContext) {
    System.out.println("Tool: add_numbers called with a=" + a + ", b=" + b);
    int result = a + b;
    // Example: Storing something in tool_context state
    toolContext.state().put("last_math_operation", "addition");
    Map<String, Object> response = new HashMap<>();
    response.put("status", "success");
    response.put("result", result);
    return response;
  }

  /**
   * Subtracts the second number from the first.
   *
   * @param a The first number.
   * @param b The second number.
   * @return A map with the difference.
   */
  public static Map<String, Object> subtractNumbers(
      @Schema(name = "a", description = "The first number") int a,
      @Schema(name = "b", description = "The second number") int b) {
    System.out.println("Tool: subtract_numbers called with a=" + a + ", b=" + b);
    Map<String, Object> response = new HashMap<>();
    response.put("status", "success");
    response.put("result", a - b);
    return response;
  }

  // 2. Create the Toolset by implementing BaseToolset
  public static class SimpleMathToolset implements BaseToolset {
    private final BaseTool addTool;
    private final BaseTool subtractTool;

    public SimpleMathToolset() throws NoSuchMethodException {
      // Create FunctionTool instances once
      this.addTool =
          FunctionTool.create(
              SimpleMathToolsetApp.class.getMethod(
                  "addNumbers", int.class, int.class, ToolContext.class));
      this.subtractTool =
          FunctionTool.create(
              SimpleMathToolsetApp.class.getMethod("subtractNumbers", int.class, int.class));
      System.out.println("SimpleMathToolset initialized");
    }

    @Override
    public Flowable<BaseTool> getTools(ReadonlyContext readonlyContext) {
      System.out.println("SimpleMathToolset.getTools() called.");
      // Example of dynamic behavior:
      // Could use readonlyContext to access state and conditionally return tools.
      // For this simple example, always return both tools:
      return Flowable.just(addTool, subtractTool);
    }

    @Override
    public void close() throws Exception {
      // No resources to clean up in this simple example
      System.out.println("SimpleMathToolset.close() called.");
    }
  }

  // 3. Define an individual tool (not part of the toolset)

  /**
   * Greets the user.
   *
   * @param name The name of the user.
   * @return A map with the greeting.
   */
  public static Map<String, Object> greetUser(
      @Schema(name = "name", description = "The name of the user") String name) {
    System.out.println("Tool: greetUser called with name=" + name);
    Map<String, Object> response = new HashMap<>();
    response.put("greeting", "Hello, " + name + "!");
    return response;
  }

  public static void main(String[] args) throws Exception {
    BaseTool greetTool =
        FunctionTool.create(SimpleMathToolsetApp.class.getMethod("greetUser", String.class));

    // 4. Instantiate the toolset
    BaseToolset mathToolsetInstance = new SimpleMathToolset();

    // 5. Define an agent that uses both the individual tool and the toolset
    LlmAgent calculatorAgent =
        LlmAgent.builder()
            .name("CalculatorAgent")
            .model("gemini-2.5-flash") // Replace with your desired model
            .instruction(
                "You are a helpful calculator and greeter. "
                    + "Use 'greetUser' for greetings. "
                    + "Use 'addNumbers' to add and 'subtractNumbers' to subtract. "
                    + "Announce the state of 'last_math_operation' if it's set.")
            .tools(greetTool, mathToolsetInstance) // Individual tool and Toolset instance
            .build();

    // System.out.println("Agent '" + calculatorAgent.name() + "' created.");

    // Runner runner = new Runner(calculatorAgent, ...);
    // ... setup and usage ...

    // Important: Clean up the toolset if it manages resources
    mathToolsetInstance.close();
  }
}
import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.agents.ReadonlyContext
import com.google.adk.kt.annotations.Param
import com.google.adk.kt.annotations.Tool
import com.google.adk.kt.models.Gemini
import com.google.adk.kt.tools.BaseTool
import com.google.adk.kt.tools.ToolContext
import com.google.adk.kt.tools.ToolFilter
import com.google.adk.kt.tools.Toolset
import com.google.adk.kt.tools.isToolSelected

/** The individual tools, exposed by the @Tool annotation. */
class MathTools {
    /**
     * Adds two integer numbers.
     */
    @Tool
    fun addNumbers(
        @Param("The first number.") a: Int,
        @Param("The second number.") b: Int,
        context: ToolContext,
    ): Map<String, Any> {
        // Example: recording something in the session state.
        context.actions.stateDelta["last_math_operation"] = "addition"
        return mapOf("status" to "success", "result" to a + b)
    }

    /**
     * Subtracts the second number from the first.
     */
    @Tool
    fun subtractNumbers(
        @Param("The first number.") a: Int,
        @Param("The second number.") b: Int,
    ): Map<String, Any> = mapOf("status" to "success", "result" to a - b)
}

/** An individual tool, defined outside any toolset. */
class GreetTools {
    /**
     * Greets the user.
     */
    @Tool
    fun greetUser(
        @Param("The name of the user to greet.") name: String,
    ): Map<String, String> {
        println("Tool: greetUser called with name=$name")
        return mapOf("greeting" to "Hello, $name!")
    }
}

/**
 * A toolset that narrows what it exposes with an optional [ToolFilter].
 *
 * A null filter selects every tool, so the filter is genuinely optional.
 */
class SimpleMathToolset(private val filter: ToolFilter? = null) : Toolset {
    private val tools = MathTools().generatedTools()

    override suspend fun getTools(readonlyContext: ReadonlyContext?): List<BaseTool> =
        tools.filter { filter.isToolSelected(it, readonlyContext) }

    /** Releases anything the toolset holds. There is nothing to release here. */
    override fun close() {}
}

/**
 * An agent using both an individual tool and a toolset. Kotlin keeps the two
 * apart: individual tools go in `tools`, toolsets in `toolsets`.
 */
val calculatorAgent =
    LlmAgent(
        name = "calculator_agent",
        model = Gemini(name = "gemini-flash-latest"),
        instruction =
            Instruction(
                "You are a helpful calculator and greeter. Use greetUser for " +
                    "greetings. Use addNumbers to add and subtractNumbers to " +
                    "subtract. Announce the state of 'last_math_operation' if it is set.",
            ),
        tools = GreetTools().generatedTools(),
        toolsets = listOf(SimpleMathToolset()),
    )

この例では:

  • SimpleMathToolsetはBaseToolsetを実装し、そのget_tools()メソッドはadd_numbersとsubtract_numbersのFunctionToolインスタンスを返します。また、プレフィックスを使用して名前をカスタマイズします。
  • calculator_agentは、個々のgreet_toolとSimpleMathToolsetのインスタンスの両方で構成されます。
  • calculator_agentが実行されると、ADKはmath_toolset_instance.get_tools()を呼び出します。エージェントのLLMは、ユーザーリクエストを処理するためにgreet_user, calculator_add_numbers, および calculator_subtract_numbers にアクセスできるようになります。
  • add_numbersツールはtool_context.stateへの書き込みを示し、エージェントの指示はこの状態の読み取りに言及しています。
  • close()メソッドは、ツールセットが保持するリソースが解放されるように呼び出されます。
  • Kotlin の例では、プレフィックス メカニズムがなく BaseTool.name が読み取り専用であるため、ツール名にプレフィックスを付けません。ツールは greetUser、addNumbers、subtractNumbers のままです。また、Kotlin では 2 種類のツールを分離して管理します。個々のツールは tools に、ツールセットは toolsets に配置します。

ツールセット内のツールのフィルタリング

ADKでサポートKotlin v0.7.0

すべての SDK では、名前または現在のコンテキストを参照する述語 (predicate) によって、ツールセットがモデルに渡す対象を絞り込むことができます。Python、Java、TypeScript では BaseToolset でそのフィルターを受け取ります。ここに示す例は Kotlin の形式です。

getTools() 内にリストをハードコードする代わりに、上記の Kotlin の例で SimpleMathToolset が行っているように、ツールセットは ToolFilter を受け取り、isToolSelected でそれを適用できます。ToolFilter.allowList は名前でツールを選択し、ToolFilter.Predicate は ReadonlyContext を受け取るため、ツールリストをセッション ステートや現在のユーザーに依存させることができます。null フィルターはすべてを選択するため、同じクラスがフィルターなしでも機能します。

// SimpleMathToolset, defined above, applies its optional ToolFilter inside
// getTools(). Passing one narrows what the same toolset exposes.

// Expose a fixed subset by name.
val addOnlyMath = SimpleMathToolset(ToolFilter.allowList("addNumbers"))

// Or decide per invocation. The predicate receives the ReadonlyContext, so the
// tool list can depend on session state, the user, or anything else on it.
val contextAwareMath =
    SimpleMathToolset(
        ToolFilter.Predicate { tool, context ->
            tool.name == "addNumbers" || context?.state?.get("enable_advanced_math") == true
        },
    )

ツールセットは、ADKエージェントにツールのコレクションを整理、管理、動的に提供するための強力な方法を提供し、よりモジュール式で保守性が高く、適応性のあるエージェントアプリケーションにつながります。