函数工具¶
当预构建的 ADK 工具(如 Google 搜索或官方连接器)不能满足你的要求时,你可以创建自定义的函数工具。构建函数工具允许你创建定制化的功能,例如连接到企业私有数据库、调用内部 API 或实现独特的计算算法。
例如,一个名为 my_finance_tool 的函数工具可以用来计算特定的财务指标。ADK 还支持长时间运行的函数,因此如果该计算较为耗时,智能体可以先处理其他任务,待结果产出后再回来响应。
ADK 提供了几种创建函数工具的方法,每种方法适合不同的复杂程度和控制级别:
基础函数工具¶
将 Python 函数直接转换为工具是将自定义逻辑集成到智能体中最简单的方法。当你将函数分配给智能体的 tools 列表时,框架会自动将其包装为 FunctionTool。
工作原理¶
ADK 框架会自动检查你的 Python 函数签名——包括其名称、文档字符串 (Docstring)、参数名称、类型提示 (Type Hints) 和默认值——以自动生成 JSON 模式 (Schema)。LLM 正是通过这个模式来理解工具的用途、何时该调用它以及需要提供哪些参数。
定义函数签名¶
定义良好的函数签名对于 LLM 正确理解并使用你的工具至关重要。
参数¶
必需参数¶
如果参数具有类型提示但没有默认值,则该参数被视为必需 (Required)。LLM 在调用工具时必须提供该参数。参数的描述将直接从函数的文档字符串中提取。
示例:必需参数
在此示例中,city 和 unit 都是必需的。如果 LLM 尝试在没有其中一个参数的情况下调用 get_weather,ADK 将向 LLM 返回错误,提示其更正调用。
在 Go 中,你使用结构体标签来控制 JSON 模式。两个主要标签是 json 和 jsonschema。
如果参数的结构体字段在其 json 标签中没有 omitempty 或 omitzero 选项,则该参数被视为必需。
jsonschema 标签用于提供参数的描述。这对于 LLM 理解参数的用途至关重要。
示例:必需参数
在此示例中,location 和 unit 都是强制性的。
在 Java 中,原始类型(如 int, double, boolean)天生就是必需的,因为它们不能为空。对于对象类型(如 String 或 Integer),除非显式标记,否则通常也被视为必需。使用 @Schema 注解来提供参数描述至关重要。
在 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."
}
}
在此示例中,location 和 unit 都是必需参数。
可选参数¶
如果你提供了默认值,则参数被视为可选 (Optional)。
仅对真正可选的参数使用默认值。不要为模型应从用户请求中推导或应向用户询问的信息添加默认值。
示例:可选参数
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 标签中具有 omitempty 或 omitzero 选项,则该参数被视为可选。
示例:可选参数
// 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)"`
}
这里,unit 和 days 是可选的。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(可变关键字参数)用于其他目的,但它们在为 LLM 生成工具模式时被 ADK 框架忽略。LLM 不会意识到它们,也无法向它们传递参数。最好依赖明确定义的参数来处理你期望从 LLM 获得的所有数据。
函数工具的首选返回类型是 Python 中的字典 (Dictionary)、Java 中的 Map 或自定义 Record/POJO、TypeScript 中的 对象 (Object)、Kotlin 中的 Map 或 数据类 (Data Class)。这允许你通过键值对结构化响应,为 LLM 提供清晰的上下文。如果你的函数返回非字典/非映射类型,框架会自动将其包装在名为 "result" 的键下。
函数工具的首选返回类型是 Python 中的字典 (Dictionary) 或 Java 中的 Map。这允许你通过键值对结构化响应,为 LLM 提供清晰的上下文。如果你的函数返回非字典类型,框架会自动将其包装在名为 "result" 的键下。
最佳实践:
- 使返回值尽可能具有描述性。不要仅返回数字错误代码,而应返回包含 error_message 键详细说明的字典。
- 建议在返回结果中包含 status 键(如 "success", "error", "pending"),为 LLM 提供明确的操作执行信号。
你的函数的文档字符串 (docstring) 作为工具的描述并发送给 LLM。因此,编写良好且全面的文档字符串对于 LLM 有效理解如何使用工具至关重要。请清晰解释函数的目的、参数的含义以及预期的返回值。在 Java 中,你可以使用方法上的 Javadoc 注释或 @Schema(description="...") 注解作为描述。在 Kotlin 中,你可以使用 KDoc 注释或 @Tool(description="...") 和 @Param(description="...") 注解来提供这些描述。
在工具之间传递数据¶
当智能体按顺序调用多个工具时,你可能需要在工具之间传递数据。推荐的方法是使用会话状态中的 temp: 前缀。
工具可以将数据写入 temp: 变量,后续工具可以读取它。此数据仅在当前调用期间可用,之后会被丢弃。
共享调用上下文
单个智能体轮次内的所有工具调用共享相同的 InvocationContext。这意味着它们也共享相同的临时(temp:)状态,这就是数据如何在它们之间传递的方式。
示例¶
??? "示例" {: #example }
=== "Python"
该工具是一个 Python 函数,用于获取给定股票代码/符号的股票价格。
<u>注意</u>:使用此工具前需先 `pip install yfinance`。
```py
# 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/agent"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/runner"
"google.golang.org/adk/session"
"google.golang.org/adk/tool"
"google.golang.org/adk/tool/functiontool"
"google.golang.org/genai"
)
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"fmt"
"log"
"strings"
"google.golang.org/adk/agent"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/runner"
"google.golang.org/adk/session"
"google.golang.org/adk/tool"
"google.golang.org/adk/tool/agenttool"
"google.golang.org/adk/tool/functiontool"
"google.golang.org/genai"
)
// mockStockPrices provides a simple in-memory database of stock prices
// to simulate a real-world stock data API. This allows the example to
// demonstrate tool functionality without making external network calls.
var mockStockPrices = map[string]float64{
"GOOG": 300.6,
"AAPL": 123.4,
"MSFT": 234.5,
}
// getStockPriceArgs defines the schema for the arguments passed to the getStockPrice tool.
// Using a struct is the recommended approach in the Go ADK as it provides strong
// typing and clear validation for the expected inputs.
type getStockPriceArgs struct {
Symbol string `json:"symbol" jsonschema:"The stock ticker symbol, e.g., GOOG"`
}
// getStockPriceResults defines the output schema for the getStockPrice tool.
type getStockPriceResults struct {
Symbol string `json:"symbol"`
Price float64 `json:"price,omitempty"`
Error string `json:"error,omitempty"`
}
// getStockPrice is a tool that retrieves the stock price for a given ticker symbol
// from the mockStockPrices map. It demonstrates how a function can be used as a
// tool by an agent. If the symbol is found, it returns a struct containing the
// symbol and its price. Otherwise, it returns a struct with an error message.
func getStockPrice(ctx tool.Context, input getStockPriceArgs) (getStockPriceResults, error) {
symbolUpper := strings.ToUpper(input.Symbol)
if price, ok := mockStockPrices[symbolUpper]; ok {
fmt.Printf("Tool: Found price for %s: %f\n", input.Symbol, price)
return getStockPriceResults{Symbol: input.Symbol, Price: price}, nil
}
return getStockPriceResults{}, fmt.Errorf("no data found for symbol")
}
// createStockAgent initializes and configures an LlmAgent.
// This agent is equipped with the getStockPrice tool and is instructed
// on how to respond to user queries about stock prices. It uses the
// Gemini model to understand user intent and decide when to use its tools.
func createStockAgent(ctx context.Context) (agent.Agent, error) {
stockPriceTool, err := functiontool.New(
functiontool.Config{
Name: "get_stock_price",
Description: "Retrieves the current stock price for a given symbol.",
},
getStockPrice)
if err != nil {
return nil, err
}
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
if err != nil {
log.Fatalf("Failed to create model: %v", err)
}
return llmagent.New(llmagent.Config{
Name: "stock_agent",
Model: model,
Instruction: "You are an agent who retrieves stock prices. If a ticker symbol is provided, fetch the current price. If only a company name is given, first perform a Google search to find the correct ticker symbol before retrieving the stock price. If the provided ticker symbol is invalid or data cannot be retrieved, inform the user that the stock price could not be found.",
Description: "This agent specializes in retrieving real-time stock prices. Given a stock ticker symbol (e.g., AAPL, GOOG, MSFT) or the stock name, use the tools and reliable data sources to provide the most up-to-date price.",
Tools: []tool.Tool{
stockPriceTool,
},
})
}
// userID and appName are constants used to identify the user and application
// throughout the session. These values are important for logging, tracking,
// and managing state across different agent interactions.
const (
userID = "example_user_id"
appName = "example_app"
)
// callAgent orchestrates the execution of the agent for a given prompt.
// It sets up the necessary services, creates a session, and uses a runner
// to manage the agent's lifecycle. It streams the agent's responses and
// prints them to the console, handling any potential errors during the run.
func callAgent(ctx context.Context, a agent.Agent, prompt string) {
sessionService := session.InMemoryService()
// Create a new session for the agent interactions.
session, err := sessionService.Create(ctx, &session.CreateRequest{
AppName: appName,
UserID: userID,
})
if err != nil {
log.Fatalf("Failed to create the session service: %v", err)
}
config := runner.Config{
AppName: appName,
Agent: a,
SessionService: sessionService,
}
// Create the runner to manage the agent execution.
r, err := runner.New(config)
if err != nil {
log.Fatalf("Failed to create the runner: %v", err)
}
sessionID := session.Session.ID()
userMsg := &genai.Content{
Parts: []*genai.Part{
genai.NewPartFromText(prompt),
},
Role: string(genai.RoleUser),
}
for event, err := range r.Run(ctx, userID, sessionID, userMsg, agent.RunConfig{
StreamingMode: agent.StreamingModeNone,
}) {
if err != nil {
fmt.Printf("\nAGENT_ERROR: %v\n", err)
} else {
for _, p := range event.Content.Parts {
fmt.Print(p.Text)
}
}
}
}
// RunAgentSimulation serves as the entry point for this example.
// It creates the stock agent and then simulates a series of user interactions
// by sending different prompts to the agent. This function showcases how the
// agent responds to various queries, including both successful and unsuccessful
// attempts to retrieve stock prices.
func RunAgentSimulation() {
// Create the stock agent
agent, err := createStockAgent(context.Background())
if err != nil {
panic(err)
}
fmt.Println("Agent created:", agent.Name())
prompts := []string{
"stock price of GOOG",
"What's the price of MSFT?",
"Can you find the stock price for an unknown company XYZ?",
}
// Simulate running the agent with different prompts
for _, prompt := range prompts {
fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
callAgent(context.Background(), agent, prompt)
fmt.Println("\n---")
}
}
// createSummarizerAgent creates an agent whose sole purpose is to summarize text.
func createSummarizerAgent(ctx context.Context) (agent.Agent, error) {
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
if err != nil {
return nil, err
}
return llmagent.New(llmagent.Config{
Name: "SummarizerAgent",
Model: model,
Instruction: "You are an expert at summarizing text. Take the user's input and provide a concise summary.",
Description: "An agent that summarizes text.",
})
}
// createMainAgent creates the primary agent that will use the summarizer agent as a tool.
func createMainAgent(ctx context.Context, tools ...tool.Tool) (agent.Agent, error) {
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
if err != nil {
return nil, err
}
return llmagent.New(llmagent.Config{
Name: "MainAgent",
Model: model,
Instruction: "You are a helpful assistant. If you are asked to summarize a long text, use the 'summarize' tool. " +
"After getting the summary, present it to the user by saying 'Here is a summary of the text:'.",
Description: "The main agent that can delegate tasks.",
Tools: tools,
})
}
func RunAgentAsToolSimulation() {
ctx := context.Background()
// 1. Create the Tool Agent (Summarizer)
summarizerAgent, err := createSummarizerAgent(ctx)
if err != nil {
log.Fatalf("Failed to create summarizer agent: %v", err)
}
// 2. Wrap the Tool Agent in an AgentTool
summarizeTool := agenttool.New(summarizerAgent, &agenttool.Config{
SkipSummarization: true,
})
// 3. Create the Main Agent and provide it with the AgentTool
mainAgent, err := createMainAgent(ctx, summarizeTool)
if err != nil {
log.Fatalf("Failed to create main agent: %v", err)
}
// 4. Run the main agent
prompt := `
Please summarize this text for me:
Quantum computing represents a fundamentally different approach to computation,
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.
`
fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
callAgent(context.Background(), mainAgent, prompt)
fmt.Println("\n---")
}
func main() {
fmt.Println("Attempting to run the agent simulation...")
RunAgentSimulation()
fmt.Println("\nAttempting to run the agent-as-a-tool simulation...")
RunAgentAsToolSimulation()
}
```
此工具的返回值将是一个 `getStockPriceResults` 实例。
```json
For input `{"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 的可用性。考虑这些指导原则:
- 参数越少越好: 最小化参数数量以降低复杂性。
- 简单数据类型: 尽可能优先使用
str和int等基本数据类型,而非自定义类。 - 有意义的名称: 函数名和参数名会显著影响 LLM 如何解释和利用该工具。选择能清晰反映函数用途和输入含义的名称。避免使用
do_stuff()或beAgent()等通用名称。 - 为并行执行构建: 通过为异步操作构建来提高多工具运行时的函数调用性能。有关为工具启用并行执行的信息,请参阅通过并行执行提升工具性能。
长时间运行函数工具¶
这个工具旨在帮助你启动和管理在你的智能体工作流操作之外处理的任务,这些任务需要大量的处理时间,而不会阻塞智能体的执行。这个工具是 FunctionTool 的子类。
在使用 LongRunningFunctionTool 时,你的函数可以启动长时间运行的操作,并可选地返回一个初始结果,例如一个长时间运行的操作 ID。一旦调用了一个长时间运行的函数工具,智能体运行器会暂停智能体运行,让智能体客户端决定是继续执行还是等待长时间运行的操作完成。智能体客户端可以查询长时间运行操作的进度,并返回中间或最终的响应。然后智能体可以继续处理其他任务。一个例子是人机协同场景,智能体在执行任务前需要获得人的批准。
警告:执行处理
长时间运行的函数工具旨在帮助你启动和 管理 长时间运行的任务作为你智能体工作流的一部分,但 不执行 实际的长时间任务。 对于需要大量时间完成的任务,你应该实现一个独立的服务器来执行这些任务。
提示:并行执行
根据你构建的工具类型,为异步操作设计可能是比创建长时间运行工具更好的解决方案。更多信息,请参阅通过并行执行提升工具性能。
工作原理¶
在 Python 中,你用 LongRunningFunctionTool 包装函数。在 Java 中,你将方法名传递给 LongRunningFunctionTool.create()。在 TypeScript 中,你实例化 LongRunningFunctionTool 类。
- 启动: 当 LLM 调用工具时,你的函数启动长时间运行操作。
- 初始更新: 你的函数可以选择性地返回一个初始结果(如长时间运行操作 id)。ADK 框架会将该结果打包在
FunctionResponse中返回给 LLM。这允许 LLM 通知用户(如状态、完成百分比、消息等),然后智能体运行会被结束/暂停。 - 继续或等待: 每次智能体运行结束后,智能体客户端可以查询长时间运行操作的进度,并决定是用中间响应(用于进度更新)继续智能体运行,还是等待最终响应。智能体客户端应将中间或最终响应返回给智能体以进行下一次运行。
- 框架处理: ADK 框架会管理整个执行流程。它会将智能体客户端发送的中间或最终
FunctionResponse传递给 LLM,以生成用户友好的消息。
创建工具¶
定义你的工具函数,并用 LongRunningFunctionTool 类进行包装:
# 1. Define the long running function
def ask_for_approval(
purpose: str, amount: float
) -> dict[str, Any]:
"""Ask for approval for the reimbursement."""
# create a ticket for the approval
# Send a notification to the approver with the link of the ticket
return {'status': 'pending', 'approver': 'Sean Zhou', 'purpose' : purpose, 'amount': amount, 'ticket-id': 'approval-ticket-1'}
def reimburse(purpose: str, amount: float) -> str:
"""Reimburse the amount of money to the employee."""
# send the reimbrusement request to payment vendor
return {'status': 'ok'}
# 2. Wrap the function with LongRunningFunctionTool
long_running_tool = LongRunningFunctionTool(func=ask_for_approval)
// 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/agent"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/tool"
"google.golang.org/adk/tool/functiontool"
"google.golang.org/genai"
)
// CreateTicketArgs defines the arguments for our long-running tool.
type CreateTicketArgs struct {
Urgency string `json:"urgency" jsonschema:"The urgency level of the ticket."`
}
// CreateTicketResults defines the *initial* output of our long-running tool.
type CreateTicketResults struct {
Status string `json:"status"`
TicketId string `json:"ticket_id"`
}
// createTicketAsync simulates the *initiation* of a long-running ticket creation task.
func createTicketAsync(ctx tool.Context, args CreateTicketArgs) (CreateTicketResults, error) {
log.Printf("TOOL_EXEC: 'create_ticket_long_running' called with urgency: %s (Call ID: %s)\n", args.Urgency, ctx.FunctionCallID())
// "Generate" a ticket ID and return it in the initial response.
ticketID := "TICKET-ABC-123"
log.Printf("ACTION: Generated Ticket ID: %s for Call ID: %s\n", ticketID, ctx.FunctionCallID())
// In a real application, you would save the association between the
// FunctionCallID and the ticketID to handle the async response later.
return CreateTicketResults{
Status: "started",
TicketId: ticketID,
}, nil
}
func createTicketAgent(ctx context.Context) (agent.Agent, error) {
ticketTool, err := functiontool.New(
functiontool.Config{
Name: "create_ticket_long_running",
Description: "Creates a new support ticket with a specified urgency level.",
},
createTicketAsync,
)
if err != nil {
return nil, fmt.Errorf("failed to create long running tool: %w", err)
}
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
if err != nil {
return nil, fmt.Errorf("failed to create model: %v", err)
}
return llmagent.New(llmagent.Config{
Name: "ticket_agent",
Model: model,
Instruction: "You are a helpful assistant for creating support tickets. Provide the status of the ticket at each interaction.",
Tools: []tool.Tool{ticketTool},
})
}
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.LongRunningFunctionTool;
import java.util.HashMap;
import java.util.Map;
public class ExampleLongRunningFunction {
// 定义你的长时间运行函数。
// 请求报销审批。
public static Map<String, Object> askForApproval(String purpose, double amount) {
// 模拟创建工单并发送通知
System.out.println("模拟为目的 " + purpose + ", 金额 " + amount + " 创建工单");
// 向审批人发送带有工单链接的通知
Map<String, Object> result = new HashMap<>();
result.put("status", "pending");
result.put("approver", "Sean Zhou");
result.put("purpose", purpose);
result.put("amount", amount);
result.put("ticket-id", "approval-ticket-1");
return result;
}
public static void main(String[] args) throws NoSuchMethodException {
// 将方法传递给 LongRunningFunctionTool.create
LongRunningFunctionTool approveTool =
LongRunningFunctionTool.create(ExampleLongRunningFunction.class, "askForApproval");
// 在智能体中包含该工具
LlmAgent approverAgent =
LlmAgent.builder()
// ...
.tools(approveTool)
.build();
}
}
在 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 功能,你还必须在长时间运行函数响应中包含调用 ID(invocation_id)参数。你提供的调用 ID 必须与生成长长时间运行函数请求的调用相同,否则系统会使用响应启动新的调用。如果你的智能体使用 Resume 功能,请考虑在长时间运行函数请求中将调用 ID 作为参数包含进来,以便它可以包含在响应中。有关使用 Resume 功能的更多详细信息,请参阅恢复停止的智能体。
仅适用于 Java ADK
当用函数工具传递 ToolContext 时,需确保以下之一:
-
在函数签名中用注解 Schema 传递 ToolContext 参数,如:
或 -
在 mvn 编译插件中设置
-parameters标志
# 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 typing import Any
from google.adk.agents import Agent
from google.adk.events import Event
from google.adk.runners import Runner
from google.adk.tools import LongRunningFunctionTool
from google.adk.sessions import InMemorySessionService
from google.genai import types
# 1. Define the long running function
def ask_for_approval(
purpose: str, amount: float
) -> dict[str, Any]:
"""Ask for approval for the reimbursement."""
# create a ticket for the approval
# Send a notification to the approver with the link of the ticket
return {'status': 'pending', 'approver': 'Sean Zhou', 'purpose' : purpose, 'amount': amount, 'ticket-id': 'approval-ticket-1'}
def reimburse(purpose: str, amount: float) -> str:
"""Reimburse the amount of money to the employee."""
# send the reimbrusement request to payment vendor
return {'status': 'ok'}
# 2. Wrap the function with LongRunningFunctionTool
long_running_tool = LongRunningFunctionTool(func=ask_for_approval)
# 3. Use the tool in an Agent
file_processor_agent = Agent(
# Use a model compatible with function calling
model="gemini-2.0-flash",
name='reimbursement_agent',
instruction="""
You are an agent whose job is to handle the reimbursement process for
the employees. If the amount is less than $100, you will automatically
approve the reimbursement.
If the amount is greater than $100, you will
ask for approval from the manager. If the manager approves, you will
call reimburse() to reimburse the amount to the employee. If the manager
rejects, you will inform the employee of the rejection.
""",
tools=[reimburse, long_running_tool]
)
APP_NAME = "human_in_the_loop"
USER_ID = "1234"
SESSION_ID = "session1234"
# Session and Runner
async def setup_session_and_runner():
session_service = InMemorySessionService()
session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
runner = Runner(agent=file_processor_agent, app_name=APP_NAME, session_service=session_service)
return session, runner
# Agent Interaction
async def call_agent_async(query):
def get_long_running_function_call(event: Event) -> types.FunctionCall:
# Get the long running function call from the event
if not event.long_running_tool_ids or not event.content or not event.content.parts:
return
for part in event.content.parts:
if (
part
and part.function_call
and event.long_running_tool_ids
and part.function_call.id in event.long_running_tool_ids
):
return part.function_call
def get_function_response(event: Event, function_call_id: str) -> types.FunctionResponse:
# Get the function response for the fuction call with specified id.
if not event.content or not event.content.parts:
return
for part in event.content.parts:
if (
part
and part.function_response
and part.function_response.id == function_call_id
):
return part.function_response
content = types.Content(role='user', parts=[types.Part(text=query)])
session, runner = await setup_session_and_runner()
print("\nRunning agent...")
events_async = runner.run_async(
session_id=session.id, user_id=USER_ID, new_message=content
)
long_running_function_call, long_running_function_response, ticket_id = None, None, None
async for event in events_async:
# Use helper to check for the specific auth request event
if not long_running_function_call:
long_running_function_call = get_long_running_function_call(event)
else:
_potential_response = get_function_response(event, long_running_function_call.id)
if _potential_response: # Only update if we get a non-None response
long_running_function_response = _potential_response
ticket_id = long_running_function_response.response['ticket-id']
if event.content and event.content.parts:
if text := ''.join(part.text or '' for part in event.content.parts):
print(f'[{event.author}]: {text}')
if long_running_function_response:
# query the status of the correpsonding ticket via tciket_id
# send back an intermediate / final response
updated_response = long_running_function_response.model_copy(deep=True)
updated_response.response = {'status': 'approved'}
async for event in runner.run_async(
session_id=session.id, user_id=USER_ID, new_message=types.Content(parts=[types.Part(function_response = updated_response)], role='user')
):
if event.content and event.content.parts:
if text := ''.join(part.text or '' for part in event.content.parts):
print(f'[{event.author}]: {text}')
# Note: In Colab, you can directly use 'await' at the top level.
# If running this code as a standalone Python script, you'll need to use asyncio.run() or manage the event loop.
# reimbursement that doesn't require approval
# asyncio.run(call_agent_async("Please reimburse 50$ for meals"))
await call_agent_async("Please reimburse 50$ for meals") # For Notebooks, uncomment this line and comment the above line
# reimbursement that requires approval
# asyncio.run(call_agent_async("Please reimburse 200$ for meals"))
await call_agent_async("Please reimburse 200$ for meals") # For Notebooks, uncomment this line and comment the above line
此示例的关键方面¶
-
LongRunningFunctionTool: 包装所提供的方法/函数;框架会将产生的进度更新和最终返回值作为一系列 FunctionResponse 顺序发送。 -
智能体指令:指导 LLM 使用工具并理解传入的 FunctionResponse 流(进度与完成)以更新用户。
-
最终返回:函数返回最终结果字典,它在结束的 FunctionResponse 中发送,表示完成。
智能体即工具¶
这个强大的功能允许你通过将系统中的其他智能体作为工具调用来利用它们的能力。智能体作为工具使你能够调用另一个智能体执行特定任务,有效地委托责任。这在概念上类似于创建一个调用另一个智能体并使用该智能体的响应作为函数返回值的 Python 函数。
与子智能体的关键区别¶
重要的是区分智能体作为工具和子智能体。
-
智能体作为工具: 当智能体 A 调用智能体 B 作为工具(使用智能体作为工具)时,智能体 B 的答案传回给智能体 A,然后智能体 A 对答案进行总结并生成对用户的响应。智能体 A 保持控制并继续处理未来的用户输入。
-
子智能体: 当智能体 A 调用智能体 B 作为子智能体时,回答用户的责任完全转移给智能体 B。智能体 A 实际上脱离了循环。所有后续用户输入都将由智能体 B 回答。
使用方法¶
要将智能体用作工具,请使用 AgentTool 类包装智能体。
自定义¶
- skip_summarization (Python/TypeScript) / skipSummarization (Kotlin/Java): (布尔值) 如果设置为 True,框架将绕过对工具智能体响应的基于 LLM 的总结。当工具的响应已经格式良好且不需要进一步处理时,这很有用。
??? "示例" {: #example-1 }
=== "Python"
```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.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)
```
=== "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 { 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();
```
=== "Go"
```go
import (
"google.golang.org/adk/agent"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/tool"
"google.golang.org/adk/tool/agenttool"
"google.golang.org/genai"
)
// createSummarizerAgent creates an agent whose sole purpose is to summarize text.
func createSummarizerAgent(ctx context.Context) (agent.Agent, error) {
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
if err != nil {
return nil, err
}
return llmagent.New(llmagent.Config{
Name: "SummarizerAgent",
Model: model,
Instruction: "You are an expert at summarizing text. Take the user's input and provide a concise summary.",
Description: "An agent that summarizes text.",
})
}
// createMainAgent creates the primary agent that will use the summarizer agent as a tool.
func createMainAgent(ctx context.Context, tools ...tool.Tool) (agent.Agent, error) {
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
if err != nil {
return nil, err
}
return llmagent.New(llmagent.Config{
Name: "MainAgent",
Model: model,
Instruction: "You are a helpful assistant. If you are asked to summarize a long text, use the 'summarize' tool. " +
"After getting the summary, present it to the user by saying 'Here is a summary of the text:'.",
Description: "The main agent that can delegate tasks.",
Tools: tools,
})
}
func RunAgentAsToolSimulation() {
ctx := context.Background()
// 1. Create the Tool Agent (Summarizer)
summarizerAgent, err := createSummarizerAgent(ctx)
if err != nil {
log.Fatalf("Failed to create summarizer agent: %v", err)
}
// 2. Wrap the Tool Agent in an AgentTool
summarizeTool := agenttool.New(summarizerAgent, &agenttool.Config{
SkipSummarization: true,
})
// 3. Create the Main Agent and provide it with the AgentTool
mainAgent, err := createMainAgent(ctx, summarizeTool)
if err != nil {
log.Fatalf("Failed to create main agent: %v", err)
}
// 4. Run the main agent
prompt := `
Please summarize this text for me:
Quantum computing represents a fundamentally different approach to computation,
leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.
`
fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
callAgent(context.Background(), mainAgent, prompt)
fmt.Println("\n---")
}
```
=== "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.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);
}
}
```
=== "Kotlin"
```kotlin
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")
}
}
}
```
工作原理¶
- 当
root_agent接收到长文本时,其指令会指示它对长文本使用 'summarize' 工具。 - 框架将 'summarize' 识别为包装
summary_agent的AgentTool。 - 在后台,
root_agent会将长文本作为输入调用summary_agent。 summary_agent将根据其指令处理文本并生成摘要。summary_agent的响应然后传回给root_agent。root_agent然后可以获取摘要并制定对用户的最终响应(例如,"这是文本的摘要:...")