[Go to site: main page, start]

Skip to content

函数工具

Supported in ADKPython v0.1.0Typescript v0.2.0Go v0.1.0Java v0.1.0Kotlin v0.1.0

当预置的 ADK 工具无法满足你的需求时,你可以创建自定义函数工具。通过构建函数工具,你可以创建定制化的功能,例如连接到私有数据库或实现独特的算法。例如,一个名为 myfinancetool 的函数工具可以是计算特定财务指标的函数。ADK 还支持长时间运行的函数,因此如果该计算需要较长时间,智能体可以继续处理其他任务。

ADK 提供了多种创建函数工具的方式,每种方式适用于不同的复杂度和控制级别:

函数工具

将 Python 函数转换为工具是一种将自定义逻辑集成到智能体中的简单方式。当你将函数分配给智能体的 tools 列表时,框架会自动将其包装为 FunctionTool

工作原理

ADK 框架会自动检查你的 Python 函数签名——包括函数名称、文档字符串、参数、类型提示和默认值——以生成一个 schema。这个 schema 是 LLM 用来理解工具用途、何时使用以及需要哪些参数的依据。

定义函数签名

定义良好的函数签名对于 LLM 正确使用你的工具至关重要。

必需参数

如果一个参数有类型提示但没有默认值,则该参数被视为必需参数。当 LLM 调用该工具时,必须为此参数提供一个值。参数的描述来自函数的文档字符串。

示例:必需参数
def get_weather(city: str, unit: str):
    """
    获取指定城市的天气信息。

    Args:
        city (str): 城市名称(如:北京市)。
        unit (str): 温度单位,'Celsius' 或 'Fahrenheit'。
    """
    # ... 函数逻辑 ...
    return {"status": "success", "report": f"{city} 的天气是晴天。"}

在此示例中,cityunit 都是必需的。如果 LLM 尝试在缺少其中一个参数的情况下调用 get_weather,ADK 将向 LLM 返回错误,提示其修正调用。

在 Go 中,你使用结构体标签来控制 JSON schema。两个主要的标签是 jsonjsonschema

如果结构体字段的 json 标签中没有 omitemptyomitzero 选项,则该参数被视为必需

jsonschema 标签用于提供参数的描述,这对于 LLM 理解参数的用途至关重要。

示例:必需参数
// GetWeatherParams 定义了 getWeather 工具参数。
type GetWeatherParams struct {
    // 此字段是必需的(无 "omitempty")。
    // jsonschema 标签提供描述。
    Location string `json:"location" jsonschema:"城市和州,例如:San Francisco, CA"`

    // 此字段也是必需的。
    Unit     string `json:"unit" jsonschema:"温度单位,'celsius' 或 'fahrenheit'"`
}

在此示例中,locationunit 都是必需的。

在 Java 中,基本类型(如 intdoubleboolean)本身就是必需的,因为它们不能为 null。对于对象类型(如 StringInteger),除非显式标记为可选,否则通常被视为必需。

@Schema 注解用于提供参数的描述,也可以显式定义参数属性。这对于 LLM 理解参数的用途至关重要。

示例:必需参数
public static Map<String, Object> getWeather(
    @Schema(description = "城市名称,例如:上海", name = "location") String location,
    @Schema(description = "温度单位,'Celsius' 或 'Fahrenheit'", name = "unit") String unit) {
    // ... 函数逻辑 ...
    return Map.of("status", "success", "report", "天气晴朗");
}

在此示例中,locationunit 都是必需的。

在 Kotlin 中,如果参数是非空类型且没有默认值,则默认被视为必需。LLM 必须为这些参数提供一个值。

@Param 注解用于提供参数的描述。这对于 LLM 理解参数的用途至关重要。

示例:必需参数
class WeatherService {
    /**
     * Retrieves the weather for a city in the specified unit.
     */
    @Tool
    fun getWeather(
        @Param("The city and state, e.g., San Francisco, CA") location: String,
        @Param("The temperature unit, either 'Celsius' or 'Fahrenheit'") unit: String,
    ): String {
        // ... function logic ...
        return "Weather for $location is sunny in $unit."
    }
}

在此示例中,locationunit 都是必需的。

可选参数

如果你提供了默认值,则该参数被视为可选。这是定义可选参数的标准 Python 方式。你也可以使用 typing.Optional[SomeType]| None 语法(Python 3.10+)将参数标记为可选。

仅对真正可选的值使用默认值。不要为模型应从用户请求中推断或要求用户提供的信息添加默认值。

示例:可选参数
def search_flights(destination: str, departure_date: str, flexible_days: int = 0):
    """
    搜索航班信息。

    Args:
        destination (str): 目的地。
        departure_date (str): 出发日期。
        flexible_days (int, optional): 搜索的灵活天数。默认为 0。
    """
    # ... 函数逻辑 ...
    if flexible_days > 0:
        return {"status": "success", "report": f"找到到 {destination} 的灵活航班。"}
    return {"status": "success", "report": f"找到 {departure_date}{destination} 的航班。"}

在此示例中,flexible_days 是可选的。LLM 可以选择提供它,但这不是必需的。

如果结构体字段的 json 标签中有 omitemptyomitzero 选项,则该参数被视为可选

示例:可选参数
// GetWeatherParams 定义了 getWeather 工具参数。
type GetWeatherParams struct {
    // Location 是必需的。
    Location string `json:"location" jsonschema:"城市和州,例如:San Francisco, CA"`

    // Unit 是可选的。
    Unit string `json:"unit,omitempty" jsonschema:"温度单位,'celsius' 或 'fahrenheit'"`

    // Days 是可选的。
    Days int `json:"days,omitzero" jsonschema:"要返回的预报天数(默认为 1)"`
}

在此示例中,unitdays 是可选的。LLM 可以选择提供它们,但它们不是必需的。

在 Java 中,可以通过使用允许 null 值的对象类型(如 Integer 而非 int),或使用 java.util.Optional 显式定义为可选,来使参数被视为可选

示例:可选参数
import java.util.Map;
import java.util.Optional;

public static Map<String, Object> searchFlights(
    @Schema(description = "目的地城市。", name = "destination")
    String destination,

    @Schema(description = "期望的出发日期。", name = "departureDate")
    String departureDate,

    @Schema(description = "搜索的灵活天数。默认为 0。", name = "flexibleDays")
    Optional<Integer> flexibleDays) {

    // ... 函数逻辑 ...
    int days = flexibleDays.orElse(0);
    if (days > 0) {
        return Map.of("status", "success", "report", "找到到 " + destination + " 的灵活航班。");
    }
    return Map.of("status", "success", "report", "找到 " + departureDate + " 到 " + destination + " 的航班。");
}

在此示例中,flexibleDays 是可选的。LLM 可以选择提供它,但这不是必需的。

在 Kotlin 中,如果参数是可空类型或具有默认值,则被视为可选

示例:可选参数
class FlightService {
    /**
     * Searches for flights.
     */
    @Tool
    fun searchFlights(
        @Param("The destination city.") destination: String,
        @Param("The desired departure date.") departureDate: String,
        @Param("Number of flexible days for the search. Defaults to 0.") flexibleDays: Int? = 0,
    ): String {
        // ... function logic ...
        val days = flexibleDays ?: 0
        if (days > 0) {
            return "Found flexible flights to $destination."
        }
        return "Found flights to $destination on $departureDate."
    }
}

在此示例中,flexibleDays 是可选的。LLM 可以选择提供它,但这不是必需的。

使用 typing.Optional 的可选参数

你也可以使用 typing.Optional[SomeType]| None 语法(Python 3.10+)将参数标记为可选。这表示该参数可以为 None。当与默认值 None 结合使用时,其行为与标准可选参数相同。

示例:typing.Optional
from typing import Optional

def create_user_profile(username: str, bio: Optional[str] = None):
    """
    创建新的用户配置文件。

    Args:
        username (str): 用户的唯一用户名。
        bio (str, optional): 用户的简短传记。默认为 None。
    """
    # ... 函数逻辑 ...
    if bio:
        return {"status": "success", "message": f"为 {username} 创建了带传记的配置文件。"}
    return {"status": "success", "message": f"为 {username} 创建了配置文件。"}
可变参数(*args**kwargs

虽然你可以在函数签名中包含 *args(可变位置参数)和 **kwargs(可变关键字参数)用于其他目的,但 ADK 框架在为 LLM 生成工具 schema 时会忽略它们。LLM 不会知道它们的存在,也无法向它们传递参数。最好依赖显式定义的参数来获取你期望从 LLM 接收的所有数据。

上下文注入

上下文注入允许你的自定义函数访问智能体的环境,例如会话状态或可用操作。要启用此功能,请在函数中添加一个类型为 ToolContext 的参数。ADK 会在你的函数运行之前自动注入上下文数据,并确保此参数对 LLM 不可见。

from google.adk.tools import ToolContext

def my_tool(arg1: str, tool_context: ToolContext):
    # 示例:访问会话状态
    user_id = tool_context.state.get("user_id")
    # 示例:触发操作
    # tool_context.actions.transfer_to_agent = "secondary_agent"

ToolContext 提供以下访问:

  • state 用于会话级数据的字典类对象。
  • actions 控制智能体行为的操作,例如 transfer_to_agent
  • 方法:用于处理工件,例如 load_artifactsave_artifact
自定义参数名称

默认情况下,注入的参数名为 tool_context,但你可以将其命名为任何你想要的名称。ADK 通过其 ToolContext 类型注解来检测它,而不是通过名称。例如,要使用 ctx 作为名称:

from google.adk.tools import ToolContext

def my_tool(arg1: str, ctx: ToolContext):
    # 由于类型注解,'ctx' 接收 ToolContext
    user_id = ctx.state.get("user_id")

返回类型

函数工具的首选返回类型是 Python 中的字典、Java 中的 Map 或自定义 Record 或 POJO、TypeScript 中的对象,或 Kotlin 中的 MapData Class。这允许你使用键值对来构建响应,为 LLM 提供上下文和清晰度。如果你的函数返回的类型不是字典或 Map,框架会自动将其包装为一个包含单个键 "result" 的字典。

尽量使返回值尽可能具有描述性。例如,不要返回数字错误代码,而是返回一个包含 "error_message" 键的字典,其中包含人类可读的解释。请记住,LLM(而非一段代码)需要理解结果。作为最佳实践,在返回字典中包含一个 "status" 键来指示总体结果(例如 "success"、"error"、"pending"),为 LLM 提供关于操作状态的明确信号。

最佳实践: - 使返回值尽可能具有描述性。不要仅返回数字错误代码,而应返回包含 error_message 键详细说明的字典。 - 建议在返回结果中包含 status 键(如 "success""error""pending"),为 LLM 提供明确的操作执行信号。

函数的文档字符串充当工具的描述,并会发送给 LLM。因此,编写良好且全面的文档字符串对于 LLM 理解如何有效使用该工具至关重要。清晰地解释函数的用途、参数的含义以及预期的返回值。在 Java 中,你可以使用 Javadoc 注释或方法上的 @Schema(description="...") 注解来充当此描述。在 Kotlin 中,你可以使用 KDoc 注释或 @Tool(description="...")@Param(description="...") 注解来提供这些描述。

在工具之间传递数据

当智能体按顺序调用多个工具时,你可能需要将数据从一个工具传递到另一个工具。推荐的方法是使用会话状态中的 temp: 前缀。

工具可以将数据写入 temp: 变量,后续工具可以读取它。此数据仅在当前调用期间可用,之后会被丢弃。

共享调用上下文

单个智能体回合中的所有工具调用共享同一个 InvocationContext。这意味着它们也共享相同的临时(temp:)状态,这就是它们之间可以传递数据的方式。

示例

??? "示例" {: #example }

=== "Python"

    此工具是一个 Python 函数,用于获取给定股票代码/符号的股票价格。

    <u>注意</u>:在使用此工具之前,你需要先安装 `yfinance` 库(`pip install yfinance`)。

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

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

    import yfinance as yf


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

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

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

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


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


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

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

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


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

    ```

    此工具的返回值会被包装为字典。

    ```json
    {"result": "$123"}
    ```

=== "Typescript"

    此工具检索模拟的股票价格值。

    ```typescript
    /**
     * 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 {Content, Part, createUserContent} from '@google/genai';
    import { stringifyContent, FunctionTool, InMemoryRunner, LlmAgent } from '@google/adk';
    import {z} from 'zod';

    // Define the function to get the stock price
    async function getStockPrice({ticker}: {ticker: string}): Promise<Record<string, unknown>> {
      console.log(`Getting stock price for ${ticker}`);
      // In a real-world scenario, you would fetch the stock price from an API
      const price = (Math.random() * 1000).toFixed(2);
      return {price: `$${price}`};
    }

    async function main() {
      // Define the schema for the tool's parameters using Zod
      const getStockPriceSchema = z.object({
        ticker: z.string().describe('The stock ticker symbol to look up.'),
      });

      // Create a FunctionTool from the function and schema
      const stockPriceTool = new FunctionTool({
        name: 'getStockPrice',
        description: 'Gets the current price of a stock.',
        parameters: getStockPriceSchema,
        execute: getStockPrice,
      });

      // Define the agent that will use the tool
      const stockAgent = new LlmAgent({
        name: 'stock_agent',
        model: 'gemini-2.5-flash',
        instruction: 'You can get the stock price of a company.',
        tools: [stockPriceTool],
      });

      // Create a runner for the agent
      const runner = new InMemoryRunner({agent: stockAgent});

      // Create a new session
      const session = await runner.sessionService.createSession({
        appName: runner.appName,
        userId: 'test-user',
      });

      const userContent: Content = createUserContent('What is the stock price of GOOG?');

      // Run the agent and get the response
      const response = [];
      for await (const event of runner.runAsync({
        userId: session.userId,
        sessionId: session.id,
        newMessage: userContent,
      })) {
        response.push(event);
      }

      // Print the final response from the agent
      const finalResponse = response[response.length - 1];
      if (finalResponse?.content?.parts?.length) {
        console.log(stringifyContent(finalResponse));
      }
    }

    main();


    ```

    此工具的返回值将是一个对象。

    ```json
    对于输入 `GOOG`: {"price": 2800.0, "currency": "USD"}
    ```

=== "Go"

    此工具检索模拟的股票价格值。

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

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

    package main

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

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

        "google.golang.org/genai"
    )

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

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

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

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

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

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

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

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

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

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

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

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

        sessionID := session.Session.ID()

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

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

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

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

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

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

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

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

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

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

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

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

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


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

    ```

    此工具的返回值将是一个 `getStockPriceResults` 实例。

    ```json
    对于输入 `{"symbol": "GOOG"}`: {"price":300.6,"symbol":"GOOG"}
    ```

=== "Java"

    此工具检索模拟的股票价格值。

    ```java

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

    public class StockPriceAgent {

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

      // No longer using mock stock data - we fetch it live!

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

        try {
          Stock stock = YahooFinance.get(symbol.toUpperCase());
          if (stock != null && stock.getQuote().getPrice() != null) {
            BigDecimal currentPrice = stock.getQuote().getPrice();
            System.out.println("Tool: Found live price for " + symbol + ": " + currentPrice);
            return Map.of("symbol", symbol, "price", currentPrice.doubleValue());
          } else {
            return Map.of("symbol", symbol, "error", "No data found for symbol");
          }
        } catch (Exception e) {
          return Map.of("symbol", symbol, "error", e.getMessage());
        }
      }

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

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

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

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

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

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

    此工具的返回值将被包装成一个 Map<String, Object>。

    ```json
    对于输入 `GOOG`: {"symbol": "GOOG", "price": "1.0"}
    ```

=== "Kotlin"

    此工具检索模拟的股票价格值。

    ```kotlin
    data class StockPrice(val symbol: String, val price: Double)

    class StockService {
        /**
         * Retrieves the stock price for a given symbol.
         */
        @Tool
        fun getStockPrice(
            @Param("The stock symbol, e.g. GOOG") symbol: String,
        ): StockPrice {
            // In a real app, you would call a stock price API here.
            return StockPrice(symbol = symbol, price = 123.45)
        }
    }

    fun main() =
        runBlocking {
            val stockService = StockService()

            val agent =
                LlmAgent(
                    name = "stock_agent",
                    model = Gemini(name = "gemini-flash-latest"),
                    instruction = Instruction("You are a helpful stock assistant."),
                    // .generatedTools() is used to get the tools from the annotated class.
                    tools = stockService.generatedTools(),
                )

            // ... use the agent ...
        }
    ```

    此工具的返回值将是一个 Map。

    ```json
    对于输入 `GOOG`: {"symbol": "GOOG", "price": 123.45}
    ```

最佳实践

虽然你在定义函数时有相当大的灵活性,但请记住简洁性可以增强 LLM 的可用性。请参考以下指南:

  • 参数越少越好: 最小化参数数量以降低复杂度。
  • 简单数据类型: 尽可能优先使用原始数据类型(如 strint),而非自定义类。
  • 有意义的名称: 函数名称和参数名称对 LLM 如何解释和使用该工具有重大影响。选择能清晰反映函数用途和输入含义的名称。避免使用通用名称如 do_stuff()beAgent()
  • 为并行执行而构建: 通过构建异步操作来提升多个工具运行时的函数调用性能。有关启用工具并行执行的信息,请参阅通过并行执行提升工具性能

长时间运行函数工具

此工具旨在帮助你启动和管理在智能体工作流操作之外处理的任务,这些任务需要大量的处理时间,而不会阻塞智能体的执行。此工具是 FunctionTool 的子类。

使用 LongRunningFunctionTool 时,你的函数可以启动长时间运行的操作,并可选择返回一个初始结果,例如长时间运行的操作 ID。一旦长时间运行函数工具被调用,智能体运行器会暂停智能体运行,让智能体客户端决定是继续还是等待长时间运行的操作完成。智能体客户端可以查询长时间运行操作的进度,并发回中间或最终响应。然后智能体可以继续处理其他任务。一个示例是人机协作场景,其中智能体在继续执行任务之前需要人工审批。

警告:执行处理

长时间运行函数工具旨在帮助你启动和管理作为智能体工作流一部分的长时间运行任务,但不是执行实际的长时间任务。对于需要大量时间才能完成的任务,你应该实现一个单独的服务器来执行该任务。

提示:并行执行

根据你构建的工具类型,设计异步操作可能是比创建长时间运行工具更好的解决方案。有关更多信息,请参阅通过并行执行提升工具性能

工作原理

在 Python 中,你使用 LongRunningFunctionTool 包装函数。在 Java 中,你将方法名传递给 LongRunningFunctionTool.create()。在 TypeScript 中,你实例化 LongRunningFunctionTool 类。

  1. 启动: 当 LLM 调用该工具时,你的函数启动长时间运行的操作。
  2. 初始更新: 你的函数应可选地返回一个初始结果(例如长时间运行的操作 ID)。ADK 框架获取该结果并将其打包在 FunctionResponse 中发回给 LLM。这允许 LLM 通知用户(例如状态、完成百分比、消息)。然后智能体运行结束/暂停。
  3. 继续或等待: 每次智能体运行完成后,智能体客户端可以查询长时间运行操作的进度,并决定是继续智能体运行(发送中间响应以更新进度)还是等待直到获取最终响应。智能体客户端应将中间或最终响应发回给智能体以进行下一次运行。
  4. 框架处理: ADK 框架管理执行过程。它将智能体客户端发送的中间或最终 FunctionResponse 发送给 LLM 以生成用户友好的消息。

创建工具

定义你的工具函数并使用 LongRunningFunctionTool 类进行包装:

from typing import Any
from google.adk.tools import LongRunningFunctionTool

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

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

# 2. Wrap the function with LongRunningFunctionTool
long_running_tool = LongRunningFunctionTool(func=ask_for_approval)
// 1. Define the long-running function
function askForApproval(args: {purpose: string; amount: number}) {
  /**
   * Ask for approval for the reimbursement.
   */
  // create a ticket for the approval
  // Send a notification to the approver with the link of the ticket
  return {
    "status": "pending",
    "approver": "Sean Zhou",
    "purpose": args.purpose,
    "amount": args.amount,
    "ticket-id": "approval-ticket-1",
  };
}

// 2. Instantiate the LongRunningFunctionTool class with the long-running function
const longRunningTool = new LongRunningFunctionTool({
  name: "ask_for_approval",
  description: "Ask for approval for the reimbursement.",
  parameters: z.object({
    purpose: z.string().describe("The purpose of the reimbursement."),
    amount: z.number().describe("The amount to reimburse."),
  }),
  execute: askForApproval,
});
import (
    "google.golang.org/adk/v2/agent"
    "google.golang.org/adk/v2/agent/llmagent"
    "google.golang.org/adk/v2/model/gemini"
    "google.golang.org/adk/v2/tool"
    "google.golang.org/adk/v2/tool/functiontool"
    "google.golang.org/genai"
)

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

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

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

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

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

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

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

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

public class ExampleLongRunningFunction {

  // 定义你的长时间运行函数。
  // 请求报销审批。
  public static Map<String, Object> askForApproval(String purpose, double amount) {
    // 模拟创建工单并发送通知
    System.out.println("模拟为目的 " + purpose + ", 金额 " + amount + " 创建工单");

    // 向审批人发送带有工单链接的通知
    Map<String, Object> result = new HashMap<>();
    result.put("status", "pending");
    result.put("approver", "Sean Zhou");
    result.put("purpose", purpose);
    result.put("amount", amount);
    result.put("ticket-id", "approval-ticket-1");
    return result;
  }

  public static void main(String[] args) throws NoSuchMethodException {
    // 将方法传递给 LongRunningFunctionTool.create
    LongRunningFunctionTool approveTool =
        LongRunningFunctionTool.create(ExampleLongRunningFunction.class, "askForApproval");

    // 在智能体中包含该工具
    LlmAgent approverAgent =
        LlmAgent.builder()
            // ...
            .tools(approveTool)
            .build();
  }
}

在 Kotlin 中,你可以通过在 @Tool 注解中将 isLongRunning 属性设置为 true 来创建长时间运行函数工具。

data class ReimbursementApproval(
    val status: String,
    val approver: String,
    val purpose: String,
    val amount: Double,
    val ticketId: String,
)

class ReimbursementService {
    /**
     * Asks for approval for the reimbursement.
     */
    @Tool(isLongRunning = true)
    fun askForApproval(
        @Param("The purpose of the reimbursement.") purpose: String,
        @Param("The amount to be reimbursed.") amount: Double,
    ): ReimbursementApproval {
        // Simulate creating a ticket and sending a notification.
        // This tool returns the initial result and then the agent pauses.
        return ReimbursementApproval(
            status = "pending",
            approver = "Sean Zhou",
            purpose = purpose,
            amount = amount,
            ticketId = "approval-ticket-1",
        )
    }
}

fun main() {
    val service = ReimbursementService()
    val agent =
        LlmAgent(
            name = "approver_agent",
            model = Gemini(name = "gemini-flash-latest"),
            instruction = Instruction("You are a helpful reimbursement assistant."),
            tools = service.generatedTools(),
        )
}

中间/最终结果更新

智能体客户端接收到包含长时间运行函数调用的事件并检查工单状态。然后智能体客户端可以发送中间或最终响应来更新进度。框架将此值(即使为 None)打包到发回给 LLM 的 FunctionResponse 内容中。

注意:与 Resume 功能的长时间运行函数响应

如果你的 ADK 智能体工作流配置了 Resume 功能,你还必须在长时间运行函数响应中包含 Invocation ID(invocation_id)参数。你提供的 Invocation ID 必须与生成长时间运行函数请求的调用相同,否则系统会使用该响应启动新的调用。如果你的智能体使用了 Resume 功能,建议将 Invocation ID 作为参数包含在长时间运行函数请求中,以便在响应中包含它。有关使用 Resume 功能的更多详细信息,请参阅恢复已停止的智能体

仅适用于 Java ADK

在使用函数工具传递 ToolContext 时,请确保以下条件之一成立:

  • 在函数签名中通过 Schema 传递 ToolContext 参数,例如:

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

  • 在 mvn 编译器插件中设置以下 -parameters 标志
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.14.0</version> <!-- 或更高版本 -->
            <configuration>
                <compilerArgs>
                    <arg>-parameters</arg>
                </compilerArgs>
            </configuration>
        </plugin>
    </plugins>
</build>
# Agent Interaction
async def call_agent_async(query):

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

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

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

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


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


    if long_running_function_response:
        # query the status of the correpsonding ticket via tciket_id
        # send back an intermediate / final response
        updated_response = long_running_function_response.model_copy(deep=True)
        updated_response.response = {'status': 'approved'}
        async for event in runner.run_async(
          session_id=session.id, user_id=USER_ID, new_message=types.Content(parts=[types.Part(function_response = updated_response)], role='user')
        ):
            if event.content and event.content.parts:
                if text := ''.join(part.text or '' for part in event.content.parts):
                    print(f'[{event.author}]: {text}')
/**
 * 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, Runner, FunctionTool, LongRunningFunctionTool, InMemorySessionService, Event, stringifyContent } from '@google/adk';
import {z} from "zod";
import {Content, FunctionCall, FunctionResponse, createUserContent} from "@google/genai";

// 1. Define the long-running function
function askForApproval(args: {purpose: string; amount: number}) {
  /**
   * Ask for approval for the reimbursement.
   */
  // create a ticket for the approval
  // Send a notification to the approver with the link of the ticket
  return {
    "status": "pending",
    "approver": "Sean Zhou",
    "purpose": args.purpose,
    "amount": args.amount,
    "ticket-id": "approval-ticket-1",
  };
}

// 2. Instantiate the LongRunningFunctionTool class with the long-running function
const longRunningTool = new LongRunningFunctionTool({
  name: "ask_for_approval",
  description: "Ask for approval for the reimbursement.",
  parameters: z.object({
    purpose: z.string().describe("The purpose of the reimbursement."),
    amount: z.number().describe("The amount to reimburse."),
  }),
  execute: askForApproval,
});

function reimburse(args: {purpose: string; amount: number}) {
  /**
   * Reimburse the amount of money to the employee.
   */
  // send the reimbursement request to payment vendor
  return {status: "ok"};
}

const reimburseTool = new FunctionTool({
  name: "reimburse",
  description: "Reimburse the amount of money to the employee.",
  parameters: z.object({
    purpose: z.string().describe("The purpose of the reimbursement."),
    amount: z.number().describe("The amount to reimburse."),
  }),
  execute: reimburse,
});

// 3. Use the tool in an Agent
const reimbursementAgent = new LlmAgent({
  model: "gemini-2.5-flash",
  name: "reimbursement_agent",
  instruction: `
      You are an agent whose job is to handle the reimbursement process for
      the employees. If the amount is less than $100, you will automatically
      approve the reimbursement.

      If the amount is greater than $100, you will
      ask for approval from the manager. If the manager approves, you will
      call reimburse() to reimburse the amount to the employee. If the manager
      rejects, you will inform the employee of the rejection.
    `,
  tools: [reimburseTool, longRunningTool],
});

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

// Session and Runner
async function setupSessionAndRunner() {
  const sessionService = new InMemorySessionService();
  const session = await sessionService.createSession({
    appName: APP_NAME,
    userId: USER_ID,
    sessionId: SESSION_ID,
  });
  const runner = new Runner({
    agent: reimbursementAgent,
    appName: APP_NAME,
    sessionService: sessionService,
  });
  return {session, runner};
}

function getLongRunningFunctionCall(event: Event): FunctionCall | undefined {
  // Get the long-running function call from the event
  if (
    !event.longRunningToolIds ||
    !event.content ||
    !event.content.parts?.length
  ) {
    return;
  }
  for (const part of event.content.parts) {
    if (
      part &&
      part.functionCall &&
      event.longRunningToolIds &&
      part.functionCall.id &&
      event.longRunningToolIds.includes(part.functionCall.id)
    ) {
      return part.functionCall;
    }
  }
}

function getFunctionResponse(
  event: Event,
  functionCallId: string
): FunctionResponse | undefined {
  // Get the function response for the function call with specified id.
  if (!event.content || !event.content.parts?.length) {
    return;
  }
  for (const part of event.content.parts) {
    if (
      part &&
      part.functionResponse &&
      part.functionResponse.id === functionCallId
    ) {
      return part.functionResponse;
    }
  }
}

// Agent Interaction
async function callAgentAsync(query: string) {
  let longRunningFunctionCall: FunctionCall | undefined;
  let longRunningFunctionResponse: FunctionResponse | undefined;
  let ticketId: string | undefined;
  const content: Content = createUserContent(query);
  const {session, runner} = await setupSessionAndRunner();

  console.log("\nRunning agent...");
  const events = runner.runAsync({
    sessionId: session.id,
    userId: USER_ID,
    newMessage: content,
  });

  for await (const event of events) {
    // Use helper to check for the specific auth request event
    if (!longRunningFunctionCall) {
      longRunningFunctionCall = getLongRunningFunctionCall(event);
    } else {
      const _potentialResponse = getFunctionResponse(
        event,
        longRunningFunctionCall.id!
      );
      if (_potentialResponse) {
        // Only update if we get a non-None response
        longRunningFunctionResponse = _potentialResponse;
        ticketId = (
          longRunningFunctionResponse.response as {[key: string]: any}
        )[`ticket-id`];
      }
    }
    const text = stringifyContent(event);
    if (text) {
      console.log(`[${event.author}]: ${text}`);
    }
  }

  if (longRunningFunctionResponse) {
    // query the status of the corresponding ticket via ticket_id
    // send back an intermediate / final response
    const updatedResponse = JSON.parse(
      JSON.stringify(longRunningFunctionResponse)
    );
    updatedResponse.response = {status: "approved"};
    for await (const event of runner.runAsync({
      sessionId: session.id,
      userId: USER_ID,
      newMessage: createUserContent(JSON.stringify({functionResponse: updatedResponse})),
    })) {
      const text = stringifyContent(event);
      if (text) {
        console.log(`[${event.author}]: ${text}`);
      }
    }
  }
}

async function main() {
  // reimbursement that doesn't require approval
  await callAgentAsync("Please reimburse 50$ for meals");
  // reimbursement that requires approval
  await callAgentAsync("Please reimburse 200$ for meals");
}

main();

以下示例演示了一个多轮工作流。首先,用户要求智能体创建一个工单。智能体调用长时间运行工具,客户端捕获 FunctionCall ID。然后客户端通过发送后续的 FunctionResponse 消息回到智能体来模拟异步工作完成,以提供工单 ID 和最终状态。

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

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

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

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

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

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

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

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

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

public class LongRunningFunctionExample {

  private static String USER_ID = "user123";

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

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

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

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

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

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

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

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

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

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

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

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


from typing import Any
from google.adk.tools import LongRunningFunctionTool

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

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

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


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

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


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

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


# Agent Interaction
async def call_agent_async(query):

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

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

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

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


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


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


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

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

此示例的关键方面

  • LongRunningFunctionTool:包装所提供的方法/函数;框架处理将产生的更新和最终返回值作为顺序的 FunctionResponse 发送。
  • 智能体指令:指导 LLM 使用该工具,并理解传入的 FunctionResponse 流(进度与完成)以进行用户更新。
  • 最终返回:函数返回最终结果字典,在最后的 FunctionResponse 中发送以指示完成。

智能体即工具

此功能允许你通过将系统中其他智能体作为工具调用来利用它们的能力。智能体即工具使你能够调用另一个智能体来执行特定任务,有效地委派职责。这在概念上类似于创建一个调用另一个智能体并将其响应用作函数返回值的 Python 函数。

与子智能体的关键区别

区分智能体即工具和子智能体非常重要。

  • 智能体即工具: 当智能体 A 将智能体 B 作为工具调用时(使用智能体即工具),智能体 B 的回答会被回传给智能体 A,然后智能体 A 总结回答并向用户生成响应。智能体 A 保留控制权并继续处理未来的用户输入。
  • 子智能体: 当智能体 A 将智能体 B 作为子智能体调用时,回答用户的职责完全转移给智能体 B。智能体 A 实际上退出了流程。所有后续用户输入都将由智能体 B 回答。

使用 AgentTool

要将智能体用作工具,请使用 AgentTool 类包装该智能体。

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

自定义

AgentTool 类提供以下属性来自定义其行为:

  • skip_summarization(Python/TypeScript)/ skipSummarization(Kotlin/Java):(布尔值)如果设置为 True,框架将跳过基于 LLM 的摘要处理工具智能体的响应。当工具的响应已经格式良好且不需要进一步处理时,这很有用。
示例
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

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

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

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

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

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


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

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


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


# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
await call_agent_async(long_text)
/**
 * 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 { AgentTool, InMemoryRunner, LlmAgent } from '@google/adk';
import {Part, createUserContent} from '@google/genai';

/**
 * This example demonstrates how to use an agent as a tool.
 */
async function main() {
  // Define the summarization agent that will be used as a tool
  const summaryAgent = new LlmAgent({
    name: 'summary_agent',
    model: 'gemini-2.5-flash',
    description: 'Agent to summarize text',
    instruction:
      'You are an expert summarizer. Please read the following text and provide a concise summary.',
  });

  // Define the main agent that uses the summarization agent as a tool.
  // skipSummarization is set to true, so the main_agent will directly output
  // the result from the summary_agent without further processing.
  const mainAgent = new LlmAgent({
    name: 'main_agent',
    model: 'gemini-2.5-flash',
    instruction:
      "You are a helpful assistant. When the user provides a text, use the 'summary_agent' tool to generate a summary. Always forward the user's message exactly as received to the 'summary_agent' tool, without modifying or summarizing it yourself. Present the response from the tool to the user.",
    tools: [new AgentTool({agent: summaryAgent, skipSummarization: true})],
  });

  const appName = 'agent-as-a-tool-app';
  const runner = new InMemoryRunner({agent: mainAgent, appName});

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

  // Create the session before running the agent
  await runner.sessionService.createSession({
    appName,
    userId: 'user1',
    sessionId: 'session1',
  });

  // Run the agent with the long text to summarize
  const events = runner.runAsync({
    userId: 'user1',
    sessionId: 'session1',
    newMessage: createUserContent(longText),
  });

  // Print the final response from the agent
  console.log('Agent Response:');
  for await (const event of events) {
    if (event.content?.parts?.length) {
      const responsePart = event.content.parts.find((p: Part) => p.functionResponse);
      if (responsePart && responsePart.functionResponse) {
        console.log(responsePart.functionResponse.response);
      }
    }
  }
}

main();
import (
    "google.golang.org/adk/v2/agent"
    "google.golang.org/adk/v2/agent/llmagent"
    "google.golang.org/adk/v2/model/gemini"
    "google.golang.org/adk/v2/tool"
    "google.golang.org/adk/v2/tool/agenttool"
    "google.golang.org/genai"
)

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

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

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

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

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

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

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

public class AgentToolCustomization {

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

  public static void initAgentAndRun(String prompt) {

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

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

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

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

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

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

    initAgentAndRun(longText);
  }
}
import com.google.adk.kt.agents.Instruction
import com.google.adk.kt.agents.LlmAgent
import com.google.adk.kt.models.Gemini
import com.google.adk.kt.runners.InMemoryRunner
import com.google.adk.kt.tools.AgentTool
import com.google.adk.kt.types.Content
import com.google.adk.kt.types.Part
import kotlinx.coroutines.runBlocking

fun main() =
    runBlocking {
        val appName = "summary_agent"
        val userId = "user1234"

        // Define a specialized agent to be used as a tool
        val summaryAgent =
            LlmAgent(
                name = "summary_agent",
                model = Gemini(name = "gemini-flash-latest"),
                description = "Agent to summarize text",
                instruction =
                    Instruction(
                        "You are an expert summarizer. Please read the following text and provide a concise summary.",
                    ),
            )

        // Wrap the agent in an AgentTool with skipSummarization = true
        val summaryTool =
            AgentTool(
                agent = summaryAgent,
                skipSummarization = true,
            )

        // Define the root agent that uses the summary tool
        val rootAgent =
            LlmAgent(
                name = "root_agent",
                model = Gemini(name = "gemini-flash-latest"),
                instruction =
                    Instruction(
                        "You are a helpful assistant. When the user provides a text, use the 'summary_agent' tool to generate a summary. Always forward the user's message exactly as received to the 'summary_agent' tool. Present the response from the tool to the user.",
                    ),
                tools = listOf(summaryTool),
            )

        // Create an InMemoryRunner
        val runner = InMemoryRunner(agent = rootAgent, appName = appName)

        val sessionId = "session_001"

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

        val userMessage = Content(parts = listOf(Part(text = longText)))

        // Run the agent and collect events
        runner.runAsync(userId = userId, sessionId = sessionId, newMessage = userMessage).collect {
                event ->
            if (event.isFinalResponse) {
                val finalResponse = event.content?.parts?.firstOrNull()?.text
                println("Agent Response: $finalResponse")
            }
        }
    }

工作原理

  1. root_agent 接收到长文本时,其指令会告知它对长文本使用 'summarize' 工具。
  2. 框架识别 'summarize' 为包装 summary_agentAgentTool
  3. 在幕后,root_agent 将以长文本作为输入调用 summary_agent
  4. summary_agent 将根据其指令处理文本并生成摘要。
  5. summary_agent 的响应随后被回传给 root_agent
  6. root_agent 然后可以获取摘要并制定其对用户的最终响应(例如:"以下是文本摘要:……")。