Custom Tools for ADK¶
ADK 自定义工具¶
In an ADK agent workflow, Tools are programming functions with structured input and output that can be called by an ADK Agent to perform actions. ADK Tools function similarly to how you use a Function Call with Gemini or other generative AI models. You can perform various actions and programming functions with an ADK Tool, such as:
在 ADK 智能体工作流中,工具是具有结构化输入和输出的编程函数,可由 ADK 智能体调用以执行操作。ADK 工具的功能与您使用 Gemini 或其他生成式 AI 模型的 函数调用类似。您可以使用 ADK 工具执行各种操作和编程函数,例如:
- Querying databases
- 查询数据库
- Making API requests: getting weather data, booking systems
- 发出 API 请求:获取天气数据,预订系统
- Searching the web
- 搜索网络
- Executing code snippets
- 执行代码片段
- Retrieving information from documents (RAG)
- 从文档中检索信息 (RAG)
- Interacting with other software or services
- 与其他软件或服务交互
Before building your own Tools for ADK, check out the ADK Tools list for pre-built tools you can use with ADK Agents. !!!提示"ADK 工具列表" 在为 ADK 构建自己的工具之前,请查看 ADK 工具列表,了解可与 ADK 智能体一起使用的预构建工具。
What is a Tool?¶
什么是工具?¶
In the context of ADK, a Tool represents a specific capability provided to an AI agent, enabling it to perform actions and interact with the world beyond its core text generation and reasoning abilities. What distinguishes capable agents from basic language models is often their effective use of tools.
在 ADK 的上下文中,工具代表提供给 AI 智能体的特定能力,使其能够执行操作并与超越其核心文本生成和推理能力的世界交互。区分有能力的智能体和基本语言模型的通常是它们对工具的有效使用。
Technically, a tool is typically a modular code component—like a Python, Java, or TypeScript function, a class method, or even another specialized agent—designed to execute a distinct, predefined task. These tasks often involve interacting with external systems or data.
从技术上讲,工具通常是模块化代码组件——如 Python、Java 或 TypeScript 函数、类方法,甚至是另一个专门的智能体——旨在执行独特的、预定义的任务。这些任务通常涉及与外部系统或数据交互。

Key Characteristics¶
关键特征¶
Action-Oriented: Tools perform specific actions for an agent, such as searching for information, calling an API, or performing calculations.
面向操作: 工具为智能体执行特定操作,例如搜索信息、调用 API 或执行计算。
Extends Agent capabilities: They empower agents to access real-time information, affect external systems, and overcome the knowledge limitations inherent in their training data.
扩展智能体能力: 它们使智能体能够访问实时信息、影响外部系统,并克服其训练数据中固有的知识限制。
Execute predefined logic: Crucially, tools execute specific, developer-defined logic. They do not possess their own independent reasoning capabilities like the agent's core Large Language Model (LLM). The LLM reasons about which tool to use, when, and with what inputs, but the tool itself just executes its designated function.
执行预定义的逻辑: 关键是,工具执行特定的、开发人员定义的逻辑。它们不具备像智能体核心大型语言模型 (LLM) 那样的独立推理能力。LLM 推理使用哪个工具、何时使用以及使用什么输入,但工具本身只执行其指定的功能。
How Agents Use Tools¶
智能体如何使用工具¶
Agents leverage tools dynamically through mechanisms often involving function calling. The process generally follows these steps:
智能体通过通常涉及函数调用的机制动态利用工具。该过程通常遵循以下步骤:
- Reasoning: The agent's LLM analyzes its system instruction, conversation history, and user request.
- 推理: 智能体的 LLM 分析其系统指令、对话历史和用户请求。
- Selection: Based on the analysis, the LLM decides on which tool, if any, to execute, based on the tools available to the agent and the docstrings that describes each tool.
- 选择: 基于分析,LLM 根据智能体可用的工具和描述每个工具的文档字符串,决定执行哪个工具 (如果有)。
- Invocation: The LLM generates the required arguments (inputs) for the selected tool and triggers its execution.
- 调用: LLM 为所选工具生成所需的参数 (输入) 并触发其执行。
- Observation: The agent receives the output (result) returned by the tool.
- 观察: 智能体接收工具返回的输出 (结果)。
- Finalization: The agent incorporates the tool's output into its ongoing reasoning process to formulate the next response, decide the subsequent step, or determine if the goal has been achieved.
- 完成: 智能体将工具的输出纳入其持续的推理过程,以制定下一个响应,决定后续步骤,或确定是否已实现目标。
Think of the tools as a specialized toolkit that the agent's intelligent core (the LLM) can access and utilize as needed to accomplish complex tasks.
可以将工具视为智能体的智能核心 (LLM) 可以根据需要访问和利用的专门工具包,以完成复杂的任务。
Tool Types in ADK¶
ADK 中的工具类型¶
ADK offers flexibility by supporting several types of tools:
ADK 通过支持多种类型的工具提供灵活性:
- Function Tools: Tools created by you, tailored to your specific application's needs.
- 函数工具: 您创建的工具,针对您的特定应用程序需求量身定制。
- Functions/Methods: Define standard synchronous functions or methods in your code (e.g., Python def).
- 函数/方法: 在您的代码中定义标准的同步函数或方法 (例如 Python def)。
- Agents-as-Tools: Use another, potentially specialized, agent as a tool for a parent agent.
- 智能体作为工具: 将另一个可能专门的智能体用作父智能体的工具。
- Long Running Function Tools: Support for tools that perform asynchronous operations or take significant time to complete.
- 长时间运行的函数工具: 支持执行异步操作或需要相当长时间才能完成的工具。
- Built-in Tools: Ready-to-use tools provided by the framework for common tasks.
- 内置工具: 框架为常见任务提供的现成可用工具。 Examples: Google Search, Code Execution, Retrieval-Augmented Generation (RAG). 示例:Google 搜索、代码执行、检索增强生成 (RAG)。
- Third-Party Tools: Integrate tools seamlessly from popular external libraries.
- 第三方工具: 从流行的外部库无缝集成工具。
Navigate to the respective documentation pages linked above for detailed information and examples for each tool type.
导航到上面链接的相应文档页面,以获取每种工具类型的详细信息和示例。
Referencing Tool in Agent's Instructions¶
在智能体指令中引用工具¶
Within an agent's instructions, you can directly reference a tool by using its function name. If the tool's function name and docstring are sufficiently descriptive, your instructions can primarily focus on when the Large Language Model (LLM) should utilize the tool. This promotes clarity and helps the model understand the intended use of each tool.
在智能体的指令中,您可以使用工具的 函数名称 直接引用它。如果工具的 函数名称 和 文档字符串 足够描述性,您的指令可以主要关注 LLM 应何时使用该工具。这有助于提高清晰度,并帮助模型理解每个工具的预期用途。
It is crucial to clearly instruct the agent on how to handle different return values that a tool might produce. For example, if a tool returns an error message, your instructions should specify whether the agent should retry the operation, give up on the task, or request additional information from the user.
至关重要的是要明确指示智能体如何处理工具可能产生的不同返回值。例如,如果工具返回错误消息,您的指令应指定智能体是否应该重试操作、放弃任务或向用户请求其他信息。
Furthermore, ADK supports the sequential use of tools, where the output of one tool can serve as the input for another. When implementing such workflows, it's important to describe the intended sequence of tool usage within the agent's instructions to guide the model through the necessary steps.
此外,ADK 支持工具的顺序使用,其中一个工具的输出可以作为另一个工具的输入。在实施此类工作流时,重要的是在智能体的指令中 描述工具使用的预期顺序,以引导模型完成必要的步骤。
Example¶
示例¶
The following example showcases how an agent can use tools by referencing their function names in its instructions. It also demonstrates how to guide the agent to handle different return values from tools, such as success or error messages, and how to orchestrate the sequential use of multiple tools to accomplish a task.
以下示例展示了智能体如何通过 在其指令中引用它们的函数名称来使用工具。它还演示了如何引导智能体 处理来自工具的不同返回值,例如成功或错误消息,以及如何编排 多个工具的顺序使用 以完成任务。
# 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/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"`
}
func getWeatherReport(ctx tool.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 tool.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-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)
}
}
}
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");
}
}
Tool Context¶
工具上下文¶
For more advanced scenarios, ADK allows you to access additional contextual information within your tool function by including the special parameter tool_context: ToolContext. By including this in the function signature, ADK will automatically provide an instance of the ToolContext class when your tool is called during agent execution.
对于更高级的场景,ADK 允许您通过包含特殊参数 tool_context: ToolContext 来访问工具函数中的其他上下文信息。通过将其包含在函数签名中,ADK 将在智能体执行期间调用您的工具时 自动 提供 ToolContext 类的实例。
The ToolContext provides access to several key pieces of information and control levers:
ToolContext 提供对几个关键信息片段和控制杠杆的访问:
state: State: Read and modify the current session's state. Changes made here are tracked and persisted.state: State: 读取和修改当前会话的状态。在此处进行的更改将被跟踪并持久化。actions: EventActions: Influence the agent's subsequent actions after the tool runs (e.g., skip summarization, transfer to another agent).actions: EventActions: 影响工具运行后智能体的后续操作 (例如,跳过摘要、转移到另一个智能体)。function_call_id: str: The unique identifier assigned by the framework to this specific invocation of the tool. Useful for tracking and correlating with authentication responses. This can also be helpful when multiple tools are called within a single model response.function_call_id: str: 框架分配给工具的此特定调用的唯一标识符。对于跟踪和与身份验证响应进行关联很有用。当在单个模型响应中调用多个工具时,这也很有帮助。function_call_event_id: str: This attribute provides the unique identifier of the event that triggered the current tool call. This can be useful for tracking and logging purposes.function_call_event_id: str: 此属性提供触发当前工具调用的 事件 的唯一标识符。这对于跟踪和日志记录目的很有用。auth_response: Any: Contains the authentication response/credentials if an authentication flow was completed before this tool call.auth_response: Any: 包含身份验证响应/凭据 (如果在此工具调用之前已完成身份验证流程)。- Access to Services: Methods to interact with configured services like Artifacts and Memory.
- 访问服务: 与配置的服务 (如 Artifacts 和 Memory) 交互的方法。
Note that you shouldn't include the tool_context parameter in the tool function docstring. Since ToolContext is automatically injected by the ADK framework after the LLM decides to call the tool function, it is not relevant for the LLM's decision-making and including it can confuse the LLM.
请注意,您不应在工具函数文档字符串中包含 tool_context 参数。由于 ToolContext 是在 LLM 决定调用工具函数之后由 ADK 框架自动注入的,因此它与 LLM 的决策无关,包含它可能会使 LLM 感到困惑。
State Management¶
状态管理¶
The tool_context.state attribute provides direct read and write access to the state associated with the current session. It behaves like a dictionary but ensures that any modifications are tracked as deltas and persisted by the session service. This enables tools to maintain and share information across different interactions and agent steps.
tool_context.state 属性提供对与当前会话关联的状态的直接读/写访问。它的行为类似于字典,但确保任何修改都被跟踪为增量并由会话服务持久化。这使工具能够在不同的交互和智能体步骤之间维护和共享信息。
- Reading State: Use standard dictionary access (
tool_context.state['my_key']) or the.get()method (tool_context.state.get('my_key', default_value)). - 读取状态: 使用标准字典访问 (
tool_context.state['my_key']) 或.get()方法 (tool_context.state.get('my_key', default_value))。 - Writing State: Assign values directly (
tool_context.state['new_key'] = 'new_value'). These changes are recorded in the state_delta of the resulting event. - 写入状态: 直接赋值 (
tool_context.state['new_key'] = 'new_value')。这些更改记录在结果事件的 state_delta 中。 - State Prefixes: Remember the standard state prefixes:
- 状态前缀: 记住标准状态前缀:
app:*: Shared across all users of the application.app:*: 在应用程序的所有用户之间共享。user:*: Specific to the current user across all their sessions.user:*: 特定于当前用户,在他们的所有会话中。- (No prefix): Specific to the current session.
- (无前缀): 特定于当前会话。
temp:*: Temporary, not persisted across invocations (useful for passing data within a single run call but generally less useful inside a tool context which operates between LLM calls).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 { ToolContext } from "@google/adk";
// Updates a user-specific preference.
export function updateUserThemePreference(
value: string,
toolContext: ToolContext
): Record<string, any> {
const userPrefsKey = "user:preferences";
// Get current preferences or initialize if none exist
const preferences = toolContext.state.get(userPrefsKey, {}) as Record<string, any>;
preferences["theme"] = value;
// Write the updated dictionary back to the state
toolContext.state.set(userPrefsKey, preferences);
console.log(
`Tool: Updated user preference ${userPrefsKey} to ${JSON.stringify(toolContext.state.get(userPrefsKey))}`
);
return {
status: "success",
updated_preference: toolContext.state.get(userPrefsKey),
};
// When the LLM calls updateUserThemePreference("dark"):
// The toolContext.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/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 {
UpdatedPreference string `json:"updated_preference"`
}
func updateUserPreference(ctx tool.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;
// Updates a user-specific preference.
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());
// When the LLM calls updateUserThemePreference("dark"):
// The toolContext.state will be updated, and the change will be part of the
// resulting tool response event's actions.stateDelta.
}
Controlling Agent Flow¶
控制智能体流程¶
The tool_context.actions attribute in Python and TypeScript, ToolContext.actions() in Java, and tool.Context.Actions() in Go, holds an EventActions object. Modifying attributes on this object allows your tool to influence what the agent or framework does after the tool finishes execution.
Python 和 TypeScript 中的 tool_context.actions 属性、Java 中的 ToolContext.actions() 以及 Go 中的 tool.Context.Actions() 保存一个 EventActions 对象。修改此对象上的属性允许您的工具影响智能体或框架在工具完成执行后执行的操作。
skip_summarization: bool: (Default: False) If set to True, instructs the ADK to bypass the LLM call that typically summarizes the tool's output. This is useful if your tool's return value is already a user-ready message.skip_summarization: bool: (默认值:False) 如果设置为 True,则指示 ADK 跳过通常汇总工具输出的 LLM 调用。如果工具的返回值已经是用户就绪的消息,这很有用。transfer_to_agent: str: Set this to the name of another agent. The framework will halt the current agent's execution and transfer control of the conversation to the specified agent. This allows tools to dynamically hand off tasks to more specialized agents.transfer_to_agent: str: 将其设置为另一个智能体的名称。框架将停止当前智能体的执行,并 将对话控制权转移到指定的智能体。这允许工具将任务动态移交给更专门的智能体。escalate: bool: (Default: False) Setting this to True signals that the current agent cannot handle the request and should pass control up to its parent agent (if in a hierarchy). In a LoopAgent, setting escalate=True in a sub-agent's tool will terminate the loop.escalate: bool: (默认值:False) 将其设置为 True 表示当前智能体无法处理请求,应将控制权传递给其父智能体 (如果在层次结构中)。在 LoopAgent 中,在子智能体的工具中设置 escalate=True 将终止循环。
Example¶
示例¶
# 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")
/**
* 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, ToolContext, InMemoryRunner, isFinalResponse, stringifyContent } from "@google/adk";
import { z } from "zod";
import { Content, createUserContent } from "@google/genai";
function checkAndTransfer(
params: { query: string },
toolContext?: ToolContext
): Record<string, any> {
if (!toolContext) {
// This should not happen in a normal ADK flow where the tool is called by an agent.
throw new Error("ToolContext is required to transfer agents.");
}
if (params.query.toLowerCase().includes("urgent")) {
console.log("Tool: Urgent query detected, transferring to support_agent.");
toolContext.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/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, 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-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)
}
}
}
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");
}
}
Explanation¶
说明¶
- We define two agents:
main_agentandsupport_agent. Themain_agentis designed to be the initial point of contact. - 我们定义了两个智能体:
main_agent和support_agent。main_agent被设计为初始联系点。 - The
check_and_transfertool, when called bymain_agent, examines the user's query. - 当
main_agent调用check_and_transfer工具时,它会检查用户的查询。 - If the query contains the word "urgent", the tool accesses the
tool_context, specificallytool_context.actions, and sets the transfer_to_agent attribute tosupport_agent. - 如果查询包含单词 "urgent",工具将访问
tool_context,特别是tool_context.actions,并将 transfer_to_agent 属性设置为support_agent。 - This action signals to the framework to transfer the control of the conversation to the agent named
support_agent. - 此操作向框架发出信号,以 将对话控制权转移到名为
support_agent的智能体。 - When the
main_agentprocesses the urgent query, thecheck_and_transfertool triggers the transfer. The subsequent response would ideally come from thesupport_agent. - 当
main_agent处理紧急查询时,check_and_transfer工具会触发转移。随后的响应理想情况下应来自support_agent。 - For a normal query without urgency, the tool simply processes it without triggering a transfer.
- 对于没有紧急情况的正常查询,工具只是处理它,而不触发转移。
This example illustrates how a tool, through EventActions in its ToolContext, can dynamically influence the flow of the conversation by transferring control to another specialized agent.
此示例说明了工具如何通过其 ToolContext 中的 EventActions,通过将控制权转移到另一个专门的智能体来动态影响对话流程。
Authentication¶
身份验证¶
ToolContext provides mechanisms for tools interacting with authenticated APIs. If your tool needs to handle authentication, you might use the following:
ToolContext 提供了与经过身份验证的 API 交互的工具的机制。如果您的工具需要处理身份验证,您可能会使用以下内容:
auth_response(in Python): Contains credentials (e.g., a token) if authentication was already handled by the framework before your tool was called (common with RestApiTool and OpenAPI security schemes). In TypeScript, this is retrieved via the getAuthResponse() method.auth_response(Python 中): 如果在调用工具之前框架已经处理了身份验证,则包含凭据 (例如令牌) (对于 RestApiTool 和 OpenAPI 安全方案很常见)。在 TypeScript 中,这是通过 getAuthResponse() 方法检索的。request_credential(auth_config: dict)(in Python) orrequestCredential(authConfig: AuthConfig)(in TypeScript): Call this method if your tool determines authentication is needed but credentials aren't available. This signals the framework to start an authentication flow based on the provided auth_config.request_credential(auth_config: dict)(Python 中) 或requestCredential(authConfig: AuthConfig)(TypeScript 中): 如果您的工具确定需要身份验证但凭据不可用,则调用此方法。这向框架发出信号,以根据提供的 auth_config 启动身份验证流程。get_auth_response()(in Python) orgetAuthResponse(authConfig: AuthConfig)(in TypeScript): Call this in a subsequent invocation (after request_credential was successfully handled) to retrieve the credentials the user provided.get_auth_response()(Python 中) 或getAuthResponse(authConfig: AuthConfig)(TypeScript 中): 在后续调用中 (在成功处理 request_credential 之后) 调用此方法以检索用户提供的凭据。
For detailed explanations of authentication flows, configuration, and examples, please refer to the dedicated Tool Authentication documentation page.
有关身份验证流程、配置和示例的详细说明,请参阅专用工具身份验证文档页面。
Context-Aware Data Access Methods¶
上下文感知的数据访问方法¶
These methods provide convenient ways for your tool to interact with persistent data associated with the session or user, managed by configured services.
这些方法为您的工具提供了与会话或用户关联的持久数据交互的便捷方法,这些数据由配置的服务管理。
list_artifacts()(in Python) orlistArtifacts()(in Java and TypeScript): Returns a list of filenames (or keys) for all artifacts currently stored for the session via the artifact_service. Artifacts are typically files (images, documents, etc.) uploaded by the user or generated by tools/agents.list_artifacts()(Python 中) 或listArtifacts()(Java 和 TypeScript 中): 返回当前通过 artifact_service 为会话存储的所有工件的文件名 (或键) 列表。工件通常是用户上传或由工具/智能体生成的文件 (图像、文档等)。load_artifact(filename: str): Retrieves a specific artifact by its filename from the artifact_service. You can optionally specify a version; if omitted, the latest version is returned. Returns agoogle.genai.types.Partobject containing the artifact data and mime type, or None if not found.load_artifact(filename: str): 从 artifact_service 按其文件名检索特定工件。您可以可选地指定版本;如果省略,则返回最新版本。返回包含工件数据和 mime 类型的google.genai.types.Part对象,如果未找到则返回 None。save_artifact(filename: str, artifact: types.Part): Saves a new version of an artifact to the artifact_service. Returns the new version number (starting from 0).save_artifact(filename: str, artifact: types.Part): 将工件的新版本保存到 artifact_service。返回新版本号 (从 0 开始)。search_memory(query: str): (Support in ADK Python, Go and TypeScript)search_memory(query: str): (ADK Python、Go 和 TypeScript 中支持) Queries the user's long-term memory using the configuredmemory_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. 使用配置的memory_service查询用户的长期记忆。这对于从过去的交互或存储的知识中检索相关信息很有用。SearchMemoryResponse 的结构取决于特定的记忆服务实现,但通常包含相关的文本片段或对话摘录。
Example¶
示例¶
# 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=...)
import { Part } from "@google/genai";
import { ToolContext } from "@google/adk";
// Analyzes a document using context from memory.
export async function processDocument(
params: { documentName: string; analysisQuery: string },
toolContext?: ToolContext
): Promise<Record<string, any>> {
if (!toolContext) {
throw new Error("ToolContext is required for this tool.");
}
// 1. List all available artifacts
const artifacts = await toolContext.listArtifacts();
console.log(`Listing all available artifacts: ${artifacts}`);
// 2. Load an artifact
console.log(`Tool: Attempting to load artifact: ${params.documentName}`);
const documentPart = await toolContext.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 toolContext.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 toolContext.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/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, 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
}
// Analyzes a document using context from memory.
// You can also list, load and save artifacts using Callback Context or LoadArtifacts tool.
public static @NonNull Maybe<ImmutableMap<String, Object>> processDocument(
@Annotations.Schema(description = "The name of the document to analyze.") String documentName,
@Annotations.Schema(description = "The query for the analysis.") 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");
// In the Agent, include this function tool.
// LlmAgent agent = LlmAgent().builder().tools(processDocumentTool).build();
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.
通过利用 ToolContext,开发人员可以创建更复杂和上下文感知的自定义工具,这些工具与 ADK 的架构无缝集成并增强其智能体的整体能力。
Defining Effective Tool Functions¶
定义有效的工具函数¶
When using a method or function as an ADK Tool, how you define it significantly impacts the agent's ability to use it correctly. The agent's Large Language Model (LLM) relies heavily on the function's name, parameters (arguments), type hints, and docstring / source code comments to understand its purpose and generate the correct call.
当使用方法或函数作为 ADK 工具时,您如何定义它会对智能体正确使用它的能力产生重大影响。智能体的大型语言模型 (LLM) 非常依赖函数的 名称、参数 (参数)、类型提示 和 文档字符串 / 源代码注释 来理解其目的并生成正确的调用。
Here are key guidelines for defining effective tool functions:
以下是定义有效工具函数的关键指南:
- Function Name:
-
函数名称:
- Use descriptive, verb-noun based names that clearly indicate the action (e.g.,
get_weather,searchDocuments,schedule_meeting). - 使用描述性的、基于动宾结构的名称,清楚指示操作 (例如
get_weather、searchDocuments、schedule_meeting)。 - Avoid generic names like
run,process,handle_data, or overly ambiguous names likedoStuff. Even with a good description, a name likedo_stuffmight confuse the model about when to use the tool versus, for example,cancelFlight. - 避免像
run、process、handle_data这样的通用名称,或像doStuff这样过于模糊的名称。即使有很好的描述,像do_stuff这样的名称也可能会使模型对何时使用该工具感到困惑,例如与cancelFlight相比。 - The LLM uses the function name as a primary identifier during tool selection.
- LLM 在工具选择期间使用函数名称作为主要标识符。
- Use descriptive, verb-noun based names that clearly indicate the action (e.g.,
-
Parameters (Arguments):
-
参数 (参数):
- Your function can have any number of parameters.
- 您的函数可以拥有任意数量的参数。
- Use clear and descriptive names (e.g.,
cityinstead ofc,search_queryinstead ofq). - 使用清晰和描述性的名称 (例如,使用
city而不是c,使用search_query而不是q)。 - Provide type hints in Python for all parameters (e.g.,
city: str,user_id: int,items: list[str]). This is essential for ADK to generate the correct schema for the LLM. - 在 Python 中为所有参数提供类型提示 (例如
city: str、user_id: int、items: list[str])。这对于 ADK 为 LLM 生成正确的模式至关重要。 - Ensure all parameter types are JSON serializable. All java primitives as well as standard Python types like
str,int,float,bool,list,dict, and their combinations are generally safe. Avoid complex custom class instances as direct parameters unless they have a clear JSON representation. - 确保所有参数类型都是 JSON 可序列化的。所有 Java 基本类型以及标准 Python 类型 (如
str、int、float、bool、list、dict及其组合) 通常都是安全的。避免将复杂的自定义类实例作为直接参数,除非它们具有清晰的 JSON 表示形式。 - Do not set default values for parameters. E.g.,
def my_func(param1: str = "default"). Default values are not reliably supported or used by the underlying models during function call generation. All necessary information should be derived by the LLM from the context or explicitly requested if missing. - 不要为参数设置默认值。例如
def my_func(param1: str = "default")。底层模型在函数调用生成期间不能可靠地支持或使用默认值。所有必要的信息都应该由 LLM 从上下文推导得出,或者在缺失时明确请求。 self/clsHandled Automatically: Implicit parameters likeself(for instance methods) orcls(for class methods) are automatically handled by ADK and excluded from the schema shown to the LLM. You only need to define type hints and descriptions for the logical parameters your tool requires the LLM to provide.self/cls自动处理: 隐式参数 (如实例方法的self或类方法的cls) 由 ADK 自动处理,并从显示给 LLM 的模式中排除。您只需要为工具需要 LLM 提供的逻辑参数定义类型提示和描述。
-
Return Type:
-
返回类型:
- The function's return value must be a dictionary (
dict) in Python, a Map in Java, or a plain object in TypeScript. - 函数的返回值在 Python 中 必须是字典 (
dict),在 Java 中是 Map,或在 TypeScript 中是普通 object。 - If your function returns a non-dictionary type (e.g., a string, number, list), ADK framework will automatically wrap it into a dictionary/Map like
{'result': your_original_return_value}before passing result back to the model. - 如果您的函数返回非字典类型 (例如字符串、数字、列表),ADK 框架将在将结果传递回模型之前自动将其包装到字典/Map 中,如
{'result': your_original_return_value}。 - Design the dictionary/Map keys and values to be descriptive and easily understood by the LLM. Remember, the model reads this output to decide its next step.
- 设计字典/Map 的键和值,使其 具有描述性并且容易被 LLM 理解。记住,模型读取此输出以决定其下一步。
- Include meaningful keys. For example, instead of returning just an error code like
500, return{'status': 'error', 'error_message': 'Database connection failed'}. - 包含有意义的键。例如,不要只返回像
500这样的错误代码,而是返回{'status': 'error', 'error_message': 'Database connection failed'}。 - It's a highly recommended practice to include a
statuskey (e.g.,'success','error','pending','ambiguous') to clearly indicate the outcome of the tool execution for the model. - 包含
status键 (例如'success'、'error'、'pending'、'ambiguous') 以清楚指示模型执行工具的结果是 高度推荐的做法。
- The function's return value must be a dictionary (
-
Docstring / Source Code Comments:
-
文档字符串 / 源代码注释:
- This is critical. The docstring is the primary source of descriptive information for the LLM.
- 这很关键。 文档字符串是 LLM 描述信息的主要来源。
- Clearly state what the tool does. Be specific about its purpose and limitations.
- 清楚地说明工具 做什么。 具体说明其目的和限制。
- Explain when the tool should be used. Provide context or example scenarios to guide the LLM's decision-making.
- 解释 何时 应该使用该工具。 提供上下文或示例场景以指导 LLM 的决策。
- Describe each parameter clearly. Explain what information the LLM needs to provide for that argument.
- 清楚地描述 每个参数。 解释 LLM 需要为该参数提供什么信息。
- Describe the structure and meaning of the expected
dictreturn value, especially the differentstatusvalues and associated data keys. - 描述预期
dict返回值的 结构和含义,特别是不同的status值和相关数据键。 - Do not describe the injected ToolContext parameter. Avoid mentioning the optional
tool_context: ToolContextparameter within the docstring description since it is not a parameter the LLM needs to know about. ToolContext is injected by ADK, after the LLM decides to call it. - 不要描述注入的 ToolContext 参数。 避免在文档字符串描述中提及可选的
tool_context: ToolContext参数,因为它不是 LLM 需要知道的参数。ToolContext 由 ADK 在 LLM 决定调用它 之后 注入。
Example of a good definition: 良好定义的示例:
def lookup_order_status(order_id: str) -> dict:
"""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.
Args:
order_id: The unique identifier of the order to look up.
Returns:
A dictionary indicating the outcome.
On success, status is 'success' and includes an 'order' dictionary.
On failure, status is 'error' and includes an 'error_message'.
Example success: {'status': 'success', 'order': {'state': 'shipped', 'tracking_number': '1Z9...'}}
Example error: {'status': 'error', 'error_message': 'Order ID not found.'}
"""
# ... function implementation to fetch status ...
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."}
/**
* 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 order ID. Do not use it for
* general inquiries.
*
* @param params The parameters for the function.
* @param params.order_id The unique identifier of the order to look up.
* @returns A dictionary indicating the outcome.
* On success, status is 'success' and includes an 'order' dictionary.
* On failure, status is 'error' and includes an 'error_message'.
* Example success: {'status': 'success', 'order': {'state': 'shipped', 'tracking_number': '1Z9...'}}
* Example error: {'status': 'error', 'error_message': 'Order ID not found.'}
*/
async function lookupOrderStatus(params: { order_id: string }): Promise<Record<string, any>> {
// ... function implementation to fetch status from a backend ...
const status_details = await fetchStatusFromBackend(params.order_id);
if (status_details) {
return {
"status": "success",
"order": {
"state": status_details.state,
"tracking_number": status_details.tracking,
},
};
} else {
return { "status": "error", "error_message": `Order ID ${params.order_id} not found.` };
}
}
// Placeholder for a backend call
async function fetchStatusFromBackend(order_id: string): Promise<{state: string, tracking: string} | null> {
if (order_id === "12345") {
return { state: "shipped", tracking: "1Z9..." };
}
return null;
}
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"`
}
func lookupOrderStatus(ctx tool.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
}
/**
* 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(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;
}
- 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;int,byte,short,long,float,double,booleanandcharin Java; orstring,number,boolean, and arrays likestring[]in TypeScript) over complex custom classes or deeply nested structures as parameters when possible. - 使用简单的数据类型: 尽可能优先使用基本类型 (Python 中的
str、int、bool、float、List[str],Java 中的int、byte、short、long、float、double、boolean和char,或 TypeScript 中的string、number、boolean和类似string[]的数组),而不是复杂的自定义类或深度嵌套结构作为参数。 - 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 likeupdate_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. - 分解复杂任务: 将执行多个不同逻辑步骤的函数分解为更小、更专注的工具。例如,不要使用单个
update_user_profile(profile: ProfileObject)工具,而是考虑单独的工具,如update_user_name(name: str)、update_user_address(address: str)、update_user_preferences(preferences: list[str])等。这使 LLM 更容易选择和使用正确的功能。
By adhering to these guidelines, you provide the LLM with clarity and structure it needs to effectively utilize your custom function tools, leading to more capable and reliable agent behavior.
通过遵循这些指南,您为 LLM 提供了有效利用自定义函数工具所需的清晰度和结构,从而实现更有能力和更可靠的智能体行为。
Toolsets: Grouping and Dynamically Providing Tools¶
工具集:分组和动态提供工具¶
Beyond individual tools, ADK introduces the concept of a Toolset via the BaseToolset interface (defined in google.adk.tools.base_toolset). A toolset allows you to manage and provide a collection of BaseTool instances, often dynamically, to an agent.
除了单个工具之外,ADK 通过 BaseToolset 接口 (在 google.adk.tools.base_toolset 中定义) 引入了 工具集 的概念。工具集允许您管理和向智能体提供一组 BaseTool 实例,通常是动态的。
This approach is beneficial for:
这种方法有益于:
- Organizing Related Tools: Grouping tools that serve a common purpose (e.g., all tools for mathematical operations, or all tools interacting with a specific API).
- 组织相关工具: 将服务于共同目的的工具分组 (例如,所有用于数学运算的工具,或所有与特定 API 交互的工具)。
- Dynamic Tool Availability: Enabling an agent to have different tools available based on the current context (e.g., user permissions, session state, or other runtime conditions). The
get_toolsmethod of a toolset can decide which tools to expose. - 动态工具可用性: 使智能体能够根据当前上下文 (例如,用户权限、会话状态或其他运行时条件) 拥有不同的可用工具。工具集的
get_tools方法可以决定公开哪些工具。 - Integrating External Tool Providers: Toolsets can act as adapters for tools coming from external systems, like an OpenAPI specification or an MCP server, converting them into ADK-compatible
BaseToolobjects. - 集成外部工具提供程序: 工具集可以充当来自外部系统的工具的适配器,例如 OpenAPI 规范或 MCP 服务器,将它们转换为 ADK 兼容的
BaseTool对象。
The BaseToolset Interface¶
BaseToolset 接口¶
Any class acting as a toolset in ADK should implement BaseToolset abstract base class. This interface primarily defines two methods:
在 ADK 中充当工具集的任何类都应该实现 BaseToolset 抽象基类。此接口主要定义两个方法:
-
async def get_tools(...) -> list[BaseTool]:This is the core method of a toolset. When an ADK agent needs to know its available tools, it will callget_tools()on eachBaseToolsetinstance provided in itstoolslist.- 这是工具集的核心方法。当 ADK 智能体需要知道其可用工具时,它将在其
tools列表中提供的每个BaseToolset实例上调用get_tools()。 - It receives an optional
readonly_context(an instance ofReadonlyContext). This context provides read-only access to information like the current session state (readonly_context.state), agent name, and invocation ID. The toolset can use this context to dynamically decide which tools to return. - 它接收一个可选的
readonly_context(ReadonlyContext的实例)。此上下文提供对当前会话状态 (readonly_context.state)、智能体名称和调用 ID 等信息的只读访问。工具集可以使用此上下文动态决定返回哪些工具。 - It must return a
listofBaseToolinstances (e.g.,FunctionTool,RestApiTool). - 它 必须 返回
BaseTool实例的list(例如FunctionTool、RestApiTool)。
- 这是工具集的核心方法。当 ADK 智能体需要知道其可用工具时,它将在其
-
async def close(self) -> None:This asynchronous method is called by the ADK framework when the toolset is no longer needed, for example, when an agent server is shutting down or theRunneris being closed. Implement this method to perform any necessary cleanup, such as closing network connections, releasing file handles, or cleaning up other resources managed by the toolset.- 此异步方法在不再需要工具集时由 ADK 框架调用,例如,当智能体服务器关闭或
Runner正在关闭时。实现此方法以执行任何必要的清理,例如关闭网络连接、释放文件句柄或清理由工具集管理的其他资源。
- 此异步方法在不再需要工具集时由 ADK 框架调用,例如,当智能体服务器关闭或
Using Toolsets with Agents¶
在智能体中使用工具集¶
You can include instances of your BaseToolset implementations directly in an LlmAgent's tools list, alongside individual BaseTool instances.
您可以直接在 LlmAgent 的 tools 列表中包含 BaseToolset 实现的实例,与单个 BaseTool 实例一起。
When the agent initializes or needs to determine its available capabilities, the ADK framework will iterate through the tools list:
当智能体初始化或需要确定其可用功能时,ADK 框架将遍历 tools 列表:
- If an item is a
BaseToolinstance, it's used directly. - 如果项目是
BaseTool实例,则直接使用。 - If an item is a
BaseToolsetinstance, itsget_tools()method is called (with the currentReadonlyContext), and the returned list ofBaseTools is added to the agent's available tools. - 如果项目是
BaseToolset实例,则调用其get_tools()方法 (使用当前的ReadonlyContext),并且返回的BaseTool列表被添加到智能体的可用工具中。
Example: A Simple Math Toolset¶
示例:简单的数学工具集¶
Let's create a basic example of a toolset that provides simple arithmetic operations.
让我们创建一个提供简单算术运算的工具集的基本示例。
# 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
)
/**
* 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, ToolContext, 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 }, toolContext?: ToolContext): Record<string, any> {
if (!toolContext) {
throw new Error("ToolContext is required for this tool.");
}
const result = params.a + params.b;
toolContext.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();
In this example:
在此示例中:
SimpleMathToolsetimplementsBaseToolsetand itsget_tools()method returnsFunctionToolinstances foradd_numbersandsubtract_numbers. It also customizes their names using a prefix.SimpleMathToolset实现BaseToolset,其get_tools()方法返回add_numbers和subtract_numbers的FunctionTool实例。它还使用前缀自定义它们的名称。- The
calculator_agentis configured with both an individualgreet_tooland an instance ofSimpleMathToolset. calculator_agent配置有单个greet_tool和SimpleMathToolset的实例。- When
calculator_agentis run, ADK will callmath_toolset_instance.get_tools(). The agent's LLM will then have access togreet_user,calculator_add_numbers, andcalculator_subtract_numbersto handle user requests. - 当运行
calculator_agent时,ADK 将调用math_toolset_instance.get_tools()。智能体的 LLM 然后将能够访问greet_user、calculator_add_numbers和calculator_subtract_numbers来处理用户请求。 - The
add_numberstool demonstrates writing totool_context.state, and the agent's instruction mentions reading this state. add_numbers工具演示写入tool_context.state,智能体的指令提及读取此状态。- The
close()method is called to ensure any resources held by the toolset are released. - 调用
close()方法以确保释放工具集持有的任何资源。
Toolsets offer a powerful way to organize, manage, and dynamically provide collections of tools to your ADK agents, leading to more modular, maintainable, and adaptable agentic applications.
工具集提供了一种强大的方法来组织、管理和动态向您的 ADK 智能体提供工具集合,从而实现更模块化、可维护和可适应的智能体应用程序。