Skip to content

ADK 自定义工具

Supported in ADKPython v0.1.0Java v0.1.0Go 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 决定执行哪个工具(如果有),基于智能体可用的工具和描述每个工具的文档字符串。
  3. 调用: LLM 为所选工具生成所需的参数(输入)并触发其执行。
  4. 观察: 智能体接收工具返回的输出(结果)。
  5. 完成: 智能体将工具的输出纳入其持续的推理过程,以制定下一个响应、决定后续步骤或确定目标是否已达成。

将工具视为智能体智能核心(LLM)可以按需访问和利用以完成复杂任务的专门工具包。

ADK 中的工具类型

ADK 通过支持多种类型的工具提供灵活性:

  1. Function Tools: 由你创建的工具,针对你的特定应用程序需求定制。
  2. Built-in Tools: 框架为常见任务提供的即用型工具。 示例:Google 搜索、代码执行、检索增强生成(RAG)。
  3. 第三方工具: 无缝集成来自流行外部库的工具。

导航到上面链接的相关文档页面,获取每种工具类型的详细信息和示例。

在智能体指令中引用工具

在智能体的指令中,你可以通过使用其 函数名称 直接引用工具。如果工具的 函数名称文档字符串 足够描述性,你的指令可以主要关注 大语言模型(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())
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");
  }
}
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

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

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/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"`
    ErrorMessage string `json:"error_message,omitempty"`
}

func getWeatherReport(ctx tool.Context, args getWeatherReportArgs) getWeatherReportResult {
    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."}
    }
    if strings.ToLower(args.City) == "paris" {
        return getWeatherReportResult{Status: "success", Report: "The weather in Paris is sunny with a temperature of 25 degrees Celsius."}
    }
    return getWeatherReportResult{Status: "error", ErrorMessage: fmt.Sprintf("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 tool.Context, args analyzeSentimentArgs) analyzeSentimentResult {
    if strings.Contains(strings.ToLower(args.Text), "good") || strings.Contains(strings.ToLower(args.Text), "sunny") {
        return analyzeSentimentResult{Sentiment: "positive", Confidence: 0.8}
    }
    if strings.Contains(strings.ToLower(args.Text), "rain") || strings.Contains(strings.ToLower(args.Text), "bad") {
        return analyzeSentimentResult{Sentiment: "negative", Confidence: 0.7}
    }
    return analyzeSentimentResult{Sentiment: "neutral", Confidence: 0.6}
}

func main() {
    ctx := context.Background()
    model, err := gemini.NewModel(ctx, "gemini-2.0-flash", &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)
        }
    }
}

Tool Context

对于更高级的场景,ADK 允许你通过包含特殊参数 tool_context: ToolContext 在你的工具函数中访问额外的上下文信息。通过在函数签名中包含此项,ADK 将在智能体执行期间调用你的工具时自动提供 ToolContext 类的实例。

ToolContext 提供对几个关键信息和控制杠杆的访问:

  • state: State: 读取和修改当前会话的状态。在此处进行的更改会被跟踪和持久化。

  • actions: EventActions: 影响工具运行后智能体的后续操作(例如,跳过摘要、转移到另一个智能体)。

  • function_call_id: str: 框架分配给此特定工具调用的唯一标识符。用于跟踪和与身份验证响应相关联。这在单个模型响应中调用多个工具时也很有用。

  • function_call_event_id: str: 此属性提供触发当前工具调用的事件的唯一标识符。这可用于跟踪和日志记录目的。

  • auth_response: Any: 如果在此工具调用之前完成了身份验证流程,则包含身份验证响应/凭据。

  • 服务访问:与配置的服务(如工件和内存)交互的方法。

请注意,你不应将 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 调用之间操作的工具上下文中通常不太有用)。

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 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";

  // Get current preferences or initialize if none exist
  String preference = toolContext.state().getOrDefault(userPrefsKey, "").toString();
  if (preference.isEmpty()) {
    preference = value;
  }

  // Write the updated dictionary back to the state
  toolContext.state().put("user:preferences", preference);
  System.out.printf("Tool: Updated user preference %s to %s", userPrefsKey, preference);

  return Map.of("status", "success", "updated_preference", toolContext.state().get(userPrefsKey).toString());
  // 当 LLM 调用 updateUserThemePreference("dark") 时:
  // The toolContext.state 将被更新,更改将成为
  // 结果工具响应事件的 actions.stateDelta 的一部分。
}
import (
    "fmt"

    "google.golang.org/adk/tool"
)

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 {
    Status            string `json:"status"`
    UpdatedPreference string `json:"updated_preference"`
}

func updateUserPreference(ctx tool.Context, args updateUserPreferenceArgs) updateUserPreferenceResult {
    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 updateUserPreferenceResult{Status: "error"}
    }

    fmt.Printf("Tool: Updated user preference '%s' to '%s'\n", args.Preference, args.Value)
    return updateUserPreferenceResult{Status: "success", UpdatedPreference: args.Preference}
}

Controlling Agent Flow

tool_context.actions 属性(Java 中的 ToolContext.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 'check_and_transfer' tool.""",
    tools=[check_and_transfer]
)

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

package main

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

    "google.golang.org/adk/agent"
    "google.golang.org/adk/agent/llmagent"
    "google.golang.org/adk/model/gemini"
    "google.golang.org/adk/runner"
    "google.golang.org/adk/session"
    "google.golang.org/adk/tool"
    "google.golang.org/adk/tool/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 tool.Context, args checkAndTransferArgs) checkAndTransferResult {
    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..."}
    }
    return checkAndTransferResult{Status: fmt.Sprintf("Processed query: '%s'. No further action needed.", args.Query)}
}

func main() {
    ctx := context.Background()
    model, err := gemini.NewModel(ctx, "gemini-2.0-flash", &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)
        }
    }
}
Explanation
  • 我们定义了两个智能体:main_agentsupport_agentmain_agent 被设计为初始联系点。
  • main_agent 调用 check_and_transfer 工具时,它会检查用户的查询。
  • 如果查询包含"urgent"一词,工具会访问 tool_context,特别是 tool_context.actions,并将 transfer_to_agent 属性设置为 support_agent
  • 此操作向框架发出信号,将对话控制权转移给名为 support_agent 的智能体
  • main_agent 处理紧急查询时,check_and_transfer 工具会触发转移。后续响应理想情况下将来自 support_agent
  • 对于没有紧急情况的正常查询,该工具会简单地处理它而不触发转移。

此示例说明了工具如何通过其 ToolContext 中的 EventActions 动态影响对话流程,通过将控制权转移给另一个专业智能体。

身份验证

Supported in ADKPython 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。返回新版本号(从 0 开始)。

  • search_memory(query: str): (Python and Go only feature) Queries the user's long-term memory using the configured memory_service. This is useful for retrieving relevant information from past interactions or stored knowledge. The structure of the SearchMemoryResponse depends on the specific memory service implementation but typically contains relevant text snippets or conversation excerpts.

示例

# 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


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 = 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 = 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=...)
// 使用内存中的上下文分析文档。
// 你还可以使用回调上下文或 LoadArtifacts 工具列出、加载和保存工件。
public static @NonNull Maybe<ImmutableMap<String, Object>> processDocument(
    @Annotations.Schema(description = "要分析的文档名称。") String documentName,
    @Annotations.Schema(description = "分析的查询。") String analysisQuery,
    ToolContext toolContext) {

  // 1. List all available artifacts
  System.out.printf(
      "Listing all available artifacts %s:", toolContext.listArtifacts().blockingGet());

  // 2. Load an artifact to memory
  System.out.println("Tool: Attempting to load artifact: " + documentName);
  Part documentPart = toolContext.loadArtifact(documentName, Optional.empty()).blockingGet();
  if (documentPart == null) {
    System.out.println("Tool: Document '" + documentName + "' not found.");
    return Maybe.just(
        ImmutableMap.<String, Object>of(
            "status", "error", "message", "Document '" + documentName + "' not found."));
  }
  String documentText = documentPart.text().orElse("");
  System.out.println(
      "Tool: Loaded document '" + documentName + "' (" + documentText.length() + " chars).");

  // 3. Perform analysis (placeholder)
  String analysisResult =
      "Analysis of '"
          + documentName
          + "' regarding '"
          + analysisQuery
          + " [Placeholder Analysis Result]";
  System.out.println("Tool: Performed analysis.");

  // 4. Save the analysis result as a new artifact
  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();
// 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/tool"
    "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 tool.Context, args processDocumentArgs) processDocumentResult {
    fmt.Printf("Tool: Attempting to load artifact: %s\n", args.DocumentName)

    // List all artifacts
    listResponse, err := ctx.Artifacts().List(ctx)
    if err != nil {
        return processDocumentResult{Status: "error", Message: "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 processDocumentResult{Status: "error", Message: fmt.Sprintf("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 processDocumentResult{Status: "error", Message: "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,
    }
}

By leveraging the ToolContext, developers can create more sophisticated and context-aware custom tools that seamlessly integrate with ADK's architecture and enhance the overall capabilities of their agents.

定义有效工具函数

将方法或函数用作 ADK 工具时,你如何定义它会显著影响智能体正确使用它的能力。智能体的大语言模型(LLM)严重依赖函数的 名称参数(参数)类型提示文档字符串 / 源代码注释 来理解其目的并生成正确的调用。

以下是定义有效工具函数的关键指导原则:

  • 函数名称:

    • 使用描述性、基于动词 - 名词的名称,清楚地指示操作(例如,get_weathersearchDocumentsschedule_meeting)。
    • 避免通用名称,如 runprocesshandle_data,或过于模糊的名称,如 doStuff。即使有好的描述,像 do_stuff 这样的名称也可能在何时使用工具方面使模型混淆,例如与 cancelFlight 相比。
    • LLM 在工具选择期间使用函数名称作为主要标识符。
  • 参数(参数):

    • 你的函数可以有任何数量的参数。
    • 使用清晰和描述性的名称(例如,city 而不是 csearch_query 而不是 q)。
    • 在 Python 中提供类型提示 用于所有参数(例如,city: struser_id: intitems: list[str])。这对于 ADK 为 LLM 生成正确模式是必不可少的。
    • 确保所有参数类型都是 JSON 可序列化。所有 Java 原始类型以及标准 Python 类型,如 strintfloatboollistdict 及其组合通常都是安全的。避免将复杂自定义类实例作为直接参数,除非它们具有清晰的 JSON 表示。
    • 不要为参数设置默认值。例如,def my_func(param1: str = "default")。在函数调用生成期间,底层模型不可靠地支持或使用默认值。所有必要信息都应由 LLM 从上下文派生或在缺失时明确请求。
    • 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': 'Database connection failed'}
    • 强烈建议 包含 status 键(例如,'success''error''pending''ambiguous')以清楚地指示工具执行对模型的结果。
  • 文档字符串 / 源代码注释:

    • 这很关键。 文档字符串是 LLM 的描述性信息的主要来源。
    • 清楚地说明工具做什么 具体说明其目的和局限性。
    • 解释何时应使用工具。 提供上下文或示例场景以指导 LLM 的决策。
    • 清楚地描述每个参数 解释 LLM 需要为该参数提供什么信息。
    • 描述 预期 dict 返回值的结构和含义,特别是不同的 status 值和相关的数据键。
    • 不要描述注入的 ToolContext 参数。避免在文档字符串描述中提及可选的 tool_context: ToolContext 参数,因为这不是 LLM 需要知道的参数。ToolContext 是由 ADK 注入的, LLM 决定调用它之后

    良好定义的示例:

def lookup_order_status(order_id: str) -> dict:
  """使用其 ID 获取客户订单的当前状态。

  仅当用户明确询问特定订单的状态并提供订单 ID 时才使用此工具。不要将其用于
  一般查询。

  Args:
      order_id: 要查找的订单的唯一标识符。

  Returns:
      指示结果的字典。
      成功时,状态为 'success' 并包含 'order' 字典。
      失败时,状态为 'error' 并包含 'error_message'。
      成功示例:{'status': 'success', 'order': {'state': 'shipped', 'tracking_number': '1Z9...'}}
      错误示例:{'status': 'error', 'error_message': f"Order ID {order_id} not found."}
  """
  # ... 获取状态的函数实现 ...
  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"Order ID {order_id} not found."}
/**
 * 检索指定城市的当前天气报告。
 *
 * @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",
                "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;
}
import (
    "fmt"

    "google.golang.org/adk/tool"
)

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"`
    ErrorMessage string `json:"error_message,omitempty"`
}

func lookupOrderStatus(ctx tool.Context, args lookupOrderStatusArgs) lookupOrderStatusResult {
    // ... function implementation to fetch status ...
    if statusDetails, ok := fetchStatusFromBackend(args.OrderID); ok {
        return lookupOrderStatusResult{
            Status: "success",
            Order: order{
                State:          statusDetails.State,
                TrackingNumber: statusDetails.Tracking,
            },
        }
    }
    return lookupOrderStatusResult{Status: "error", ErrorMessage: fmt.Sprintf("Order ID %s not found.", args.OrderID)}
}
  • Simplicity and Focus:
    • Keep Tools Focused: Each tool should ideally perform one well-defined task.
    • Fewer Parameters are Better: Models generally handle tools with fewer, clearly defined parameters more reliably than those with many optional or complex ones.
    • Use Simple Data Types: Prefer basic types (str, int, bool, float, List[str], in Python, or int, byte, short, long, float, double, boolean and char in Java) over complex custom classes or deeply nested structures as parameters when possible.
    • Decompose Complex Tasks: Break down functions that perform multiple distinct logical steps into smaller, more focused tools. For instance, instead of a single update_user_profile(profile: ProfileObject) tool, consider separate tools like update_user_name(name: str), update_user_address(address: str), update_user_preferences(preferences: list[str]), etc. This makes it easier for the LLM to select and use the correct capability.

通过遵循这些指导原则,你为 LLM 提供了有效利用你的自定义函数工具所需的清晰度和结构,从而实现更强大和可靠的智能体行为。

工具集:分组和动态提供工具

Supported in ADKPython v0.5.0

除了单个工具外,ADK 还通过 BaseToolset 接口(在 google.adk.tools.base_toolset 中定义)引入了 工具集 的概念。工具集允许你管理并向智能体动态提供 BaseTool 实例集合。

这种方法的好处包括:

  • 组织相关工具: 将服务于共同目的的工具分组(例如,所有用于数学运算的工具,或所有与特定 API 交互的工具)。
  • 动态工具可用性: 使智能体能够根据当前上下文(例如,用户权限、会话状态或其他运行时条件)使用不同的可用工具。工具集的 get_tools 方法可以决定暴露哪些工具。
  • 集成外部工具提供者: 工具集可以作为来自外部系统(如 OpenAPI 规范或 MCP 服务器)的工具的适配器,将它们转换为 ADK 兼容的 BaseTool 对象。

BaseToolset 接口

任何在 ADK 中作为工具集的类都应实现 BaseToolset 抽象基类。此接口主要定义了两种方法:

  • async def get_tools(...) -> list[BaseTool]: 这是工具集的核心方法。当 ADK 智能体需要知道其可用工具时,它将在其 tools 列表中提供的每个 BaseToolset 实例上调用 get_tools()

    • 它接收一个可选的 readonly_contextReadonlyContext 的实例)。此上下文提供对信息的只读访问,如当前会话状态(readonly_context.state)、智能体名称和调用 ID。工具集可以使用此上下文动态决定返回哪些工具。
    • 必须返回 BaseTool 实例的 list(例如,FunctionToolRestApiTool)。
  • async def close(self) -> None: 当工具集不再需要时,例如当智能体服务器关闭或 Runner 正在关闭时,ADK 框架会调用此异步方法。实现此方法以执行任何必要的清理,例如关闭网络连接、释放文件句柄或清理工具集管理的其他资源。

将工具集与智能体一起使用

你可以将 BaseToolset 实现的实例直接包含在 LlmAgenttools 列表中,与单个 BaseTool 实例一起。

当智能体初始化或需要确定其可用功能时,ADK 框架将遍历 tools 列表:

  • 如果项目是 BaseTool 实例,则直接使用它。
  • 如果项目是 BaseToolset 实例,则调用其 get_tools() 方法(使用当前 ReadonlyContext),并将返回的 BaseTool 列表添加到智能体的可用工具中。

示例:简单数学工具集

让我们创建一个提供简单算术运算的工具集的基本示例。

# 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
        # Create FunctionTool instances once
        self._add_tool = FunctionTool(
            func=add_numbers,
            name=f"{self.prefix}add_numbers",  # Toolset can customize names
        )
        self._subtract_tool = FunctionTool(
            func=subtract_numbers, name=f"{self.prefix}subtract_numbers"
        )
        print(f"SimpleMathToolset initialized with prefix '{self.prefix}'")

    async def get_tools(
        self, readonly_context: Optional[ReadonlyContext] = None
    ) -> List[BaseTool]:
        print(f"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-2.0-flash",  # 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
)

在此示例中:

  • SimpleMathToolset 实现 BaseToolset,其 get_tools() 方法返回 add_numberssubtract_numbersFunctionTool 实例。它还使用前缀自定义它们的名称。
  • calculator_agent 配置了单个 greet_toolSimpleMathToolset 的实例。
  • 当运行 calculator_agent 时,ADK 将调用 math_toolset_instance.get_tools()。智能体的 LLM 将能够访问 greet_usercalculator_add_numberscalculator_subtract_numbers 以处理用户请求。
  • add_numbers 工具演示写入 tool_context.state,智能体的指令提到读取此状态。
  • 调用 close() 方法以确保工具集持有的任何资源都得到释放。

工具集为组织、管理和动态提供工具集合到你的 ADK 智能体提供了强大的方式,从而实现更模块化、可维护和可适应的智能体应用程序。