From c3846304317c4ac17d386c9bf089884de512078a Mon Sep 17 00:00:00 2001 From: Joe Fernandez Date: Sat, 6 Dec 2025 00:51:07 +0000 Subject: [PATCH] docs: Add version tags for Build Agents topics - add version tags - move TS content to second position (after Python) --- docs/agents/custom-agents.md | 200 ++- docs/agents/index.md | 2 +- docs/agents/llm-agents.md | 288 +++-- docs/agents/models.md | 2 +- docs/agents/multi-agents.md | 1088 ++++++++--------- docs/agents/workflow-agents/index.md | 4 +- docs/agents/workflow-agents/loop-agents.md | 12 +- .../agents/workflow-agents/parallel-agents.md | 11 +- .../workflow-agents/sequential-agents.md | 12 +- docs/index.md | 14 +- docs/tools-custom/function-tools.md | 148 +-- docs/tools-custom/index.md | 2 +- docs/tools-custom/mcp-tools.md | 2 +- 13 files changed, 889 insertions(+), 896 deletions(-) diff --git a/docs/agents/custom-agents.md b/docs/agents/custom-agents.md index fcd7e7c73..35e6f7a1e 100644 --- a/docs/agents/custom-agents.md +++ b/docs/agents/custom-agents.md @@ -1,11 +1,7 @@ # Custom agents
- Supported in ADK - Python v0.1.0 - Go v0.1.0 - Java v0.1.0 - TypeScript v0.1.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.1.0
Custom agents provide the ultimate flexibility in ADK, allowing you to define **arbitrary orchestration logic** by inheriting directly from `BaseAgent` and implementing your own control flow. This goes beyond the predefined patterns of `SequentialAgent`, `LoopAgent`, and `ParallelAgent`, enabling you to build highly specific and complex agentic workflows. @@ -18,7 +14,7 @@ Custom agents provide the ultimate flexibility in ADK, allowing you to define ** ### What is a Custom Agent? -A Custom Agent is essentially any class you create that inherits from `google.adk.agents.BaseAgent` and implements its core execution logic within the `_run_async_impl` asynchronous method. You have complete control over how this method calls other agents (sub-agents), manages state, and handles events. +A Custom Agent is essentially any class you create that inherits from `google.adk.agents.BaseAgent` and implements its core execution logic within the `_run_async_impl` asynchronous method. You have complete control over how this method calls other agents (sub-agents), manages state, and handles events. !!! Note The specific method name for implementing an agent's core asynchronous logic may vary slightly by SDK language (e.g., `runAsyncImpl` in Java, `_run_async_impl` in Python, or `runAsyncImpl` in TypeScript). Refer to the language-specific API documentation for details. @@ -44,11 +40,19 @@ The core of any custom agent is the method where you define its unique asynchron === "Python" The heart of any custom agent is the `_run_async_impl` method. This is where you define its unique behavior. - + * **Signature:** `async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]:` * **Asynchronous Generator:** It must be an `async def` function and return an `AsyncGenerator`. This allows it to `yield` events produced by sub-agents or its own logic back to the runner. * **`ctx` (InvocationContext):** Provides access to crucial runtime information, most importantly `ctx.session.state`, which is the primary way to share data between steps orchestrated by your custom agent. +=== "TypeScript" + + The heart of any custom agent is the `runAsyncImpl` method. This is where you define its unique behavior. + + * **Signature:** `async* runAsyncImpl(ctx: InvocationContext): AsyncGenerator` + * **Asynchronous Generator:** It must be an `async` generator function (`async*`). + * **`ctx` (InvocationContext):** Provides access to crucial runtime information, most importantly `ctx.session.state`, which is the primary way to share data between steps orchestrated by your custom agent. + === "Go" In Go, you implement the `Run` method as part of a struct that satisfies the `agent.Agent` interface. The actual logic is typically a method on your custom agent struct. @@ -66,14 +70,6 @@ The core of any custom agent is the method where you define its unique asynchron * **Reactive Stream (`Flowable`):** It must return an `io.reactivex.rxjava3.core.Flowable`. This `Flowable` represents a stream of events that will be produced by the custom agent's logic, often by combining or transforming multiple `Flowable` from sub-agents. * **`ctx` (InvocationContext):** Provides access to crucial runtime information, most importantly `ctx.session().state()`, which is a `java.util.concurrent.ConcurrentMap`. This is the primary way to share data between steps orchestrated by your custom agent. -=== "TypeScript" - - The heart of any custom agent is the `runAsyncImpl` method. This is where you define its unique behavior. - - * **Signature:** `async* runAsyncImpl(ctx: InvocationContext): AsyncGenerator` - * **Asynchronous Generator:** It must be an `async` generator function (`async*`). - * **`ctx` (InvocationContext):** Provides access to crucial runtime information, most importantly `ctx.session.state`, which is the primary way to share data between steps orchestrated by your custom agent. - **Key Capabilities within the Core Asynchronous Method:** === "Python" @@ -87,22 +83,53 @@ The core of any custom agent is the method where you define its unique asynchron ``` 2. **Managing State:** Read from and write to the session state dictionary (`ctx.session.state`) to pass data between sub-agent calls or make decisions: + ```python # Read data set by a previous agent previous_result = ctx.session.state.get("some_key") - + # Make a decision based on state if previous_result == "some_value": # ... call a specific sub-agent ... else: # ... call another sub-agent ... - + # Store a result for a later step (often done via a sub-agent's output_key) # ctx.session.state["my_custom_result"] = "calculated_value" ``` 3. **Implementing Control Flow:** Use standard Python constructs (`if`/`elif`/`else`, `for`/`while` loops, `try`/`except`) to create sophisticated, conditional, or iterative workflows involving your sub-agents. +=== "TypeScript" + + 1. **Calling Sub-Agents:** You invoke sub-agents (which are typically stored as instance properties like `this.myLlmAgent`) using their `run` method and yield their events: + + ```typescript + for await (const event of this.someSubAgent.runAsync(ctx)) { + // Optionally inspect or log the event + yield event; // Pass the event up to the runner + } + ``` + + 2. **Managing State:** Read from and write to the session state object (`ctx.session.state`) to pass data between sub-agent calls or make decisions: + + ```typescript + // Read data set by a previous agent + const previousResult = ctx.session.state['some_key']; + + // Make a decision based on state + if (previousResult === 'some_value') { + // ... call a specific sub-agent ... + } else { + // ... call another sub-agent ... + } + + // Store a result for a later step (often done via a sub-agent's outputKey) + // ctx.session.state['my_custom_result'] = 'calculated_value'; + ``` + + 3. **Implementing Control Flow:** Use standard TypeScript/JavaScript constructs (`if`/`else`, `for`/`while` loops, `try`/`catch`) to create sophisticated, conditional, or iterative workflows involving your sub-agents. + === "Go" 1. **Calling Sub-Agents:** You invoke sub-agents by calling their `Run` method. @@ -154,22 +181,22 @@ The core of any custom agent is the method where you define its unique asynchron ```java // Example: Running one sub-agent // return someSubAgent.runAsync(ctx); - + // Example: Running sub-agents sequentially Flowable firstAgentEvents = someSubAgent1.runAsync(ctx) .doOnNext(event -> System.out.println("Event from agent 1: " + event.id())); - + Flowable secondAgentEvents = Flowable.defer(() -> someSubAgent2.runAsync(ctx) .doOnNext(event -> System.out.println("Event from agent 2: " + event.id())) ); - + return firstAgentEvents.concatWith(secondAgentEvents); ``` The `Flowable.defer()` is often used for subsequent stages if their execution depends on the completion or state after prior stages. 2. **Managing State:** Read from and write to the session state to pass data between sub-agent calls or make decisions. The session state is a `java.util.concurrent.ConcurrentMap` obtained via `ctx.session().state()`. - + ```java // Read data set by a previous agent Object previousResult = ctx.session().state().get("some_key"); @@ -190,36 +217,6 @@ The core of any custom agent is the method where you define its unique asynchron * **Conditional:** `Flowable.defer()` to choose which `Flowable` to subscribe to based on a condition, or `filter()` if you're filtering events within a stream. * **Iterative:** Operators like `repeat()`, `retry()`, or by structuring your `Flowable` chain to recursively call parts of itself based on conditions (often managed with `flatMapPublisher` or `concatMap`). -=== "TypeScript" - - 1. **Calling Sub-Agents:** You invoke sub-agents (which are typically stored as instance properties like `this.myLlmAgent`) using their `run` method and yield their events: - - ```typescript - for await (const event of this.someSubAgent.runAsync(ctx)) { - // Optionally inspect or log the event - yield event; // Pass the event up to the runner - } - ``` - - 2. **Managing State:** Read from and write to the session state object (`ctx.session.state`) to pass data between sub-agent calls or make decisions: - - ```typescript - // Read data set by a previous agent - const previousResult = ctx.session.state['some_key']; - - // Make a decision based on state - if (previousResult === 'some_value') { - // ... call a specific sub-agent ... - } else { - // ... call another sub-agent ... - } - - // Store a result for a later step (often done via a sub-agent's outputKey) - // ctx.session.state['my_custom_result'] = 'calculated_value'; - ``` - - 3. **Implementing Control Flow:** Use standard TypeScript/JavaScript constructs (`if`/`else`, `for`/`while` loops, `try`/`catch`) to create sophisticated, conditional, or iterative workflows involving your sub-agents. - ## Managing Sub-Agents and State Typically, a custom agent orchestrates other agents (like `LlmAgent`, `LoopAgent`, etc.). @@ -243,11 +240,22 @@ Let's illustrate the power of custom agents with an example pattern: a multi-sta === "Python" We define the `StoryFlowAgent` inheriting from `BaseAgent`. In `__init__`, we store the necessary sub-agents (passed in) as instance attributes and tell the `BaseAgent` framework about the top-level agents this custom agent will directly orchestrate. - + ```python --8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py:init" ``` +=== "TypeScript" + + We define the `StoryFlowAgent` by extending `BaseAgent`. In its constructor, we: + 1. Create any internal composite agents (like `LoopAgent` or `SequentialAgent`). + 2. Pass the list of all top-level sub-agents to the `super()` constructor. + 3. Store the sub-agents (passed in or created internally) as instance properties (e.g., `this.storyGenerator`) so they can be accessed in the custom `runImpl` logic. + + ```typescript + --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts:init" + ``` + === "Go" We define the `StoryFlowAgent` struct and a constructor. In the constructor, we store the necessary sub-agents and tell the `BaseAgent` framework about the top-level agents this custom agent will directly orchestrate. @@ -264,17 +272,6 @@ Let's illustrate the power of custom agents with an example pattern: a multi-sta --8<-- "examples/java/snippets/src/main/java/agents/StoryFlowAgentExample.java:init" ``` -=== "TypeScript" - - We define the `StoryFlowAgent` by extending `BaseAgent`. In its constructor, we: - 1. Create any internal composite agents (like `LoopAgent` or `SequentialAgent`). - 2. Pass the list of all top-level sub-agents to the `super()` constructor. - 3. Store the sub-agents (passed in or created internally) as instance properties (e.g., `this.storyGenerator`) so they can be accessed in the custom `runImpl` logic. - - ```typescript - --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts:init" - ``` - --- ### Part 2: Defining the Custom Execution Logic { #part-2-defining-the-custom-execution-logic } @@ -282,7 +279,7 @@ Let's illustrate the power of custom agents with an example pattern: a multi-sta === "Python" This method orchestrates the sub-agents using standard Python async/await and control flow. - + ```python --8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py:executionlogic" ``` @@ -293,6 +290,20 @@ Let's illustrate the power of custom agents with an example pattern: a multi-sta 3. The `sequential_agent` runs, calling `grammar_check` then `tone_check`, reading `current_story` and writing `grammar_suggestions` and `tone_check_result` to the state. 4. **Custom Part:** The `if` statement checks the `tone_check_result` from the state. If it's "negative", the `story_generator` is called *again*, overwriting the `current_story` in the state. Otherwise, the flow ends. +=== "TypeScript" + + The `runImpl` method orchestrates the sub-agents using standard TypeScript `async`/`await` and control flow. The `runLiveImpl` is also added to handle live streaming scenarios. + + ```typescript + --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts:executionlogic" + ``` + **Explanation of Logic:** + + 1. The initial `storyGenerator` runs. Its output is expected to be in `ctx.session.state['current_story']`. + 2. The `loopAgent` runs, which internally calls the `critic` and `reviser` sequentially for `maxIterations` times. They read/write `current_story` and `criticism` from/to the state. + 3. The `sequentialAgent` runs, calling `grammarCheck` then `toneCheck`, reading `current_story` and writing `grammar_suggestions` and `tone_check_result` to the state. + 4. **Custom Part:** The `if` statement checks the `tone_check_result` from the state. If it's "negative", the `storyGenerator` is called *again*, overwriting the `current_story` in the state. Otherwise, the flow ends. + === "Go" The `Run` method orchestrates the sub-agents by calling their respective `Run` methods in a loop and yielding their events. @@ -307,9 +318,8 @@ Let's illustrate the power of custom agents with an example pattern: a multi-sta 3. The `postProcessorAgent` runs, calling `grammar_check` then `tone_check`, reading `current_story` and writing `grammar_suggestions` and `tone_check_result` to the state. 4. **Custom Part:** The code checks the `tone_check_result` from the state. If it's "negative", the `story_generator` is called *again*, overwriting the `current_story` in the state. Otherwise, the flow ends. - === "Java" - + The `runAsyncImpl` method orchestrates the sub-agents using RxJava's Flowable streams and operators for asynchronous control flow. ```java @@ -322,21 +332,6 @@ Let's illustrate the power of custom agents with an example pattern: a multi-sta 3. Then, the `sequentialAgent's` Flowable executes. It calls the `grammar_check` then `tone_check`, reading `current_story` and writing `grammar_suggestions` and `tone_check_result` to the state. 4. **Custom Part:** After the sequentialAgent completes, logic within a `Flowable.defer` checks the "tone_check_result" from `invocationContext.session().state()`. If it's "negative", the `storyGenerator` Flowable is *conditionally concatenated* and executed again, overwriting "current_story". Otherwise, an empty Flowable is used, and the overall workflow proceeds to completion. -=== "TypeScript" - - The `runImpl` method orchestrates the sub-agents using standard TypeScript `async`/`await` and control flow. The `runLiveImpl` is also added to handle live streaming scenarios. - - ```typescript - --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts:executionlogic" - ``` - **Explanation of Logic:** - - 1. The initial `storyGenerator` runs. Its output is expected to be in `ctx.session.state['current_story']`. - 2. The `loopAgent` runs, which internally calls the `critic` and `reviser` sequentially for `maxIterations` times. They read/write `current_story` and `criticism` from/to the state. - 3. The `sequentialAgent` runs, calling `grammarCheck` then `toneCheck`, reading `current_story` and writing `grammar_suggestions` and `tone_check_result` to the state. - 4. **Custom Part:** The `if` statement checks the `tone_check_result` from the state. If it's "negative", the `storyGenerator` is called *again*, overwriting the `current_story` in the state. Otherwise, the flow ends. - - --- ### Part 3: Defining the LLM Sub-Agents { #part-3-defining-the-llm-sub-agents } @@ -353,6 +348,12 @@ These are standard `LlmAgent` definitions, responsible for specific tasks. Their --8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py:llmagents" ``` +=== "TypeScript" + + ```typescript + --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts:llmagents" + ``` + === "Go" ```go @@ -365,11 +366,6 @@ These are standard `LlmAgent` definitions, responsible for specific tasks. Their --8<-- "examples/java/snippets/src/main/java/agents/StoryFlowAgentExample.java:llmagents" ``` -=== "TypeScript" - - ```typescript - --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts:llmagents" - --- ### Part 4: Instantiating and Running the custom agent { #part-4-instantiating-and-running-the-custom-agent } @@ -382,6 +378,12 @@ Finally, you instantiate your `StoryFlowAgent` and use the `Runner` as usual. --8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py:story_flow_agent" ``` +=== "TypeScript" + + ```typescript + --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts:story_flow_agent" + ``` + === "Go" ```go @@ -394,12 +396,6 @@ Finally, you instantiate your `StoryFlowAgent` and use the `Runner` as usual. --8<-- "examples/java/snippets/src/main/java/agents/StoryFlowAgentExample.java:story_flow_agent" ``` -=== "TypeScript" - - ```typescript - --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts:story_flow_agent" - ``` - *(Note: The full runnable code, including imports and execution logic, can be found linked below.)* --- @@ -409,12 +405,19 @@ Finally, you instantiate your `StoryFlowAgent` and use the `Runner` as usual. ???+ "Storyflow Agent" === "Python" - + ```python # Full runnable code for the StoryFlowAgent example --8<-- "examples/python/snippets/agents/custom-agent/storyflow_agent.py" ``` - + + === "TypeScript" + + ```typescript + # Full runnable code for the StoryFlowAgent example + --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts" + ``` + === "Go" ```go @@ -423,15 +426,8 @@ Finally, you instantiate your `StoryFlowAgent` and use the `Runner` as usual. ``` === "Java" - + ```java # Full runnable code for the StoryFlowAgent example --8<-- "examples/java/snippets/src/main/java/agents/StoryFlowAgentExample.java:full_code" ``` - - === "TypeScript" - - ```typescript - # Full runnable code for the StoryFlowAgent example - --8<-- "examples/typescript/snippets/agents/cutsom-agent/storyflow_agent.ts" - ``` diff --git a/docs/agents/index.md b/docs/agents/index.md index f58e21466..03011bbbe 100644 --- a/docs/agents/index.md +++ b/docs/agents/index.md @@ -1,7 +1,7 @@ # Agents
- Supported in ADKPythonGoJava + Supported in ADKPythonTypeScriptGoJava
In the Agent Development Kit (ADK), an **Agent** is a self-contained execution unit designed to act autonomously to achieve specific goals. Agents can perform tasks, interact with users, utilize external tools, and coordinate with other agents. diff --git a/docs/agents/llm-agents.md b/docs/agents/llm-agents.md index 509a7532a..6c4b36eff 100644 --- a/docs/agents/llm-agents.md +++ b/docs/agents/llm-agents.md @@ -1,11 +1,7 @@ # LLM Agent
- Supported in ADK - Python v0.1.0 - Go v0.1.0 - Java v0.1.0 - TypeScript v0.1.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.1.0
The `LlmAgent` (often aliased simply as `Agent`) is a core component in ADK, @@ -57,6 +53,18 @@ First, you need to establish what the agent *is* and what it's *for*. ) ``` +=== "Typescript" + + ```typescript + // Example: Defining the basic identity + const capitalAgent = new LlmAgent({ + model: 'gemini-2.5-flash', + name: 'capital_agent', + description: 'Answers user questions about the capital city of a given country.', + // instruction and tools will be added next + }); + ``` + === "Go" ```go @@ -76,18 +84,6 @@ First, you need to establish what the agent *is* and what it's *for*. .build(); ``` -=== "Typescript" - - ```typescript - // Example: Defining the basic identity - const capitalAgent = new LlmAgent({ - model: 'gemini-2.5-flash', - name: 'capital_agent', - description: 'Answers user questions about the capital city of a given country.', - // instruction and tools will be added next - }); - ``` - ## Guiding the Agent: Instructions (`instruction`) The `instruction` parameter is arguably the most critical for shaping an @@ -134,6 +130,26 @@ tells the agent: ) ``` +=== "Typescript" + + ```typescript + // Example: Adding instructions + const capitalAgent = new LlmAgent({ + model: 'gemini-2.5-flash', + name: 'capital_agent', + description: 'Answers user questions about the capital city of a given country.', + instruction: `You are an agent that provides the capital city of a country. + When a user asks for the capital of a country: + 1. Identify the country name from the user's query. + 2. Use the \`getCapitalCity\` tool to find the capital. + 3. Respond clearly to the user, stating the capital city. + Example Query: "What's the capital of {country}?" + Example Response: "The capital of France is Paris." + `, + // tools will be added next + }); + ``` + === "Go" ```go @@ -163,26 +179,6 @@ tells the agent: .build(); ``` -=== "Typescript" - - ```typescript - // Example: Adding instructions - const capitalAgent = new LlmAgent({ - model: 'gemini-2.5-flash', - name: 'capital_agent', - description: 'Answers user questions about the capital city of a given country.', - instruction: `You are an agent that provides the capital city of a country. - When a user asks for the capital of a country: - 1. Identify the country name from the user's query. - 2. Use the \`getCapitalCity\` tool to find the capital. - 3. Respond clearly to the user, stating the capital city. - Example Query: "What's the capital of {country}?" - Example Response: "The capital of France is Paris." - `, - // tools will be added next - }); - ``` - *(Note: For instructions that apply to *all* agents in a system, consider using `global_instruction` on the root agent, detailed further in the [Multi-Agents](multi-agents.md) section.)* @@ -211,7 +207,7 @@ on the conversation and its instructions. # Replace with actual logic (e.g., API call, database lookup) capitals = {"france": "Paris", "japan": "Tokyo", "canada": "Ottawa"} return capitals.get(country.lower(), f"Sorry, I don't know the capital of {country}.") - + # Add the tool to the agent capital_agent = LlmAgent( model="gemini-2.0-flash", @@ -222,45 +218,6 @@ on the conversation and its instructions. ) ``` -=== "Go" - - ```go - --8<-- "examples/go/snippets/agents/llm-agents/snippets/main.go:tool_example" - ``` - -=== "Java" - - ```java - - // Define a tool function - // Retrieves the capital city of a given country. - public static Map getCapitalCity( - @Schema(name = "country", description = "The country to get capital for") - String country) { - // Replace with actual logic (e.g., API call, database lookup) - Map countryCapitals = new HashMap<>(); - countryCapitals.put("canada", "Ottawa"); - countryCapitals.put("france", "Paris"); - countryCapitals.put("japan", "Tokyo"); - - String result = - countryCapitals.getOrDefault( - country.toLowerCase(), "Sorry, I couldn't find the capital for " + country + "."); - return Map.of("result", result); // Tools must return a Map - } - - // Add the tool to the agent - FunctionTool capitalTool = FunctionTool.create(experiment.getClass(), "getCapitalCity"); - LlmAgent capitalAgent = - LlmAgent.builder() - .model("gemini-2.0-flash") - .name("capital_agent") - .description("Answers user questions about the capital city of a given country.") - .instruction("You are an agent that provides the capital city of a country... (previous instruction text)") - .tools(capitalTool) // Provide the function wrapped as a FunctionTool - .build(); - ``` - === "Typescript" ```typescript @@ -302,6 +259,45 @@ on the conversation and its instructions. }); ``` +=== "Go" + + ```go + --8<-- "examples/go/snippets/agents/llm-agents/snippets/main.go:tool_example" + ``` + +=== "Java" + + ```java + + // Define a tool function + // Retrieves the capital city of a given country. + public static Map getCapitalCity( + @Schema(name = "country", description = "The country to get capital for") + String country) { + // Replace with actual logic (e.g., API call, database lookup) + Map countryCapitals = new HashMap<>(); + countryCapitals.put("canada", "Ottawa"); + countryCapitals.put("france", "Paris"); + countryCapitals.put("japan", "Tokyo"); + + String result = + countryCapitals.getOrDefault( + country.toLowerCase(), "Sorry, I couldn't find the capital for " + country + "."); + return Map.of("result", result); // Tools must return a Map + } + + // Add the tool to the agent + FunctionTool capitalTool = FunctionTool.create(experiment.getClass(), "getCapitalCity"); + LlmAgent capitalAgent = + LlmAgent.builder() + .model("gemini-2.0-flash") + .name("capital_agent") + .description("Answers user questions about the capital city of a given country.") + .instruction("You are an agent that provides the capital city of a country... (previous instruction text)") + .tools(capitalTool) // Provide the function wrapped as a FunctionTool + .build(); + ``` + Learn more about Tools in the [Tools](../tools/index.md) section. ## Advanced Configuration & Control @@ -334,6 +330,22 @@ You can adjust how the underlying LLM generates responses using `generate_conten ) ``` +=== "Typescript" + + ```typescript + import { GenerateContentConfig } from '@google/genai'; + + const generateContentConfig: GenerateContentConfig = { + temperature: 0.2, // More deterministic output + maxOutputTokens: 250, + }; + + const agent = new LlmAgent({ + // ... other params + generateContentConfig, + }); + ``` + === "Go" ```go @@ -357,22 +369,6 @@ You can adjust how the underlying LLM generates responses using `generate_conten .build(); ``` -=== "Typescript" - - ```typescript - import { GenerateContentConfig } from '@google/genai'; - - const generateContentConfig: GenerateContentConfig = { - temperature: 0.2, // More deterministic output - maxOutputTokens: 250, - }; - - const agent = new LlmAgent({ - // ... other params - generateContentConfig, - }); - ``` - ### Structuring Data (`input_schema`, `output_schema`, `output_key`) For scenarios requiring structured data exchange with an `LLM Agent`, the ADK provides mechanisms to define expected input and desired output formats using schema definitions. @@ -392,10 +388,10 @@ For scenarios requiring structured data exchange with an `LLM Agent`, the ADK pr ```python from pydantic import BaseModel, Field - + class CapitalOutput(BaseModel): capital: str = Field(description="The capital of the country.") - + structured_capital_agent = LlmAgent( # ... name, model, description instruction="""You are a Capital Information Agent. Given a country, respond ONLY with a JSON object containing the capital. Format: {"capital": "capital_name"}""", @@ -405,6 +401,34 @@ For scenarios requiring structured data exchange with an `LLM Agent`, the ADK pr ) ``` +=== "Typescript" + + ```typescript + import {z} from 'zod'; + import { Schema, Type } from '@google/genai'; + + // Define the schema for the output + const CapitalOutputSchema: Schema = { + type: Type.OBJECT, + properties: { + capital: { + type: Type.STRING, + description: 'The capital of the country.', + }, + }, + required: ['capital'], + }; + + // Create the LlmAgent instance + const structuredCapitalAgent = new LlmAgent({ + // ... name, model, description + instruction: `You are a Capital Information Agent. Given a country, respond ONLY with a JSON object containing the capital. Format: {"capital": "capital_name"}`, + outputSchema: CapitalOutputSchema, // Enforce JSON output + outputKey: 'found_capital', // Store result in state['found_capital'] + // Cannot use tools effectively here + }); + ``` + === "Go" The input and output schema is a `google.genai.types.Schema` object. @@ -430,7 +454,7 @@ For scenarios requiring structured data exchange with an `LLM Agent`, the ADK pr .description("The capital city of the country.") .build())) .build(); - + LlmAgent structuredCapitalAgent = LlmAgent.builder() // ... name, model, description @@ -442,34 +466,6 @@ For scenarios requiring structured data exchange with an `LLM Agent`, the ADK pr .build(); ``` -=== "Typescript" - - ```typescript - import {z} from 'zod'; - import { Schema, Type } from '@google/genai'; - - // Define the schema for the output - const CapitalOutputSchema: Schema = { - type: Type.OBJECT, - properties: { - capital: { - type: Type.STRING, - description: 'The capital of the country.', - }, - }, - required: ['capital'], - }; - - // Create the LlmAgent instance - const structuredCapitalAgent = new LlmAgent({ - // ... name, model, description - instruction: `You are a Capital Information Agent. Given a country, respond ONLY with a JSON object containing the capital. Format: {"capital": "capital_name"}`, - outputSchema: CapitalOutputSchema, // Enforce JSON output - outputKey: 'found_capital', // Store result in state['found_capital'] - // Cannot use tools effectively here - }); - ``` - ### Managing Context (`include_contents`) Control whether the agent receives the prior conversation history. @@ -487,6 +483,15 @@ Control whether the agent receives the prior conversation history. ) ``` +=== "Typescript" + + ```typescript + const statelessAgent = new LlmAgent({ + // ... other params + includeContents: 'none', + }); + ``` + === "Go" ```go @@ -499,7 +504,7 @@ Control whether the agent receives the prior conversation history. ```java import com.google.adk.agents.LlmAgent.IncludeContents; - + LlmAgent statelessAgent = LlmAgent.builder() // ... other params @@ -507,15 +512,6 @@ Control whether the agent receives the prior conversation history. .build(); ``` -=== "Typescript" - - ```typescript - const statelessAgent = new LlmAgent({ - // ... other params - includeContents: 'none', - }); - ``` - ### Planner
@@ -544,7 +540,7 @@ Control whether the agent receives the prior conversation history. # ... your tools here ) ``` - + * **`PlanReActPlanner`:** This planner instructs the model to follow a specific structure in its output: first create a plan, then execute actions (like calling tools), and provide reasoning for its steps. *It's particularly useful for models that don't have a built-in "thinking" feature*. ```python @@ -703,9 +699,9 @@ call_agent("If it's raining in New York right now, what is the current temperatu - **`code_executor` (Optional):** Provide a `BaseCodeExecutor` instance to allow the agent to execute code blocks found in the LLM's response. ([See Tools/Built-in tools](../tools/built-in-tools.md)). -== "Python" +=== "Python" - ```py + ```python --8<-- "examples/python/snippets/tools/built-in-tools/code_execution.py" ``` @@ -721,11 +717,17 @@ call_agent("If it's raining in New York right now, what is the current temperatu Here's the complete basic `capital_agent`: === "Python" - + ```python --8<-- "examples/python/snippets/agents/llm-agent/capital_agent.py" ``` - + + === "Typescript" + + ```javascript + --8<-- "examples/typescript/snippets/agents/llm-agent/capital_agent.ts" + ``` + === "Go" ```go @@ -733,16 +735,10 @@ call_agent("If it's raining in New York right now, what is the current temperatu ``` === "Java" - + ```java --8<-- "examples/java/snippets/src/main/java/agents/LlmAgentExample.java:full_code" ``` - - === "Typescript" - - ```javascript - --8<-- "examples/typescript/snippets/agents/llm-agent/capital_agent.ts" - ``` _(This example demonstrates the core concepts. More complex agents might incorporate schemas, context control, planning, etc.)_ diff --git a/docs/agents/models.md b/docs/agents/models.md index 771318939..39854df17 100644 --- a/docs/agents/models.md +++ b/docs/agents/models.md @@ -1,7 +1,7 @@ # Using Different Models with ADK
- Supported in ADKPython v0.1.0Go v0.1.0Java v0.1.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.1.0
The Agent Development Kit (ADK) is designed for flexibility, allowing you to diff --git a/docs/agents/multi-agents.md b/docs/agents/multi-agents.md index fa4e149c6..b98b2db60 100644 --- a/docs/agents/multi-agents.md +++ b/docs/agents/multi-agents.md @@ -1,11 +1,7 @@ # Multi-Agent Systems in ADK
- Supported in ADK - Python v0.1.0 - Go v0.1.0 - Java v0.1.0 - TypeScript v0.1.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.1.0
As agentic applications grow in complexity, structuring them as a single, monolithic agent can become challenging to develop, maintain, and reason about. The Agent Development Kit (ADK) supports building sophisticated applications by composing multiple, distinct `BaseAgent` instances into a **Multi-Agent System (MAS)**. @@ -40,11 +36,11 @@ The foundation for structuring multi-agent systems is the parent-child relations ```python # Conceptual Example: Defining Hierarchy from google.adk.agents import LlmAgent, BaseAgent - + # Define individual agents greeter = LlmAgent(name="Greeter", model="gemini-2.0-flash") task_doer = BaseAgent(name="TaskExecutor") # Custom non-LLM agent - + # Create parent agent and assign children via sub_agents coordinator = LlmAgent( name="Coordinator", @@ -55,47 +51,12 @@ The foundation for structuring multi-agent systems is the parent-child relations task_doer ] ) - + # Framework automatically sets: # assert greeter.parent_agent == coordinator # assert task_doer.parent_agent == coordinator ``` -=== "Go" - - ```go - import ( - "google.golang.org/adk/agent" - "google.golang.org/adk/agent/llmagent" - ) - - --8<-- "examples/go/snippets/agents/multi-agent/main.go:hierarchy" - ``` - -=== "Java" - - ```java - // Conceptual Example: Defining Hierarchy - import com.google.adk.agents.SequentialAgent; - import com.google.adk.agents.LlmAgent; - - // Define individual agents - LlmAgent greeter = LlmAgent.builder().name("Greeter").model("gemini-2.0-flash").build(); - SequentialAgent taskDoer = SequentialAgent.builder().name("TaskExecutor").subAgents(...).build(); // Sequential Agent - - // Create parent agent and assign sub_agents - LlmAgent coordinator = LlmAgent.builder() - .name("Coordinator") - .model("gemini-2.0-flash") - .description("I coordinate greetings and tasks") - .subAgents(greeter, taskDoer) // Assign sub_agents here - .build(); - - // Framework automatically sets: - // assert greeter.parentAgent().equals(coordinator); - // assert taskDoer.parentAgent().equals(coordinator); - ``` - === "Typescript" ```typescript @@ -137,7 +98,42 @@ The foundation for structuring multi-agent systems is the parent-child relations // Framework automatically sets: // console.assert(greeter.parentAgent === coordinator); // console.assert(taskDoer.parentAgent === coordinator); + ``` + +=== "Go" + + ```go + import ( + "google.golang.org/adk/agent" + "google.golang.org/adk/agent/llmagent" + ) + + --8<-- "examples/go/snippets/agents/multi-agent/main.go:hierarchy" + ``` + +=== "Java" + ```java + // Conceptual Example: Defining Hierarchy + import com.google.adk.agents.SequentialAgent; + import com.google.adk.agents.LlmAgent; + + // Define individual agents + LlmAgent greeter = LlmAgent.builder().name("Greeter").model("gemini-2.0-flash").build(); + SequentialAgent taskDoer = SequentialAgent.builder().name("TaskExecutor").subAgents(...).build(); // Sequential Agent + + // Create parent agent and assign sub_agents + LlmAgent coordinator = LlmAgent.builder() + .name("Coordinator") + .model("gemini-2.0-flash") + .description("I coordinate greetings and tasks") + .subAgents(greeter, taskDoer) // Assign sub_agents here + .build(); + + // Framework automatically sets: + // assert greeter.parentAgent().equals(coordinator); + // assert taskDoer.parentAgent().equals(coordinator); + ``` ### 1.2. Workflow Agents as Orchestrators { #workflow-agents-as-orchestrators } @@ -159,6 +155,19 @@ ADK includes specialized agents derived from `BaseAgent` that don't perform task # When pipeline runs, Step2 can access the state['data'] set by Step1. ``` +=== "Typescript" + + ```typescript + // Conceptual Example: Sequential Pipeline + import { SequentialAgent, LlmAgent } from '@google/adk'; + + const step1 = new LlmAgent({name: 'Step1_Fetch', outputKey: 'data'}); // Saves output to state['data'] + const step2 = new LlmAgent({name: 'Step2_Process', instruction: 'Process data from {data}.'}); + + const pipeline = new SequentialAgent({name: 'MyPipeline', subAgents: [step1, step2]}); + // When pipeline runs, Step2 can access the state['data'] set by Step1. + ``` + === "Go" ```go @@ -185,19 +194,6 @@ ADK includes specialized agents derived from `BaseAgent` that don't perform task // When pipeline runs, Step2 can access the state.get("data") set by Step1. ``` -=== "Typescript" - - ```typescript - // Conceptual Example: Sequential Pipeline - import { SequentialAgent, LlmAgent } from '@google/adk'; - - const step1 = new LlmAgent({name: 'Step1_Fetch', outputKey: 'data'}); // Saves output to state['data'] - const step2 = new LlmAgent({name: 'Step2_Process', instruction: 'Process data from {data}.'}); - - const pipeline = new SequentialAgent({name: 'MyPipeline', subAgents: [step1, step2]}); - // When pipeline runs, Step2 can access the state['data'] set by Step1. - - * **[`ParallelAgent`](workflow-agents/parallel-agents.md):** Executes its `sub_agents` in parallel. Events from sub-agents may be interleaved. * **Context:** Modifies the `InvocationContext.branch` for each child agent (e.g., `ParentBranch.ChildName`), providing a distinct contextual path which can be useful for isolating history in some memory implementations. * **State:** Despite different branches, all parallel children access the *same shared* `session.state`, enabling them to read initial state and write results (use distinct keys to avoid race conditions). @@ -215,7 +211,21 @@ ADK includes specialized agents derived from `BaseAgent` that don't perform task # When gatherer runs, WeatherFetcher and NewsFetcher run concurrently. # A subsequent agent could read state['weather'] and state['news']. ``` - + +=== "Typescript" + + ```typescript + // Conceptual Example: Parallel Execution + import { ParallelAgent, LlmAgent } from '@google/adk'; + + const fetchWeather = new LlmAgent({name: 'WeatherFetcher', outputKey: 'weather'}); + const fetchNews = new LlmAgent({name: 'NewsFetcher', outputKey: 'news'}); + + const gatherer = new ParallelAgent({name: 'InfoGatherer', subAgents: [fetchWeather, fetchNews]}); + // When gatherer runs, WeatherFetcher and NewsFetcher run concurrently. + // A subsequent agent could read state['weather'] and state['news']. + ``` + === "Go" ```go @@ -234,39 +244,25 @@ ADK includes specialized agents derived from `BaseAgent` that don't perform task // Conceptual Example: Parallel Execution import com.google.adk.agents.LlmAgent; import com.google.adk.agents.ParallelAgent; - + LlmAgent fetchWeather = LlmAgent.builder() .name("WeatherFetcher") .outputKey("weather") .build(); - + LlmAgent fetchNews = LlmAgent.builder() .name("NewsFetcher") .instruction("news") .build(); - + ParallelAgent gatherer = ParallelAgent.builder() .name("InfoGatherer") .subAgents(fetchWeather, fetchNews) .build(); - - // When gatherer runs, WeatherFetcher and NewsFetcher run concurrently. - // A subsequent agent could read state['weather'] and state['news']. - ``` - -=== "Typescript" - ```typescript - // Conceptual Example: Parallel Execution - import { ParallelAgent, LlmAgent } from '@google/adk'; - - const fetchWeather = new LlmAgent({name: 'WeatherFetcher', outputKey: 'weather'}); - const fetchNews = new LlmAgent({name: 'NewsFetcher', outputKey: 'news'}); - - const gatherer = new ParallelAgent({name: 'InfoGatherer', subAgents: [fetchWeather, fetchNews]}); // When gatherer runs, WeatherFetcher and NewsFetcher run concurrently. // A subsequent agent could read state['weather'] and state['news']. - + ``` * **[`LoopAgent`](workflow-agents/loop-agents.md):** Executes its `sub_agents` sequentially in a loop. * **Termination:** The loop stops if the optional `max_iterations` is reached, or if any sub-agent returns an [`Event`](../events/index.md) with `escalate=True` in it's Event Actions. @@ -297,7 +293,52 @@ ADK includes specialized agents derived from `BaseAgent` that don't perform task # When poller runs, it executes process_step then Checker repeatedly # until Checker escalates (state['status'] == 'completed') or 10 iterations pass. ``` - + +=== "Typescript" + + ```typescript + // Conceptual Example: Loop with Condition + import { LoopAgent, LlmAgent, BaseAgent, InvocationContext } from '@google/adk'; + import type { Event, createEventActions, EventActions } from '@google/adk'; + + class CheckConditionAgent extends BaseAgent { // Custom agent to check state + async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator { + const status = ctx.session.state['status'] || 'pending'; + const isDone = status === 'completed'; + yield createEvent({ author: 'check_condition', actions: createEventActions({ escalate: isDone }) }); + } + + async *runLiveImpl(ctx: InvocationContext): AsyncGenerator { + // This is not implemented. + } + }; + + const processStep = new LlmAgent({name: 'ProcessingStep'}); // Agent that might update state['status'] + + const poller = new LoopAgent({ + name: 'StatusPoller', + maxIterations: 10, + // Executes its sub_agents sequentially in a loop + subAgents: [processStep, new CheckConditionAgent ({name: 'Checker'})] + }); + // When poller runs, it executes processStep then Checker repeatedly + // until Checker escalates (state['status'] === 'completed') or 10 iterations pass. + ``` + +=== "Go" + + ```go + import ( + "iter" + "google.golang.org/adk/agent" + "google.golang.org/adk/agent/llmagent" + "google.golang.org/adk/agent/workflowagents/loopagent" + "google.golang.org/adk/session" + ) + + --8<-- "examples/go/snippets/agents/multi-agent/main.go:loop-with-condition" + ``` + === "Java" ```java @@ -307,7 +348,7 @@ ADK includes specialized agents derived from `BaseAgent` that don't perform task public CheckConditionAgent(String name, String description) { super(name, description, List.of(), null, null); } - + @Override protected Flowable runAsyncImpl(InvocationContext ctx) { String status = (String) ctx.session().state().getOrDefault("status", "pending"); @@ -323,7 +364,7 @@ ADK includes specialized agents derived from `BaseAgent` that don't perform task return Flowable.just(checkEvent); } } - + // Agent that might update state.put("status") LlmAgent processingStepAgent = LlmAgent.builder().name("ProcessingStep").build(); // Custom agent instance for checking the condition @@ -336,51 +377,6 @@ ADK includes specialized agents derived from `BaseAgent` that don't perform task // until Checker escalates (state.get("status") == "completed") or 10 iterations pass. ``` -=== "Go" - - ```go - import ( - "iter" - "google.golang.org/adk/agent" - "google.golang.org/adk/agent/llmagent" - "google.golang.org/adk/agent/workflowagents/loopagent" - "google.golang.org/adk/session" - ) - - --8<-- "examples/go/snippets/agents/multi-agent/main.go:loop-with-condition" - ``` - -=== "Typescript" - - ```typescript - // Conceptual Example: Loop with Condition - import { LoopAgent, LlmAgent, BaseAgent, InvocationContext } from '@google/adk'; - import type { Event, createEventActions, EventActions } from '@google/adk'; - - class CheckConditionAgent extends BaseAgent { // Custom agent to check state - async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator { - const status = ctx.session.state['status'] || 'pending'; - const isDone = status === 'completed'; - yield createEvent({ author: 'check_condition', actions: createEventActions({ escalate: isDone }) }); - } - - async *runLiveImpl(ctx: InvocationContext): AsyncGenerator { - // This is not implemented. - } - }; - - const processStep = new LlmAgent({name: 'ProcessingStep'}); // Agent that might update state['status'] - - const poller = new LoopAgent({ - name: 'StatusPoller', - maxIterations: 10, - // Executes its sub_agents sequentially in a loop - subAgents: [processStep, new CheckConditionAgent ({name: 'Checker'})] - }); - // When poller runs, it executes processStep then Checker repeatedly - // until Checker escalates (state['status'] === 'completed') or 10 iterations pass. - - ### 1.3. Interaction & Communication Mechanisms { #interaction-communication-mechanisms } Agents within a system often need to exchange data or trigger actions in one another. ADK facilitates this through: @@ -402,37 +398,27 @@ The most fundamental way for agents operating within the same invocation (and th ```python # Conceptual Example: Using output_key and reading state from google.adk.agents import LlmAgent, SequentialAgent - + agent_A = LlmAgent(name="AgentA", instruction="Find the capital of France.", output_key="capital_city") agent_B = LlmAgent(name="AgentB", instruction="Tell me about the city stored in {capital_city}.") - + pipeline = SequentialAgent(name="CityInfo", sub_agents=[agent_A, agent_B]) # AgentA runs, saves "Paris" to state['capital_city']. # AgentB runs, its instruction processor reads state['capital_city'] to get "Paris". ``` -=== "Java" +=== "Typescript" - ```java + ```typescript // Conceptual Example: Using outputKey and reading state - import com.google.adk.agents.LlmAgent; - import com.google.adk.agents.SequentialAgent; - - LlmAgent agentA = LlmAgent.builder() - .name("AgentA") - .instruction("Find the capital of France.") - .outputKey("capital_city") - .build(); - - LlmAgent agentB = LlmAgent.builder() - .name("AgentB") - .instruction("Tell me about the city stored in {capital_city}.") - .outputKey("capital_city") - .build(); - - SequentialAgent pipeline = SequentialAgent.builder().name("CityInfo").subAgents(agentA, agentB).build(); - // AgentA runs, saves "Paris" to state('capital_city'). - // AgentB runs, its instruction processor reads state.get("capital_city") to get "Paris". + import { LlmAgent, SequentialAgent } from '@google/adk'; + + const agentA = new LlmAgent({name: 'AgentA', instruction: 'Find the capital of France.', outputKey: 'capital_city'}); + const agentB = new LlmAgent({name: 'AgentB', instruction: 'Tell me about the city stored in {capital_city}.'}); + + const pipeline = new SequentialAgent({name: 'CityInfo', subAgents: [agentA, agentB]}); + // AgentA runs, saves "Paris" to state['capital_city']. + // AgentB runs, its instruction processor reads state['capital_city'] to get "Paris". ``` === "Go" @@ -447,19 +433,29 @@ The most fundamental way for agents operating within the same invocation (and th --8<-- "examples/go/snippets/agents/multi-agent/main.go:output-key-state" ``` -=== "Typescript" +=== "Java" - ```typescript + ```java // Conceptual Example: Using outputKey and reading state - import { LlmAgent, SequentialAgent } from '@google/adk'; + import com.google.adk.agents.LlmAgent; + import com.google.adk.agents.SequentialAgent; - const agentA = new LlmAgent({name: 'AgentA', instruction: 'Find the capital of France.', outputKey: 'capital_city'}); - const agentB = new LlmAgent({name: 'AgentB', instruction: 'Tell me about the city stored in {capital_city}.'}); + LlmAgent agentA = LlmAgent.builder() + .name("AgentA") + .instruction("Find the capital of France.") + .outputKey("capital_city") + .build(); - const pipeline = new SequentialAgent({name: 'CityInfo', subAgents: [agentA, agentB]}); - // AgentA runs, saves "Paris" to state['capital_city']. - // AgentB runs, its instruction processor reads state['capital_city'] to get "Paris". + LlmAgent agentB = LlmAgent.builder() + .name("AgentB") + .instruction("Tell me about the city stored in {capital_city}.") + .outputKey("capital_city") + .build(); + SequentialAgent pipeline = SequentialAgent.builder().name("CityInfo").subAgents(agentA, agentB).build(); + // AgentA runs, saves "Paris" to state('capital_city'). + // AgentB runs, its instruction processor reads state.get("capital_city") to get "Paris". + ``` #### b) LLM-Driven Delegation (Agent Transfer) @@ -475,10 +471,10 @@ Leverages an [`LlmAgent`](llm-agents.md)'s understanding to dynamically route ta ```python # Conceptual Setup: LLM Transfer from google.adk.agents import LlmAgent - + booking_agent = LlmAgent(name="Booker", description="Handles flight and hotel bookings.") info_agent = LlmAgent(name="Info", description="Provides general information and answers questions.") - + coordinator = LlmAgent( name="Coordinator", model="gemini-2.0-flash", @@ -492,22 +488,54 @@ Leverages an [`LlmAgent`](llm-agents.md)'s understanding to dynamically route ta # ADK framework then routes execution to booking_agent. ``` +=== "Typescript" + + ```typescript + // Conceptual Setup: LLM Transfer + import { LlmAgent } from '@google/adk'; + + const bookingAgent = new LlmAgent({name: 'Booker', description: 'Handles flight and hotel bookings.'}); + const infoAgent = new LlmAgent({name: 'Info', description: 'Provides general information and answers questions.'}); + + const coordinator = new LlmAgent({ + name: 'Coordinator', + model: 'gemini-2.5-flash', + instruction: 'You are an assistant. Delegate booking tasks to Booker and info requests to Info.', + description: 'Main coordinator.', + // AutoFlow is typically used implicitly here + subAgents: [bookingAgent, infoAgent] + }); + // If coordinator receives "Book a flight", its LLM should generate: + // {functionCall: {name: 'transfer_to_agent', args: {agent_name: 'Booker'}}} + // ADK framework then routes execution to bookingAgent. + ``` + +=== "Go" + + ```go + import ( + "google.golang.org/adk/agent/llmagent" + ) + + --8<-- "examples/go/snippets/agents/multi-agent/main.go:llm-transfer" + ``` + === "Java" ```java // Conceptual Setup: LLM Transfer import com.google.adk.agents.LlmAgent; - + LlmAgent bookingAgent = LlmAgent.builder() .name("Booker") .description("Handles flight and hotel bookings.") .build(); - + LlmAgent infoAgent = LlmAgent.builder() .name("Info") .description("Provides general information and answers questions.") .build(); - + // Define the coordinator agent LlmAgent coordinator = LlmAgent.builder() .name("Coordinator") @@ -524,38 +552,6 @@ Leverages an [`LlmAgent`](llm-agents.md)'s understanding to dynamically route ta // ADK framework then routes execution to bookingAgent. ``` -=== "Go" - - ```go - import ( - "google.golang.org/adk/agent/llmagent" - ) - - --8<-- "examples/go/snippets/agents/multi-agent/main.go:llm-transfer" - ``` - -=== "Typescript" - - ```typescript - // Conceptual Setup: LLM Transfer - import { LlmAgent } from '@google/adk'; - - const bookingAgent = new LlmAgent({name: 'Booker', description: 'Handles flight and hotel bookings.'}); - const infoAgent = new LlmAgent({name: 'Info', description: 'Provides general information and answers questions.'}); - - const coordinator = new LlmAgent({ - name: 'Coordinator', - model: 'gemini-2.5-flash', - instruction: 'You are an assistant. Delegate booking tasks to Booker and info requests to Info.', - description: 'Main coordinator.', - // AutoFlow is typically used implicitly here - subAgents: [bookingAgent, infoAgent] - }); - // If coordinator receives "Book a flight", its LLM should generate: - // {functionCall: {name: 'transfer_to_agent', args: {agent_name: 'Booker'}}} - // ADK framework then routes execution to bookingAgent. - - #### c) Explicit Invocation (`AgentTool`) Allows an [`LlmAgent`](llm-agents.md) to treat another `BaseAgent` instance as a callable function or [Tool](../tools/index.md). @@ -572,7 +568,7 @@ Allows an [`LlmAgent`](llm-agents.md) to treat another `BaseAgent` instance as a from google.adk.agents import LlmAgent, BaseAgent from google.adk.tools import agent_tool from pydantic import BaseModel - + # Define a target agent (could be LlmAgent or custom BaseAgent) class ImageGeneratorAgent(BaseAgent): # Example custom agent name: str = "ImageGen" @@ -583,10 +579,10 @@ Allows an [`LlmAgent`](llm-agents.md) to treat another `BaseAgent` instance as a # ... generate image bytes ... image_bytes = b"..." yield Event(author=self.name, content=types.Content(parts=[types.Part.from_bytes(image_bytes, "image/png")])) - + image_agent = ImageGeneratorAgent() image_tool = agent_tool.AgentTool(agent=image_agent) # Wrap the agent - + # Parent agent uses the AgentTool artist_agent = LlmAgent( name="Artist", @@ -600,6 +596,66 @@ Allows an [`LlmAgent`](llm-agents.md) to treat another `BaseAgent` instance as a # The resulting image Part is returned to the Artist agent as the tool result. ``` +=== "Typescript" + + ```typescript + // Conceptual Setup: Agent as a Tool + import { LlmAgent, BaseAgent, AgentTool, InvocationContext } from '@google/adk'; + import type { Part, createEvent, Event } from '@google/genai'; + + // Define a target agent (could be LlmAgent or custom BaseAgent) + class ImageGeneratorAgent extends BaseAgent { // Example custom agent + constructor() { + super({name: 'ImageGen', description: 'Generates an image based on a prompt.'}); + } + // ... internal logic ... + async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator { // Simplified run logic + const prompt = ctx.session.state['image_prompt'] || 'default prompt'; + // ... generate image bytes ... + const imageBytes = new Uint8Array(); // placeholder + const imagePart: Part = {inlineData: {data: Buffer.from(imageBytes).toString('base64'), mimeType: 'image/png'}}; + yield createEvent({content: {parts: [imagePart]}}); + } + + async *runLiveImpl(ctx: InvocationContext): AsyncGenerator { + // Not implemented for this agent. + } + } + + const imageAgent = new ImageGeneratorAgent(); + const imageTool = new AgentTool({agent: imageAgent}); // Wrap the agent + + // Parent agent uses the AgentTool + const artistAgent = new LlmAgent({ + name: 'Artist', + model: 'gemini-2.5-flash', + instruction: 'Create a prompt and use the ImageGen tool to generate the image.', + tools: [imageTool] // Include the AgentTool + }); + // Artist LLM generates a prompt, then calls: + // {functionCall: {name: 'ImageGen', args: {image_prompt: 'a cat wearing a hat'}}} + // Framework calls imageTool.runAsync(...), which runs ImageGeneratorAgent. + // The resulting image Part is returned to the Artist agent as the tool result. + ``` + +=== "Go" + + ```go + import ( + "fmt" + "iter" + "google.golang.org/adk/agent" + "google.golang.org/adk/agent/llmagent" + "google.golang.org/adk/model" + "google.golang.org/adk/session" + "google.golang.org/adk/tool" + "google.golang.org/adk/tool/agenttool" + "google.golang.org/genai" + ) + + --8<-- "examples/go/snippets/agents/multi-agent/main.go:agent-as-tool" + ``` + === "Java" ```java @@ -610,26 +666,26 @@ Allows an [`LlmAgent`](llm-agents.md) to treat another `BaseAgent` instance as a // Example custom agent (could be LlmAgent or custom BaseAgent) public class ImageGeneratorAgent extends BaseAgent { - + public ImageGeneratorAgent(String name, String description) { super(name, description, List.of(), null, null); } - + // ... internal logic ... @Override protected Flowable runAsyncImpl(InvocationContext invocationContext) { // Simplified run logic invocationContext.session().state().get("image_prompt"); // Generate image bytes // ... - + Event responseEvent = Event.builder() .author(this.name()) .content(Content.fromParts(Part.fromText("..."))) .build(); - + return Flowable.just(responseEvent); } - + @Override protected Flowable runLiveImpl(InvocationContext invocationContext) { return null; @@ -639,7 +695,7 @@ Allows an [`LlmAgent`](llm-agents.md) to treat another `BaseAgent` instance as a // Wrap the agent using AgentTool ImageGeneratorAgent imageAgent = new ImageGeneratorAgent("image_agent", "generates images"); AgentTool imageTool = AgentTool.create(imageAgent); - + // Parent agent uses the AgentTool LlmAgent artistAgent = LlmAgent.builder() .name("Artist") @@ -652,74 +708,14 @@ Allows an [`LlmAgent`](llm-agents.md) to treat another `BaseAgent` instance as a "'result' field, containing 'image_base64', 'mime_type', and 'status'." ) .description("An agent that can create images using a generation tool.") - .tools(imageTool) // Include the AgentTool - .build(); - - // Artist LLM generates a prompt, then calls: - // FunctionCall(name='ImageGen', args={'imagePrompt': 'a cat wearing a hat'}) - // Framework calls imageTool.runAsync(...), which runs ImageGeneratorAgent. - // The resulting image Part is returned to the Artist agent as the tool result. - ``` - -=== "Go" - - ```go - import ( - "fmt" - "iter" - "google.golang.org/adk/agent" - "google.golang.org/adk/agent/llmagent" - "google.golang.org/adk/model" - "google.golang.org/adk/session" - "google.golang.org/adk/tool" - "google.golang.org/adk/tool/agenttool" - "google.golang.org/genai" - ) - - --8<-- "examples/go/snippets/agents/multi-agent/main.go:agent-as-tool" - ``` - -=== "Typescript" - - ```typescript - // Conceptual Setup: Agent as a Tool - import { LlmAgent, BaseAgent, AgentTool, InvocationContext } from '@google/adk'; - import type { Part, createEvent, Event } from '@google/genai'; - - // Define a target agent (could be LlmAgent or custom BaseAgent) - class ImageGeneratorAgent extends BaseAgent { // Example custom agent - constructor() { - super({name: 'ImageGen', description: 'Generates an image based on a prompt.'}); - } - // ... internal logic ... - async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator { // Simplified run logic - const prompt = ctx.session.state['image_prompt'] || 'default prompt'; - // ... generate image bytes ... - const imageBytes = new Uint8Array(); // placeholder - const imagePart: Part = {inlineData: {data: Buffer.from(imageBytes).toString('base64'), mimeType: 'image/png'}}; - yield createEvent({content: {parts: [imagePart]}}); - } - - async *runLiveImpl(ctx: InvocationContext): AsyncGenerator { - // Not implemented for this agent. - } -} - - const imageAgent = new ImageGeneratorAgent(); - const imageTool = new AgentTool({agent: imageAgent}); // Wrap the agent - - // Parent agent uses the AgentTool - const artistAgent = new LlmAgent({ - name: 'Artist', - model: 'gemini-2.5-flash', - instruction: 'Create a prompt and use the ImageGen tool to generate the image.', - tools: [imageTool] // Include the AgentTool - }); + .tools(imageTool) // Include the AgentTool + .build(); + // Artist LLM generates a prompt, then calls: - // {functionCall: {name: 'ImageGen', args: {image_prompt: 'a cat wearing a hat'}}} + // FunctionCall(name='ImageGen', args={'imagePrompt': 'a cat wearing a hat'}) // Framework calls imageTool.runAsync(...), which runs ImageGeneratorAgent. // The resulting image Part is returned to the Artist agent as the tool result. - + ``` These primitives provide the flexibility to design multi-agent interactions ranging from tightly coupled sequential workflows to dynamic, LLM-driven delegation networks. @@ -740,10 +736,10 @@ By combining ADK's composition primitives, you can implement various established ```python # Conceptual Code: Coordinator using LLM Transfer from google.adk.agents import LlmAgent - + billing_agent = LlmAgent(name="Billing", description="Handles billing inquiries.") support_agent = LlmAgent(name="Support", description="Handles technical support requests.") - + coordinator = LlmAgent( name="HelpDeskCoordinator", model="gemini-2.0-flash", @@ -756,6 +752,27 @@ By combining ADK's composition primitives, you can implement various established # User asks "I can't log in" -> Coordinator's LLM should call transfer_to_agent(agent_name='Support') ``` +=== "Typescript" + + ```typescript + // Conceptual Code: Coordinator using LLM Transfer + import { LlmAgent } from '@google/adk'; + + const billingAgent = new LlmAgent({name: 'Billing', description: 'Handles billing inquiries.'}); + const supportAgent = new LlmAgent({name: 'Support', description: 'Handles technical support requests.'}); + + const coordinator = new LlmAgent({ + name: 'HelpDeskCoordinator', + model: 'gemini-2.5-flash', + instruction: 'Route user requests: Use Billing agent for payment issues, Support agent for technical problems.', + description: 'Main help desk router.', + // allowTransfer=true is often implicit with subAgents in AutoFlow + subAgents: [billingAgent, supportAgent] + }); + // User asks "My payment failed" -> Coordinator's LLM should call {functionCall: {name: 'transfer_to_agent', args: {agent_name: 'Billing'}}} + // User asks "I can't log in" -> Coordinator's LLM should call {functionCall: {name: 'transfer_to_agent', args: {agent_name: 'Support'}}} + ``` + === "Go" ```go @@ -799,26 +816,6 @@ By combining ADK's composition primitives, you can implement various established // transferToAgent(agentName='Support') ``` -=== "Typescript" - - ```typescript - // Conceptual Code: Coordinator using LLM Transfer - import { LlmAgent } from '@google/adk'; - - const billingAgent = new LlmAgent({name: 'Billing', description: 'Handles billing inquiries.'}); - const supportAgent = new LlmAgent({name: 'Support', description: 'Handles technical support requests.'}); - - const coordinator = new LlmAgent({ - name: 'HelpDeskCoordinator', - model: 'gemini-2.5-flash', - instruction: 'Route user requests: Use Billing agent for payment issues, Support agent for technical problems.', - description: 'Main help desk router.', - // allowTransfer=true is often implicit with subAgents in AutoFlow - subAgents: [billingAgent, supportAgent] - }); - // User asks "My payment failed" -> Coordinator's LLM should call {functionCall: {name: 'transfer_to_agent', args: {agent_name: 'Billing'}}} - // User asks "I can't log in" -> Coordinator's LLM should call {functionCall: {name: 'transfer_to_agent', args: {agent_name: 'Support'}}} - ### Sequential Pipeline Pattern * **Structure:** A [`SequentialAgent`](workflow-agents/sequential-agents.md) contains `sub_agents` executed in a fixed order. @@ -832,11 +829,11 @@ By combining ADK's composition primitives, you can implement various established ```python # Conceptual Code: Sequential Data Pipeline from google.adk.agents import SequentialAgent, LlmAgent - + validator = LlmAgent(name="ValidateInput", instruction="Validate the input.", output_key="validation_status") processor = LlmAgent(name="ProcessData", instruction="Process data if {validation_status} is 'valid'.", output_key="result") reporter = LlmAgent(name="ReportResult", instruction="Report the result from {result}.") - + data_pipeline = SequentialAgent( name="DataPipeline", sub_agents=[validator, processor, reporter] @@ -846,6 +843,25 @@ By combining ADK's composition primitives, you can implement various established # reporter runs -> reads state['result'] ``` +=== "Typescript" + + ```typescript + // Conceptual Code: Sequential Data Pipeline + import { SequentialAgent, LlmAgent } from '@google/adk'; + + const validator = new LlmAgent({name: 'ValidateInput', instruction: 'Validate the input.', outputKey: 'validation_status'}); + const processor = new LlmAgent({name: 'ProcessData', instruction: 'Process data if {validation_status} is "valid".', outputKey: 'result'}); + const reporter = new LlmAgent({name: 'ReportResult', instruction: 'Report the result from {result}.'}); + + const dataPipeline = new SequentialAgent({ + name: 'DataPipeline', + subAgents: [validator, processor, reporter] + }); + // validator runs -> saves to state['validation_status'] + // processor runs -> reads state['validation_status'], saves to state['result'] + // reporter runs -> reads state['result'] + ``` + === "Go" ```go @@ -863,51 +879,33 @@ By combining ADK's composition primitives, you can implement various established ```java // Conceptual Code: Sequential Data Pipeline import com.google.adk.agents.SequentialAgent; - + LlmAgent validator = LlmAgent.builder() .name("ValidateInput") .instruction("Validate the input") .outputKey("validation_status") // Saves its main text output to session.state["validation_status"] .build(); - + LlmAgent processor = LlmAgent.builder() .name("ProcessData") .instruction("Process data if {validation_status} is 'valid'") .outputKey("result") // Saves its main text output to session.state["result"] .build(); - + LlmAgent reporter = LlmAgent.builder() .name("ReportResult") .instruction("Report the result from {result}") .build(); - + SequentialAgent dataPipeline = SequentialAgent.builder() .name("DataPipeline") .subAgents(validator, processor, reporter) .build(); - - // validator runs -> saves to state['validation_status'] - // processor runs -> reads state['validation_status'], saves to state['result'] - // reporter runs -> reads state['result'] - ``` - -=== "Typescript" - - ```typescript - // Conceptual Code: Sequential Data Pipeline - import { SequentialAgent, LlmAgent } from '@google/adk'; - - const validator = new LlmAgent({name: 'ValidateInput', instruction: 'Validate the input.', outputKey: 'validation_status'}); - const processor = new LlmAgent({name: 'ProcessData', instruction: 'Process data if {validation_status} is "valid".', outputKey: 'result'}); - const reporter = new LlmAgent({name: 'ReportResult', instruction: 'Report the result from {result}.'}); - const dataPipeline = new SequentialAgent({ - name: 'DataPipeline', - subAgents: [validator, processor, reporter] - }); // validator runs -> saves to state['validation_status'] // processor runs -> reads state['validation_status'], saves to state['result'] // reporter runs -> reads state['result'] + ``` ### Parallel Fan-Out/Gather Pattern @@ -922,20 +920,20 @@ By combining ADK's composition primitives, you can implement various established ```python # Conceptual Code: Parallel Information Gathering from google.adk.agents import SequentialAgent, ParallelAgent, LlmAgent - + fetch_api1 = LlmAgent(name="API1Fetcher", instruction="Fetch data from API 1.", output_key="api1_data") fetch_api2 = LlmAgent(name="API2Fetcher", instruction="Fetch data from API 2.", output_key="api2_data") - + gather_concurrently = ParallelAgent( name="ConcurrentFetch", sub_agents=[fetch_api1, fetch_api2] ) - + synthesizer = LlmAgent( name="Synthesizer", instruction="Combine results from {api1_data} and {api2_data}." ) - + overall_workflow = SequentialAgent( name="FetchAndSynthesize", sub_agents=[gather_concurrently, synthesizer] # Run parallel fetch, then synthesize @@ -944,6 +942,33 @@ By combining ADK's composition primitives, you can implement various established # synthesizer runs afterwards, reading state['api1_data'] and state['api2_data']. ``` +=== "Typescript" + + ```typescript + // Conceptual Code: Parallel Information Gathering + import { SequentialAgent, ParallelAgent, LlmAgent } from '@google/adk'; + + const fetchApi1 = new LlmAgent({name: 'API1Fetcher', instruction: 'Fetch data from API 1.', outputKey: 'api1_data'}); + const fetchApi2 = new LlmAgent({name: 'API2Fetcher', instruction: 'Fetch data from API 2.', outputKey: 'api2_data'}); + + const gatherConcurrently = new ParallelAgent({ + name: 'ConcurrentFetch', + subAgents: [fetchApi1, fetchApi2] + }); + + const synthesizer = new LlmAgent({ + name: 'Synthesizer', + instruction: 'Combine results from {api1_data} and {api2_data}.' + }); + + const overallWorkflow = new SequentialAgent({ + name: 'FetchAndSynthesize', + subAgents: [gatherConcurrently, synthesizer] // Run parallel fetch, then synthesize + }); + // fetchApi1 and fetchApi2 run concurrently, saving to state. + // synthesizer runs afterwards, reading state['api1_data'] and state['api2_data']. + ``` + === "Go" ```go @@ -996,32 +1021,6 @@ By combining ADK's composition primitives, you can implement various established // synthesizer runs afterwards, reading state['api1_data'] and state['api2_data']. ``` -=== "Typescript" - - ```typescript - // Conceptual Code: Parallel Information Gathering - import { SequentialAgent, ParallelAgent, LlmAgent } from '@google/adk'; - - const fetchApi1 = new LlmAgent({name: 'API1Fetcher', instruction: 'Fetch data from API 1.', outputKey: 'api1_data'}); - const fetchApi2 = new LlmAgent({name: 'API2Fetcher', instruction: 'Fetch data from API 2.', outputKey: 'api2_data'}); - - const gatherConcurrently = new ParallelAgent({ - name: 'ConcurrentFetch', - subAgents: [fetchApi1, fetchApi2] - }); - - const synthesizer = new LlmAgent({ - name: 'Synthesizer', - instruction: 'Combine results from {api1_data} and {api2_data}.' - }); - - const overallWorkflow = new SequentialAgent({ - name: 'FetchAndSynthesize', - subAgents: [gatherConcurrently, synthesizer] // Run parallel fetch, then synthesize - }); - // fetchApi1 and fetchApi2 run concurrently, saving to state. - // synthesizer runs afterwards, reading state['api1_data'] and state['api2_data']. - ### Hierarchical Task Decomposition * **Structure:** A multi-level tree of agents where higher-level agents break down complex goals and delegate sub-tasks to lower-level agents. @@ -1036,11 +1035,11 @@ By combining ADK's composition primitives, you can implement various established # Conceptual Code: Hierarchical Research Task from google.adk.agents import LlmAgent from google.adk.tools import agent_tool - + # Low-level tool-like agents web_searcher = LlmAgent(name="WebSearch", description="Performs web searches for facts.") summarizer = LlmAgent(name="Summarizer", description="Summarizes text.") - + # Mid-level agent combining tools research_assistant = LlmAgent( name="ResearchAssistant", @@ -1048,7 +1047,7 @@ By combining ADK's composition primitives, you can implement various established description="Finds and summarizes information on a topic.", tools=[agent_tool.AgentTool(agent=web_searcher), agent_tool.AgentTool(agent=summarizer)] ) - + # High-level agent delegating research report_writer = LlmAgent( name="ReportWriter", @@ -1063,6 +1062,38 @@ By combining ADK's composition primitives, you can implement various established # Results flow back up. ``` +=== "Typescript" + + ```typescript + // Conceptual Code: Hierarchical Research Task + import { LlmAgent, AgentTool } from '@google/adk'; + + // Low-level tool-like agents + const webSearcher = new LlmAgent({name: 'WebSearch', description: 'Performs web searches for facts.'}); + const summarizer = new LlmAgent({name: 'Summarizer', description: 'Summarizes text.'}); + + // Mid-level agent combining tools + const researchAssistant = new LlmAgent({ + name: 'ResearchAssistant', + model: 'gemini-2.5-flash', + description: 'Finds and summarizes information on a topic.', + tools: [new AgentTool({agent: webSearcher}), new AgentTool({agent: summarizer})] + }); + + // High-level agent delegating research + const reportWriter = new LlmAgent({ + name: 'ReportWriter', + model: 'gemini-2.5-flash', + instruction: 'Write a report on topic X. Use the ResearchAssistant to gather information.', + tools: [new AgentTool({agent: researchAssistant})] + // Alternatively, could use LLM Transfer if researchAssistant is a subAgent + }); + // User interacts with ReportWriter. + // ReportWriter calls ResearchAssistant tool. + // ResearchAssistant calls WebSearch and Summarizer tools. + // Results flow back up. + ``` + === "Go" ```go @@ -1081,18 +1112,18 @@ By combining ADK's composition primitives, you can implement various established // Conceptual Code: Hierarchical Research Task import com.google.adk.agents.LlmAgent; import com.google.adk.tools.AgentTool; - + // Low-level tool-like agents LlmAgent webSearcher = LlmAgent.builder() .name("WebSearch") .description("Performs web searches for facts.") .build(); - + LlmAgent summarizer = LlmAgent.builder() .name("Summarizer") .description("Summarizes text.") .build(); - + // Mid-level agent combining tools LlmAgent researchAssistant = LlmAgent.builder() .name("ResearchAssistant") @@ -1100,7 +1131,7 @@ By combining ADK's composition primitives, you can implement various established .description("Finds and summarizes information on a topic.") .tools(AgentTool.create(webSearcher), AgentTool.create(summarizer)) .build(); - + // High-level agent delegating research LlmAgent reportWriter = LlmAgent.builder() .name("ReportWriter") @@ -1109,43 +1140,12 @@ By combining ADK's composition primitives, you can implement various established .tools(AgentTool.create(researchAssistant)) // Alternatively, could use LLM Transfer if research_assistant is a subAgent .build(); - - // User interacts with ReportWriter. - // ReportWriter calls ResearchAssistant tool. - // ResearchAssistant calls WebSearch and Summarizer tools. - // Results flow back up. - ``` - -=== "Typescript" - - ```typescript - // Conceptual Code: Hierarchical Research Task - import { LlmAgent, AgentTool } from '@google/adk'; - - // Low-level tool-like agents - const webSearcher = new LlmAgent({name: 'WebSearch', description: 'Performs web searches for facts.'}); - const summarizer = new LlmAgent({name: 'Summarizer', description: 'Summarizes text.'}); - - // Mid-level agent combining tools - const researchAssistant = new LlmAgent({ - name: 'ResearchAssistant', - model: 'gemini-2.5-flash', - description: 'Finds and summarizes information on a topic.', - tools: [new AgentTool({agent: webSearcher}), new AgentTool({agent: summarizer})] - }); - // High-level agent delegating research - const reportWriter = new LlmAgent({ - name: 'ReportWriter', - model: 'gemini-2.5-flash', - instruction: 'Write a report on topic X. Use the ResearchAssistant to gather information.', - tools: [new AgentTool({agent: researchAssistant})] - // Alternatively, could use LLM Transfer if researchAssistant is a subAgent - }); // User interacts with ReportWriter. // ReportWriter calls ResearchAssistant tool. // ResearchAssistant calls WebSearch and Summarizer tools. // Results flow back up. + ``` ### Review/Critique Pattern (Generator-Critic) @@ -1160,21 +1160,21 @@ By combining ADK's composition primitives, you can implement various established ```python # Conceptual Code: Generator-Critic from google.adk.agents import SequentialAgent, LlmAgent - + generator = LlmAgent( name="DraftWriter", instruction="Write a short paragraph about subject X.", output_key="draft_text" ) - + reviewer = LlmAgent( name="FactChecker", instruction="Review the text in {draft_text} for factual accuracy. Output 'valid' or 'invalid' with reasons.", output_key="review_status" ) - + # Optional: Further steps based on review_status - + review_pipeline = SequentialAgent( name="WriteAndReview", sub_agents=[generator, reviewer] @@ -1183,6 +1183,34 @@ By combining ADK's composition primitives, you can implement various established # reviewer runs -> reads state['draft_text'], saves status to state['review_status'] ``` +=== "Typescript" + + ```typescript + // Conceptual Code: Generator-Critic + import { SequentialAgent, LlmAgent } from '@google/adk'; + + const generator = new LlmAgent({ + name: 'DraftWriter', + instruction: 'Write a short paragraph about subject X.', + outputKey: 'draft_text' + }); + + const reviewer = new LlmAgent({ + name: 'FactChecker', + instruction: 'Review the text in {draft_text} for factual accuracy. Output "valid" or "invalid" with reasons.', + outputKey: 'review_status' + }); + + // Optional: Further steps based on review_status + + const reviewPipeline = new SequentialAgent({ + name: 'WriteAndReview', + subAgents: [generator, reviewer] + }); + // generator runs -> saves draft to state['draft_text'] + // reviewer runs -> reads state['draft_text'], saves status to state['review_status'] + ``` + === "Go" ```go @@ -1201,57 +1229,29 @@ By combining ADK's composition primitives, you can implement various established // Conceptual Code: Generator-Critic import com.google.adk.agents.LlmAgent; import com.google.adk.agents.SequentialAgent; - + LlmAgent generator = LlmAgent.builder() .name("DraftWriter") .instruction("Write a short paragraph about subject X.") .outputKey("draft_text") .build(); - + LlmAgent reviewer = LlmAgent.builder() .name("FactChecker") .instruction("Review the text in {draft_text} for factual accuracy. Output 'valid' or 'invalid' with reasons.") .outputKey("review_status") .build(); - + // Optional: Further steps based on review_status - + SequentialAgent reviewPipeline = SequentialAgent.builder() .name("WriteAndReview") .subAgents(generator, reviewer) .build(); - - // generator runs -> saves draft to state['draft_text'] - // reviewer runs -> reads state['draft_text'], saves status to state['review_status'] - ``` - -=== "Typescript" - - ```typescript - // Conceptual Code: Generator-Critic - import { SequentialAgent, LlmAgent } from '@google/adk'; - - const generator = new LlmAgent({ - name: 'DraftWriter', - instruction: 'Write a short paragraph about subject X.', - outputKey: 'draft_text' - }); - - const reviewer = new LlmAgent({ - name: 'FactChecker', - instruction: 'Review the text in {draft_text} for factual accuracy. Output "valid" or "invalid" with reasons.', - outputKey: 'review_status' - }); - - // Optional: Further steps based on review_status - const reviewPipeline = new SequentialAgent({ - name: 'WriteAndReview', - subAgents: [generator, reviewer] - }); // generator runs -> saves draft to state['draft_text'] // reviewer runs -> reads state['draft_text'], saves status to state['review_status'] - + ``` ### Iterative Refinement Pattern @@ -1270,28 +1270,28 @@ By combining ADK's composition primitives, you can implement various established from google.adk.events import Event, EventActions from google.adk.agents.invocation_context import InvocationContext from typing import AsyncGenerator - + # Agent to generate/refine code based on state['current_code'] and state['requirements'] code_refiner = LlmAgent( name="CodeRefiner", instruction="Read state['current_code'] (if exists) and state['requirements']. Generate/refine Python code to meet requirements. Save to state['current_code'].", output_key="current_code" # Overwrites previous code in state ) - + # Agent to check if the code meets quality standards quality_checker = LlmAgent( name="QualityChecker", instruction="Evaluate the code in state['current_code'] against state['requirements']. Output 'pass' or 'fail'.", output_key="quality_status" ) - + # Custom agent to check the status and escalate if 'pass' class CheckStatusAndEscalate(BaseAgent): async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]: status = ctx.session.state.get("quality_status", "fail") should_stop = (status == "pass") yield Event(author=self.name, actions=EventActions(escalate=should_stop)) - + refinement_loop = LoopAgent( name="CodeRefinementLoop", max_iterations=5, @@ -1302,6 +1302,56 @@ By combining ADK's composition primitives, you can implement various established # Loop stops if QualityChecker outputs 'pass' (leading to StopChecker escalating) or after 5 iterations. ``` +=== "Typescript" + + ```typescript + // Conceptual Code: Iterative Code Refinement + import { LoopAgent, LlmAgent, BaseAgent, InvocationContext } from '@google/adk'; + import type { Event, createEvent, createEventActions } from '@google/genai'; + + // Agent to generate/refine code based on state['current_code'] and state['requirements'] + const codeRefiner = new LlmAgent({ + name: 'CodeRefiner', + instruction: 'Read state["current_code"] (if exists) and state["requirements"]. Generate/refine Typescript code to meet requirements. Save to state["current_code"].', + outputKey: 'current_code' // Overwrites previous code in state + }); + + // Agent to check if the code meets quality standards + const qualityChecker = new LlmAgent({ + name: 'QualityChecker', + instruction: 'Evaluate the code in state["current_code"] against state["requirements"]. Output "pass" or "fail".', + outputKey: 'quality_status' + }); + + // Custom agent to check the status and escalate if 'pass' + class CheckStatusAndEscalate extends BaseAgent { + async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator { + const status = ctx.session.state.quality_status; + const shouldStop = status === 'pass'; + if (shouldStop) { + yield createEvent({ + author: 'StopChecker', + actions: createEventActions(), + }); + } + } + + async *runLiveImpl(ctx: InvocationContext): AsyncGenerator { + // This agent doesn't have a live implementation + yield createEvent({ author: 'StopChecker' }); + } + } + + // Loop runs: Refiner -> Checker -> StopChecker + // State['current_code'] is updated each iteration. + // Loop stops if QualityChecker outputs 'pass' (leading to StopChecker escalating) or after 5 iterations. + const refinementLoop = new LoopAgent({ + name: 'CodeRefinementLoop', + maxIterations: 5, + subAgents: [codeRefiner, qualityChecker, new CheckStatusAndEscalate({name: 'StopChecker'})] + }); + ``` + === "Go" ```go @@ -1328,29 +1378,29 @@ By combining ADK's composition primitives, you can implement various established import com.google.adk.agents.InvocationContext; import io.reactivex.rxjava3.core.Flowable; import java.util.List; - + // Agent to generate/refine code based on state['current_code'] and state['requirements'] LlmAgent codeRefiner = LlmAgent.builder() .name("CodeRefiner") .instruction("Read state['current_code'] (if exists) and state['requirements']. Generate/refine Java code to meet requirements. Save to state['current_code'].") .outputKey("current_code") // Overwrites previous code in state .build(); - + // Agent to check if the code meets quality standards LlmAgent qualityChecker = LlmAgent.builder() .name("QualityChecker") .instruction("Evaluate the code in state['current_code'] against state['requirements']. Output 'pass' or 'fail'.") .outputKey("quality_status") .build(); - + BaseAgent checkStatusAndEscalate = new BaseAgent( "StopChecker","Checks quality_status and escalates if 'pass'.", List.of(), null, null) { - + @Override protected Flowable runAsyncImpl(InvocationContext invocationContext) { String status = (String) invocationContext.session().state().getOrDefault("quality_status", "fail"); boolean shouldStop = "pass".equals(status); - + EventActions actions = EventActions.builder().escalate(shouldStop).build(); Event event = Event.builder() .author(this.name()) @@ -1359,68 +1409,19 @@ By combining ADK's composition primitives, you can implement various established return Flowable.just(event); } }; - + LoopAgent refinementLoop = LoopAgent.builder() .name("CodeRefinementLoop") .maxIterations(5) .subAgents(codeRefiner, qualityChecker, checkStatusAndEscalate) .build(); - + // Loop runs: Refiner -> Checker -> StopChecker // State['current_code'] is updated each iteration. // Loop stops if QualityChecker outputs 'pass' (leading to StopChecker escalating) or after 5 // iterations. ``` -=== "Typescript" - - ```typescript - // Conceptual Code: Iterative Code Refinement - import { LoopAgent, LlmAgent, BaseAgent, InvocationContext } from '@google/adk'; - import type { Event, createEvent, createEventActions } from '@google/genai'; - - // Agent to generate/refine code based on state['current_code'] and state['requirements'] - const codeRefiner = new LlmAgent({ - name: 'CodeRefiner', - instruction: 'Read state["current_code"] (if exists) and state["requirements"]. Generate/refine Typescript code to meet requirements. Save to state["current_code"].', - outputKey: 'current_code' // Overwrites previous code in state - }); - - // Agent to check if the code meets quality standards - const qualityChecker = new LlmAgent({ - name: 'QualityChecker', - instruction: 'Evaluate the code in state["current_code"] against state["requirements"]. Output "pass" or "fail".', - outputKey: 'quality_status' - }); - - // Custom agent to check the status and escalate if 'pass' - class CheckStatusAndEscalate extends BaseAgent { - async *runAsyncImpl(ctx: InvocationContext): AsyncGenerator { - const status = ctx.session.state.quality_status; - const shouldStop = status === 'pass'; - if (shouldStop) { - yield createEvent({ - author: 'StopChecker', - actions: createEventActions(), - }); - } - } - - async *runLiveImpl(ctx: InvocationContext): AsyncGenerator { - // This agent doesn't have a live implementation - yield createEvent({ author: 'StopChecker' }); - } - } - - // Loop runs: Refiner -> Checker -> StopChecker - // State['current_code'] is updated each iteration. - // Loop stops if QualityChecker outputs 'pass' (leading to StopChecker escalating) or after 5 iterations. - const refinementLoop = new LoopAgent({ - name: 'CodeRefinementLoop', - maxIterations: 5, - subAgents: [codeRefiner, qualityChecker, new CheckStatusAndEscalate({name: 'StopChecker'})] - }); - ### Human-in-the-Loop Pattern * **Structure:** Integrates human intervention points within an agent workflow. @@ -1437,7 +1438,7 @@ By combining ADK's composition primitives, you can implement various established # Conceptual Code: Using a Tool for Human Approval from google.adk.agents import LlmAgent, SequentialAgent from google.adk.tools import FunctionTool - + # --- Assume external_approval_tool exists --- # This tool would: # 1. Take details (e.g., request_id, amount, reason). @@ -1446,14 +1447,14 @@ By combining ADK's composition primitives, you can implement various established # 4. Return the human's decision. # async def external_approval_tool(amount: float, reason: str) -> str: ... approval_tool = FunctionTool(func=external_approval_tool) - + # Agent that prepares the request prepare_request = LlmAgent( name="PrepareApproval", instruction="Prepare the approval request details based on user input. Store amount and reason in state.", # ... likely sets state['approval_amount'] and state['approval_reason'] ... ) - + # Agent that calls the human approval tool request_approval = LlmAgent( name="RequestHumanApproval", @@ -1461,76 +1462,19 @@ By combining ADK's composition primitives, you can implement various established tools=[approval_tool], output_key="human_decision" ) - + # Agent that proceeds based on human decision process_decision = LlmAgent( name="ProcessDecision", instruction="Check {human_decision}. If 'approved', proceed. If 'rejected', inform user." ) - + approval_workflow = SequentialAgent( name="HumanApprovalWorkflow", sub_agents=[prepare_request, request_approval, process_decision] ) ``` -=== "Java" - - ```java - // Conceptual Code: Using a Tool for Human Approval - import com.google.adk.agents.LlmAgent; - import com.google.adk.agents.SequentialAgent; - import com.google.adk.tools.FunctionTool; - - // --- Assume external_approval_tool exists --- - // This tool would: - // 1. Take details (e.g., request_id, amount, reason). - // 2. Send these details to a human review system (e.g., via API). - // 3. Poll or wait for the human response (approved/rejected). - // 4. Return the human's decision. - // public boolean externalApprovalTool(float amount, String reason) { ... } - FunctionTool approvalTool = FunctionTool.create(externalApprovalTool); - - // Agent that prepares the request - LlmAgent prepareRequest = LlmAgent.builder() - .name("PrepareApproval") - .instruction("Prepare the approval request details based on user input. Store amount and reason in state.") - // ... likely sets state['approval_amount'] and state['approval_reason'] ... - .build(); - - // Agent that calls the human approval tool - LlmAgent requestApproval = LlmAgent.builder() - .name("RequestHumanApproval") - .instruction("Use the external_approval_tool with amount from state['approval_amount'] and reason from state['approval_reason'].") - .tools(approvalTool) - .outputKey("human_decision") - .build(); - - // Agent that proceeds based on human decision - LlmAgent processDecision = LlmAgent.builder() - .name("ProcessDecision") - .instruction("Check {human_decision}. If 'approved', proceed. If 'rejected', inform user.") - .build(); - - SequentialAgent approvalWorkflow = SequentialAgent.builder() - .name("HumanApprovalWorkflow") - .subAgents(prepareRequest, requestApproval, processDecision) - .build(); - ``` - -=== "Go" - - ```go - import ( - "google.golang.org/adk/agent" - "google.golang.org/adk/agent/llmagent" - "google.golang.org/adk/agent/workflowagents/sequentialagent" - "google.golang.org/adk/tool" - ) - - --8<-- "examples/go/snippets/agents/multi-agent/main.go:human-in-loop-pattern" - ``` - === "Typescript" ```typescript @@ -1587,6 +1531,63 @@ By combining ADK's composition primitives, you can implement various established }); ``` +=== "Go" + + ```go + import ( + "google.golang.org/adk/agent" + "google.golang.org/adk/agent/llmagent" + "google.golang.org/adk/agent/workflowagents/sequentialagent" + "google.golang.org/adk/tool" + ) + + --8<-- "examples/go/snippets/agents/multi-agent/main.go:human-in-loop-pattern" + ``` + +=== "Java" + + ```java + // Conceptual Code: Using a Tool for Human Approval + import com.google.adk.agents.LlmAgent; + import com.google.adk.agents.SequentialAgent; + import com.google.adk.tools.FunctionTool; + + // --- Assume external_approval_tool exists --- + // This tool would: + // 1. Take details (e.g., request_id, amount, reason). + // 2. Send these details to a human review system (e.g., via API). + // 3. Poll or wait for the human response (approved/rejected). + // 4. Return the human's decision. + // public boolean externalApprovalTool(float amount, String reason) { ... } + FunctionTool approvalTool = FunctionTool.create(externalApprovalTool); + + // Agent that prepares the request + LlmAgent prepareRequest = LlmAgent.builder() + .name("PrepareApproval") + .instruction("Prepare the approval request details based on user input. Store amount and reason in state.") + // ... likely sets state['approval_amount'] and state['approval_reason'] ... + .build(); + + // Agent that calls the human approval tool + LlmAgent requestApproval = LlmAgent.builder() + .name("RequestHumanApproval") + .instruction("Use the external_approval_tool with amount from state['approval_amount'] and reason from state['approval_reason'].") + .tools(approvalTool) + .outputKey("human_decision") + .build(); + + // Agent that proceeds based on human decision + LlmAgent processDecision = LlmAgent.builder() + .name("ProcessDecision") + .instruction("Check {human_decision}. If 'approved', proceed. If 'rejected', inform user.") + .build(); + + SequentialAgent approvalWorkflow = SequentialAgent.builder() + .name("HumanApprovalWorkflow") + .subAgents(prepareRequest, requestApproval, processDecision) + .build(); + ``` + #### Human in the Loop with Policy A more advanced and structured way to implement Human-in-the-Loop is by using a `PolicyEngine`. This approach allows you to define policies that can trigger a confirmation step from a user before a tool is executed. The `SecurityPlugin` intercepts a tool call, consults the `PolicyEngine`, and if the policy dictates, it will automatically request user confirmation. This pattern is more robust for enforcing governance and security rules. @@ -1606,7 +1607,7 @@ A conceptual example of using a `CustomPolicyEngine` to require user confirmatio === "TypeScript" - ```typescript + ```typescript const rootAgent = new LlmAgent({ name: 'weather_time_agent', model: 'gemini-2.5-flash', @@ -1616,7 +1617,7 @@ A conceptual example of using a `CustomPolicyEngine` to require user confirmatio 'You are a helpful agent who can answer user questions about the time and weather in a city.', tools: [getWeatherTool], }); - + class CustomPolicyEngine implements BasePolicyEngine { async evaluate(_context: ToolCallPolicyContext): Promise { // Default permissive implementation @@ -1631,12 +1632,11 @@ A conceptual example of using a `CustomPolicyEngine` to require user confirmatio agent: rootAgent, appName, plugins: [new SecurityPlugin({policyEngine: new CustomPolicyEngine()})] - }); + }); ``` -You can find the full code sample [here](../../examples/typescript/snippets/agents/workflow-agents/hitl_confirmation_agent.ts). + You can find the full code sample [here](../../examples/typescript/snippets/agents/workflow-agents/hitl_confirmation_agent.ts). ### Combining Patterns These patterns provide starting points for structuring your multi-agent systems. You can mix and match them as needed to create the most effective architecture for your specific application. - diff --git a/docs/agents/workflow-agents/index.md b/docs/agents/workflow-agents/index.md index ea32a762d..6ba7420ef 100644 --- a/docs/agents/workflow-agents/index.md +++ b/docs/agents/workflow-agents/index.md @@ -1,10 +1,10 @@ # Workflow Agents
- Supported in ADKPythonGoJava + Supported in ADKPythonTypeScriptGoJava
-This section introduces "*workflow agents*" - **specialized agents that control the execution flow of its sub-agents**. +This section introduces "*workflow agents*" - **specialized agents that control the execution flow of its sub-agents**. Workflow agents are specialized components in ADK designed purely for **orchestrating the execution flow of sub-agents**. Their primary role is to manage *how* and *when* other agents run, defining the control flow of a process. diff --git a/docs/agents/workflow-agents/loop-agents.md b/docs/agents/workflow-agents/loop-agents.md index c4e1aa3ea..69696f480 100644 --- a/docs/agents/workflow-agents/loop-agents.md +++ b/docs/agents/workflow-agents/loop-agents.md @@ -1,7 +1,7 @@ # Loop agents
- Supported in ADKPython v0.1.0Go v0.1.0Java v0.2.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.2.0
The `LoopAgent` is a workflow agent that executes its sub-agents in a loop (i.e. iteratively). It **_repeatedly runs_ a sequence of agents** for a specified number of iterations or until a termination condition is met. @@ -48,6 +48,11 @@ In this setup, the `LoopAgent` would manage the iterative process. The `CriticA --8<-- "examples/python/snippets/agents/workflow-agents/loop_agent_doc_improv_agent.py:init" ``` + === "Typescript" + ```typescript + --8<-- "examples/typescript/snippets/agents/workflow/loop_agent_doc_improv_agent.ts:init" + ``` + === "Go" ```go --8<-- "examples/go/snippets/agents/workflow-agents/loop/main.go:init" @@ -57,8 +62,3 @@ In this setup, the `LoopAgent` would manage the iterative process. The `CriticA ```java --8<-- "examples/java/snippets/src/main/java/agents/workflow/LoopAgentExample.java:init" ``` - - === "Typescript" - ```typescript - --8<-- "examples/typescript/snippets/agents/workflow/loop_agent_doc_improv_agent.ts:init" - ``` diff --git a/docs/agents/workflow-agents/parallel-agents.md b/docs/agents/workflow-agents/parallel-agents.md index 426d69590..edb501f04 100644 --- a/docs/agents/workflow-agents/parallel-agents.md +++ b/docs/agents/workflow-agents/parallel-agents.md @@ -1,7 +1,7 @@ # Parallel agents
- Supported in ADKPython v0.1.0Go v0.1.0Java v0.2.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.2.0
The `ParallelAgent` is a [workflow agent](index.md) that executes its sub-agents *concurrently*. This dramatically speeds up workflows where tasks can be performed independently. @@ -53,6 +53,11 @@ These research tasks are independent. Using a `ParallelAgent` allows them to ru --8<-- "examples/python/snippets/agents/workflow-agents/parallel_agent_web_research.py:init" ``` + === "Typescript" + ```typescript + --8<-- "examples/typescript/snippets/agents/workflow-agents/parallel_agent_web_research.ts:init" + ``` + === "Go" ```go --8<-- "examples/go/snippets/agents/workflow-agents/parallel/main.go:init" @@ -62,7 +67,3 @@ These research tasks are independent. Using a `ParallelAgent` allows them to ru ```java --8<-- "examples/java/snippets/src/main/java/agents/workflow/ParallelResearchPipeline.java:full_code" ``` - === "Typescript" - ```typescript - --8<-- "examples/typescript/snippets/agents/workflow-agents/parallel_agent_web_research.ts:init" - ``` \ No newline at end of file diff --git a/docs/agents/workflow-agents/sequential-agents.md b/docs/agents/workflow-agents/sequential-agents.md index c1cc4a390..1fef381cb 100644 --- a/docs/agents/workflow-agents/sequential-agents.md +++ b/docs/agents/workflow-agents/sequential-agents.md @@ -1,7 +1,7 @@ # Sequential agents
- Supported in ADKPython v0.1.0Go v0.1.0Java v0.2.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.2.0
The `SequentialAgent` is a [workflow agent](index.md) that executes its sub-agents in the order they are specified in the list. @@ -48,6 +48,11 @@ This ensures the code is written, *then* reviewed, and *finally* refactored, in --8<-- "examples/python/snippets/agents/workflow-agents/sequential_agent_code_development_agent.py:init" ``` + === "Typescript" + ```typescript + --8<-- "examples/typescript/snippets/agents/workflow/sequential_agent_code_development_agent.ts:init" + ``` + === "Go" ```go --8<-- "examples/go/snippets/agents/workflow-agents/sequential/main.go:init" @@ -57,8 +62,3 @@ This ensures the code is written, *then* reviewed, and *finally* refactored, in ```java --8<-- "examples/java/snippets/src/main/java/agents/workflow/SequentialAgentExample.java:init" ``` - - === "Typescript" - ```typescript - --8<-- "examples/typescript/snippets/agents/workflow/sequential_agent_code_development_agent.ts:init" - ``` \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index e76defb44..6a9be39d3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -50,6 +50,12 @@ from simple tasks to complex workflows. pip install google-adk

+=== "TypeScript" +
+

+ npm install @google/adk +

+ === "Go"

@@ -72,17 +78,11 @@ from simple tasks to complex workflows. } ``` -=== "TypeScript" -
-

- npm install @google/adk -

-

Start with Python - Start with TypeScript + Start with TypeScript Start with Go Start with Java

diff --git a/docs/tools-custom/function-tools.md b/docs/tools-custom/function-tools.md index 3924dc345..713c71b65 100644 --- a/docs/tools-custom/function-tools.md +++ b/docs/tools-custom/function-tools.md @@ -1,7 +1,7 @@ # Function tools
- Supported in ADKPython v0.1.0Go v0.1.0Java v0.1.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.1.0
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. @@ -159,21 +159,35 @@ A tool can write data to a `temp:` variable, and a subsequent tool can read it. ??? "Example" === "Python" - + This tool is a python function which obtains the Stock price of a given Stock ticker/ symbol. - + Note: You need to `pip install yfinance` library before using this tool. - - ```py + + ```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. @@ -200,39 +214,25 @@ A tool can write data to a `temp:` variable, and a subsequent tool can read it. ``` === "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. - - ```json - For input `GOOG`: {"symbol": "GOOG", "price": "1.0"} - ``` - - === "Typescript" This tool retrieves the mocked value of a stock price. - ```typescript - --8<-- "examples/typescript/snippets/tools/function-tools/function-tools-example.ts" + ```java + --8<-- "examples/java/snippets/src/main/java/tools/StockPriceAgent.java:full_code" ``` - The return value from this tool will be an object. + The return value from this tool will be wrapped into a Map. ```json - For input `GOOG`: {"price": 2800.0, "currency": "USD"} + For input `GOOG`: {"symbol": "GOOG", "price": "1.0"} ``` ### 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. +* **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](/adk-docs/tools-custom/performance/). @@ -274,10 +274,16 @@ Define your tool function and wrap it using the `LongRunningFunctionTool` class: === "Python" - ```py + ```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 @@ -300,16 +306,16 @@ Define your tool function and wrap it using the `LongRunningFunctionTool` class: 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 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 result = new HashMap<>(); result.put("status", "pending"); @@ -319,12 +325,12 @@ Define your tool function and wrap it using the `LongRunningFunctionTool` class: 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() @@ -335,27 +341,21 @@ Define your tool function and wrap it using the `LongRunningFunctionTool` class: } ``` -=== "TypeScript" - - ```typescript - --8<-- "examples/typescript/snippets/tools/function-tools/long-running-function-tool-example.ts:define_long_running_function" - ``` - ### 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 + If your ADK agent workflow is configured with the [Resume](/adk-docs/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 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 + included with the response. For more details on using the Resume feature, see [Resume stopped agents](/adk-docs/runtime/resume/). @@ -392,10 +392,16 @@ Agent client received an event with long running function calls and check the st === "Python" - ```py + ```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. @@ -410,16 +416,10 @@ Agent client received an event with long running function calls and check the st --8<-- "examples/java/snippets/src/main/java/tools/LongRunningFunctionExample.java:full_code" ``` -=== "TypeScript" - - ```typescript - --8<-- "examples/typescript/snippets/tools/function-tools/long-running-function-tool-example.ts - ``` - ??? "Python complete example: File Processing Simulation" - ```py + ```python --8<-- "examples/python/snippets/tools/function-tools/human_in_the_loop.py" ``` @@ -439,7 +439,7 @@ This powerful feature allows you to leverage the capabilities of other agents wi 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. +* **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. @@ -449,10 +449,16 @@ To use an agent as a tool, wrap the agent with the AgentTool class. === "Python" - ```py + ```python tools=[AgentTool(agent=agent_b)] ``` +=== "TypeScript" + + ```typescript + tools: [new AgentTool({agent: agentB})] + ``` + === "Go" ```go @@ -465,12 +471,6 @@ To use an agent as a tool, wrap the agent with the AgentTool class. AgentTool.create(agent) ``` -=== "TypeScript" - - ```typescript - tools: [new AgentTool({agent: agentB})] - ``` - ### Customization The `AgentTool` class provides the following attributes for customizing its behavior: @@ -481,10 +481,16 @@ The `AgentTool` class provides the following attributes for customizing its beha === "Python" - ```py + ```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 @@ -506,17 +512,11 @@ The `AgentTool` class provides the following attributes for customizing its beha --8<-- "examples/java/snippets/src/main/java/tools/AgentToolCustomization.java:full_code" ``` - === "TypeScript" - - ```typescript - --8<-- "examples/typescript/snippets/tools/function-tools/agent-as-a-tool-example.ts" - ``` - ### How it works -1. When the `main_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 `main_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 `main_agent`.** +1. When the `main_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 `main_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 `main_agent`.** 6. The `main_agent` can then take the summary and formulate its final response to the user (e.g., "Here's a summary of the text: ...") diff --git a/docs/tools-custom/index.md b/docs/tools-custom/index.md index c9db4f198..c6147ea82 100644 --- a/docs/tools-custom/index.md +++ b/docs/tools-custom/index.md @@ -1,7 +1,7 @@ # Custom Tools for ADK
- Supported in ADKPython v0.1.0Go v0.1.0Java v0.1.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.1.0
In an ADK agent workflow, Tools are programming functions with structured input diff --git a/docs/tools-custom/mcp-tools.md b/docs/tools-custom/mcp-tools.md index 39cb01e36..ef0f54e2d 100644 --- a/docs/tools-custom/mcp-tools.md +++ b/docs/tools-custom/mcp-tools.md @@ -1,7 +1,7 @@ # Model Context Protocol Tools
- Supported in ADKPython v0.1.0Go v0.1.0Java v0.1.0 + Supported in ADKPython v0.1.0TypeScript v0.1.0Go v0.1.0Java v0.1.0
This guide walks you through two ways of integrating Model Context Protocol (MCP) with ADK.