Files
google__adk-docs/docs/tools-custom/function-tools.md
Shahin Saadati 674851324e Add the Kotlin tab for answering a long-running tool call (#2149)
* Show how a Kotlin client answers a long-running tool call

The function-tools page documents long-running tools in two halves: defining one
(which Kotlin already covered) and driving it from the client, which Kotlin did
not. Nothing in the docs showed a Kotlin reader how the deferred result gets
back to the model - the only Kotlin mention of longRunningToolIds in the repo is
a commented-out field listing in events/index.md.

The new region continues the reimbursement scenario the Kotlin tab above it
already sets up, rather than importing the nav-agent scenario the upstream demos
use. It shows the two things that are easy to get wrong:

- A pending call is one whose id the event also lists in `longRunningToolIds`;
  the FunctionResponse must reuse that id or the model cannot match the answer
  to the request it is waiting on.
- A resumable app must pass `invocationId` to the second `runAsync`. Without it
  the response opens a new invocation instead of resuming the paused one, which
  the page's own resume note warns about for Python. Grounded in
  ResumableLongRunningToolDemoAgent.kt:84-99 at the v0.8.0 tag.

Appended to the existing, already-registered LongRunningTool.kt instead of the
new file the backlog row proposed: this page already owns that snippet, and a
second file elsewhere would split one page's Kotlin across two directories.

Also added a bullet to "Key aspects of this example", which explains the group
purely in terms of `LongRunningFunctionTool` - a class Kotlin does not have. The
Kotlin form is `@Tool(isLongRunning = true)` or a `BaseTool` subclass, and a
long-running tool returning `Unit` suppresses even the placeholder response
(InvocationContext.kt:447).

Verified: runner.sh build and lint both PASS on the snippet (JDK 17),
check_kotlin_snippets.sh passes, L0/L5/L6 pass. L3 reports two orphaned-tab
problems at lines 123 and 227; both pre-date this change and are false
positives - rendering the page with the repo's own markdown extensions shows
every group, including the one edited here, as a single tabbed set with Kotlin
among its labels.

* Correct the long-running snippet's account of resume and turn count

Review against the v0.8.0 sources found three claims in this branch that a
reader would have acted on and been wrong.

The invocationId argument was the worst of them. The snippet took an
`appIsResumable` flag and passed `invocationId` on the second `runAsync`,
commenting that a resumable app must do so or the response opens a new
invocation. The runner does not work that way: `resolveInvocationId`
(AbstractRunner.kt:468-483) looks the id up from the function-call event that
matches the response's own id and discards whatever the caller passed. The flag
was inert, and anyone plumbing it through their call sites would have got
nothing for it. Both are gone; the comment now says what actually resumes the
invocation - the response id itself.

"Returns a placeholder and the turn ends" was wrong for the snippet's own
default. This tool returns a data class, not `Unit`, so a non-resumable app
emits the placeholder as a function response and calls the model again:
LongRunningToolIntegrationTest's scenario table records two model calls and a
trailing text event for that combination, and asserts it in
runAsync_longRunningToolReturnsDict_propagatesPayloadAndAcknowledges. A reader
building a HITL flow would have budgeted one model call and been surprised by an
interim reply. The KDoc and the page bullet now describe both modes.

Reusing the call id was described as something the model needs to match the
answer to its request. The model never gets that far: an unknown id throws from
HistoryRewriterProcessor.findMatchingFunctionCallEvent, and a null one throws
too, because the id set is built with mapNotNull and an empty set matches no
event. The comment now says it throws.

Also prints turn 1, which is where the interim reply appears, and says so when
the model answers without calling the tool instead of returning silently.

Verified: runner.sh build and lint both PASS (JDK 17), L0/L1/L2/L5/L6 pass, and
rendering the page with the repo's markdown extensions puts Kotlin in the target
group's tab set. L3's two orphaned-tab reports are pre-existing on main and are
false positives - the render shows those groups whole.

* Update function-tools.md

* Say that Kotlin resolves the invocation from the response itself

Adding a Kotlin tab to this section quietly extended the Resume note to
Kotlin, where it does not hold: resolveInvocationId matches the function
response's own call ID against the session and ignores the invocationId
the caller passes, so requiring one sends readers looking for a
parameter that changes nothing. An ID matching no call throws rather
than starting a fresh invocation.

Also fix subject-verb agreement in the turn-count bullet.

---------

Co-authored-by: Joe Fernandez <931947+joefernandez@users.noreply.github.com>
2026-08-31 14:32:08 -07:00

920 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Function tools
<div class="language-support-tag">
<span class="lst-supported">Supported in ADK</span><span class="lst-python">Python v0.1.0</span><span class="lst-typescript">TypeScript v0.2.0</span><span class="lst-go">Go v0.1.0</span><span class="lst-java">Java v0.1.0</span><span class="lst-kotlin">Kotlin v0.1.0</span>
</div>
When pre-built ADK tools don't meet your requirements, you can create custom
*function tools*. Building function tools allows you to create tailored
functionality, such as connecting to proprietary databases or implementing
unique algorithms. For example, a function tool, `myfinancetool`, might be a
function that calculates a specific financial metric. ADK also supports
long-running functions, so if that calculation takes a while, the agent can
continue working on other tasks.
ADK offers several ways to create functions tools, each suited to different
levels of complexity and control:
- [Function tools](#function-tool)
- [Long running function tools](#long-run-tool)
- [Agent-as-a-Tool](#agent-tool)
## Function tools {#function-tool}
Transforming a Python function into a tool is a straightforward way to integrate
custom logic into your agents. When you assign a function to an agent’s `tools`
list, the framework automatically wraps it as a `FunctionTool`.
### How it works
The ADK framework automatically inspects your Python function's
signature—including its name, docstring, parameters, type hints, and default
values—to generate a schema. This schema is what the LLM uses to understand the
tool's purpose, when to use it, and what arguments it requires.
### Define function signatures
A well-defined function signature is crucial for the LLM to use your tool
correctly.
#### Parameters
##### Required parameters
=== "Python"
A parameter is considered **required** if it has a type hint but **no
default value**. The LLM must provide a value for this argument when it
calls the tool. The parameter's description is taken from the function's
docstring.
???+ "Example: Required Parameters"
```python
def get_weather(city: str, unit: str):
"""
Retrieves the weather for a city in the specified unit.
Args:
city (str): The city name.
unit (str): The temperature unit, either 'Celsius' or 'Fahrenheit'.
"""
# ... function logic ...
return {"status": "success", "report": f"Weather for {city} is sunny."}
```
In this example, both `city` and `unit` are mandatory. If the LLM tries to
call `get_weather` without one of them, the ADK will return an error to the
LLM, prompting it to correct the call.
=== "Go"
In Go, you use struct tags to control the JSON schema. The two primary tags
are `json` and `jsonschema`.
A parameter is considered **required** if its struct field does **not** have
the `omitempty` or `omitzero` option in its `json` tag.
The `jsonschema` tag is used to provide the argument's description. This is
crucial for the LLM to understand what the argument is for.
???+ "Example: Required Parameters"
```go
// GetWeatherParams defines the arguments for the getWeather tool.
type GetWeatherParams struct {
// This field is REQUIRED (no "omitempty").
// The jsonschema tag provides the description.
Location string `json:"location" jsonschema:"The city and state, e.g., San Francisco, CA"`
// This field is also REQUIRED.
Unit string `json:"unit" jsonschema:"The temperature unit, either 'celsius' or 'fahrenheit'"`
}
```
In this example, both `location` and `unit` are mandatory.
=== "Java"
In Java, primitive types (e.g., `int`, `double`, `boolean`) are inherently
**required** because they cannot be null. For object types (like `String` or
`Integer`), they are typically considered required unless explicitly marked
as optional.
The `@Schema` annotation is used to provide the argument's description and
can explicitly define parameter properties. This is crucial for the LLM to
understand what the argument is for.
???+ "Example: Required Parameters"
```java
// The @Schema annotation on the parameter provides the description.
public static Map<String, Object> getWeather(
@Schema(description = "The city and state, e.g., San Francisco, CA", name = "location")
String location,
@Schema(description = "The temperature unit, either 'Celsius' or 'Fahrenheit'", name = "unit")
String unit) {
// ... function logic ...
return Map.of("status", "success", "report", "Weather for " + location + " is sunny.");
}
```
In this example, both `location` and `unit` are mandatory.
=== "Kotlin"
In Kotlin, parameters are considered **required** by default if they are of
a non-nullable type and have no default value. The LLM must provide a value
for these arguments.
The `@Param` annotation is used to provide the argument's description. This
is crucial for the LLM to understand what the argument is for.
???+ "Example: Required Parameters"
```kotlin
--8<-- "examples/kotlin/snippets/tools/function-tools/RequiredParams.kt:required_params"
```
In this example, both `location` and `unit` are mandatory.
##### Optional parameters
=== "Python"
A parameter is considered **optional** if you provide a **default value**.
This is the standard Python way to define optional arguments. You can also
mark a parameter as optional using `typing.Optional[SomeType]` or the `|
None` syntax (Python 3.10+).
Use defaults only for values that are truly optional. Do not add defaults
for information the model should derive from the user request or ask the
user to provide.
???+ "Example: Optional Parameters"
```python
def search_flights(destination: str, departure_date: str, flexible_days: int = 0):
"""
Searches for flights.
Args:
destination (str): The destination city.
departure_date (str): The desired departure date.
flexible_days (int, optional): Number of flexible days for the search. Defaults to 0.
"""
# ... function logic ...
if flexible_days > 0:
return {"status": "success", "report": f"Found flexible flights to {destination}."}
return {"status": "success", "report": f"Found flights to {destination} on {departure_date}."}
```
Here, `flexible_days` is optional. The LLM can choose to provide it, but
it's not required.
=== "Go"
A parameter is considered **optional** if its struct field has the
`omitempty` or `omitzero` option in its `json` tag.
???+ "Example: Optional Parameters"
```go
// GetWeatherParams defines the arguments for the getWeather tool.
type GetWeatherParams struct {
// Location is required.
Location string `json:"location" jsonschema:"The city and state, e.g., San Francisco, CA"`
// Unit is optional.
Unit string `json:"unit,omitempty" jsonschema:"The temperature unit, either 'celsius' or 'fahrenheit'"`
// Days is optional.
Days int `json:"days,omitzero" jsonschema:"The number of forecast days to return (defaults to 1)"`
}
```
Here, `unit` and `days` are optional. The LLM can choose to provide them,
but they are not required.
=== "Java"
A parameter can be considered **optional** in Java by using object types
that allow `null` values (such as `Integer` instead of `int`), or by
explicitly defining it as optional using `java.util.Optional`.
???+ "Example: Optional Parameters"
```java
import java.util.Map;
import java.util.Optional;
public static Map<String, Object> searchFlights(
@Schema(description = "The destination city.", name = "destination")
String destination,
@Schema(description = "The desired departure date.", name = "departureDate")
String departureDate,
@Schema(description = "Number of flexible days for the search. Defaults to 0.", name = "flexibleDays")
Optional<Integer> flexibleDays) {
// ... function logic ...
int days = flexibleDays.orElse(0);
if (days > 0) {
return Map.of("status", "success", "report", "Found flexible flights to " + destination + ".");
}
return Map.of("status", "success", "report", "Found flights to " + destination + " on " + departureDate + ".");
}
```
Here, `flexibleDays` is optional. The LLM can choose to provide it, but it's
not required.
=== "Kotlin"
In Kotlin, a parameter is considered **optional** if it is of a **nullable
type** or if it has a **default value**.
???+ "Example: Optional Parameters"
```kotlin
--8<-- "examples/kotlin/snippets/tools/function-tools/OptionalParams.kt:optional_params"
```
Here, `flexibleDays` is optional. The LLM can choose to provide it, but it's
not required.
##### Optional parameters with `typing.Optional`
You can also mark a parameter as optional using `typing.Optional[SomeType]` or
the `| None` syntax (Python 3.10+). This signals that the parameter can be
`None`. When combined with a default value of `None`, it behaves as a standard
optional parameter.
???+ "Example: `typing.Optional`"
=== "Python"
```python
from typing import Optional
def create_user_profile(username: str, bio: Optional[str] = None):
"""
Creates a new user profile.
Args:
username (str): The user's unique username.
bio (str, optional): A short biography for the user. Defaults to None.
"""
# ... function logic ...
if bio:
return {"status": "success", "message": f"Profile for {username} created with a bio."}
return {"status": "success", "message": f"Profile for {username} created."}
```
##### Variadic parameters (`*args` and `**kwargs`)
While you can include `*args` (variable positional arguments) and `**kwargs`
(variable keyword arguments) in your function signature for other purposes, they
are **ignored by the ADK framework** when generating the tool schema for the
LLM. The LLM will not be aware of them and cannot pass arguments to them. It's
best to rely on explicitly defined parameters for all data you expect from the
LLM.
#### Context injection
Context injection allows your custom functions to access the agent's
environment, such as session state or available actions. To enable, add a
parameter typed as `ToolContext` to your function. ADK automatically injects the
context data before your function runs and ensures this parameter is not visible
to the LLM.
```python
from google.adk.tools import ToolContext
def my_tool(arg1: str, tool_context: ToolContext):
# Example: Accessing session state
user_id = tool_context.state.get("user_id")
# Example: Triggering an action
# tool_context.actions.transfer_to_agent = "secondary_agent"
```
`ToolContext` provides access to:
- **`state`:** A dictionary-like object for session-scoped data.
- **`actions`:** Controls for agent behavior, for example `transfer_to_agent`.
- **Methods**: To handle artifacts, such as `load_artifact` or `save_artifact`.
##### Customize the parameter name
By default, the injected parameter is called `tool_context`, but you can name
the parameter anything you want. ADK detects it by its `ToolContext` type
annotation rather than by name. For example, to use the name `ctx`:
```python
from google.adk.tools import ToolContext
def my_tool(arg1: str, ctx: ToolContext):
# 'ctx' receives the ToolContext because of its type annotation
user_id = ctx.state.get("user_id")
```
#### Return type
The preferred return type for a Function Tool is a **dictionary** in Python, a
**Map** or custom **Record or POJO** in Java, an **object** in TypeScript, or a
**Map** or **Data Class** in Kotlin. This allows you to structure the response
with key-value pairs, providing context and clarity to the LLM. If your function
returns a type other than a dictionary or map, the framework automatically wraps
it into a dictionary with a single key named **"result"**.
Strive to make your return values as descriptive as possible. *For example,*
instead of returning a numeric error code, return a dictionary with an
"error_message" key containing a human-readable explanation. **Remember that the
LLM**, not a piece of code, needs to understand the result. As a best practice,
include a "status" key in your return dictionary to indicate the overall outcome
(e.g., "success", "error", "pending"), providing the LLM with a clear signal
about the operation's state.
#### Docstrings
The docstring of your function serves as the tool's **description** and is sent
to the LLM. Therefore, a well-written and comprehensive docstring is crucial for
the LLM to understand how to use the tool effectively. Clearly explain the
purpose of the function, the meaning of its parameters, and the expected return
values. In Java, you can use Javadoc comments or the
`@Schema(description="...")` annotation on your method to serve as this
description. In Kotlin, you can use KDoc comments or the
`@Tool(description="...")` and `@Param(description="...")` annotations to
provide these descriptions.
### Pass data between tools
When an agent calls multiple tools in a sequence, you might need to pass data
from one tool to another. The recommended way to do this is by using the `temp:`
prefix in the session state.
A tool can write data to a `temp:` variable, and a subsequent tool can read it.
This data is only available for the current invocation and is discarded
afterwards.
!!! note "Shared Invocation Context"
All tool calls within a single agent turn share the same
`InvocationContext`. This means they also share the same temporary (`temp:`)
state, which is how data can be passed between them.
### Example
??? "Example"
=== "Python"
This tool is a python function which obtains the Stock price of a given
Stock ticker/ symbol.
<u>Note</u>: You need to `pip install yfinance` library before using
this tool.
```python
--8<-- "examples/python/snippets/tools/function-tools/func_tool.py"
```
The return value from this tool will be wrapped into a dictionary.
```json
{"result": "$123"}
```
=== "TypeScript"
This tool retrieves the mocked value of a stock price.
```typescript
--8<-- "examples/typescript/snippets/tools/function-tools/function-tools-example.ts"
```
The return value from this tool will be an object.
```json
For input `GOOG`: {"price": 2800.0, "currency": "USD"}
```
=== "Go"
This tool retrieves the mocked value of a stock price.
```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"
)
--8<-- "examples/go/snippets/tools/function-tools/func_tool.go"
```
The return value from this tool will be a `getStockPriceResults` instance.
```json
For input `{"symbol": "GOOG"}`: {"price":300.6,"symbol":"GOOG"}
```
=== "Java"
This tool retrieves the mocked value of a stock price.
```java
--8<-- "examples/java/snippets/src/main/java/tools/StockPriceAgent.java:full_code"
```
The return value from this tool will be wrapped into a Map<String, Object>.
```json
For input `GOOG`: {"symbol": "GOOG", "price": "1.0"}
```
=== "Kotlin"
This tool retrieves the mocked value of a stock price.
```kotlin
--8<-- "examples/kotlin/snippets/tools/function-tools/FuncTool.kt:full_example"
```
The return value from this tool will be a Map.
```json
For input `GOOG`: {"symbol": "GOOG", "price": 123.45}
```
### Best practices
While you have considerable flexibility in defining your function, remember that
simplicity enhances usability for the LLM. Consider these guidelines:
- **Fewer Parameters are Better:** Minimize the number of parameters to reduce
complexity.
- **Simple Data Types:** Favor primitive data types like `str` and `int` over
custom classes whenever possible.
- **Meaningful Names:** The function's name and parameter names significantly
influence how the LLM interprets and utilizes the tool. Choose names that
clearly reflect the function's purpose and the meaning of its inputs. Avoid
generic names like `do_stuff()` or `beAgent()`.
- **Build for Parallel Execution:** Improve function calling performance when
multiple tools are run by building for asynchronous operation. For information
on enabling parallel execution for tools, see [Increase tool performance with
parallel execution](/tools-custom/performance/).
## Long running function tools {#long-run-tool}
This tool is designed to help you start and manage tasks that are handled
outside the operation of your agent workflow, and require a significant amount
of processing time, without blocking the agent's execution. This tool is a
subclass of `FunctionTool`.
When using a `LongRunningFunctionTool`, your function can initiate the
long-running operation and optionally return an **initial result**, such as a
long-running operation id. Once a long running function tool is invoked the
agent runner pauses the agent run and lets the agent client to decide whether to
continue or wait until the long-running operation finishes. The agent client can
query the progress of the long-running operation and send back an intermediate
or final response. The agent can then continue with other tasks. An example is
the human-in-the-loop scenario where the agent needs human approval before
proceeding with a task.
!!! warning "Warning: Execution handling"
Long Running Function Tools are designed to help you start and *manage* long
running tasks as part of your agent workflow, but ***not perform*** the
actual, long task. For tasks that require significant time to complete, you
should implement a separate server to do the task.
!!! tip "Tip: Parallel execution"
Depending on the type of tool you are building, designing for asynchronous
operation may be a better solution than creating a long running tool. For
more information, see [Increase tool performance with parallel
execution](/tools-custom/performance/).
### How it works
In Python, you wrap a function with `LongRunningFunctionTool`. In Java, you pass
a Method name to `LongRunningFunctionTool.create()`. In TypeScript, you
instantiate the `LongRunningFunctionTool` class.
1. **Initiation:** When the LLM calls the tool, your function starts the
long-running operation.
2. **Initial Updates:** Your function should optionally return an initial result
(e.g. the long-running operation id). The ADK framework takes the result and
sends it back to the LLM packaged within a `FunctionResponse`. This allows
the LLM to inform the user (e.g., status, percentage complete, messages). And
then the agent run is ended / paused.
3. **Continue or Wait:** After each agent run is completed. Agent client can
query the progress of the long-running operation and decide whether to
continue the agent run with an intermediate response (to update the progress)
or wait until a final response is retrieved. Agent client should send the
intermediate or final response back to the agent for the next run.
4. **Framework Handling:** The ADK framework manages the execution. It sends the
intermediate or final `FunctionResponse` sent by agent client to the LLM to
generate a user friendly message.
### Create the tool
Define your tool function and wrap it using the `LongRunningFunctionTool` class:
=== "Python"
```python
--8<-- "examples/python/snippets/tools/function-tools/human_in_the_loop.py:define_long_running_function"
```
=== "TypeScript"
```typescript
--8<-- "examples/typescript/snippets/tools/function-tools/long-running-function-tool-example.ts:define_long_running_function"
```
=== "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/tool"
"google.golang.org/adk/v2/tool/functiontool"
"google.golang.org/genai"
)
--8<-- "examples/go/snippets/tools/function-tools/long-running-tool/long_running_tool.go:create_long_running_tool"
```
=== "Java"
```java
import com.google.adk.agents.LlmAgent;
import com.google.adk.tools.LongRunningFunctionTool;
import java.util.HashMap;
import java.util.Map;
public class ExampleLongRunningFunction {
// Define your Long Running function.
// Ask for approval for the reimbursement.
public static Map<String, Object> askForApproval(String purpose, double amount) {
// Simulate creating a ticket and sending a notification
System.out.println(
"Simulating ticket creation for purpose: " + purpose + ", amount: " + amount);
// Send a notification to the approver with the link of the ticket
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 {
// Pass the method to LongRunningFunctionTool.create
LongRunningFunctionTool approveTool =
LongRunningFunctionTool.create(ExampleLongRunningFunction.class, "askForApproval");
// Include the tool in the agent
LlmAgent approverAgent =
LlmAgent.builder()
// ...
.tools(approveTool)
.build();
}
}
```
=== "Kotlin"
In Kotlin, you can create a long-running function tool by setting the
`isLongRunning` property to `true` in the `@Tool` annotation.
```kotlin
--8<-- "examples/kotlin/snippets/tools/function-tools/LongRunningTool.kt:long_running_tool"
```
### Intermediate / final result updates
Agent client received an event with long running function calls and check the
status of the ticket. Then Agent client can send the intermediate or final
response back to update the progress. The framework packages this value (even if
it's None) into the content of the `FunctionResponse` sent back to the LLM.
!!! note "Note: Long running function response with Resume feature"
If your ADK agent workflow is configured with the [Resume](/runtime/resume/)
feature, you also must include the Invocation ID (`invocation_id`) parameter
with the long running function response. The Invocation ID you provide must
be the same invocation that generated the long running function request,
otherwise the system starts a new invocation with the response. If your
agent uses the Resume feature, consider including the Invocation ID as a
parameter with your long running function request, so it can be included
with the response. For more details on using the Resume feature, see [Resume
stopped agents](/runtime/resume/).
In **Kotlin**, the runner resolves the invocation from the function
response's own call ID, so you do not need to pass `invocationId` to
`runAsync`. A response whose ID matches no function call in the session
throws instead.
??? Tip "Applies to only Java ADK"
When passing `ToolContext` with Function Tools, ensure that one of the
following is true:
- The Schema is passed with the ToolContext parameter in the function
signature, like:
```
@com.google.adk.tools.Annotations.Schema(name = "toolContext") ToolContext toolContext
```
OR
- The following `-parameters` flag is set to the mvn compiler plugin
```
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.0</version> <!-- or newer -->
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
```
=== "Python"
```python
--8<-- "examples/python/snippets/tools/function-tools/human_in_the_loop.py:call_reimbursement_tool"
```
=== "TypeScript"
```typescript
--8<-- "examples/typescript/snippets/tools/function-tools/long-running-function-tool-example.ts"
```
=== "Go"
The following example demonstrates a multi-turn workflow. First, the user
asks the agent to create a ticket. The agent calls the long-running tool and
the client captures the `FunctionCall` ID. The client then simulates the
asynchronous work completing by sending subsequent `FunctionResponse`
messages back to the agent to provide the ticket ID and final status.
```go
--8<-- "examples/go/snippets/tools/function-tools/long-running-tool/long_running_tool.go:run_long_running_tool"
```
=== "Java"
```java
--8<-- "examples/java/snippets/src/main/java/tools/LongRunningFunctionExample.java:full_code"
```
=== "Kotlin"
```kotlin
--8<-- "examples/kotlin/snippets/tools/function-tools/LongRunningTool.kt:call_reimbursement_tool"
```
??? "Python complete example: File Processing Simulation"
```python
--8<-- "examples/python/snippets/tools/function-tools/human_in_the_loop.py"
```
#### Key aspects of this example
- **`LongRunningFunctionTool`**: Wraps the supplied method/function; the
framework handles sending yielded updates and the final return value as
sequential FunctionResponses.
- **Agent instruction**: Directs the LLM to use the tool and understand the
incoming FunctionResponse stream (progress vs. completion) for user updates.
- **Final return**: The function returns the final result dictionary, which is
sent in the concluding FunctionResponse to indicate completion.
- **Kotlin has no `LongRunningFunctionTool` class**: Annotate the function with
`@Tool(isLongRunning = true)`, or pass `isLongRunning = true` to a `BaseTool`
subclass.
- **Kotlin turn count**: The tool above returns a value rather than
`Unit`, so non-resumable apps send that placeholder to the model and call it a
second time, ending turn 1 in an interim reply. A resumable app pauses on the
function call with no second model call. Returning `Unit` suppresses the
placeholder response entirely, ending the turn on the function call in either
mode.
## Agent-as-a-Tool {#agent-tool}
This feature allows you to leverage the capabilities of other agents within your
system by calling them as tools. The Agent-as-a-Tool enables you to invoke
another agent to perform a specific task, effectively **delegating
responsibility**. This is conceptually similar to creating a Python function
that calls another agent and uses the agent's response as the function's return
value.
### Key difference from sub-agents
It's important to distinguish an Agent-as-a-Tool from a sub-agent.
- **Agent-as-a-Tool:** When Agent A calls Agent B as a tool (using
Agent-as-a-Tool), Agent B's answer is **passed back** to Agent A, which then
summarizes the answer and generates a response to the user. Agent A retains
control and continues to handle future user input.
- **Sub-agent:** When Agent A calls Agent B as a sub-agent, the responsibility
of answering the user is completely **transferred to Agent B**. Agent A is
effectively out of the loop. All subsequent user input will be answered by
Agent B.
### Use `AgentTool`
To use an agent as a tool, wrap the agent with the `AgentTool` class.
=== "Python"
```python
tools=[AgentTool(agent=agent_b)]
```
=== "TypeScript"
```typescript
tools: [new AgentTool({agent: agentB})]
```
=== "Go"
```go
agenttool.New(agent, &agenttool.Config{...})
```
=== "Java"
```java
AgentTool.create(agent)
```
=== "Kotlin"
```kotlin
AgentTool(agent = agentB)
```
### Customize your agent tool
The `AgentTool` class provides the following attributes for customizing its
behavior.
#### Skip summarization
**`skip_summarization`** (boolean)
If set to `True`, this customization instructs the framework to bypass the LLM-based summarization of the tool agent's response. This feature is best used when the tool's output is already well-formatted and requires no further processing.
- **Use:** Python/TypeScript (`skip_summarization`); Kotlin/Java
(`skipSummarization`).
??? "Example"
=== "Python"
```python
--8<-- "examples/python/snippets/tools/function-tools/summarizer.py"
```
=== "TypeScript"
```typescript
--8<-- "examples/typescript/snippets/tools/function-tools/agent-as-a-tool-example.ts"
```
=== "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/tool"
"google.golang.org/adk/v2/tool/agenttool"
"google.golang.org/genai"
)
--8<-- "examples/go/snippets/tools/function-tools/func_tool.go:agent_tool_example"
```
=== "Java"
```java
--8<-- "examples/java/snippets/src/main/java/tools/AgentToolCustomization.java:full_code"
```
=== "Kotlin"
```kotlin
--8<-- "examples/kotlin/snippets/tools/function-tools/AgentTool.kt:agent_tool"
```
##### How it works
1. When the `root_agent` receives the long text, its instruction tells it to use
the 'summarize' tool for long texts.
2. The framework recognizes 'summarize' as an `AgentTool` that wraps the
`summary_agent`.
3. Behind the scenes, the `root_agent` will call the `summary_agent` with the
long text as input.
4. The `summary_agent` will process the text according to its instruction and
generate a summary.
5. **The response from the `summary_agent` is then passed back to the
`root_agent`.**
6. The `root_agent` can then take the summary and formulate its final response
to the user (e.g., "Here's a summary of the text: ...")
#### Propagate grounding metadata
**`propagate_grounding_metadata`** (boolean, default: `False`)
If set to `True`, the tool automatically forwards any grounding metadata, such as Google Search citations, generated by the sub-agent up to the parent agent's session state. This customization ensures that citations are preserved when using specialized search agents as tools.
=== "Python"
```python
from google.adk.agents import Agent
from google.adk.tools import AgentTool
search_specialist_agent = Agent(
# Specify your generative model
model="gemini-flash-latest",
name="search_specialist_agent",
instruction=(
"You are a search expert. Find and "
"compile citations on requested topics."
),
# Add any search tools here
)
search_agent_tool = AgentTool(
agent=search_specialist_agent,
# Keeps citations intact back to the root
propagate_grounding_metadata=True
)
root_agent = Agent(
model="gemini-flash-latest",
name="root_agent",
description=(
"A central coordinator that delegates "
"to specialist agents."
),
tools=[search_agent_tool]
)
```
#### Control plugin inheritance
When you wrap an agent with `AgentTool`, you can control whether it
inherits plugins from the parent runner using the `include_plugins`
parameter.
* **`include_plugins=True` (default):** The child agent inherits all
plugins from the parent, preserving trace spans and event streaming.
* **`include_plugins=False`:** The child agent runs in an isolated
environment without inheriting any plugins from the parent. Use this
setting to ensure an agent's execution is self-contained and unaffected
by the parent's plugin environment.
=== "Python"
```python
from google.adk.tools import agent_tool
# Placeholder definition for MyImageAgent
class MyImageAgent:
def __init__(
self, name="My Agent", description="A simple image agent."
):
self.name = name
# Added description attribute
self.description = description
# Example 1: Isolate MyImageAgent from parent plugins
my_isolated_tool = agent_tool.AgentTool(
agent=MyImageAgent(), # Instantiate MyImageAgent
include_plugins=False
)
# Example 2: Inherit plugins
my_observable_tool = agent_tool.AgentTool(
agent=MyImageAgent(), # Instantiate MyImageAgent
include_plugins=True
)
```