diff --git a/docs/tools/function-tools.md b/docs/tools/function-tools.md
index 5243a20b..e2184813 100644
--- a/docs/tools/function-tools.md
+++ b/docs/tools/function-tools.md
@@ -1,7 +1,7 @@
# Function tools
- Supported in ADKPython v0.1.0Java v0.1.0
+ Supported in ADKPython v0.1.0Java v0.1.0Go 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.
@@ -27,13 +27,12 @@ A well-defined function signature is crucial for the LLM to use your tool correc
#### Parameters
-You can define functions with required parameters, optional parameters, and variadic arguments. Here’s how each is handled:
-
##### Required Parameters
-A parameter is considered **required** if it has a type hint but **no default value**. The LLM must provide a value for this argument when it calls the tool.
-???+ "Example: Required Parameters"
- === "Python"
+=== "Python"
+ A parameter is considered **required** if it has a type hint but **no default value**. The LLM must provide a value for this argument when it calls the tool. The parameter's description is taken from the function's docstring.
+
+ ???+ "Example: Required Parameters"
```python
def get_weather(city: str, unit: str):
"""
@@ -48,11 +47,33 @@ A parameter is considered **required** if it has a type hint but **no default va
```
In this example, both `city` and `unit` are mandatory. If the LLM tries to call `get_weather` without one of them, the ADK will return an error to the LLM, prompting it to correct the call.
-##### Optional Parameters with Default Values
-A parameter is considered **optional** if you provide a **default value**. This is the standard Python way to define optional arguments. The ADK correctly interprets these and does not list them in the `required` field of the tool schema sent to the LLM.
+=== "Go"
+ In Go, you use struct tags to control the JSON schema. The two primary tags are `json` and `jsonschema`.
-???+ "Example: Optional Parameter with Default Value"
- === "Python"
+ A parameter is considered **required** if its struct field does **not** have the `omitempty` or `omitzero` option in its `json` tag.
+
+ The `jsonschema` tag is used to provide the argument's description. This is crucial for the LLM to understand what the argument is for.
+
+ ???+ "Example: Required Parameters"
+ ```go
+ // GetWeatherParams defines the arguments for the getWeather tool.
+ type GetWeatherParams struct {
+ // This field is REQUIRED (no "omitempty").
+ // The jsonschema tag provides the description.
+ Location string `json:"location" jsonschema:"The city and state, e.g., San Francisco, CA"`
+
+ // This field is also REQUIRED.
+ Unit string `json:"unit" jsonschema:"The temperature unit, either 'celsius' or 'fahrenheit'"`
+ }
+ ```
+ In this example, both `location` and `unit` are mandatory.
+
+##### Optional Parameters
+
+=== "Python"
+ A parameter is considered **optional** if you provide a **default value**. This is the standard Python way to define optional arguments. You can also mark a parameter as optional using `typing.Optional[SomeType]` or the `| None` syntax (Python 3.10+).
+
+ ???+ "Example: Optional Parameters"
```python
def search_flights(destination: str, departure_date: str, flexible_days: int = 0):
"""
@@ -70,6 +91,25 @@ A parameter is considered **optional** if you provide a **default value**. This
```
Here, `flexible_days` is optional. The LLM can choose to provide it, but it's not required.
+=== "Go"
+ A parameter is considered **optional** if its struct field has the `omitempty` or `omitzero` option in its `json` tag.
+
+ ???+ "Example: Optional Parameters"
+ ```go
+ // GetWeatherParams defines the arguments for the getWeather tool.
+ type GetWeatherParams struct {
+ // Location is required.
+ Location string `json:"location" jsonschema:"The city and state, e.g., San Francisco, CA"`
+
+ // Unit is optional.
+ Unit string `json:"unit,omitempty" jsonschema:"The temperature unit, either 'celsius' or 'fahrenheit'"`
+
+ // Days is optional.
+ Days int `json:"days,omitzero" jsonschema:"The number of forecast days to return (defaults to 1)"`
+ }
+ ```
+ Here, `unit` and `days` are optional. The LLM can choose to provide them, but they are not required.
+
##### Optional Parameters with `typing.Optional`
You can also mark a parameter as optional using `typing.Optional[SomeType]` or the `| None` syntax (Python 3.10+). This signals that the parameter can be `None`. When combined with a default value of `None`, it behaves as a standard optional parameter.
@@ -148,6 +188,31 @@ A tool can write data to a `temp:` variable, and a subsequent tool can read it.
For input `GOOG`: {"symbol": "GOOG", "price": "1.0"}
```
+ === "Go"
+
+ This tool retrieves the mocked value of a stock price.
+
+ ```go
+ import (
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+ )
+
+ --8<-- "examples/go/snippets/tools/function-tools/func_tool.go"
+ ```
+
+ The return value from this tool will be a `getStockPriceResults` instance.
+
+ ```json
+ For input `{"symbol": "GOOG"}`: {"price":300.6,"symbol":"GOOG"}
+ ```
+
### Best Practices
While you have considerable flexibility in defining your function, remember that simplicity enhances usability for the LLM. Consider these guidelines:
@@ -241,6 +306,21 @@ Define your tool function and wrap it using the `LongRunningFunctionTool` class:
}
```
+=== "Go"
+
+ ```go
+ import (
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+ )
+
+ --8<-- "examples/go/snippets/tools/function-tools/long-running-tool/long_running_tool.go:create_long_running_tool"
+ ```
+
### Intermediate / Final result Updates
Agent client received an event with long running function calls and check the status of the ticket. Then Agent client can send the intermediate or final response back to update the progress. The framework packages this value (even if it's None) into the content of the `FunctionResponse` sent back to the LLM.
@@ -302,6 +382,14 @@ 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"
```
+=== "Go"
+
+ The following example demonstrates a multi-turn workflow. First, the user asks the agent to create a ticket. The agent calls the long-running tool and the client captures the `FunctionCall` ID. The client then simulates the asynchronous work completing by sending subsequent `FunctionResponse` messages back to the agent to provide the ticket ID and final status.
+
+ ```go
+ --8<-- "examples/go/snippets/tools/function-tools/long-running-tool/long_running_tool.go:run_long_running_tool"
+ ```
+
??? "Python complete example: File Processing Simulation"
@@ -345,6 +433,13 @@ To use an agent as a tool, wrap the agent with the AgentTool class.
AgentTool.create(agent)
```
+=== "Go"
+
+ ```go
+ agenttool.New(agent, &agenttool.Config{...})
+ ```
+
+
### Customization
The `AgentTool` class provides the following attributes for customizing its behavior:
@@ -365,6 +460,21 @@ The `AgentTool` class provides the following attributes for customizing its beha
--8<-- "examples/java/snippets/src/main/java/tools/AgentToolCustomization.java:full_code"
```
+ === "Go"
+
+ ```go
+ import (
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/agenttool"
+ "google.golang.org/genai"
+ )
+
+ --8<-- "examples/go/snippets/tools/function-tools/func_tool.go:agent_tool_example"
+ ```
+
### How it works
1. When the `main_agent` receives the long text, its instruction tells it to use the 'summarize' tool for long texts.
diff --git a/docs/tools/google-cloud/mcp-toolbox-for-databases.md b/docs/tools/google-cloud/mcp-toolbox-for-databases.md
index 3f096dd2..303bfbd3 100644
--- a/docs/tools/google-cloud/mcp-toolbox-for-databases.md
+++ b/docs/tools/google-cloud/mcp-toolbox-for-databases.md
@@ -79,7 +79,9 @@ documentation:
* [Installing the Server](https://googleapis.github.io/genai-toolbox/getting-started/introduction/#installing-the-server)
* [Configuring Toolbox](https://googleapis.github.io/genai-toolbox/getting-started/configure/)
-## Install client SDK for ADK
+## Install Client SDK for ADK
+
+## Python SDK
ADK relies on the `toolbox-core` python package to use Toolbox. Install the
package before getting started:
@@ -88,7 +90,7 @@ package before getting started:
pip install toolbox-core
```
-## Loading Toolbox Tools
+### Loading Toolbox Tools
Once you’re Toolbox server is configured and up and running, you can load tools
from your server using ADK:
@@ -111,6 +113,67 @@ root_agent = Agent(
)
```
+## Go SDK
+
+ADK relies on the `mcp-toolbox-sdk-go` go module to use Toolbox. Install the
+module before getting started:
+
+```shell
+go get github.com/googleapis/mcp-toolbox-sdk-go
+```
+
+### Loading Toolbox Tools
+
+Once you’re Toolbox server is configured and up and running, you can load tools
+from your server using ADK:
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/googleapis/mcp-toolbox-sdk-go/tbadk"
+ "google.golang.org/adk/agent/llmagent"
+)
+
+func main() {
+
+ toolboxClient, err := tbadk.NewToolboxClient("https://127.0.0.1:5000")
+ if err != nil {
+ log.Fatalf("Failed to create MCP Toolbox client: %v", err)
+ }
+
+ // Load a specific set of tools
+ toolboxtools, err := toolboxClient.LoadToolset("my-toolset-name", ctx)
+ if err != nil {
+ return fmt.Sprintln("Could not load Toolbox Toolset", err)
+ }
+
+ toolsList := make([]tool.Tool, len(toolboxtools))
+ for i := range toolboxtools {
+ toolsList[i] = &toolboxtools[i]
+ }
+
+ llmagent, err := llmagent.New(llmagent.Config{
+ ...,
+ Tools: toolsList,
+ })
+
+ // Load a single tool
+ tool, err := client.LoadTool("my-tool-name", ctx)
+ if err != nil {
+ return fmt.Sprintln("Could not load Toolbox Tool", err)
+ }
+
+ llmagent, err := llmagent.New(llmagent.Config{
+ ...,
+ Tools: []tool.Tool{&toolboxtool},
+ })
+}
+```
+
## Advanced Toolbox Features
Toolbox has a variety of features to make developing Gen AI tools for databases.
diff --git a/docs/tools/mcp-tools.md b/docs/tools/mcp-tools.md
index a3eb5b11..8e3ca0ce 100644
--- a/docs/tools/mcp-tools.md
+++ b/docs/tools/mcp-tools.md
@@ -1,7 +1,7 @@
# Model Context Protocol Tools
- Supported in ADKPython v0.1.0Java v0.1.0
+ Supported in ADKPython v0.1.0Java v0.1.0Go v0.1.0
This guide walks you through two ways of integrating Model Context Protocol (MCP) with ADK.
diff --git a/examples/go/cloud-run/main.go b/examples/go/cloud-run/main.go
new file mode 100644
index 00000000..cbba5928
--- /dev/null
+++ b/examples/go/cloud-run/main.go
@@ -0,0 +1,101 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/cmd/launcher/adk"
+ "google.golang.org/adk/cmd/launcher/full"
+ "google.golang.org/adk/server/restapi/services"
+
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+type getCapitalCityArgs struct {
+ Country string `json:"country"`
+}
+
+type getCapitalCityResult struct {
+ Result string `json:"result,omitempty"`
+ ErrorMessage string `json:"error_message,omitempty"`
+}
+
+func getCapitalCity(ctx tool.Context, args getCapitalCityArgs) getCapitalCityResult {
+ capitals := map[string]string{
+ "united states": "Washington, D.C.",
+ "canada": "Ottawa",
+ "france": "Paris",
+ "japan": "Tokyo",
+ }
+ capital, ok := capitals[strings.ToLower(args.Country)]
+ if !ok {
+ result := fmt.Sprintf("Sorry, I couldn't find the capital for %s.", args.Country)
+ return getCapitalCityResult{ErrorMessage: result}
+ }
+
+ return getCapitalCityResult{Result: capital}
+}
+
+func main() {
+ ctx := context.Background()
+
+ model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{
+ APIKey: os.Getenv("GOOGLE_API_KEY"),
+ })
+ if err != nil {
+ log.Fatalf("Failed to create model: %v", err)
+ }
+
+ capitalTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "get_capital_city",
+ Description: "Retrieves the capital city for a given country.",
+ },
+ getCapitalCity,
+ )
+ if err != nil {
+ log.Fatalf("Failed to create function tool: %v", err)
+ }
+
+ agent, err := llmagent.New(llmagent.Config{
+ Name: "capital_agent",
+ Model: model,
+ Description: "Agent to find the capital city of a country.",
+ Instruction: "I can answer your questions about the capital city of a country.",
+ Tools: []tool.Tool{capitalTool},
+ })
+ if err != nil {
+ log.Fatalf("Failed to create agent: %v", err)
+ }
+
+ config := &adk.Config{
+ AgentLoader: services.NewSingleAgentLoader(agent),
+ }
+
+ l := full.NewLauncher()
+ err = l.Execute(ctx, config, os.Args[1:])
+ if err != nil {
+ log.Fatalf("run failed: %v\n\n%s", err, l.CommandLineSyntax())
+ }
+}
diff --git a/examples/go/snippets/agents/custom-agent/storyflow_agent.go b/examples/go/snippets/agents/custom-agent/storyflow_agent.go
new file mode 100644
index 00000000..4e6af708
--- /dev/null
+++ b/examples/go/snippets/agents/custom-agent/storyflow_agent.go
@@ -0,0 +1,292 @@
+// --8<-- [start:full_code]
+package main
+
+import (
+ "context"
+ "fmt"
+ "iter"
+ "log"
+
+ "google.golang.org/adk/agent/workflowagents/loopagent"
+ "google.golang.org/adk/agent/workflowagents/sequentialagent"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/genai"
+)
+
+// --8<-- [start:init]
+// StoryFlowAgent is a custom agent that orchestrates a story generation workflow.
+// It encapsulates the logic of running sub-agents in a specific sequence.
+type StoryFlowAgent struct {
+ storyGenerator agent.Agent
+ revisionLoopAgent agent.Agent
+ postProcessorAgent agent.Agent
+}
+
+// NewStoryFlowAgent creates and configures the entire custom agent workflow.
+// It takes individual LLM agents as input and internally creates the necessary
+// workflow agents (loop, sequential), returning the final orchestrator agent.
+func NewStoryFlowAgent(
+ storyGenerator,
+ critic,
+ reviser,
+ grammarCheck,
+ toneCheck agent.Agent,
+) (agent.Agent, error) {
+ loopAgent, err := loopagent.New(loopagent.Config{
+ MaxIterations: 2,
+ AgentConfig: agent.Config{
+ Name: "CriticReviserLoop",
+ SubAgents: []agent.Agent{critic, reviser},
+ },
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to create loop agent: %w", err)
+ }
+
+ sequentialAgent, err := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{
+ Name: "PostProcessing",
+ SubAgents: []agent.Agent{grammarCheck, toneCheck},
+ },
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to create sequential agent: %w", err)
+ }
+
+ // The StoryFlowAgent struct holds the agents needed for the Run method.
+ orchestrator := &StoryFlowAgent{
+ storyGenerator: storyGenerator,
+ revisionLoopAgent: loopAgent,
+ postProcessorAgent: sequentialAgent,
+ }
+
+ // agent.New creates the final agent, wiring up the Run method.
+ return agent.New(agent.Config{
+ Name: "StoryFlowAgent",
+ Description: "Orchestrates story generation, critique, revision, and checks.",
+ SubAgents: []agent.Agent{storyGenerator, loopAgent, sequentialAgent},
+ Run: orchestrator.Run,
+ })
+}
+
+// --8<-- [end:init]
+
+// --8<-- [start:executionlogic]
+// Run defines the custom execution logic for the StoryFlowAgent.
+func (s *StoryFlowAgent) Run(ctx agent.InvocationContext) iter.Seq2[*session.Event, error] {
+ return func(yield func(*session.Event, error) bool) {
+ // Stage 1: Initial Story Generation
+ for event, err := range s.storyGenerator.Run(ctx) {
+ if err != nil {
+ yield(nil, fmt.Errorf("story generator failed: %w", err))
+ return
+ }
+ if !yield(event, nil) {
+ return
+ }
+ }
+
+ // Check if story was generated before proceeding
+ currentStory, err := ctx.Session().State().Get("current_story")
+ if err != nil || currentStory == "" {
+ log.Println("Failed to generate initial story. Aborting workflow.")
+ return
+ }
+
+ // Stage 2: Critic-Reviser Loop
+ for event, err := range s.revisionLoopAgent.Run(ctx) {
+ if err != nil {
+ yield(nil, fmt.Errorf("loop agent failed: %w", err))
+ return
+ }
+ if !yield(event, nil) {
+ return
+ }
+ }
+
+ // Stage 3: Post-Processing
+ for event, err := range s.postProcessorAgent.Run(ctx) {
+ if err != nil {
+ yield(nil, fmt.Errorf("sequential agent failed: %w", err))
+ return
+ }
+ if !yield(event, nil) {
+ return
+ }
+ }
+
+ // Stage 4: Conditional Regeneration
+ toneResult, err := ctx.Session().State().Get("tone_check_result")
+ if err != nil {
+ log.Printf("Could not read tone_check_result from state: %v. Assuming tone is not negative.", err)
+ return
+ }
+
+ if tone, ok := toneResult.(string); ok && tone == "negative" {
+ log.Println("Tone is negative. Regenerating story...")
+ for event, err := range s.storyGenerator.Run(ctx) {
+ if err != nil {
+ yield(nil, fmt.Errorf("story regeneration failed: %w", err))
+ return
+ }
+ if !yield(event, nil) {
+ return
+ }
+ }
+ } else {
+ log.Println("Tone is not negative. Keeping current story.")
+ }
+ }
+}
+
+// --8<-- [end:executionlogic]
+
+const (
+ modelName = "gemini-2.0-flash"
+ appName = "story_app"
+ userID = "user_12345"
+)
+
+func main() {
+ ctx := context.Background()
+ model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("Failed to create model: %v", err)
+ }
+
+ // --8<-- [start:llmagents]
+ // --- Define the individual LLM agents ---
+ storyGenerator, err := llmagent.New(llmagent.Config{
+ Name: "StoryGenerator",
+ Model: model,
+ Description: "Generates the initial story.",
+ Instruction: "You are a story writer. Write a short story (around 100 words) about a cat, based on the topic: {topic}",
+ OutputKey: "current_story",
+ })
+ if err != nil {
+ log.Fatalf("Failed to create StoryGenerator agent: %v", err)
+ }
+
+ critic, err := llmagent.New(llmagent.Config{
+ Name: "Critic",
+ Model: model,
+ Description: "Critiques the story.",
+ Instruction: "You are a story critic. Review the story: {current_story}. Provide 1-2 sentences of constructive criticism on how to improve it. Focus on plot or character.",
+ OutputKey: "criticism",
+ })
+ if err != nil {
+ log.Fatalf("Failed to create Critic agent: %v", err)
+ }
+
+ reviser, err := llmagent.New(llmagent.Config{
+ Name: "Reviser",
+ Model: model,
+ Description: "Revises the story based on criticism.",
+ Instruction: "You are a story reviser. Revise the story: {current_story}, based on the criticism: {criticism}. Output only the revised story.",
+ OutputKey: "current_story",
+ })
+ if err != nil {
+ log.Fatalf("Failed to create Reviser agent: %v", err)
+ }
+
+ grammarCheck, err := llmagent.New(llmagent.Config{
+ Name: "GrammarCheck",
+ Model: model,
+ Description: "Checks grammar and suggests corrections.",
+ Instruction: "You are a grammar checker. Check the grammar of the story: {current_story}. Output only the suggested corrections as a list, or output 'Grammar is good!' if there are no errors.",
+ OutputKey: "grammar_suggestions",
+ })
+ if err != nil {
+ log.Fatalf("Failed to create GrammarCheck agent: %v", err)
+ }
+
+ toneCheck, err := llmagent.New(llmagent.Config{
+ Name: "ToneCheck",
+ Model: model,
+ Description: "Analyzes the tone of the story.",
+ Instruction: "You are a tone analyzer. Analyze the tone of the story: {current_story}. Output only one word: 'positive' if the tone is generally positive, 'negative' if the tone is generally negative, or 'neutral' otherwise.",
+ OutputKey: "tone_check_result",
+ })
+ if err != nil {
+ log.Fatalf("Failed to create ToneCheck agent: %v", err)
+ }
+ // --8<-- [end:llmagents]
+
+ // --8<-- [start:story_flow_agent]
+ // Instantiate the custom agent, which encapsulates the workflow agents.
+ storyFlowAgent, err := NewStoryFlowAgent(
+ storyGenerator,
+ critic,
+ reviser,
+ grammarCheck,
+ toneCheck,
+ )
+ if err != nil {
+ log.Fatalf("Failed to create story flow agent: %v", err)
+ }
+
+ // --- Run the Agent ---
+ sessionService := session.InMemoryService()
+ initialState := map[string]any{
+ "topic": "a brave kitten exploring a haunted house",
+ }
+ sessionInstance, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ State: initialState,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create session: %v", err)
+ }
+
+ userTopic := "a lonely robot finding a friend in a junkyard"
+
+ r, err := runner.New(runner.Config{
+ AppName: appName,
+ Agent: storyFlowAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create runner: %v", err)
+ }
+
+ input := genai.NewContentFromText("Generate a story about: "+userTopic, genai.RoleUser)
+ events := r.Run(ctx, userID, sessionInstance.Session.ID(), input, agent.RunConfig{
+ StreamingMode: agent.StreamingModeSSE,
+ })
+
+ var finalResponse string
+ for event, err := range events {
+ if err != nil {
+ log.Fatalf("An error occurred during agent execution: %v", err)
+ }
+
+ for _, part := range event.Content.Parts {
+ // Accumulate text from all parts of the final response.
+ finalResponse += part.Text
+ }
+ }
+
+ fmt.Println("\n--- Agent Interaction Result ---")
+ fmt.Println("Agent Final Response: " + finalResponse)
+
+ finalSession, err := sessionService.Get(ctx, &session.GetRequest{
+ UserID: userID,
+ AppName: appName,
+ SessionID: sessionInstance.Session.ID(),
+ })
+
+ if err != nil {
+ log.Fatalf("Failed to retrieve final session: %v", err)
+ }
+
+ fmt.Println("Final Session State:", finalSession.Session.State())
+}
+
+// --8<-- [end:story_flow_agent]
+// --8<-- [end:full_code]
diff --git a/examples/go/snippets/agents/llm-agents/main.go b/examples/go/snippets/agents/llm-agents/main.go
new file mode 100644
index 00000000..5c393a35
--- /dev/null
+++ b/examples/go/snippets/agents/llm-agents/main.go
@@ -0,0 +1,223 @@
+// --8<-- [start:full_code]
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+
+ "google.golang.org/genai"
+)
+
+// --- Main Runnable Example ---
+
+const (
+ modelName = "gemini-2.0-flash"
+ appName = "agent_comparison_app"
+ userID = "test_user_456"
+)
+
+type getCapitalCityArgs struct {
+ Country string `json:"country"`
+}
+
+// getCapitalCity retrieves the capital city of a given country.
+func getCapitalCity(ctx tool.Context, args getCapitalCityArgs) map[string]any {
+ fmt.Printf("\n-- Tool Call: getCapitalCity(country='%s') --\n", args.Country)
+ capitals := map[string]string{
+ "united states": "Washington, D.C.",
+ "canada": "Ottawa",
+ "france": "Paris",
+ "japan": "Tokyo",
+ }
+ capital, ok := capitals[strings.ToLower(args.Country)]
+ if !ok {
+ result := fmt.Sprintf("Sorry, I couldn't find the capital for %s.", args.Country)
+ fmt.Printf("-- Tool Result: '%s' --\n", result)
+ return map[string]any{"result": result}
+ }
+ fmt.Printf("-- Tool Result: '%s' --\n", capital)
+ return map[string]any{"result": capital}
+}
+
+// callAgent is a helper function to execute an agent with a given prompt and handle its output.
+func callAgent(ctx context.Context, a agent.Agent, outputKey string, prompt string) {
+ fmt.Printf("\n>>> Calling Agent: '%s' | Query: %s\n", a.Name(), prompt)
+ // Create an in-memory session service to manage agent state.
+ sessionService := session.InMemoryService()
+
+ // Create a new session for the agent interaction.
+ sessionCreateResponse, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create the session service: %v", err)
+ }
+
+ session := sessionCreateResponse.Session
+
+ // Configure the runner with the application name, agent, and session service.
+ config := runner.Config{
+ AppName: appName,
+ Agent: a,
+ SessionService: sessionService,
+ }
+
+ // Create a new runner instance.
+ r, err := runner.New(config)
+ if err != nil {
+ log.Fatalf("Failed to create the runner: %v", err)
+ }
+
+ // Prepare the user's message to send to the agent.
+ sessionID := session.ID()
+ userMsg := &genai.Content{
+ Parts: []*genai.Part{
+ genai.NewPartFromText(prompt),
+ },
+ Role: string(genai.RoleUser),
+ }
+
+ // Run the agent and process the streaming events.
+ for event, err := range r.Run(ctx, userID, sessionID, userMsg, agent.RunConfig{
+ StreamingMode: agent.StreamingModeSSE,
+ }) {
+ if err != nil {
+ fmt.Printf("\nAGENT_ERROR: %v\n", err)
+ } else if event.Partial {
+ // Print partial responses as they are received.
+ for _, p := range event.Content.Parts {
+ fmt.Print(p.Text)
+ }
+ }
+ }
+
+ // After the run, check if there's an expected output key in the session state.
+ if outputKey != "" {
+ storedOutput, error := session.State().Get(outputKey)
+ if error == nil {
+ // Pretty-print the stored output if it's a JSON string.
+ fmt.Printf("\n--- Session State ['%s']: ", outputKey)
+ storedString, isString := storedOutput.(string)
+ if isString {
+ var prettyJSON map[string]interface{}
+ if err := json.Unmarshal([]byte(storedString), &prettyJSON); err == nil {
+ indentedJSON, err := json.MarshalIndent(prettyJSON, "", " ")
+ if err == nil {
+ fmt.Println(string(indentedJSON))
+ } else {
+ fmt.Println(storedString)
+ }
+ } else {
+ fmt.Println(storedString)
+ }
+ } else {
+ fmt.Println(storedOutput)
+ }
+ fmt.Println(strings.Repeat("-", 30))
+ }
+ }
+}
+
+func main() {
+ ctx := context.Background()
+
+ model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("Failed to create model: %v", err)
+ }
+
+ capitalTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "get_capital_city",
+ Description: "Retrieves the capital city for a given country.",
+ },
+ getCapitalCity,
+ )
+ if err != nil {
+ log.Fatalf("Failed to create function tool: %v", err)
+ }
+
+ countryInputSchema := &genai.Schema{
+ Type: genai.TypeObject,
+ Description: "Input for specifying a country.",
+ Properties: map[string]*genai.Schema{
+ "country": {
+ Type: genai.TypeString,
+ Description: "The country to get information about.",
+ },
+ },
+ Required: []string{"country"},
+ }
+
+ capitalAgentWithTool, err := llmagent.New(llmagent.Config{
+ Name: "capital_agent_tool",
+ Model: model,
+ Description: "Retrieves the capital city using a specific tool.",
+ Instruction: `You are a helpful agent that provides the capital city of a country using a tool.
+The user will provide the country name in a JSON format like {"country": "country_name"}.
+1. Extract the country name.
+2. Use the 'get_capital_city' tool to find the capital.
+3. Respond clearly to the user, stating the capital city found by the tool.`,
+ Tools: []tool.Tool{capitalTool},
+ InputSchema: countryInputSchema,
+ OutputKey: "capital_tool_result",
+ })
+ if err != nil {
+ log.Fatalf("Failed to create capital agent with tool: %v", err)
+ }
+
+ capitalInfoOutputSchema := &genai.Schema{
+ Type: genai.TypeObject,
+ Description: "Schema for capital city information.",
+ Properties: map[string]*genai.Schema{
+ "capital": {
+ Type: genai.TypeString,
+ Description: "The capital city of the country.",
+ },
+ "population_estimate": {
+ Type: genai.TypeString,
+ Description: "An estimated population of the capital city.",
+ },
+ },
+ Required: []string{"capital", "population_estimate"},
+ }
+ schemaJSON, _ := json.Marshal(capitalInfoOutputSchema)
+ structuredInfoAgentSchema, err := llmagent.New(llmagent.Config{
+ Name: "structured_info_agent_schema",
+ Model: model,
+ Description: "Provides capital and estimated population in a specific JSON format.",
+ Instruction: fmt.Sprintf(`You are an agent that provides country information.
+The user will provide the country name in a JSON format like {"country": "country_name"}.
+Respond ONLY with a JSON object matching this exact schema:
+%s
+Use your knowledge to determine the capital and estimate the population. Do not use any tools.`, string(schemaJSON)),
+ InputSchema: countryInputSchema,
+ OutputSchema: capitalInfoOutputSchema,
+ OutputKey: "structured_info_result",
+ })
+ if err != nil {
+ log.Fatalf("Failed to create structured info agent: %v", err)
+ }
+
+ fmt.Println("--- Testing Agent with Tool ---")
+ callAgent(ctx, capitalAgentWithTool, "capital_tool_result", `{"country": "France"}`)
+ callAgent(ctx, capitalAgentWithTool, "capital_tool_result", `{"country": "Canada"}`)
+
+ fmt.Println("\n\n--- Testing Agent with Output Schema (No Tool Use) ---")
+ callAgent(ctx, structuredInfoAgentSchema, "structured_info_result", `{"country": "France"}`)
+ callAgent(ctx, structuredInfoAgentSchema, "structured_info_result", `{"country": "Japan"}`)
+}
+
+// --8<-- [end:full_code]
diff --git a/examples/go/snippets/agents/llm-agents/snippets/main.go b/examples/go/snippets/agents/llm-agents/snippets/main.go
new file mode 100644
index 00000000..3f5ae958
--- /dev/null
+++ b/examples/go/snippets/agents/llm-agents/snippets/main.go
@@ -0,0 +1,187 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+
+ "google.golang.org/genai"
+)
+
+// --- Documentation Snippets ---
+// The following functions are self-contained examples for documentation.
+// They are not called by the main application.
+
+func _snippet_identity(model model.LLM) {
+ // --8<-- [start:identity]
+ // Example: Defining the basic identity
+ agent, err := llmagent.New(llmagent.Config{
+ Name: "capital_agent",
+ Model: model,
+ Description: "Answers user questions about the capital city of a given country.",
+ // instruction and tools will be added next
+ })
+ // --8<-- [end:identity]
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println("Agent created:", agent.Name())
+}
+
+func _snippet_instruction(model model.LLM) {
+ // --8<-- [start:instruction]
+ // Example: Adding instructions
+ agent, err := llmagent.New(llmagent.Config{
+ Name: "capital_agent",
+ Model: model,
+ 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 'get_capital_city' 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
+ })
+ // --8<-- [end:instruction]
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println("Agent with instruction created:", agent.Name())
+}
+
+func _snippet_tool_example(model model.LLM) {
+ // --8<-- [start:tool_example]
+ // Define a tool function
+ type getCapitalCityArgs struct {
+ Country string `json:"country"`
+ }
+ getCapitalCity := func(ctx tool.Context, args getCapitalCityArgs) map[string]any {
+ // Replace with actual logic (e.g., API call, database lookup)
+ capitals := map[string]string{"france": "Paris", "japan": "Tokyo", "canada": "Ottawa"}
+ capital, ok := capitals[strings.ToLower(args.Country)]
+ if !ok {
+ return map[string]any{"result": fmt.Sprintf("Sorry, I don't know the capital of %s.", args.Country)}
+ }
+ return map[string]any{"result": capital}
+ }
+
+ // Add the tool to the agent
+ capitalTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "get_capital_city",
+ Description: "Retrieves the capital city for a given country.",
+ },
+ getCapitalCity,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+ agent, err := llmagent.New(llmagent.Config{
+ Name: "capital_agent",
+ Model: model,
+ 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: []tool.Tool{capitalTool},
+ })
+ // --8<-- [end:tool_example]
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println("Agent with tool created:", agent.Name())
+}
+
+func _snippet_schema_example(model model.LLM) {
+ // --8<-- [start:schema_example]
+ capitalOutput := &genai.Schema{
+ Type: genai.TypeObject,
+ Description: "Schema for capital city information.",
+ Properties: map[string]*genai.Schema{
+ "capital": {
+ Type: genai.TypeString,
+ Description: "The capital city of the country.",
+ },
+ },
+ }
+
+ agent, err := llmagent.New(llmagent.Config{
+ Name: "structured_capital_agent",
+ Model: model,
+ Description: "Provides capital information in a structured format.",
+ Instruction: `You are a Capital Information Agent. Given a country, respond ONLY with a JSON object containing the capital. Format: {"capital": "capital_name"}`,
+ OutputSchema: capitalOutput,
+ OutputKey: "found_capital",
+ // Cannot use the capitalTool tool effectively here
+ })
+ // --8<-- [end:schema_example]
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println("Agent with output schema created:", agent.Name())
+}
+
+func _snippet_gen_config(model model.LLM) {
+ // --8<-- [start:gen_config]
+ temperature := float32(0.2)
+ agent, err := llmagent.New(llmagent.Config{
+ Name: "gen_config_agent",
+ Model: model,
+ GenerateContentConfig: &genai.GenerateContentConfig{
+ Temperature: &temperature,
+ MaxOutputTokens: 250,
+ },
+ })
+ // --8<-- [end:gen_config]
+
+ if err != nil {
+ log.Fatalf("Failed to create agent with generation config: %v", err)
+ }
+ fmt.Println("Agent with generation config created:", agent.Name())
+}
+
+func _snippet_include_contents(model model.LLM) {
+ // --8<-- [start:include_contents]
+ agent, err := llmagent.New(llmagent.Config{
+ Name: "stateless_agent",
+ Model: model,
+ IncludeContents: llmagent.IncludeContentsNone,
+ })
+ // --8<-- [end:include_contents]
+ if err != nil {
+ log.Fatalf("Failed to create agent with include contents none: %v", err)
+ }
+ fmt.Println("Stateless agent created:", agent.Name())
+}
+
+func main() {
+ // Call all snippet functions to ensure they compile.
+ ctx := context.Background()
+
+ modelName := "gemini-2.5-flash"
+ model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("Failed to create model: %v", err)
+ }
+
+ _snippet_include_contents(model)
+ _snippet_identity(model)
+ _snippet_instruction(model)
+ _snippet_tool_example(model)
+ _snippet_gen_config(model)
+ _snippet_schema_example(model)
+ // Note: The full runnable example is in the ../main.go file.
+}
diff --git a/examples/go/snippets/agents/models/models.go b/examples/go/snippets/agents/models/models.go
new file mode 100644
index 00000000..be65fe42
--- /dev/null
+++ b/examples/go/snippets/agents/models/models.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "context"
+ "log"
+
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/genai"
+)
+
+func main() {
+ ctx := context.Background()
+ // --8<-- [start:gemini-example]
+ // --- Example using a stable Gemini Flash model ---
+ modelFlash, err := gemini.NewModel(ctx, "gemini-2.0-flash", &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("failed to create model: %v", err)
+ }
+ agentGeminiFlash, err := llmagent.New(llmagent.Config{
+ // Use the latest stable Flash model identifier
+ Model: modelFlash,
+ Name: "gemini_flash_agent",
+ Instruction: "You are a fast and helpful Gemini assistant.",
+ // ... other agent parameters
+ })
+ if err != nil {
+ log.Fatalf("failed to create agent: %v", err)
+ }
+
+ // --- Example using a powerful Gemini Pro model ---
+ // Note: Always check the official Gemini documentation for the latest model names,
+ // including specific preview versions if needed. Preview models might have
+ // different availability or quota limitations.
+ modelPro, err := gemini.NewModel(ctx, "gemini-2.5-pro-preview-03-25", &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("failed to create model: %v", err)
+ }
+ agentGeminiPro, err := llmagent.New(llmagent.Config{
+ // Use the latest generally available Pro model identifier
+ Model: modelPro,
+ Name: "gemini_pro_agent",
+ Instruction: "You are a powerful and knowledgeable Gemini assistant.",
+ // ... other agent parameters
+ })
+ if err != nil {
+ log.Fatalf("failed to create agent: %v", err)
+ }
+ // --8<-- [end:gemini-example]
+ log.Println("agentGeminiFlash created successfully.")
+ log.Println("agentGeminiPro created successfully.")
+ _, _ = agentGeminiFlash, agentGeminiPro // Avoid unused variable error
+}
diff --git a/examples/go/snippets/agents/multi-agent/main.go b/examples/go/snippets/agents/multi-agent/main.go
new file mode 100644
index 00000000..8db3ca12
--- /dev/null
+++ b/examples/go/snippets/agents/multi-agent/main.go
@@ -0,0 +1,371 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "iter"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/agent/workflowagents/loopagent"
+ "google.golang.org/adk/agent/workflowagents/parallelagent"
+ "google.golang.org/adk/agent/workflowagents/sequentialagent"
+ "google.golang.org/adk/model"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/agenttool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+func basicWorkflowSnippets(m model.LLM) {
+ // --8<-- [start:sequential-pipeline]
+ // Conceptual Example: Sequential Pipeline
+ step1, _ := llmagent.New(llmagent.Config{Name: "Step1_Fetch", OutputKey: "data", Model: m}) // Saves output to state["data"]
+ step2, _ := llmagent.New(llmagent.Config{Name: "Step2_Process", Instruction: "Process data from {data}.", Model: m})
+
+ pipeline, _ := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{Name: "MyPipeline", SubAgents: []agent.Agent{step1, step2}},
+ })
+ // When pipeline runs, Step2 can access the state["data"] set by Step1.
+ // --8<-- [end:sequential-pipeline]
+ _ = pipeline // Avoid unused variable error
+
+ // --8<-- [start:parallel-execution]
+ // Conceptual Example: Parallel Execution
+ fetchWeather, _ := llmagent.New(llmagent.Config{Name: "WeatherFetcher", OutputKey: "weather", Model: m})
+ fetchNews, _ := llmagent.New(llmagent.Config{Name: "NewsFetcher", OutputKey: "news", Model: m})
+
+ gatherer, _ := parallelagent.New(parallelagent.Config{
+ AgentConfig: agent.Config{Name: "InfoGatherer", SubAgents: []agent.Agent{fetchWeather, fetchNews}},
+ })
+ // When gatherer runs, WeatherFetcher and NewsFetcher run concurrently.
+ // A subsequent agent could read state["weather"] and state["news"].
+ // --8<-- [end:parallel-execution]
+ _ = gatherer // Avoid unused variable error
+
+ // --8<-- [start:loop-with-condition]
+ // Conceptual Example: Loop with Condition
+ // Custom agent to check state
+ checkCondition, _ := agent.New(agent.Config{
+ Name: "Checker",
+ Run: func(ctx agent.InvocationContext) iter.Seq2[*session.Event, error] {
+ return func(yield func(*session.Event, error) bool) {
+ status, err := ctx.Session().State().Get("status")
+ // If "status" is not in the state, default to "pending".
+ // This is idiomatic Go for handling a potential error on lookup.
+ if err != nil {
+ status = "pending"
+ }
+ isDone := status == "completed"
+ yield(&session.Event{Author: "Checker", Actions: session.EventActions{Escalate: isDone}}, nil)
+ }
+ },
+ })
+
+ processStep, _ := llmagent.New(llmagent.Config{Name: "ProcessingStep", Model: m}) // Agent that might update state["status"]
+
+ poller, _ := loopagent.New(loopagent.Config{
+ MaxIterations: 10,
+ AgentConfig: agent.Config{Name: "StatusPoller", SubAgents: []agent.Agent{processStep, checkCondition}},
+ })
+ // When poller runs, it executes processStep then Checker repeatedly
+ // until Checker escalates (state["status"] == "completed") or 10 iterations pass.
+ // --8<-- [end:loop-with-condition]
+ _ = poller // Avoid unused variable error
+}
+
+func agentInteractionSnippets(m model.LLM) {
+ // --8<-- [start:hierarchy]
+ // Conceptual Example: Defining Hierarchy
+ // Define individual agents
+ greeter, _ := llmagent.New(llmagent.Config{Name: "Greeter", Model: m})
+ taskDoer, _ := agent.New(agent.Config{Name: "TaskExecutor"}) // Custom non-LLM agent
+
+ // Create parent agent and assign children via sub_agents
+ coordinator, _ := llmagent.New(llmagent.Config{
+ Name: "Coordinator",
+ Model: m,
+ Description: "I coordinate greetings and tasks.",
+ SubAgents: []agent.Agent{greeter, taskDoer}, // Assign sub_agents here
+ })
+ // --8<-- [end:hierarchy]
+ _ = coordinator // Avoid unused variable error
+
+ // --8<-- [start:output-key-state]
+ // Conceptual Example: Using output_key and reading state
+ agentA, _ := llmagent.New(llmagent.Config{Name: "AgentA", Instruction: "Find the capital of France.", OutputKey: "capital_city", Model: m})
+ agentB, _ := llmagent.New(llmagent.Config{Name: "AgentB", Instruction: "Tell me about the city stored in {capital_city}.", Model: m})
+
+ pipeline2, _ := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{Name: "CityInfo", SubAgents: []agent.Agent{agentA, agentB}},
+ })
+ // AgentA runs, saves "Paris" to state["capital_city"].
+ // AgentB runs, its instruction processor reads state["capital_city"] to get "Paris".
+ // --8<-- [end:output-key-state]
+ _ = pipeline2 // Avoid unused variable error
+
+ // --8<-- [start:llm-transfer]
+ // Conceptual Setup: LLM Transfer
+ bookingAgent, _ := llmagent.New(llmagent.Config{Name: "Booker", Description: "Handles flight and hotel bookings.", Model: m})
+ infoAgent, _ := llmagent.New(llmagent.Config{Name: "Info", Description: "Provides general information and answers questions.", Model: m})
+
+ coordinator, _ = llmagent.New(llmagent.Config{
+ Name: "Coordinator",
+ Model: m,
+ Instruction: "You are an assistant. Delegate booking tasks to Booker and info requests to Info.",
+ Description: "Main coordinator.",
+ SubAgents: []agent.Agent{bookingAgent, infoAgent},
+ })
+
+ // If coordinator receives "Book a flight", its LLM should generate:
+ // FunctionCall{Name: "transfer_to_agent", Args: map[string]any{"agent_name": "Booker"}}
+ // ADK framework then routes execution to bookingAgent.
+ // --8<-- [end:llm-transfer]
+
+ fmt.Println("Coordinator agent created:", coordinator.Name())
+
+ // --8<-- [start:agent-as-tool]
+ // Conceptual Setup: Agent as a Tool
+ // Define a target agent (could be LlmAgent or custom BaseAgent)
+ imageAgent, _ := agent.New(agent.Config{
+ Name: "ImageGen",
+ Description: "Generates an image based on a prompt.",
+ Run: func(ctx agent.InvocationContext) iter.Seq2[*session.Event, error] {
+ return func(yield func(*session.Event, error) bool) {
+ prompt, _ := ctx.Session().State().Get("image_prompt")
+ fmt.Printf("Generating image for prompt: %v\n", prompt)
+ imageBytes := []byte("...") // Simulate image bytes
+ yield(&session.Event{
+ Author: "ImageGen",
+ LLMResponse: model.LLMResponse{
+ Content: &genai.Content{
+ Parts: []*genai.Part{genai.NewPartFromBytes(imageBytes, "image/png")},
+ },
+ },
+ }, nil)
+ }
+ },
+ })
+
+ // Wrap the agent
+ imageTool := agenttool.New(imageAgent, nil)
+
+ // Now imageTool can be used as a tool by other agents.
+
+ // Parent agent uses the AgentTool
+ artistAgent, _ := llmagent.New(llmagent.Config{
+ Name: "Artist",
+ Model: m,
+ Instruction: "Create a prompt and use the ImageGen tool to generate the image.",
+ Tools: []tool.Tool{imageTool}, // Include the AgentTool
+ })
+ // Artist LLM generates a prompt, then calls:
+ // FunctionCall{Name: "ImageGen", Args: map[string]any{"image_prompt": "a cat wearing a hat"}}
+ // Framework calls imageTool.Run(...), which runs ImageGeneratorAgent.
+ // The resulting image Part is returned to the Artist agent as the tool result.
+ // --8<-- [end:agent-as-tool]
+ _ = artistAgent // Avoid unused variable error
+}
+
+func advancedPatternSnippets(m model.LLM) {
+ // --8<-- [start:coordinator-pattern]
+ // Conceptual Code: Coordinator using LLM Transfer
+ billingAgent, _ := llmagent.New(llmagent.Config{Name: "Billing", Description: "Handles billing inquiries.", Model: m})
+ supportAgent, _ := llmagent.New(llmagent.Config{Name: "Support", Description: "Handles technical support requests.", Model: m})
+
+ coordinator, _ := llmagent.New(llmagent.Config{
+ Name: "HelpDeskCoordinator",
+ Model: m,
+ Instruction: "Route user requests: Use Billing agent for payment issues, Support agent for technical problems.",
+ Description: "Main help desk router.",
+ SubAgents: []agent.Agent{billingAgent, supportAgent},
+ })
+ // User asks "My payment failed" -> Coordinator's LLM should call transfer_to_agent(agent_name='Billing')
+ // User asks "I can't log in" -> Coordinator's LLM should call transfer_to_agent(agent_name='Support')
+ // --8<-- [end:coordinator-pattern]
+ _ = coordinator // Avoid unused variable error
+
+ // --8<-- [start:sequential-pipeline-pattern]
+ // Conceptual Code: Sequential Data Pipeline
+ validator, _ := llmagent.New(llmagent.Config{Name: "ValidateInput", Instruction: "Validate the input.", OutputKey: "validation_status", Model: m})
+ processor, _ := llmagent.New(llmagent.Config{Name: "ProcessData", Instruction: "Process data if {validation_status} is 'valid'.", OutputKey: "result", Model: m})
+ reporter, _ := llmagent.New(llmagent.Config{Name: "ReportResult", Instruction: "Report the result from {result}.", Model: m})
+
+ dataPipeline, _ := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{Name: "DataPipeline", SubAgents: []agent.Agent{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"]
+ // --8<-- [end:sequential-pipeline-pattern]
+ _ = dataPipeline // Avoid unused variable error
+
+ // --8<-- [start:parallel-gather-pattern]
+ // Conceptual Code: Parallel Information Gathering
+ fetchAPI1, _ := llmagent.New(llmagent.Config{Name: "API1Fetcher", Instruction: "Fetch data from API 1.", OutputKey: "api1_data", Model: m})
+ fetchAPI2, _ := llmagent.New(llmagent.Config{Name: "API2Fetcher", Instruction: "Fetch data from API 2.", OutputKey: "api2_data", Model: m})
+
+ gatherConcurrently, _ := parallelagent.New(parallelagent.Config{
+ AgentConfig: agent.Config{Name: "ConcurrentFetch", SubAgents: []agent.Agent{fetchAPI1, fetchAPI2}},
+ })
+
+ synthesizer, _ := llmagent.New(llmagent.Config{Name: "Synthesizer", Instruction: "Combine results from {api1_data} and {api2_data}.", Model: m})
+
+ overallWorkflow, _ := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{Name: "FetchAndSynthesize", SubAgents: []agent.Agent{gatherConcurrently, synthesizer}},
+ })
+ // fetch_api1 and fetch_api2 run concurrently, saving to state.
+ // synthesizer runs afterwards, reading state["api1_data"] and state["api2_data"].
+ // --8<-- [end:parallel-gather-pattern]
+ _ = overallWorkflow // Avoid unused variable error
+
+ // --8<-- [start:hierarchical-pattern]
+ // Conceptual Code: Hierarchical Research Task
+ // Low-level tool-like agents
+ webSearcher, _ := llmagent.New(llmagent.Config{Name: "WebSearch", Description: "Performs web searches for facts.", Model: m})
+ summarizer, _ := llmagent.New(llmagent.Config{Name: "Summarizer", Description: "Summarizes text.", Model: m})
+
+ // Mid-level agent combining tools
+ webSearcherTool := agenttool.New(webSearcher, nil)
+ summarizerTool := agenttool.New(summarizer, nil)
+ researchAssistant, _ := llmagent.New(llmagent.Config{
+ Name: "ResearchAssistant",
+ Model: m,
+ Description: "Finds and summarizes information on a topic.",
+ Tools: []tool.Tool{webSearcherTool, summarizerTool},
+ })
+
+ // High-level agent delegating research
+ researchAssistantTool := agenttool.New(researchAssistant, nil)
+ reportWriter, _ := llmagent.New(llmagent.Config{
+ Name: "ReportWriter",
+ Model: m,
+ Instruction: "Write a report on topic X. Use the ResearchAssistant to gather information.",
+ Tools: []tool.Tool{researchAssistantTool},
+ })
+ // User interacts with ReportWriter.
+ // ReportWriter calls ResearchAssistant tool.
+ // ResearchAssistant calls WebSearch and Summarizer tools.
+ // Results flow back up.
+ // --8<-- [end:hierarchical-pattern]
+ _ = reportWriter // Avoid unused variable error
+
+ // --8<-- [start:generator-critic-pattern]
+ // Conceptual Code: Generator-Critic
+ generator, _ := llmagent.New(llmagent.Config{
+ Name: "DraftWriter",
+ Instruction: "Write a short paragraph about subject X.",
+ OutputKey: "draft_text",
+ Model: m,
+ })
+
+ reviewer, _ := llmagent.New(llmagent.Config{
+ Name: "FactChecker",
+ Instruction: "Review the text in {draft_text} for factual accuracy. Output 'valid' or 'invalid' with reasons.",
+ OutputKey: "review_status",
+ Model: m,
+ })
+
+ reviewPipeline, _ := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{Name: "WriteAndReview", SubAgents: []agent.Agent{generator, reviewer}},
+ })
+ // generator runs -> saves draft to state["draft_text"]
+ // reviewer runs -> reads state["draft_text"], saves status to state["review_status"]
+ // --8<-- [end:generator-critic-pattern]
+ _ = reviewPipeline // Avoid unused variable error
+
+ // --8<-- [start:iterative-refinement-pattern]
+ // Conceptual Code: Iterative Code Refinement
+ codeRefiner, _ := llmagent.New(llmagent.Config{
+ Name: "CodeRefiner",
+ Instruction: "Read state['current_code'] (if exists) and state['requirements']. Generate/refine Python code to meet requirements. Save to state['current_code'].",
+ OutputKey: "current_code",
+ Model: m,
+ })
+
+ qualityChecker, _ := llmagent.New(llmagent.Config{
+ Name: "QualityChecker",
+ Instruction: "Evaluate the code in state['current_code'] against state['requirements']. Output 'pass' or 'fail'.",
+ OutputKey: "quality_status",
+ Model: m,
+ })
+
+ checkStatusAndEscalate, _ := agent.New(agent.Config{
+ Name: "StopChecker",
+ Run: func(ctx agent.InvocationContext) iter.Seq2[*session.Event, error] {
+ return func(yield func(*session.Event, error) bool) {
+ status, _ := ctx.Session().State().Get("quality_status")
+ shouldStop := status == "pass"
+ yield(&session.Event{Author: "StopChecker", Actions: session.EventActions{Escalate: shouldStop}}, nil)
+ }
+ },
+ })
+
+ refinementLoop, _ := loopagent.New(loopagent.Config{
+ MaxIterations: 5,
+ AgentConfig: agent.Config{Name: "CodeRefinementLoop", SubAgents: []agent.Agent{codeRefiner, qualityChecker, checkStatusAndEscalate}},
+ })
+ // 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.
+ // --8<-- [end:iterative-refinement-pattern]
+ _ = refinementLoop // Avoid unused variable error
+
+ // --8<-- [start:human-in-loop-pattern]
+ // Conceptual Code: Using a Tool for Human Approval
+ // --- Assume externalApprovalTool exists ---
+ // func externalApprovalTool(amount float64, reason string) string { ... }
+ type externalApprovalToolArgs struct {
+ Amount float64 `json:"amount"`
+ Reason string `json:"reason"`
+ }
+ var externalApprovalTool func(tool.Context, externalApprovalToolArgs) string
+ approvalTool, _ := functiontool.New(
+ functiontool.Config{
+ Name: "external_approval_tool",
+ Description: "Sends a request for human approval.",
+ },
+ externalApprovalTool,
+ )
+
+ prepareRequest, _ := llmagent.New(llmagent.Config{
+ Name: "PrepareApproval",
+ Instruction: "Prepare the approval request details based on user input. Store amount and reason in state.",
+ Model: m,
+ })
+
+ requestApproval, _ := llmagent.New(llmagent.Config{
+ Name: "RequestHumanApproval",
+ Instruction: "Use the external_approval_tool with amount from state['approval_amount'] and reason from state['approval_reason'].",
+ Tools: []tool.Tool{approvalTool},
+ OutputKey: "human_decision",
+ Model: m,
+ })
+
+ processDecision, _ := llmagent.New(llmagent.Config{
+ Name: "ProcessDecision",
+ Instruction: "Check {human_decision}. If 'approved', proceed. If 'rejected', inform user.",
+ Model: m,
+ })
+
+ approvalWorkflow, _ := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{Name: "HumanApprovalWorkflow", SubAgents: []agent.Agent{prepareRequest, requestApproval, processDecision}},
+ })
+ // --8<-- [end:human-in-loop-pattern]
+ _ = approvalWorkflow // Avoid unused variable error
+}
+
+func conceptualSnippets() {
+ ctx := context.Background()
+ model, _ := gemini.NewModel(ctx, "gemini-1.5-flash", &genai.ClientConfig{})
+
+ basicWorkflowSnippets(model)
+ agentInteractionSnippets(model)
+ advancedPatternSnippets(model)
+}
+
+func main() {
+ conceptualSnippets()
+}
diff --git a/examples/go/snippets/agents/workflow-agents/loop/main.go b/examples/go/snippets/agents/workflow-agents/loop/main.go
new file mode 100644
index 00000000..f9d99099
--- /dev/null
+++ b/examples/go/snippets/agents/workflow-agents/loop/main.go
@@ -0,0 +1,214 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/agent/workflowagents/loopagent"
+ "google.golang.org/adk/agent/workflowagents/sequentialagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+const (
+ appName = "IterativeWritingPipeline"
+ userID = "test_user_456"
+ modelName = "gemini-2.5-flash"
+ stateDoc = "current_document"
+ stateCrit = "criticism"
+ donePhrase = "No major issues found."
+)
+
+// --8<-- [start:init]
+// ExitLoopArgs defines the (empty) arguments for the ExitLoop tool.
+type ExitLoopArgs struct{}
+
+// ExitLoopResults defines the output of the ExitLoop tool.
+type ExitLoopResults struct{}
+
+// ExitLoop is a tool that signals the loop to terminate by setting Escalate to true.
+func ExitLoop(ctx tool.Context, input ExitLoopArgs) ExitLoopResults {
+ fmt.Printf("[Tool Call] exitLoop triggered by %s \n", ctx.AgentName())
+ ctx.Actions().Escalate = true
+ return ExitLoopResults{}
+}
+
+func main() {
+ ctx := context.Background()
+
+ if err := runAgent(ctx, "Write a document about a cat"); err != nil {
+ log.Fatalf("Agent execution failed: %v", err)
+ }
+}
+
+func runAgent(ctx context.Context, prompt string) error {
+ model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ return fmt.Errorf("failed to create model: %v", err)
+ }
+
+ // STEP 1: Initial Writer Agent (Runs ONCE at the beginning)
+ initialWriterAgent, err := llmagent.New(llmagent.Config{
+ Name: "InitialWriterAgent",
+ Model: model,
+ Description: "Writes the initial document draft based on the topic.",
+ Instruction: `You are a Creative Writing Assistant tasked with starting a story.
+Write the *first draft* of a short story (aim for 2-4 sentences).
+Base the content *only* on the topic provided in the user's prompt.
+Output *only* the story/document text. Do not add introductions or explanations.`,
+ OutputKey: stateDoc,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create initial writer agent: %v", err)
+ }
+
+ // STEP 2a: Critic Agent (Inside the Refinement Loop)
+ criticAgentInLoop, err := llmagent.New(llmagent.Config{
+ Name: "CriticAgent",
+ Model: model,
+ Description: "Reviews the current draft, providing critique or signaling completion.",
+ Instruction: fmt.Sprintf(`You are a Constructive Critic AI reviewing a short document draft.
+**Document to Review:**
+"""
+{%s}
+"""
+**Task:**
+Review the document.
+IF you identify 1-2 *clear and actionable* ways it could be improved:
+Provide these specific suggestions concisely. Output *only* the critique text.
+ELSE IF the document is coherent and addresses the topic adequately:
+Respond *exactly* with the phrase "%s" and nothing else.`, stateDoc, donePhrase),
+ OutputKey: stateCrit,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create critic agent: %v", err)
+ }
+
+ exitLoopTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "exitLoop",
+ Description: "Call this function ONLY when the critique indicates no further changes are needed.",
+ },
+ ExitLoop,
+ )
+ if err != nil {
+ return fmt.Errorf("failed to create exit loop tool: %v", err)
+ }
+
+ // STEP 2b: Refiner/Exiter Agent (Inside the Refinement Loop)
+ refinerAgentInLoop, err := llmagent.New(llmagent.Config{
+ Name: "RefinerAgent",
+ Model: model,
+ Instruction: fmt.Sprintf(`You are a Creative Writing Assistant refining a document based on feedback OR exiting the process.
+**Current Document:**
+
+"""
+{%s}
+"""
+
+**Critique/Suggestions:**
+{%s}
+**Task:**
+Analyze the 'Critique/Suggestions'.
+IF the critique is *exactly* "%s":
+You MUST call the 'exitLoop' function. Do not output any text.
+ELSE (the critique contains actionable feedback):
+Carefully apply the suggestions to improve the 'Current Document'. Output *only* the refined document text.`, stateDoc, stateCrit, donePhrase),
+ Description: "Refines the document based on critique, or calls exitLoop if critique indicates completion.",
+ Tools: []tool.Tool{exitLoopTool},
+ OutputKey: stateDoc,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create refiner agent: %v", err)
+ }
+
+ // STEP 2: Refinement Loop Agent
+ refinementLoop, err := loopagent.New(loopagent.Config{
+ AgentConfig: agent.Config{
+ Name: "RefinementLoop",
+ SubAgents: []agent.Agent{criticAgentInLoop, refinerAgentInLoop},
+ },
+ MaxIterations: 5,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create loop agent: %v", err)
+ }
+
+ // STEP 3: Overall Sequential Pipeline
+ iterativeWriterAgent, err := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{
+ Name: appName,
+ SubAgents: []agent.Agent{initialWriterAgent, refinementLoop},
+ },
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create sequential agent pipeline: %v", err)
+ }
+ // --8<-- [end:init]
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{
+ AppName: appName,
+ Agent: iterativeWriterAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create runner: %v", err)
+ }
+
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create session: %v", err)
+ }
+
+ userMsg := &genai.Content{
+ Parts: []*genai.Part{{Text: prompt}},
+ Role: string(genai.RoleUser),
+ }
+
+ fmt.Printf("---"+" Starting Iterative Writing Pipeline for topic: %q ---"+"\n", prompt)
+ loopIteration := 0
+
+ for event, err := range r.Run(ctx, userID, session.Session.ID(), userMsg, agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ }) {
+ if err != nil {
+ return fmt.Errorf("error during agent execution: %v", err)
+ }
+
+ outputText := ""
+ for _, p := range event.Content.Parts {
+ outputText += p.Text
+ }
+ outputText = strings.TrimSpace(outputText)
+
+ switch event.Author {
+ case "InitialWriterAgent":
+ fmt.Printf("\n[Initial Draft] By %s (%s):\n%s\n", event.Author, stateDoc, outputText)
+ case "CriticAgent":
+ loopIteration++
+ fmt.Printf("\n[Loop Iteration %d] Critique by %s (%s):\n%s\n", loopIteration, event.Author, stateCrit, outputText)
+ case "RefinerAgent":
+ if !event.Actions.Escalate {
+ fmt.Printf("[Loop Iteration %d] Refinement by %s (%s):\n%s\n", loopIteration, event.Author, stateDoc, outputText)
+ }
+ }
+
+ if event.Actions.Escalate {
+ fmt.Println("\n--- Refinement Loop terminated (Escalation detected) ---")
+ }
+ }
+ fmt.Printf("\n--- Pipeline Finished ---\n")
+ return nil
+}
diff --git a/examples/go/snippets/agents/workflow-agents/parallel/main.go b/examples/go/snippets/agents/workflow-agents/parallel/main.go
new file mode 100644
index 00000000..60fe71e1
--- /dev/null
+++ b/examples/go/snippets/agents/workflow-agents/parallel/main.go
@@ -0,0 +1,206 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/agent/workflowagents/parallelagent"
+ "google.golang.org/adk/agent/workflowagents/sequentialagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/genai"
+)
+
+const (
+ appName = "parallel_research_app"
+ userID = "research_user_01"
+ modelName = "gemini-2.0-flash"
+)
+
+func main() {
+ ctx := context.Background()
+
+ if err := runAgent(ctx, "Summarize recent sustainable tech advancements."); err != nil {
+ log.Fatalf("Agent execution failed: %v", err)
+ }
+}
+
+func runAgent(ctx context.Context, prompt string) error {
+ // --8<-- [start:init]
+ model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ return fmt.Errorf("failed to create model: %v", err)
+ }
+
+ // --- 1. Define Researcher Sub-Agents (to run in parallel) ---
+ researcher1, err := llmagent.New(llmagent.Config{
+ Name: "RenewableEnergyResearcher",
+ Model: model,
+ Instruction: `You are an AI Research Assistant specializing in energy.
+Research the latest advancements in 'renewable energy sources'.
+Use the Google Search tool provided.
+Summarize your key findings concisely (1-2 sentences).
+Output *only* the summary.`,
+ Description: "Researches renewable energy sources.",
+ OutputKey: "renewable_energy_result",
+ })
+ if err != nil {
+ return err
+ }
+ researcher2, err := llmagent.New(llmagent.Config{
+ Name: "EVResearcher",
+ Model: model,
+ Instruction: `You are an AI Research Assistant specializing in transportation.
+Research the latest developments in 'electric vehicle technology'.
+Use the Google Search tool provided.
+Summarize your key findings concisely (1-2 sentences).
+Output *only* the summary.`,
+ Description: "Researches electric vehicle technology.",
+ OutputKey: "ev_technology_result",
+ })
+ if err != nil {
+ return err
+ }
+ researcher3, err := llmagent.New(llmagent.Config{
+ Name: "CarbonCaptureResearcher",
+ Model: model,
+ Instruction: `You are an AI Research Assistant specializing in climate solutions.
+Research the current state of 'carbon capture methods'.
+Use the Google Search tool provided.
+Summarize your key findings concisely (1-2 sentences).
+Output *only* the summary.`,
+ Description: "Researches carbon capture methods.",
+ OutputKey: "carbon_capture_result",
+ })
+ if err != nil {
+ return err
+ }
+
+ // --- 2. Create the ParallelAgent (Runs researchers concurrently) ---
+ parallelResearchAgent, err := parallelagent.New(parallelagent.Config{
+ AgentConfig: agent.Config{
+ Name: "ParallelWebResearchAgent",
+ Description: "Runs multiple research agents in parallel to gather information.",
+ SubAgents: []agent.Agent{researcher1, researcher2, researcher3},
+ },
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create parallel agent: %v", err)
+ }
+
+ // --- 3. Define the Merger Agent (Runs *after* the parallel agents) ---
+ synthesisAgent, err := llmagent.New(llmagent.Config{
+ Name: "SynthesisAgent",
+ Model: model,
+ Instruction: `You are an AI Assistant responsible for combining research findings into a structured report.
+Your primary task is to synthesize the following research summaries, clearly attributing findings to their source areas. Structure your response using headings for each topic. Ensure the report is coherent and integrates the key points smoothly.
+**Crucially: Your entire response MUST be grounded *exclusively* on the information provided in the 'Input Summaries' below. Do NOT add any external knowledge, facts, or details not present in these specific summaries.**
+**Input Summaries:**
+
+* **Renewable Energy:**
+ {renewable_energy_result}
+
+* **Electric Vehicles:**
+ {ev_technology_result}
+
+* **Carbon Capture:**
+ {carbon_capture_result}
+
+**Output Format:**
+
+## Summary of Recent Sustainable Technology Advancements
+
+### Renewable Energy Findings
+(Based on RenewableEnergyResearcher's findings)
+[Synthesize and elaborate *only* on the renewable energy input summary provided above.]
+
+### Electric Vehicle Findings
+(Based on EVResearcher's findings)
+[Synthesize and elaborate *only* on the EV input summary provided above.]
+
+### Carbon Capture Findings
+(Based on CarbonCaptureResearcher's findings)
+[Synthesize and elaborate *only* on the carbon capture input summary provided above.]
+
+### Overall Conclusion
+[Provide a brief (1-2 sentence) concluding statement that connects *only* the findings presented above.]
+
+Output *only* the structured report following this format. Do not include introductory or concluding phrases outside this structure, and strictly adhere to using only the provided input summary content.`,
+ Description: "Combines research findings from parallel agents into a structured, cited report, strictly grounded on provided inputs.",
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create synthesis agent: %v", err)
+ }
+
+ // --- 4. Create the SequentialAgent (Orchestrates the overall flow) ---
+ pipeline, err := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{
+ Name: "ResearchAndSynthesisPipeline",
+ Description: "Coordinates parallel research and synthesizes the results.",
+ SubAgents: []agent.Agent{parallelResearchAgent, synthesisAgent},
+ },
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create sequential agent pipeline: %v", err)
+ }
+ // --8<-- [end:init]
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{
+ AppName: appName,
+ Agent: pipeline,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create runner: %v", err)
+ }
+
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create session: %v", err)
+ }
+
+ userMsg := &genai.Content{
+ Parts: []*genai.Part{{Text: prompt}},
+ Role: string(genai.RoleUser),
+ }
+
+ fmt.Printf("Running Research & Synthesis Pipeline for query: %q\n---\n", prompt)
+ researcherNames := map[string]bool{
+ "RenewableEnergyResearcher": true,
+ "EVResearcher": true,
+ "CarbonCaptureResearcher": true,
+ }
+ synthesisAgentName := "SynthesisAgent"
+
+ for event, err := range r.Run(ctx, userID, session.Session.ID(), userMsg, agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ }) {
+ if err != nil {
+ return fmt.Errorf("error during agent execution: %v", err)
+ }
+
+ if _, ok := researcherNames[event.Author]; ok {
+ fmt.Printf(" -> Intermediate Result from %s:\n", event.Author)
+ for _, p := range event.Content.Parts {
+ fmt.Print(p.Text)
+ }
+ fmt.Println()
+ } else if event.Author == synthesisAgentName {
+ fmt.Printf("\n<<< Final Synthesized Response (from %s):\n", event.Author)
+ for _, p := range event.Content.Parts {
+ fmt.Print(p.Text)
+ }
+ fmt.Println()
+ }
+ }
+ fmt.Println("\n---\nPipeline finished.")
+ return nil
+}
diff --git a/examples/go/snippets/agents/workflow-agents/sequential/main.go b/examples/go/snippets/agents/workflow-agents/sequential/main.go
new file mode 100644
index 00000000..78709c05
--- /dev/null
+++ b/examples/go/snippets/agents/workflow-agents/sequential/main.go
@@ -0,0 +1,163 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/agent/workflowagents/sequentialagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/genai"
+)
+
+const (
+ appName = "CodePipelineAgent"
+ userID = "test_user_456"
+ modelName = "gemini-2.5-flash"
+)
+
+func main() {
+ ctx := context.Background()
+
+ if err := runAgent(ctx, "Write a Go function to calculate the factorial of a number."); err != nil {
+ log.Fatalf("Agent execution failed: %v", err)
+ }
+}
+
+func runAgent(ctx context.Context, prompt string) error {
+ // --8<-- [start:init]
+ model, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ return fmt.Errorf("failed to create model: %v", err)
+ }
+
+ codeWriterAgent, err := llmagent.New(llmagent.Config{
+ Name: "CodeWriterAgent",
+ Model: model,
+ Description: "Writes initial Go code based on a specification.",
+ Instruction: `You are a Go Code Generator.
+Based *only* on the user's request, write Go code that fulfills the requirement.
+Output *only* the complete Go code block, enclosed in triple backticks ('''go ... ''').
+Do not add any other text before or after the code block.`,
+ OutputKey: "generated_code",
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create code writer agent: %v", err)
+ }
+
+ codeReviewerAgent, err := llmagent.New(llmagent.Config{
+ Name: "CodeReviewerAgent",
+ Model: model,
+ Description: "Reviews code and provides feedback.",
+ Instruction: `You are an expert Go Code Reviewer.
+Your task is to provide constructive feedback on the provided code.
+
+**Code to Review:**
+'''go
+{generated_code}
+'''
+
+**Review Criteria:**
+1. **Correctness:** Does the code work as intended? Are there logic errors?
+2. **Readability:** Is the code clear and easy to understand? Follows Go style guidelines?
+3. **Idiomatic Go:** Does the code use Go's features in a natural and standard way?
+4. **Edge Cases:** Does the code handle potential edge cases or invalid inputs gracefully?
+5. **Best Practices:** Does the code follow common Go best practices?
+
+**Output:**
+Provide your feedback as a concise, bulleted list. Focus on the most important points for improvement.
+If the code is excellent and requires no changes, simply state: "No major issues found."
+Output *only* the review comments or the "No major issues" statement.`,
+ OutputKey: "review_comments",
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create code reviewer agent: %v", err)
+ }
+
+ codeRefactorerAgent, err := llmagent.New(llmagent.Config{
+ Name: "CodeRefactorerAgent",
+ Model: model,
+ Description: "Refactors code based on review comments.",
+ Instruction: `You are a Go Code Refactoring AI.
+Your goal is to improve the given Go code based on the provided review comments.
+
+**Original Code:**
+'''go
+{generated_code}
+'''
+
+**Review Comments:**
+{review_comments}
+
+**Task:**
+Carefully apply the suggestions from the review comments to refactor the original code.
+If the review comments state "No major issues found," return the original code unchanged.
+Ensure the final code is complete, functional, and includes necessary imports.
+
+**Output:**
+Output *only* the final, refactored Go code block, enclosed in triple backticks ('''go ... ''').
+Do not add any other text before or after the code block.`,
+ OutputKey: "refactored_code",
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create code refactorer agent: %v", err)
+ }
+
+ codePipelineAgent, err := sequentialagent.New(sequentialagent.Config{
+ AgentConfig: agent.Config{
+ Name: appName,
+ Description: "Executes a sequence of code writing, reviewing, and refactoring.",
+ SubAgents: []agent.Agent{
+ codeWriterAgent,
+ codeReviewerAgent,
+ codeRefactorerAgent,
+ },
+ },
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create sequential agent: %v", err)
+ }
+ // --8<-- [end:init]
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{
+ AppName: appName,
+ Agent: codePipelineAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create runner: %v", err)
+ }
+
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create session: %v", err)
+ }
+
+ userMsg := &genai.Content{
+ Parts: []*genai.Part{{Text: prompt}},
+ Role: string(genai.RoleUser),
+ }
+
+ fmt.Printf("Running agent pipeline for prompt: %q\n---\n", prompt)
+ for event, err := range r.Run(ctx, userID, session.Session.ID(), userMsg, agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ }) {
+ if err != nil {
+ return fmt.Errorf("error during agent execution: %v", err)
+ }
+
+ for _, p := range event.Content.Parts {
+ fmt.Print(p.Text)
+ }
+ }
+ fmt.Println("\n---\nPipeline finished.")
+ return nil
+}
diff --git a/examples/go/snippets/artifacts/image.png b/examples/go/snippets/artifacts/image.png
new file mode 100644
index 00000000..b8457eae
Binary files /dev/null and b/examples/go/snippets/artifacts/image.png differ
diff --git a/examples/go/snippets/artifacts/main.go b/examples/go/snippets/artifacts/main.go
new file mode 100644
index 00000000..d4380047
--- /dev/null
+++ b/examples/go/snippets/artifacts/main.go
@@ -0,0 +1,388 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/artifact"
+ "google.golang.org/adk/model"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/genai"
+)
+
+// This file contains snippets for the artifacts documentation.
+
+// BeforeModelCallback saves any images from the user input before calling the model.
+func BeforeModelCallback(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ log.Println("[Callback] BeforeModelCallback triggered.")
+ // Get the artifact manager from the context.
+ artifacts := ctx.Artifacts()
+ // Check if there are any contents in the request.
+ if req.Contents != nil && len(req.Contents) > 0 {
+ // Get the last content from the user.
+ lastContent := req.Contents[len(req.Contents)-1]
+ // Check if the last content is from the user.
+ if lastContent.Role == genai.RoleUser {
+ // Iterate over the parts of the content.
+ for i, part := range lastContent.Parts {
+ // Check if the part is an image.
+ if part.InlineData != nil && strings.HasPrefix(part.InlineData.MIMEType, "image/") {
+ // Create a unique filename for the image.
+ fileName := fmt.Sprintf("user_image_%d.%s", i, strings.Split(part.InlineData.MIMEType, "/")[1])
+ // Save the image as an artifact.
+ if _, err := artifacts.Save(ctx, fileName, part); err != nil {
+ log.Printf("[WARN] Failed to save user image: %v\n", err)
+ } else {
+ log.Printf("[INFO] Saved user image artifact: %s\n", fileName)
+ }
+ }
+ }
+ }
+ }
+ // Return nil to continue to the next callback or the model.
+ return nil, nil // Continue to next callback or LLM call
+}
+
+// configureRunner configures the runner with an in-memory artifact service.
+func configureRunner() {
+ // --8<-- [start:configure-runner]
+ // --8<-- [start:prerequisite]
+ // Create a new context.
+ ctx := context.Background()
+ // Set the app name.
+ const appName = "my_artifact_app"
+ // Create a new Gemini model.
+ model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("Failed to create model: %v", err)
+ }
+
+ // Create a new LLM agent.
+ myAgent, err := llmagent.New(llmagent.Config{
+ Model: model,
+ Name: "artifact_user_agent",
+ Instruction: "You are an agent that describes images.",
+ BeforeModelCallbacks: []llmagent.BeforeModelCallback{
+ BeforeModelCallback,
+ },
+ })
+ if err != nil {
+ log.Fatalf("Failed to create agent: %v", err)
+ }
+
+ // Create a new in-memory artifact service.
+ artifactService := artifact.InMemoryService()
+ // Create a new in-memory session service.
+ sessionService := session.InMemoryService()
+
+ // Create a new runner.
+ r, err := runner.New(runner.Config{
+ Agent: myAgent,
+ AppName: appName,
+ SessionService: sessionService,
+ ArtifactService: artifactService, // Provide the service instance here
+ })
+ if err != nil {
+ log.Fatalf("Failed to create runner: %v", err)
+ }
+ log.Printf("Runner created successfully: %v", r)
+ // --8<-- [end:prerequisite]
+ // --8<-- [end:configure-runner]
+}
+
+// inMemoryServiceExample demonstrates how to set up an in-memory artifact service.
+func inMemoryServiceExample() {
+ // --8<-- [start:in-memory-service]
+ // Simply instantiate the service
+ artifactService := artifact.InMemoryService()
+ log.Printf("InMemoryArtifactService (Go) instantiated: %T", artifactService)
+
+ // Use the service in your runner
+ // r, _ := runner.New(runner.Config{
+ // Agent: agent,
+ // AppName: "my_app",
+ // SessionService: sessionService,
+ // ArtifactService: artifactService,
+ // })
+
+ // --8<-- [end:in-memory-service]
+}
+
+// --8<-- [start:loading-artifacts]
+// loadArtifactsCallback is a BeforeModel callback that loads a specific artifact
+// and adds its content to the LLM request.
+func loadArtifactsCallback(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ log.Println("[Callback] loadArtifactsCallback triggered.")
+ // In a real app, you would parse the user's request to find a filename.
+ // For this example, we'll hardcode a filename to demonstrate.
+ const filenameToLoad = "generated_report.pdf"
+
+ // Load the artifact from the artifact service.
+ loadedPartResponse, err := ctx.Artifacts().Load(ctx, filenameToLoad)
+ if err != nil {
+ log.Printf("Callback could not load artifact '%s': %v", filenameToLoad, err)
+ return nil, nil // File not found or error, continue to model.
+ }
+
+ loadedPart := loadedPartResponse.Part
+
+ log.Printf("Callback successfully loaded artifact '%s'.", filenameToLoad)
+
+ // Ensure there's at least one content in the request to append to.
+ if len(req.Contents) == 0 {
+ req.Contents = []*genai.Content{{Parts: []*genai.Part{
+ genai.NewPartFromText("SYSTEM: The following file is provided for context:\n"),
+ }}}
+ }
+
+ // Add the loaded artifact to the request for the model.
+ lastContent := req.Contents[len(req.Contents)-1]
+ lastContent.Parts = append(lastContent.Parts, loadedPart)
+ log.Printf("Added artifact '%s' to LLM request.", filenameToLoad)
+
+ // Return nil to continue to the next callback or the model.
+ return nil, nil // Continue to next callback or LLM call
+}
+
+// --8<-- [end:loading-artifacts]
+
+// representation demonstrates how to manually construct an artifact.
+func representation() {
+ // --8<-- [start:representation]
+ // Create a byte slice with the image data.
+ imageBytes, err := os.ReadFile("image.png")
+ if err != nil {
+ log.Fatalf("Failed to read image file: %v", err)
+ }
+
+ // Create a new artifact with the image data.
+ imageArtifact := &genai.Part{
+ InlineData: &genai.Blob{
+ MIMEType: "image/png",
+ Data: imageBytes,
+ },
+ }
+ log.Printf("Artifact MIME Type: %s", imageArtifact.InlineData.MIMEType)
+ log.Printf("Artifact Data (first 8 bytes): %x...", imageArtifact.InlineData.Data[:8])
+ // --8<-- [end:representation]
+}
+
+// artifactData demonstrates how to create an artifact from a file.
+func artifactData() {
+ // --8<-- [start:artifact-data]
+ // Load imageBytes from a file
+ imageBytes, err := os.ReadFile("image.png")
+ if err != nil {
+ log.Fatalf("Failed to read image file: %v", err)
+ }
+
+ // genai.NewPartFromBytes is a convenience function that is a shorthand for
+ // creating a &genai.Part with the InlineData field populated.
+ // Create a new artifact from the image data.
+ imageArtifact := genai.NewPartFromBytes([]byte(imageBytes), "image/png")
+
+ log.Printf("Artifact MIME Type: %s", imageArtifact.InlineData.MIMEType)
+ // --8<-- [end:artifact-data]
+}
+
+// namespacing demonstrates the difference between session and user-scoped artifacts.
+func namespacing() {
+ // --8<-- [start:namespacing]
+ // Note: Namespacing is only supported when using the GCS ArtifactService implementation.
+ // A session-scoped artifact is only available within the current session.
+ sessionReportFilename := "summary.txt"
+ // A user-scoped artifact is available across all sessions for the current user.
+ userConfigFilename := "user:settings.json"
+
+ // When saving 'summary.txt' via ctx.Artifacts().Save,
+ // it's tied to the current app_name, user_id, and session_id.
+ // ctx.Artifacts().Save(sessionReportFilename, *artifact);
+
+ // When saving 'user:settings.json' via ctx.Artifacts().Save,
+ // the ArtifactService implementation should recognize the "user:" prefix
+ // and scope it to app_name and user_id, making it accessible across sessions for that user.
+ // ctx.Artifacts().Save(userConfigFilename, *artifact);
+ // --8<-- [end:namespacing]
+
+ log.Printf("Session filename: %s", sessionReportFilename)
+ log.Printf("User filename: %s", userConfigFilename)
+}
+
+// --8<-- [start:saving-artifacts]
+// saveReportCallback is a BeforeModel callback that saves a report from session state.
+func saveReportCallback(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ // Get the report data from the session state.
+ reportData, err := ctx.State().Get("report_bytes")
+ if err != nil {
+ log.Printf("No report data found in session state: %v", err)
+ return nil, nil // No report to save, continue normally.
+ }
+
+ // Check if the report data is in the expected format.
+ reportBytes, ok := reportData.([]byte)
+ if !ok {
+ log.Printf("Report data in session state was not in the expected byte format.")
+ return nil, nil
+ }
+
+ // Create a new artifact with the report data.
+ reportArtifact := &genai.Part{
+ InlineData: &genai.Blob{
+ MIMEType: "application/pdf",
+ Data: reportBytes,
+ },
+ }
+ // Set the filename for the artifact.
+ filename := "generated_report.pdf"
+ // Save the artifact to the artifact service.
+ _, err = ctx.Artifacts().Save(ctx, filename, reportArtifact)
+ if err != nil {
+ log.Printf("An unexpected error occurred during Go artifact save: %v", err)
+ // Depending on requirements, you might want to return an error to the user.
+ return nil, nil
+ }
+ log.Printf("Successfully saved Go artifact '%s'.", filename)
+ // Return nil to continue to the next callback or the model.
+ return nil, nil
+}
+
+// --8<-- [end:saving-artifacts]
+
+// --8<-- [start:listing-artifacts]
+// listUserFilesCallback is a BeforeModel callback that lists available artifacts
+// and adds the list as context to the LLM request.
+func listUserFilesCallback(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ log.Println("[Callback] listUserFilesCallback triggered.")
+ // List the available artifacts from the artifact service.
+ listResponse, err := ctx.Artifacts().List(ctx)
+ if err != nil {
+ log.Printf("An unexpected error occurred during Go artifact list: %v", err)
+ return nil, nil // Continue, but log the error.
+ }
+
+ availableFiles := listResponse.FileNames
+
+ log.Printf("Found %d available files.", len(availableFiles))
+
+ // If there are available files, add them to the LLM request.
+ if len(availableFiles) > 0 {
+ var fileListStr strings.Builder
+ fileListStr.WriteString("SYSTEM: The following files are available:\n")
+ for _, fname := range availableFiles {
+ fileListStr.WriteString(fmt.Sprintf("- %s\n", fname))
+ }
+ // Prepend this information to the user's request for the model.
+ if len(req.Contents) > 0 {
+ lastContent := req.Contents[len(req.Contents)-1]
+ if len(lastContent.Parts) > 0 {
+ fileListStr.WriteString("\n") // Add a newline for separation.
+ lastContent.Parts[0] = genai.NewPartFromText(fileListStr.String() + lastContent.Parts[0].Text)
+ log.Println("Added file list to LLM request context.")
+ }
+ }
+ log.Printf("Available files:\n%s", fileListStr.String())
+ } else {
+ log.Println("No available files found to list.")
+ }
+
+ // Return nil to continue to the next callback or the model.
+ return nil, nil // Continue to next callback or LLM call
+}
+
+// --8<-- [end:listing-artifacts]
+
+func main() {
+ log.Println("--- Running Snippets ---")
+
+ // Call each standalone snippet function.
+ log.Println("\n--- representation ---")
+ representation()
+
+ log.Println("\n--- artifactData ---")
+ artifactData()
+
+ log.Println("\n--- namespacing ---")
+ namespacing()
+
+ log.Println("\n--- configureRunner (demonstrates BeforeModelCallback for saving) ---")
+ configureRunner()
+
+ log.Println("\n--- inMemoryServiceExample ---")
+ inMemoryServiceExample()
+
+ log.Println("\n--- Running Agent with Multiple Callbacks ---")
+ // 1. Set up services
+ ctx := context.Background()
+ artifactService := artifact.InMemoryService()
+ sessionService := session.InMemoryService()
+
+ // 2. Set up the agent with multiple callbacks
+ model, _ := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
+ reportingAgent, _ := llmagent.New(llmagent.Config{
+ Model: model,
+ Name: "reporting_agent",
+ Instruction: "You are a reporting agent. You can see available files and their contents if they are loaded for you. Summarize any provided files.",
+ BeforeModelCallbacks: []llmagent.BeforeModelCallback{
+ saveReportCallback, // Saves report from state
+ listUserFilesCallback, // Lists available files and adds to prompt
+ loadArtifactsCallback, // Loads a specific file and adds to prompt
+ },
+ })
+
+ // 3. Create a session with some initial state to trigger `saveReportCallback`
+ reportBytes, _ := os.ReadFile("story.pdf") // Load a sample PDF file
+ initialState := map[string]any{
+ "report_bytes": reportBytes,
+ }
+ userID := "test-user"
+ session, _ := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: "my_app",
+ UserID: userID,
+ SessionID: "test-session-callbacks",
+ State: initialState,
+ })
+
+ // 4. Create and run the runner
+ r, _ := runner.New(runner.Config{
+ Agent: reportingAgent,
+ AppName: "my_app",
+ SessionService: sessionService,
+ ArtifactService: artifactService,
+ })
+
+ log.Println("\n--- Agent Run 1: Triggering callbacks ---")
+ log.Println("This run will trigger `saveReportCallback` (from session state), `listUserFilesCallback` (will see the newly saved file), and `loadArtifactsCallback` (will load it).")
+ userInput := &genai.Content{Parts: []*genai.Part{genai.NewPartFromText("Please summarize the report for me.")}}
+ for event, err := range r.Run(ctx, session.Session.UserID(), session.Session.ID(), userInput, agent.RunConfig{
+ StreamingMode: agent.StreamingModeSSE,
+ }) {
+ if err != nil {
+ log.Printf("AGENT ERROR: %v\n", err)
+ } else if event != nil && event.Content != nil {
+ for _, p := range event.Content.Parts {
+ fmt.Print(string(p.Text))
+ }
+ }
+ }
+ fmt.Println()
+
+ log.Println("\n--- Verifying artifacts after run ---")
+ // We can list artifacts directly from the service to see what the agent did.
+ listReq := &artifact.ListRequest{
+ AppName: "my_app",
+ UserID: userID,
+ SessionID: "test-session-callbacks",
+ }
+ files, err := artifactService.List(ctx, listReq)
+ if err != nil {
+ log.Fatalf("Failed to list artifacts from service: %v", err)
+ }
+ log.Printf("Artifacts in service: %v", files)
+}
diff --git a/examples/go/snippets/artifacts/story.pdf b/examples/go/snippets/artifacts/story.pdf
new file mode 100644
index 00000000..6810fe4d
Binary files /dev/null and b/examples/go/snippets/artifacts/story.pdf differ
diff --git a/examples/go/snippets/callbacks/main.go b/examples/go/snippets/callbacks/main.go
new file mode 100644
index 00000000..52a05e97
--- /dev/null
+++ b/examples/go/snippets/callbacks/main.go
@@ -0,0 +1,191 @@
+// --8<-- [start:imports]
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/genai"
+)
+
+// --8<-- [end:imports]
+
+const (
+ modelName = "gemini-2.5-flash"
+)
+
+// --8<-- [start:callback_basic]
+// onBeforeModel is a callback function that gets triggered before an LLM call.
+func onBeforeModel(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ log.Println("--- onBeforeModel Callback Triggered ---")
+ log.Printf("Model Request to be sent: %v\n", req)
+ // Returning nil allows the default LLM call to proceed.
+ return nil, nil
+}
+
+func runBasicExample() {
+ const (
+ appName = "CallbackBasicApp"
+ userID = "test_user_123"
+ )
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("Failed to create model: %v", err)
+ }
+
+ // Register the callback function in the agent configuration.
+ agentCfg := llmagent.Config{
+ Name: "SimpleAgent",
+ Model: geminiModel,
+ BeforeModelCallbacks: []llmagent.BeforeModelCallback{onBeforeModel},
+ }
+ simpleAgent, err := llmagent.New(agentCfg)
+ if err != nil {
+ log.Fatalf("Failed to create agent: %v", err)
+ }
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{
+ AppName: appName,
+ Agent: simpleAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create runner: %v", err)
+ }
+ // --8<-- [end:callback_basic]
+
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create session: %v", err)
+ }
+
+ input := genai.NewContentFromText("Why is the sky blue?", genai.RoleUser)
+ log.Println("--- Running Agent ---")
+ events := r.Run(ctx, userID, session.Session.ID(), input, agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ })
+
+ for event, err := range events {
+ if err != nil {
+ log.Fatalf("Error during agent execution: %v", err)
+ }
+ for _, p := range event.Content.Parts {
+ fmt.Printf("Final Response: %s\n", p.Text)
+ }
+ }
+ log.Println("--- Agent Run Finished ---")
+}
+
+// --8<-- [start:guardrail_init]
+// onBeforeModelGuardrail is a callback that inspects the LLM request.
+// If it contains a forbidden topic, it blocks the request and returns a
+// predefined response. Otherwise, it allows the request to proceed.
+func onBeforeModelGuardrail(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ log.Println("--- onBeforeModelGuardrail Callback Triggered ---")
+
+ // Inspect the request content for forbidden topics.
+ for _, content := range req.Contents {
+ for _, part := range content.Parts {
+ if strings.Contains(part.Text, "finance") {
+ log.Println("Forbidden topic 'finance' detected. Blocking LLM call.")
+ // By returning a non-nil response, we override the default behavior
+ // and prevent the actual LLM call.
+ return &model.LLMResponse{
+ Content: &genai.Content{
+ Parts: []*genai.Part{{Text: "I'm sorry, but I cannot discuss financial topics."}},
+ Role: "model",
+ },
+ }, nil
+ }
+ }
+ }
+
+ log.Println("No forbidden topics found. Allowing LLM call to proceed.")
+ // Returning nil allows the default LLM call to proceed.
+ return nil, nil
+}
+
+func runGuardrailExample() {
+ const (
+ appName = "GuardrailApp"
+ userID = "test_user_456"
+ )
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("Failed to create model: %v", err)
+ }
+
+ agentCfg := llmagent.Config{
+ Name: "ChatAgent",
+ Model: geminiModel,
+ BeforeModelCallbacks: []llmagent.BeforeModelCallback{onBeforeModelGuardrail},
+ }
+ chatAgent, err := llmagent.New(agentCfg)
+ if err != nil {
+ log.Fatalf("Failed to create agent: %v", err)
+ }
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{
+ AppName: appName,
+ Agent: chatAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create runner: %v", err)
+ }
+ // --8<-- [end:guardrail_init]
+
+ // --- Run with a safe prompt ---
+ runAndPrint(ctx, r, sessionService, appName, "Tell me a fun fact about the Roman Empire.")
+
+ // --- Run with a forbidden prompt ---
+ runAndPrint(ctx, r, sessionService, appName, "What is the best way to manage my finance portfolio?")
+}
+
+func runAndPrint(ctx context.Context, r *runner.Runner, sessionService session.Service, appName, prompt string) {
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: "test_user", // UserID can be generic here for the helper
+ })
+ if err != nil {
+ log.Fatalf("Failed to create session: %v", err)
+ }
+
+ input := genai.NewContentFromText(prompt, genai.RoleUser)
+ log.Printf("\n--- Running Agent with prompt: %q ---\n", prompt)
+ events := r.Run(ctx, session.Session.UserID(), session.Session.ID(), input, agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ })
+
+ for event, err := range events {
+ if err != nil {
+ log.Fatalf("Error during agent execution: %v", err)
+ }
+ for _, p := range event.Content.Parts {
+ fmt.Printf("Final Response: %s\n", p.Text)
+ }
+ }
+ log.Println("--- Agent Run Finished ---")
+}
+
+func main() {
+ fmt.Println("--- Running Basic Callback Example ---")
+ runBasicExample()
+ fmt.Println("\n\n--- Running Guardrail Callback Example ---")
+ runGuardrailExample()
+}
diff --git a/examples/go/snippets/callbacks/types_of_callbacks/main.go b/examples/go/snippets/callbacks/types_of_callbacks/main.go
new file mode 100644
index 00000000..b487425e
--- /dev/null
+++ b/examples/go/snippets/callbacks/types_of_callbacks/main.go
@@ -0,0 +1,467 @@
+// --8<-- [start:imports]
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "regexp"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+// --8<-- [end:imports]
+
+const (
+ modelName = "gemini-2.5-flash"
+ userID = "user_1"
+ appName = "CallbackExamplesApp"
+)
+
+// --8<-- [start:before_agent_example]
+// 1. Define the Callback Function
+func onBeforeAgent(ctx agent.CallbackContext) (*genai.Content, error) {
+ agentName := ctx.AgentName()
+ log.Printf("[Callback] Entering agent: %s", agentName)
+ if skip, _ := ctx.State().Get("skip_llm_agent"); skip == true {
+ log.Printf("[Callback] State condition met: Skipping agent %s", agentName)
+ return genai.NewContentFromText(
+ fmt.Sprintf("Agent %s skipped by before_agent_callback.", agentName),
+ genai.RoleModel,
+ ),
+ nil
+ }
+ log.Printf("[Callback] State condition not met: Running agent %s", agentName)
+ return nil, nil
+}
+
+// 2. Define a function to set up and run the agent with the callback.
+func runBeforeAgentExample() {
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+
+ // 3. Register the callback in the agent configuration.
+ llmCfg := llmagent.Config{
+ Name: "AgentWithBeforeAgentCallback",
+ BeforeAgentCallbacks: []agent.BeforeAgentCallback{onBeforeAgent},
+ Model: geminiModel,
+ Instruction: "You are a concise assistant.",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{AppName: appName, Agent: testAgent, SessionService: sessionService})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create runner: %v", err)
+ }
+
+ // 4. Run scenarios to demonstrate the callback's behavior.
+ log.Println("--- SCENARIO 1: Agent should run normally ---")
+ runScenario(ctx, r, sessionService, appName, "session_normal", nil, "Hello, world!")
+
+ log.Println("\n--- SCENARIO 2: Agent should be skipped ---")
+ runScenario(ctx, r, sessionService, appName, "session_skip", map[string]any{"skip_llm_agent": true}, "This should be skipped.")
+}
+
+// --8<-- [end:before_agent_example]
+
+// --8<-- [start:after_agent_example]
+func onAfterAgent(ctx agent.CallbackContext) (*genai.Content, error) {
+ agentName := ctx.AgentName()
+ invocationID := ctx.InvocationID()
+ state := ctx.State()
+
+ log.Printf("\n[Callback] Exiting agent: %s (Inv: %s)", agentName, invocationID)
+ log.Printf("[Callback] Current State: %v", state)
+
+ if addNote, _ := state.Get("add_concluding_note"); addNote == true {
+ log.Printf("[Callback] State condition 'add_concluding_note=True' met: Replacing agent %s's output.", agentName)
+ return genai.NewContentFromText(
+ "Concluding note added by after_agent_callback, replacing original output.",
+ genai.RoleModel,
+ ), nil
+ }
+
+ log.Printf("[Callback] State condition not met: Using agent %s's original output.", agentName)
+ return nil, nil
+}
+
+func runAfterAgentExample() {
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+
+ llmCfg := llmagent.Config{
+ Name: "AgentWithAfterAgentCallback",
+ AfterAgentCallbacks: []agent.AfterAgentCallback{onAfterAgent},
+ Model: geminiModel,
+ Instruction: "You are a simple agent. Just say 'Processing complete!'",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{AppName: appName, Agent: testAgent, SessionService: sessionService})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create runner: %v", err)
+ }
+
+ log.Println("--- SCENARIO 1: Should use original output ---")
+ runScenario(ctx, r, sessionService, appName, "session_normal", nil, "Process this.")
+
+ log.Println("\n--- SCENARIO 2: Should replace output ---")
+ runScenario(ctx, r, sessionService, appName, "session_modify", map[string]any{"add_concluding_note": true}, "Process and add note.")
+}
+
+// --8<-- [end:after_agent_example]
+
+// --8<-- [start:before_model_example]
+func onBeforeModel(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ log.Printf("[Callback] BeforeModel triggered for agent %q.", ctx.AgentName())
+
+ // Modification Example: Add a prefix to the system instruction.
+ if req.Config.SystemInstruction != nil {
+ prefix := "[Modified by Callback] "
+ // This is a simplified example; production code might need deeper checks.
+ if len(req.Config.SystemInstruction.Parts) > 0 {
+ req.Config.SystemInstruction.Parts[0].Text = prefix + req.Config.SystemInstruction.Parts[0].Text
+ } else {
+ req.Config.SystemInstruction.Parts = append(req.Config.SystemInstruction.Parts, &genai.Part{Text: prefix})
+ }
+ log.Printf("[Callback] Modified system instruction.")
+ }
+
+ // Skip Example: Check for "BLOCK" in the user's prompt.
+ for _, content := range req.Contents {
+ for _, part := range content.Parts {
+ if strings.Contains(strings.ToUpper(part.Text), "BLOCK") {
+ log.Println("[Callback] 'BLOCK' keyword found. Skipping LLM call.")
+ return &model.LLMResponse{
+ Content: &genai.Content{
+ Parts: []*genai.Part{{Text: "LLM call was blocked by before_model_callback."}},
+ Role: "model",
+ },
+ }, nil
+ }
+ }
+ }
+
+ log.Println("[Callback] Proceeding with LLM call.")
+ return nil, nil
+}
+
+func runBeforeModelExample() {
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+
+ llmCfg := llmagent.Config{
+ Name: "AgentWithBeforeModelCallback",
+ Model: geminiModel,
+ BeforeModelCallbacks: []llmagent.BeforeModelCallback{onBeforeModel},
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{AppName: appName, Agent: testAgent, SessionService: sessionService})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create runner: %v", err)
+ }
+
+ log.Println("--- SCENARIO 1: Should proceed to LLM ---")
+ runScenario(ctx, r, sessionService, appName, "session_normal", nil, "Tell me a fun fact.")
+
+ log.Println("\n--- SCENARIO 2: Should be blocked by callback ---")
+ runScenario(ctx, r, sessionService, appName, "session_blocked", nil, "write a joke on BLOCK")
+}
+
+// --8<-- [end:before_model_example]
+
+// --8<-- [start:after_model_example]
+func onAfterModel(ctx agent.CallbackContext, resp *model.LLMResponse, respErr error) (*model.LLMResponse, error) {
+ log.Printf("[Callback] AfterModel triggered for agent %q.", ctx.AgentName())
+ if respErr != nil {
+ log.Printf("[Callback] Model returned an error: %v. Passing it through.", respErr)
+ return nil, respErr
+ }
+ if resp == nil || resp.Content == nil || len(resp.Content.Parts) == 0 {
+ log.Println("[Callback] Response is nil or has no parts, nothing to process.")
+ return nil, nil
+ }
+ // Check for function calls and pass them through without modification.
+ if resp.Content.Parts[0].FunctionCall != nil {
+ log.Println("[Callback] Response is a function call. No modification.")
+ return nil, nil
+ }
+
+ originalText := resp.Content.Parts[0].Text
+
+ // Use a case-insensitive regex with word boundaries to find "joke".
+ re := regexp.MustCompile(`(?i)\bjoke\b`)
+ if !re.MatchString(originalText) {
+ log.Println("[Callback] 'joke' not found. Passing original response through.")
+ return nil, nil
+ }
+
+ log.Println("[Callback] 'joke' found. Modifying response.")
+ // Use a replacer function to handle capitalization.
+ modifiedText := re.ReplaceAllStringFunc(originalText, func(s string) string {
+ if strings.ToUpper(s) == "JOKE" {
+ if s == "Joke" {
+ return "Funny story"
+ }
+ return "funny story"
+ }
+ return s // Should not be reached with this regex, but it's safe.
+ })
+
+ resp.Content.Parts[0].Text = modifiedText
+ return resp, nil
+}
+
+func runAfterModelExample() {
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+
+ llmCfg := llmagent.Config{
+ Name: "AgentWithAfterModelCallback",
+ Model: geminiModel,
+ AfterModelCallbacks: []llmagent.AfterModelCallback{onAfterModel},
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{AppName: appName, Agent: testAgent, SessionService: sessionService})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create runner: %v", err)
+ }
+
+ log.Println("--- SCENARIO 1: Response should be modified ---")
+ runScenario(ctx, r, sessionService, appName, "session_modify", nil, `Give me a paragraph about different styles of jokes.`)
+}
+
+// --8<-- [end:after_model_example]
+
+// --8<-- [start:tool_defs]
+// GetCapitalCityArgs defines the arguments for the getCapitalCity tool.
+type GetCapitalCityArgs struct {
+ Country string `json:"country" adk:"description=The country to get the capital of."`
+}
+
+// getCapitalCity is a tool that returns the capital of a given country.
+func getCapitalCity(ctx tool.Context, args *GetCapitalCityArgs) string {
+ capitals := map[string]string{
+ "canada": "Ottawa",
+ "france": "Paris",
+ "germany": "Berlin",
+ "united states": "Washington, D.C.",
+ }
+ capital, ok := capitals[strings.ToLower(args.Country)]
+ if !ok {
+ return "
"
+ }
+ return capital
+}
+
+// --8<-- [end:tool_defs]
+
+// --8<-- [start:before_tool_example]
+func onBeforeTool(ctx tool.Context, t tool.Tool, args map[string]any) (map[string]any, error) {
+ log.Printf("[Callback] BeforeTool triggered for tool %q in agent %q.", t.Name(), ctx.AgentName())
+ log.Printf("[Callback] Original args: %v", args)
+
+ if t.Name() == "getCapitalCity" {
+ if country, ok := args["country"].(string); ok {
+ if strings.ToLower(country) == "canada" {
+ log.Println("[Callback] Detected 'Canada'. Modifying args to 'France'.")
+ args["country"] = "France"
+ return args, nil // Proceed with modified args
+ } else if strings.ToUpper(country) == "BLOCK" {
+ log.Println("[Callback] Detected 'BLOCK'. Skipping tool execution.")
+ // Skip tool and return a custom result.
+ return map[string]any{"result": "Tool execution was blocked by before_tool_callback."}, nil
+ }
+ }
+ }
+ log.Println("[Callback] Proceeding with original or previously modified args.")
+ return nil, nil // Proceed with original args
+}
+
+func runBeforeToolExample() {
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+ capitalTool, err := functiontool.New[*GetCapitalCityArgs, string](functiontool.Config{
+ Name: "getCapitalCity",
+ Description: "Retrieves the capital city of a given country.",
+ }, getCapitalCity)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create function tool: %v", err)
+ }
+
+ llmCfg := llmagent.Config{
+ Name: "AgentWithBeforeToolCallback",
+ Model: geminiModel,
+ Tools: []tool.Tool{capitalTool},
+ BeforeToolCallbacks: []llmagent.BeforeToolCallback{onBeforeTool},
+ Instruction: "You are an agent that can find capital cities. Use the getCapitalCity tool.",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{AppName: appName, Agent: testAgent, SessionService: sessionService})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create runner: %v", err)
+ }
+
+ log.Println("--- SCENARIO 1: Args should be modified ---")
+ runScenario(ctx, r, sessionService, appName, "session_tool_modify", nil, "What is the capital of Canada?")
+
+ log.Println("--- SCENARIO 2: Tool call should be blocked ---")
+ runScenario(ctx, r, sessionService, appName, "session_tool_block", nil, "capital of BLOCK")
+}
+
+// --8<-- [end:before_tool_example]
+
+// --8<-- [start:after_tool_example]
+func onAfterTool(ctx tool.Context, t tool.Tool, args map[string]any, result map[string]any, err error) (map[string]any, error) {
+ log.Printf("[Callback] AfterTool triggered for tool %q in agent %q.", t.Name(), ctx.AgentName())
+ log.Printf("[Callback] Original result: %v", result)
+
+ if err != nil {
+ log.Printf("[Callback] Tool run produced an error: %v. Passing through.", err)
+ return nil, err
+ }
+
+ if t.Name() == "getCapitalCity" {
+ if originalResult, ok := result["result"].(string); ok && originalResult == "Washington, D.C." {
+ log.Println("[Callback] Detected 'Washington, D.C.'. Modifying tool response.")
+ modifiedResult := make(map[string]any)
+ for k, v := range result {
+ modifiedResult[k] = v
+ }
+ modifiedResult["result"] = fmt.Sprintf("%s (Note: This is the capital of the USA).", originalResult)
+ modifiedResult["note_added_by_callback"] = true
+ return modifiedResult, nil
+ }
+ }
+
+ log.Println("[Callback] Passing original tool response through.")
+ return nil, nil
+}
+
+func runAfterToolExample() {
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+ capitalTool, err := functiontool.New[*GetCapitalCityArgs, string](functiontool.Config{
+ Name: "getCapitalCity",
+ Description: "Retrieves the capital city of a given country.",
+ }, getCapitalCity)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create function tool: %v", err)
+ }
+
+ llmCfg := llmagent.Config{
+ Name: "AgentWithAfterToolCallback",
+ Model: geminiModel,
+ Tools: []tool.Tool{capitalTool},
+ AfterToolCallbacks: []llmagent.AfterToolCallback{onAfterTool},
+ Instruction: "You are an agent that finds capital cities. Use the getCapitalCity tool.",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+ sessionService := session.InMemoryService()
+ r, err := runner.New(runner.Config{AppName: appName, Agent: testAgent, SessionService: sessionService})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create runner: %v", err)
+ }
+
+ log.Println("--- SCENARIO 1: Result should be modified ---")
+ runScenario(ctx, r, sessionService, appName, "session_tool_after_modify", nil, "capital of united states")
+}
+
+// --8<-- [end:after_tool_example]
+
+func main() {
+ log.Println("--- Running BeforeAgent Example ---")
+ runBeforeAgentExample()
+
+ log.Println("\n\n--- Running AfterAgent Example ---")
+ runAfterAgentExample()
+
+ log.Println("\n\n--- Running BeforeModel Example ---")
+ runBeforeModelExample()
+
+ log.Println("\n\n--- Running AfterModel Example ---")
+ runAfterModelExample()
+
+ log.Println("\n\n--- Running BeforeTool Example ---")
+ runBeforeToolExample()
+
+ log.Println("\n\n--- Running AfterTool Example ---")
+ runAfterToolExample()
+}
+
+// Generic helper to run a single scenario.
+func runScenario(ctx context.Context, r *runner.Runner, sessionService session.Service, appName, sessionID string, initialState map[string]any, prompt string) {
+ log.Printf("Running scenario for session: %s, initial state: %v", sessionID, initialState)
+ sessionResp, err := sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID, SessionID: sessionID, State: initialState})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create session: %v", err)
+ }
+
+ input := genai.NewContentFromText(prompt, genai.RoleUser)
+ events := r.Run(ctx, sessionResp.Session.UserID(), sessionResp.Session.ID(), input, agent.RunConfig{})
+ for event, err := range events {
+ if err != nil {
+ log.Printf("ERROR during agent execution: %v", err)
+ return
+ }
+
+ // Print only the final output from the agent.
+ if event != nil && event.Content != nil && len(event.Content.Parts) > 0 {
+ fmt.Printf("Final Output for %s: [%s] %s\n", sessionID, event.Author, event.LLMResponse.Content.Parts[0].Text)
+ } else {
+ log.Printf("Final response for %s received, but it has no content to display.", sessionID)
+ }
+ }
+}
diff --git a/examples/go/snippets/context/main.go b/examples/go/snippets/context/main.go
new file mode 100644
index 00000000..86e662c7
--- /dev/null
+++ b/examples/go/snippets/context/main.go
@@ -0,0 +1,837 @@
+package main
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "iter"
+ "log"
+ "os"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/artifact"
+ "google.golang.org/adk/model"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+// --- Conceptual Snippets for adk-docs/docs/context/index.md ---
+const (
+ modelName = "gemini-2.5-flash"
+ appName = "context_doc_app"
+ userID = "test_user_123"
+)
+
+// Generic helper to run a single scenario.
+func runScenario(ctx context.Context, agentToRun agent.Agent, sessionID string, initialState map[string]any, prompt string) {
+ log.Printf("Running scenario for session: %s, initial state: %v", sessionID, initialState)
+
+ sessionService := session.InMemoryService()
+ artifactService := artifact.InMemoryService()
+ rcfg := runner.Config{
+ AppName: appName,
+ Agent: agentToRun,
+ SessionService: sessionService,
+ ArtifactService: artifactService,
+ }
+
+ r, err := runner.New(rcfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create runner: %v", err)
+ }
+
+ sessionResp, err := sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID, SessionID: sessionID, State: initialState})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create session: %v", err)
+ }
+
+ input := genai.NewContentFromText(prompt, genai.RoleUser)
+ events := r.Run(ctx, sessionResp.Session.UserID(), sessionResp.Session.ID(), input, agent.RunConfig{})
+ for event, err := range events {
+ if err != nil {
+ log.Printf("ERROR during agent execution: %v", err)
+ return
+ }
+
+ // Print only the final output from the agent.
+ if event != nil && event.Content != nil && len(event.Content.Parts) > 0 {
+ fmt.Printf("Final Output for %s: [%s] %s\n", sessionID, event.Author, event.LLMResponse.Content.Parts[0].Text)
+ } else {
+ log.Printf("Final response for %s received, but it has no content to display.", sessionID)
+ }
+ }
+}
+
+func conceptualRunnerExample(ctx context.Context, myAgent agent.Agent) {
+ // --8<-- [start:conceptual_runner_example]
+ sessionService := session.InMemoryService()
+
+ r, err := runner.New(runner.Config{
+ AppName: appName,
+ Agent: myAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create runner: %v", err)
+ }
+
+ s, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ })
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create session: %v", err)
+ }
+
+ scanner := bufio.NewScanner(os.Stdin)
+ for {
+ fmt.Print("\nYou > ")
+ if !scanner.Scan() {
+ break
+ }
+ userInput := scanner.Text()
+ if strings.EqualFold(userInput, "quit") {
+ break
+ }
+ userMsg := genai.NewContentFromText(userInput, genai.RoleUser)
+ events := r.Run(ctx, s.Session.UserID(), s.Session.ID(), userMsg, agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ })
+ fmt.Print("\nAgent > ")
+ for event, err := range events {
+ if err != nil {
+ log.Printf("ERROR during agent execution: %v", err)
+ break
+ }
+ fmt.Print(event.Content.Parts[0].Text)
+ }
+ }
+ // --8<-- [end:conceptual_runner_example]
+}
+
+func runConceptualExample() {
+ ctx := context.Background()
+ // 2. Create an agent with the tool.
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+ llmCfg := llmagent.Config{
+ Name: "agent",
+ Model: geminiModel,
+ Instruction: "You are an assistant",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ conceptualRunnerExample(ctx, testAgent)
+}
+
+// --8<-- [start:invocation_context_agent]
+// Pseudocode: Agent implementation receiving InvocationContext
+type MyAgent struct {
+}
+
+func (a *MyAgent) Run(ctx agent.InvocationContext) iter.Seq2[*session.Event, error] {
+ return func(yield func(*session.Event, error) bool) {
+ // Direct access example
+ agentName := ctx.Agent().Name()
+ sessionID := ctx.Session().ID()
+ fmt.Printf("Agent %s running in session %s for invocation %s\n", agentName, sessionID, ctx.InvocationID())
+ // ... agent logic using ctx ...
+ yield(&session.Event{Author: agentName}, nil)
+ }
+}
+
+// --8<-- [end:invocation_context_agent]
+
+// NewMyAgent creates a new MyAgent.
+func NewMyAgent() (agent.Agent, error) {
+ a := &MyAgent{}
+ // Use agent.New to construct the base agent functionality.
+ return agent.New(agent.Config{
+ Name: "MyAgent",
+ Description: "An example agent.",
+ Run: a.Run, // Pass the Run method of our struct.
+ })
+}
+
+func runMyAgent() {
+ ctx := context.Background()
+
+ testAgent, err := NewMyAgent()
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ runScenario(ctx, testAgent, "session", nil, "Hello, world!")
+}
+
+// --8<-- [start:readonly_context_instruction]
+// Pseudocode: Instruction provider receiving ReadonlyContext
+func myInstructionProvider(ctx agent.ReadonlyContext) (string, error) {
+ // Read-only access example
+ userTier, err := ctx.ReadonlyState().Get("user_tier")
+ if err != nil {
+ userTier = "standard" // Default value
+ }
+ // ctx.ReadonlyState() has no Set method since State() is read-only.
+ return fmt.Sprintf("Process the request for a %v user.", userTier), nil
+}
+
+// --8<-- [end:readonly_context_instruction]
+
+// --8<-- [start:callback_context_callback]
+// Pseudocode: Callback receiving CallbackContext
+func myBeforeModelCb(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ // Read/Write state example
+ callCount, err := ctx.State().Get("model_calls")
+ if err != nil {
+ callCount = 0 // Default value
+ }
+ newCount := callCount.(int) + 1
+ if err := ctx.State().Set("model_calls", newCount); err != nil {
+ return nil, err
+ }
+
+ // Optionally load an artifact
+ // configPart, err := ctx.Artifacts().Load("model_config.json")
+ fmt.Printf("Preparing model call #%d for invocation %s\n", newCount, ctx.InvocationID())
+ return nil, nil // Allow model call to proceed
+}
+
+// --8<-- [end:callback_context_callback]
+
+func runBeforeAgentCallbackCheck() {
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+
+ // 3. Register the callback in the agent configuration.
+ llmCfg := llmagent.Config{
+ Name: "agent",
+ BeforeModelCallbacks: []llmagent.BeforeModelCallback{myBeforeModelCb},
+ Model: geminiModel,
+ Instruction: "You are an assistant.",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ runScenario(ctx, testAgent, "session", nil, "Hello, world!")
+}
+
+// --8<-- [start:tool_context_tool]
+// Pseudocode: Tool function receiving ToolContext
+type searchExternalAPIArgs struct {
+ Query string
+}
+
+type searchExternalAPIResults struct {
+ Result string
+ Status string
+}
+
+func searchExternalAPI(tc tool.Context, input searchExternalAPIArgs) searchExternalAPIResults {
+ apiKey, err := tc.State().Get("api_key")
+ if err != nil || apiKey == "" {
+ // In a real scenario, you would define and request credentials here.
+ // This is a conceptual placeholder.
+ return searchExternalAPIResults{Status: "Auth Required"}
+ }
+
+ // Use the API key...
+ fmt.Printf("Tool executing for query '%s' using API key. Invocation: %s\n", input.Query, tc.InvocationID())
+
+ // Optionally search memory or list artifacts
+ // relevantDocs, _ := tc.SearchMemory(tc, "info related to %s", input.Query))
+ // availableFiles, _ := tc.Artifacts().List()
+
+ return searchExternalAPIResults{Result: fmt.Sprintf("Data for %s fetched.", input.Query)}
+}
+
+// --8<-- [end:tool_context_tool]
+
+func runSearchExternalAPIExample() {
+ myTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "search_external_api",
+ Description: "Searches an external API using a query string.",
+ },
+ searchExternalAPI)
+
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("Tool created: %s\n", myTool.Name())
+}
+
+// --8<-- [start:accessing_state_tool]
+// Pseudocode: In a Tool function
+type toolArgs struct {
+ // Define tool-specific arguments here
+}
+
+type toolResults struct {
+ // Define tool-specific results here
+}
+
+// Example tool function demonstrating state access
+func myTool(tc tool.Context, input toolArgs) toolResults {
+ userPref, err := tc.State().Get("user_display_preference")
+ if err != nil {
+ userPref = "default_mode"
+ }
+ apiEndpoint, _ := tc.State().Get("app:api_endpoint") // Read app-level state
+
+ if userPref == "dark_mode" {
+ // ... apply dark mode logic ...
+ }
+ fmt.Printf("Using API endpoint: %v\n", apiEndpoint)
+ // ... rest of tool logic ...
+ return toolResults{}
+}
+
+// --8<-- [end:accessing_state_tool]
+
+func runMyToolExample() {
+ myToolTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "my_tool",
+ Description: "A tool for doing something.",
+ },
+ myTool)
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("Tool created: %s\n", myToolTool.Name())
+}
+
+// --8<-- [start:accessing_state_callback]
+// Pseudocode: In a Callback function
+func myCallback(ctx agent.CallbackContext) (*genai.Content, error) {
+ lastToolResult, err := ctx.State().Get("temp:last_api_result") // Read temporary state
+ if err == nil {
+ fmt.Printf("Found temporary result from last tool: %v\n", lastToolResult)
+ } else {
+ fmt.Println("No temporary result found.")
+ }
+ // ... callback logic ...
+ return nil, nil
+}
+
+// --8<-- [end:accessing_state_callback]
+
+func runMyCallbackExample() {
+ log.Println("\n--- Running Accessing State (Callback) Example ---")
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+
+ // Register myCallback as an AfterAgentCallback.
+ llmCfg := llmagent.Config{
+ Name: "callbackAgent",
+ AfterAgentCallbacks: []agent.AfterAgentCallback{myCallback},
+ Model: geminiModel,
+ Instruction: "You are an assistant that does nothing.",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ // Scenario 1: Run without the state key.
+ log.Println("Scenario 1: State key is NOT present.")
+ runScenario(ctx, testAgent, "callback_session_1", nil, "Trigger callback")
+
+ // Scenario 2: Run with the state key.
+ log.Println("Scenario 2: State key IS present.")
+ initialState := map[string]any{"temp:last_api_result": "Success from previous step"}
+ runScenario(ctx, testAgent, "callback_session_2", initialState, "Trigger callback again")
+}
+
+// --8<-- [start:accessing_ids]
+// Pseudocode: In any context (ToolContext shown)
+type logToolUsageArgs struct{}
+type logToolUsageResult struct {
+ Status string
+}
+
+func logToolUsage(tc tool.Context, args logToolUsageArgs) logToolUsageResult {
+ agentName := tc.AgentName()
+ invID := tc.InvocationID()
+ funcCallID := tc.FunctionCallID()
+
+ fmt.Printf("Log: Invocation=%s, Agent=%s, FunctionCallID=%s - Tool Executed.\n", invID, agentName, funcCallID)
+ return logToolUsageResult{Status: "Logged successfully"}
+}
+
+// --8<-- [end:accessing_ids]
+
+func runAccessIdsExample() {
+ log.Println("\n--- Running Accessing IDs Example ---")
+ ctx := context.Background()
+
+ // 1. Create the tool.
+ loggingTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "log_tool_usage",
+ Description: "Logs the invocation and agent details.",
+ },
+ logToolUsage,
+ )
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create tool: %v", err)
+ }
+
+ // 2. Create an agent with the tool.
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+ llmCfg := llmagent.Config{
+ Name: "idAgent",
+ Model: geminiModel,
+ Instruction: "You are an assistant that uses the logging tool.",
+ Tools: []tool.Tool{loggingTool},
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ // 3. Run a scenario that will trigger the tool.
+ runScenario(ctx, testAgent, "ids_session", nil, "Please log the current usage.")
+}
+
+// --8<-- [start:accessing_user_content_agent]
+// Pseudocode: In a Callback
+func checkInitialIntent(ctx agent.CallbackContext) (*genai.Content, error) {
+ initialText := "N/A"
+ userContent := ctx.UserContent()
+ if userContent != nil && len(userContent.Parts) > 0 {
+ // The API for Part content has changed from a type assertion to direct field access.
+ if text := userContent.Parts[0].Text; text != "" {
+ initialText = text
+ } else {
+ initialText = "Non-text input"
+ }
+ }
+ fmt.Printf("This invocation started with user input: '%s'\n", initialText)
+ return nil, nil // No modification to the content
+}
+
+// --8<-- [end:accessing_user_content_agent]
+
+func runInitialIntentCheck() {
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+
+ // 3. Register the callback in the agent configuration.
+ llmCfg := llmagent.Config{
+ Name: "agent",
+ BeforeAgentCallbacks: []agent.BeforeAgentCallback{checkInitialIntent},
+ Model: geminiModel,
+ Instruction: "You are an assistant.",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ runScenario(ctx, testAgent, "session", nil, "Hello, world!")
+}
+
+// --8<-- [start:accessing_initial_user_input]
+// Pseudocode: In a Callback
+func logInitialUserInput(ctx agent.CallbackContext) (*genai.Content, error) {
+ userContent := ctx.UserContent()
+ if userContent != nil && len(userContent.Parts) > 0 {
+ if text := userContent.Parts[0].Text; text != "" {
+ fmt.Printf("User's initial input for this turn: '%s'\n", text)
+ }
+ }
+ return nil, nil // No modification
+}
+
+// --8<-- [end:accessing_initial_user_input]
+
+func runAccessingInitialUserInputExample() {
+ log.Println("\n--- Running Accessing Initial User Input Example ---")
+ ctx := context.Background()
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+
+ llmCfg := llmagent.Config{
+ Name: "userInputLoggerAgent",
+ BeforeAgentCallbacks: []agent.BeforeAgentCallback{logInitialUserInput},
+ Model: geminiModel,
+ Instruction: "You are an assistant.",
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ runScenario(ctx, testAgent, "user_input_session", nil, "What is the weather in London?")
+}
+
+// --8<-- [start:passing_data_tool1]
+// Pseudocode: Tool 1 - Fetches user ID
+type GetUserProfileArgs struct {
+}
+
+type getUserProfileResult struct {
+ ProfileStatus string
+ Error string
+}
+
+func getUserProfile(tc tool.Context, input GetUserProfileArgs) getUserProfileResult {
+ // A random user ID for demonstration purposes
+ userID := "random_user_456"
+
+ // Save the ID to state for the next tool
+ if err := tc.State().Set("temp:current_user_id", userID); err != nil {
+ return getUserProfileResult{Error: "Failed to set user ID in state"}
+ }
+ return getUserProfileResult{ProfileStatus: "ID generated"}
+}
+
+// --8<-- [end:passing_data_tool1]
+
+// --8<-- [start:passing_data_tool2]
+// Pseudocode: Tool 2 - Uses user ID from state
+type GetUserOrdersArgs struct {
+}
+
+type getUserOrdersResult struct {
+ Orders []string
+ Error string
+}
+
+func getUserOrders(tc tool.Context, input GetUserOrdersArgs) getUserOrdersResult {
+ userID, err := tc.State().Get("temp:current_user_id")
+ if err != nil {
+ return getUserOrdersResult{Error: "User ID not found in state"}
+ }
+
+ fmt.Printf("Fetching orders for user ID: %v\n", userID)
+ // ... logic to fetch orders using user_id ...
+ return getUserOrdersResult{Orders: []string{"order123", "order456"}}
+}
+
+// --8<-- [end:passing_data_tool2]
+
+func runPassingDataExample() {
+ log.Println("\n--- Running Passing Data Between Tools Example ---")
+ ctx := context.Background()
+
+ // 1. Create the tools.
+ getUserProfileTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "get_user_profile",
+ Description: "Gets the profile for a user.",
+ },
+ getUserProfile,
+ )
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create getUserProfile tool: %v", err)
+ }
+ getUserOrdersTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "get_user_orders",
+ Description: "Gets the orders for a user.",
+ },
+ getUserOrders,
+ )
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create getUserOrders tool: %v", err)
+ }
+
+ // 2. Create an agent with the tools.
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+ llmCfg := llmagent.Config{
+ Name: "dataPassingAgent",
+ Model: geminiModel,
+ Instruction: "You are an assistant that first gets the user profile, then gets their orders.",
+ Tools: []tool.Tool{getUserProfileTool, getUserOrdersTool},
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ // 3. Set up runner and session.
+ initialState := map[string]any{
+ "temp:current_user_id": userID,
+ }
+
+ // 4. Run a scenario that will trigger the tools.
+ runScenario(ctx, testAgent, "passing_data_session", initialState, "Get my orders.")
+}
+
+// --8<-- [start:updating_preferences]
+// Pseudocode: Tool or Callback identifies a preference
+type setUserPreferenceArgs struct {
+ Preference string
+ Value string
+}
+
+type setUserPreferenceResult struct {
+ Status string
+}
+
+func setUserPreference(tc tool.Context, args setUserPreferenceArgs) setUserPreferenceResult {
+ // Use 'user:' prefix for user-level state (if using a persistent SessionService)
+ stateKey := fmt.Sprintf("user:%s", args.Preference)
+ if err := tc.State().Set(stateKey, args.Value); err != nil {
+ return setUserPreferenceResult{Status: "Failed to set preference"}
+ }
+ fmt.Printf("Set user preference '%s' to '%s'\n", args.Preference, args.Value)
+ return setUserPreferenceResult{Status: "Preference updated"}
+}
+
+// --8<-- [end:updating_preferences]
+
+func runUpdatingPreferencesExample() {
+ log.Println("\n--- Running Updating Preferences Example ---")
+ ctx := context.Background()
+
+ // 1. Create the tool.
+ setPrefTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "set_user_preference",
+ Description: "Sets a user's preference in the system.",
+ },
+ setUserPreference,
+ )
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create setPrefTool: %v", err)
+ }
+
+ // 2. Create an agent with the tool.
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+ llmCfg := llmagent.Config{
+ Name: "preferenceAgent",
+ Model: geminiModel,
+ Instruction: "You are an assistant that helps users set their preferences.",
+ Tools: []tool.Tool{setPrefTool},
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ // 3. Run a scenario that will trigger the tool.
+ runScenario(ctx, testAgent, "preferences_session", nil, "Please set my theme preference to dark_mode.")
+}
+
+// --8<-- [start:artifacts_summarize]
+// Pseudocode: In the Summarizer tool function
+type summarizeDocumentArgs struct{}
+
+type summarizeDocumentResult struct {
+ Summary string
+ Error string
+}
+
+func summarizeDocumentTool(tc tool.Context, input summarizeDocumentArgs) summarizeDocumentResult {
+ artifactName, err := tc.State().Get("temp:doc_artifact_name")
+ if err != nil {
+ return summarizeDocumentResult{Error: "No document artifact name found in state"}
+ }
+
+ // 1. Load the artifact part containing the path/URI
+ artifactPart, err := tc.Artifacts().Load(tc, artifactName.(string))
+ if err != nil {
+ return summarizeDocumentResult{Error: err.Error()}
+ }
+
+ if artifactPart.Part.Text == "" {
+ return summarizeDocumentResult{Error: "Could not load artifact or artifact has no text path."}
+ }
+ filePath := artifactPart.Part.Text
+ fmt.Printf("Loaded document reference: %s\n", filePath)
+
+ // 2. Read the actual document content (outside ADK context)
+ // In a real implementation, you would use a GCS client or local file reader.
+ documentContent := "This is the fake content of the document at " + filePath
+ _ = documentContent // Avoid unused variable error.
+
+ // 3. Summarize the content
+ summary := "Summary of content from " + filePath // Placeholder
+
+ return summarizeDocumentResult{Summary: summary}
+}
+
+// --8<-- [end:artifacts_summarize]
+
+// --8<-- [start:artifacts_list]
+// Pseudocode: In a tool function
+type checkAvailableDocsArgs struct{}
+
+type checkAvailableDocsResult struct {
+ AvailableDocs []string
+ Error string
+}
+
+func checkAvailableDocs(tc tool.Context, args checkAvailableDocsArgs) checkAvailableDocsResult {
+ artifactKeys, err := tc.Artifacts().List(tc)
+ if err != nil {
+ return checkAvailableDocsResult{Error: err.Error()}
+ }
+ fmt.Printf("Available artifacts: %v\n", artifactKeys)
+ return checkAvailableDocsResult{AvailableDocs: artifactKeys.FileNames}
+}
+
+// --8<-- [end:artifacts_list]
+
+// --8<-- [start:artifacts_save_ref]
+// Adapt the saveDocumentReference callback into a tool for this example.
+type saveDocRefArgs struct {
+ FilePath string
+}
+
+type saveDocRefResult struct {
+ Status string
+ Error string
+}
+
+func saveDocRef(tc tool.Context, args saveDocRefArgs) saveDocRefResult {
+ artifactPart := genai.NewPartFromText(args.FilePath)
+ _, err := tc.Artifacts().Save(tc, "document_to_summarize.txt", artifactPart)
+ if err != nil {
+ return saveDocRefResult{"", err.Error()}
+ }
+ fmt.Printf("Saved document reference '%s' as artifact\n", args.FilePath)
+ if err := tc.State().Set("temp:doc_artifact_name", "document_to_summarize.txt"); err != nil {
+ return saveDocRefResult{"", "Failed to set artifact name in state"}
+ }
+ return saveDocRefResult{"Reference saved", ""}
+}
+
+// --8<-- [end:artifacts_save_ref]
+
+func runArtifactsExample() {
+ log.Println("\n--- Running Artifacts Example ---")
+ ctx := context.Background()
+
+ // 1. Create the tools.
+ saveRefTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "save_document_reference",
+ Description: "Saves a reference to a document path as an artifact.",
+ },
+ saveDocRef,
+ )
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create saveRefTool: %v", err)
+ }
+ summarizeTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "summarize_document",
+ Description: "Summarizes the document stored in artifacts.",
+ },
+ summarizeDocumentTool,
+ )
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create summarizeTool: %v", err)
+ }
+
+ // 2. Create an agent with the tools.
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+ llmCfg := llmagent.Config{
+ Name: "artifactAgent",
+ Model: geminiModel,
+ Instruction: "First save the document reference, then summarize it.",
+ Tools: []tool.Tool{saveRefTool, summarizeTool},
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ // 3. Run a scenario that will trigger the tools.
+ runScenario(ctx, testAgent, "artifacts_session", nil, "Save the doc at 'gs://my-bucket/report.pdf' and then summarize it.")
+}
+
+func runCheckAvailableDocsExample() {
+ log.Println("\n--- Running Check Available Docs Example ---")
+ ctx := context.Background()
+
+ // 1. Create the tool.
+ checkDocsTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "check_available_docs",
+ Description: "Checks for available documents in artifacts.",
+ },
+ checkAvailableDocs,
+ )
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create checkDocsTool: %v", err)
+ }
+
+ // 2. Create an agent with the tool.
+ geminiModel, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create model: %v", err)
+ }
+ llmCfg := llmagent.Config{
+ Name: "docCheckerAgent",
+ Model: geminiModel,
+ Instruction: "You are an assistant that can check for available documents.",
+ Tools: []tool.Tool{checkDocsTool},
+ }
+ testAgent, err := llmagent.New(llmCfg)
+ if err != nil {
+ log.Fatalf("FATAL: Failed to create agent: %v", err)
+ }
+
+ // 3. Run a scenario that will trigger the tool.
+ runScenario(ctx, testAgent, "check_docs_session", nil, "Are there any documents available?")
+}
+
+// This main function is for compilation purposes and does not run the snippets.
+func main() {
+ runInitialIntentCheck()
+ runMyAgent()
+ runBeforeAgentCallbackCheck()
+ runSearchExternalAPIExample()
+ runMyToolExample()
+ runMyCallbackExample()
+ runAccessIdsExample()
+ runAccessingInitialUserInputExample()
+ runPassingDataExample()
+ runArtifactsExample()
+ runUpdatingPreferencesExample()
+ runCheckAvailableDocsExample()
+}
diff --git a/examples/go/snippets/sessions/memory_example/memory_example.go b/examples/go/snippets/sessions/memory_example/memory_example.go
new file mode 100644
index 00000000..6eda0fdf
--- /dev/null
+++ b/examples/go/snippets/sessions/memory_example/memory_example.go
@@ -0,0 +1,191 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+// --8<-- [start:full_example]
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/memory"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+const (
+ appName = "go_memory_example_app"
+ userID = "go_mem_user"
+ modelID = "gemini-2.5-pro"
+)
+
+// Args defines the input structure for the memory search tool.
+type Args struct {
+ Query string `json:"query"`
+}
+
+// Result defines the output structure for the memory search tool.
+type Result struct {
+ Results []string `json:"results"`
+}
+
+// --8<-- [start:tool_search]
+
+// memorySearchToolFunc is the implementation of the memory search tool.
+// This function demonstrates accessing memory via tool.Context.
+func memorySearchToolFunc(tctx tool.Context, args Args) Result {
+ fmt.Printf("Tool: Searching memory for query: '%s'\n", args.Query)
+ // The SearchMemory function is available on the context.
+ searchResults, err := tctx.SearchMemory(context.Background(), args.Query)
+ if err != nil {
+ log.Printf("Error searching memory: %v", err)
+ return Result{Results: []string{"Error searching memory."}}
+ }
+
+ var results []string
+ for _, res := range searchResults.Memories {
+ if res.Content != nil {
+ results = append(results, textParts(res.Content)...)
+ }
+ }
+ return Result{Results: results}
+}
+
+// Define a tool that can search memory.
+var memorySearchTool = must(functiontool.New[Args, Result](
+ functiontool.Config{
+ Name: "search_past_conversations",
+ Description: "Searches past conversations for relevant information.",
+ },
+ memorySearchToolFunc,
+))
+
+// --8<-- [end:tool_search]
+
+// This example demonstrates how to use the MemoryService in the Go ADK.
+// It covers two main scenarios:
+// 1. Adding a completed session to memory and recalling it in a new session.
+// 2. Searching memory from within a custom tool using the tool.Context.
+func main() {
+ ctx := context.Background()
+
+ // --- Services ---
+ // Services must be shared across runners to share state and memory.
+ sessionService := session.InMemoryService()
+ memoryService := memory.InMemoryService() // Use in-memory for this demo.
+
+ // --- Scenario 1: Capture information in one session ---
+ fmt.Println("--- Turn 1: Capturing Information ---")
+ infoCaptureAgent := must(llmagent.New(llmagent.Config{
+ Name: "InfoCaptureAgent",
+ Model: must(gemini.NewModel(ctx, modelID, nil)),
+ Instruction: "Acknowledge the user's statement.",
+ }))
+
+ runner1 := must(runner.New(runner.Config{
+ AppName: appName,
+ Agent: infoCaptureAgent,
+ SessionService: sessionService,
+ MemoryService: memoryService, // Provide the memory service to the Runner
+ }))
+
+ session1ID := "session_info"
+ must(sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID, SessionID: session1ID}))
+
+ userInput1 := genai.NewContentFromText("My favorite project is Project Alpha.", "user")
+ var finalResponseText string
+ for event, err := range runner1.Run(ctx, userID, session1ID, userInput1, agent.RunConfig{}) {
+ if err != nil {
+ log.Printf("Agent 1 Error: %v", err)
+ continue
+ }
+ if event.Content != nil && !event.LLMResponse.Partial {
+ finalResponseText = strings.Join(textParts(event.LLMResponse.Content), "")
+ }
+ }
+ fmt.Printf("Agent 1 Response: %s\n", finalResponseText)
+
+ // Add the completed session to the Memory Service
+ fmt.Println("\n--- Adding Session 1 to Memory ---")
+ completedSession := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: session1ID}).Session
+ if err := memoryService.AddSession(ctx, completedSession); err != nil {
+ log.Fatalf("Failed to add session to memory: %v", err)
+ }
+ fmt.Println("Session added to memory.")
+
+ // --- Scenario 2: Recall the information in a new session using a tool ---
+ fmt.Println("\n--- Turn 2: Recalling Information ---")
+
+ memoryRecallAgent := must(llmagent.New(llmagent.Config{
+ Name: "MemoryRecallAgent",
+ Model: must(gemini.NewModel(ctx, modelID, nil)),
+ Instruction: "Answer the user's question. Use the 'search_past_conversations' tool if the answer might be in past conversations.",
+ Tools: []tool.Tool{memorySearchTool}, // Give the agent the tool
+ }))
+
+ runner2 := must(runner.New(runner.Config{
+ Agent: memoryRecallAgent,
+ AppName: appName,
+ SessionService: sessionService,
+ MemoryService: memoryService,
+ }))
+
+ session2ID := "session_recall"
+ must(sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID, SessionID: session2ID}))
+ userInput2 := genai.NewContentFromText("What is my favorite project?", "user")
+
+ var finalResponseText2 string
+ for event, err := range runner2.Run(ctx, userID, session2ID, userInput2, agent.RunConfig{}) {
+ if err != nil {
+ log.Printf("Agent 2 Error: %v", err)
+ continue
+ }
+ if event.Content != nil && !event.LLMResponse.Partial {
+ finalResponseText2 = strings.Join(textParts(event.LLMResponse.Content), "")
+ }
+ }
+ fmt.Printf("Agent 2 Response: %s\n", finalResponseText2)
+}
+
+// --8<-- [end:full_example]
+
+// --- Helper Functions ---
+
+func must[T any](v T, err error) T {
+ if err != nil {
+ log.Fatalf("Setup failed: %v", err)
+ }
+ return v
+}
+
+func textParts(c *genai.Content) (ret []string) {
+ if c == nil {
+ return nil
+ }
+ for _, p := range c.Parts {
+ if p.Text != "" {
+ ret = append(ret, p.Text)
+ }
+ }
+ return ret
+}
diff --git a/examples/go/snippets/sessions/session_management_example/session_management_example.go b/examples/go/snippets/sessions/session_management_example/session_management_example.go
new file mode 100644
index 00000000..e9292dd2
--- /dev/null
+++ b/examples/go/snippets/sessions/session_management_example/session_management_example.go
@@ -0,0 +1,97 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "google.golang.org/adk/session"
+)
+
+// This example demonstrates session management in the Go ADK, covering:
+// 1. Initializing different SessionService implementations.
+// 2. Creating a session and examining its properties.
+
+func main() {
+ ctx := context.Background()
+
+ // --- SessionService Implementations ---
+
+ // 1. InMemoryService
+ // Stores all session data directly in the application's memory.
+ // All conversation data is lost if the application restarts.
+ inMemoryService := session.InMemoryService()
+ fmt.Println("Initialized InMemoryService.")
+
+ // --8<-- [start:vertexai_service]
+ // 2. VertexAIService
+ // Before running, ensure your environment is authenticated:
+ // gcloud auth application-default login
+ // export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
+ // export GOOGLE_CLOUD_LOCATION="your-gcp-location"
+ modelName := "gemini-1.5-flash-001" // Replace with your desired model
+ vertexService, err := session.VertexAIService(ctx, modelName)
+ if err != nil {
+ log.Printf("Could not initialize VertexAIService (this is expected if the gcloud project is not set): %v", err)
+ } else {
+ fmt.Println("Successfully initialized VertexAIService.")
+ }
+ // --8<-- [end:vertexai_service]
+ _ = vertexService // Avoid unused variable error if initialization fails.
+
+ // --- Examining Session Properties ---
+ // We'll use the InMemoryService for this demonstration.
+ // --8<-- [start:examine_session]
+ appName := "my_go_app"
+ userID := "example_go_user"
+ initialState := map[string]any{"initial_key": "initial_value"}
+
+ // Create a session to examine its properties.
+ createResp, err := inMemoryService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ State: initialState,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create session: %v", err)
+ }
+ exampleSession := createResp.Session
+
+ fmt.Println("\n--- Examining Session Properties ---")
+ fmt.Printf("ID (`ID()`): %s\n", exampleSession.ID())
+ fmt.Printf("Application Name (`AppName()`): %s\n", exampleSession.AppName())
+ // To access state, you call Get().
+ val, _ := exampleSession.State().Get("initial_key")
+ fmt.Printf("State (`State().Get()`): initial_key = %v\n", val)
+
+ // Events are initially empty.
+ fmt.Printf("Events (`Events().Len()`): %d\n", exampleSession.Events().Len())
+ fmt.Printf("Last Update (`LastUpdateTime()`): %s\n", exampleSession.LastUpdateTime().Format("2006-01-02 15:04:05"))
+ fmt.Println("---------------------------------")
+
+ // Clean up the session.
+ err = inMemoryService.Delete(ctx, &session.DeleteRequest{
+ AppName: exampleSession.AppName(),
+ UserID: exampleSession.UserID(),
+ SessionID: exampleSession.ID(),
+ })
+ if err != nil {
+ log.Fatalf("Failed to delete session: %v", err)
+ }
+ fmt.Println("Session deleted successfully.")
+ // --8<-- [end:examine_session]
+}
diff --git a/examples/go/snippets/tools-custom/customer_support_agent/main.go b/examples/go/snippets/tools-custom/customer_support_agent/main.go
new file mode 100644
index 00000000..1c0c44cb
--- /dev/null
+++ b/examples/go/snippets/tools-custom/customer_support_agent/main.go
@@ -0,0 +1,115 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+type checkAndTransferArgs struct {
+ Query string `json:"query"`
+}
+
+type checkAndTransferResult struct {
+ Status string `json:"status"`
+}
+
+func checkAndTransfer(ctx tool.Context, args checkAndTransferArgs) checkAndTransferResult {
+ if strings.Contains(strings.ToLower(args.Query), "urgent") {
+ fmt.Println("Tool: Detected urgency, transferring to the support agent.")
+ ctx.Actions().TransferToAgent = "support_agent"
+ return checkAndTransferResult{Status: "Transferring to the support agent..."}
+ }
+ return checkAndTransferResult{Status: fmt.Sprintf("Processed query: '%s'. No further action needed.", args.Query)}
+}
+
+func main() {
+ ctx := context.Background()
+ model, err := gemini.NewModel(ctx, "gemini-2.0-flash", &genai.ClientConfig{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ supportAgent, err := llmagent.New(llmagent.Config{
+ Name: "support_agent",
+ Model: model,
+ Instruction: "You are the dedicated support agent. Mentioned you are a support handler and please help the user with their urgent issue.",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ checkAndTransferTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "check_and_transfer",
+ Description: "Checks if the query requires escalation and transfers to another agent if needed.",
+ },
+ checkAndTransfer,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ mainAgent, err := llmagent.New(llmagent.Config{
+ Name: "main_agent",
+ Model: model,
+ Instruction: "You are the first point of contact for customer support of an analytics tool. Answer general queries. If the user indicates urgency, use the 'check_and_transfer' tool.",
+ Tools: []tool.Tool{checkAndTransferTool},
+ SubAgents: []agent.Agent{supportAgent},
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sessionService := session.InMemoryService()
+ runner, err := runner.New(runner.Config{
+ AppName: "customer_support_agent",
+ Agent: mainAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: "customer_support_agent",
+ UserID: "user1234",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ run(ctx, runner, session.Session.ID(), "this is urgent, i cant login")
+}
+
+func run(ctx context.Context, r *runner.Runner, sessionID string, prompt string) {
+ fmt.Printf("\n> %s\n", prompt)
+ events := r.Run(
+ ctx,
+ "user1234",
+ sessionID,
+ genai.NewContentFromText(prompt, genai.RoleUser),
+ agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ },
+ )
+ for event, err := range events {
+ if err != nil {
+ log.Fatalf("ERROR during agent execution: %v", err)
+ }
+
+ if event.Content.Parts[0].Text != "" {
+ fmt.Printf("Agent Response: %s\n", event.Content.Parts[0].Text)
+ }
+ }
+}
diff --git a/examples/go/snippets/tools-custom/doc_analysis/doc_analysis.go b/examples/go/snippets/tools-custom/doc_analysis/doc_analysis.go
new file mode 100644
index 00000000..763a06f9
--- /dev/null
+++ b/examples/go/snippets/tools-custom/doc_analysis/doc_analysis.go
@@ -0,0 +1,71 @@
+package main
+
+import (
+ "fmt"
+
+ "google.golang.org/adk/tool"
+ "google.golang.org/genai"
+)
+
+type processDocumentArgs struct {
+ DocumentName string `json:"document_name"`
+ AnalysisQuery string `json:"analysis_query"`
+}
+
+type processDocumentResult struct {
+ Status string `json:"status"`
+ AnalysisArtifact string `json:"analysis_artifact,omitempty"`
+ Version int64 `json:"version,omitempty"`
+ Message string `json:"message,omitempty"`
+}
+
+func processDocument(ctx tool.Context, args processDocumentArgs) processDocumentResult {
+ fmt.Printf("Tool: Attempting to load artifact: %s\n", args.DocumentName)
+
+ // List all artifacts
+ listResponse, err := ctx.Artifacts().List(ctx)
+ if err != nil {
+ return processDocumentResult{Status: "error", Message: "Failed to list artifacts."}
+ }
+
+ fmt.Println("Tool: Available artifacts:")
+ for _, file := range listResponse.FileNames {
+ fmt.Printf(" - %s\n", file)
+ }
+
+ documentPart, err := ctx.Artifacts().Load(ctx, args.DocumentName)
+ if err != nil {
+ return processDocumentResult{Status: "error", Message: fmt.Sprintf("Document '%s' not found.", args.DocumentName)}
+ }
+
+ fmt.Printf("Tool: Loaded document '%s' of size %d bytes.\n", args.DocumentName, len(documentPart.Part.InlineData.Data))
+
+ // 3. Search memory for related context
+ fmt.Printf("Tool: Searching memory for context related to: '%s'\n", args.AnalysisQuery)
+ memoryResp, err := ctx.SearchMemory(ctx, args.AnalysisQuery)
+ if err != nil {
+ fmt.Printf("Tool: Error searching memory: %v\n", err)
+ }
+ memoryResultCount := 0
+ if memoryResp != nil {
+ memoryResultCount = len(memoryResp.Memories)
+ }
+ fmt.Printf("Tool: Found %d memory results.\n", memoryResultCount)
+
+ analysisResult := fmt.Sprintf("Analysis of '%s' regarding '%s' using memory context: [Placeholder Analysis Result]", args.DocumentName, args.AnalysisQuery)
+ fmt.Println("Tool: Performed analysis.")
+
+ analysisPart := genai.NewPartFromText(analysisResult)
+ newArtifactName := fmt.Sprintf("analysis_%s", args.DocumentName)
+ version, err := ctx.Artifacts().Save(ctx, newArtifactName, analysisPart)
+ if err != nil {
+ return processDocumentResult{Status: "error", Message: "Failed to save artifact."}
+ }
+ fmt.Printf("Tool: Saved analysis result as '%s' version %d.\n", newArtifactName, version.Version)
+
+ return processDocumentResult{
+ Status: "success",
+ AnalysisArtifact: newArtifactName,
+ Version: version.Version,
+ }
+}
diff --git a/examples/go/snippets/tools-custom/doc_analysis/main.go b/examples/go/snippets/tools-custom/doc_analysis/main.go
new file mode 100644
index 00000000..6097d6e5
--- /dev/null
+++ b/examples/go/snippets/tools-custom/doc_analysis/main.go
@@ -0,0 +1,151 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/artifact"
+ "google.golang.org/adk/memory"
+ "google.golang.org/adk/model"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+func saveStoryBytes(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMResponse, error) {
+ // Get the report data from the session state.
+ storyData, err := ctx.State().Get("story_bytes")
+ if err != nil {
+ log.Printf("No report data found in session state: %v", err)
+ return nil, nil // No report to save, continue normally.
+ }
+
+ // Check if the report data is in the expected format.
+ storyBytes, ok := storyData.([]byte)
+ if !ok {
+ log.Printf("Report data in session state was not in the expected byte format.")
+ return nil, nil
+ }
+
+ // Create a new artifact with the report data.
+ documentArtifact := &genai.Part{
+ InlineData: &genai.Blob{
+ MIMEType: "application/pdf",
+ Data: storyBytes,
+ },
+ }
+ // Set the filename for the artifact.
+ filename := "my_document.pdf"
+ // Save the artifact to the artifact service.
+ _, err = ctx.Artifacts().Save(ctx, filename, documentArtifact)
+ if err != nil {
+ log.Printf("An unexpected error occurred during Go artifact save: %v", err)
+ // Depending on requirements, you might want to return an error to the user.
+ return nil, nil
+ }
+ log.Printf("Successfully saved Go artifact '%s'.", filename)
+
+ // Return nil to continue to the next callback or the model.
+ return nil, nil
+}
+
+func main() {
+ ctx := context.Background()
+ model, err := gemini.NewModel(ctx, "gemini-2.0-flash", &genai.ClientConfig{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ docAnalysisTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "process_document",
+ Description: "Analyzes a document using context from memory.",
+ },
+ processDocument,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ mainAgent, err := llmagent.New(llmagent.Config{
+ Name: "main_agent",
+ Model: model,
+ Instruction: "You are an agent that can process documents.",
+ Tools: []tool.Tool{docAnalysisTool},
+ BeforeModelCallbacks: []llmagent.BeforeModelCallback{saveStoryBytes},
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sessionService := session.InMemoryService()
+ artifactService := artifact.InMemoryService()
+ memoryService := memory.InMemoryService()
+ runner, err := runner.New(runner.Config{
+ AppName: "doc_analysis",
+ Agent: mainAgent,
+ SessionService: sessionService,
+ ArtifactService: artifactService,
+ MemoryService: memoryService,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ session1, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: "doc_analysis",
+ UserID: "user1234",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ storyBytes, _ := os.ReadFile("story.pdf") // Load a sample PDF file
+ initialState := map[string]any{
+ "story_bytes": storyBytes,
+ }
+
+ session2, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: "doc_analysis",
+ UserID: "user1234",
+ State: initialState,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // First run to populate memory. The agent will respond, and the runner will
+ // automatically add the interaction to the memory service.
+ run(ctx, runner, session1.Session.ID(), "I am very interested in positive sentiment analysis.")
+ // Second run that uses the tool to search the memory populated by the first run.
+ run(ctx, runner, session2.Session.ID(), "process the document named 'my_document.pdf' and analyze it for 'sentiment'")
+}
+
+func run(ctx context.Context, r *runner.Runner, sessionID string, prompt string) {
+ fmt.Printf("\n> %s\n", prompt)
+ events := r.Run(
+ ctx,
+ "user1234",
+ sessionID,
+ genai.NewContentFromText(prompt, genai.RoleUser),
+ agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ },
+ )
+ for event, err := range events {
+ if err != nil {
+ log.Fatalf("ERROR during agent execution: %v", err)
+ }
+
+ if event.Content.Parts[0].Text != "" {
+ fmt.Printf("Agent Response: %s\n", event.Content.Parts[0].Text)
+ }
+ }
+}
diff --git a/examples/go/snippets/tools-custom/order_status/main.go b/examples/go/snippets/tools-custom/order_status/main.go
new file mode 100644
index 00000000..237cdb38
--- /dev/null
+++ b/examples/go/snippets/tools-custom/order_status/main.go
@@ -0,0 +1,87 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+func main() {
+ ctx := context.Background()
+ model, err := gemini.NewModel(ctx, "gemini-2.0-flash", &genai.ClientConfig{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ orderStatusTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "lookup_order_status",
+ Description: "Fetches the current status of a customer's order using its ID.",
+ },
+ lookupOrderStatus,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ mainAgent, err := llmagent.New(llmagent.Config{
+ Name: "main_agent",
+ Model: model,
+ Instruction: "You are an agent that can lookup order status.",
+ Tools: []tool.Tool{orderStatusTool},
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sessionService := session.InMemoryService()
+ runner, err := runner.New(runner.Config{
+ AppName: "order_status",
+ Agent: mainAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: "order_status",
+ UserID: "user1234",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ run(ctx, runner, session.Session.ID(), "what is the status of order 12345?")
+}
+
+func run(ctx context.Context, r *runner.Runner, sessionID string, prompt string) {
+ fmt.Printf("\n> %s\n", prompt)
+ events := r.Run(
+ ctx,
+ "user1234",
+ sessionID,
+ genai.NewContentFromText(prompt, genai.RoleUser),
+ agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ },
+ )
+ for event, err := range events {
+ if err != nil {
+ log.Fatalf("ERROR during agent execution: %v", err)
+ }
+
+ if event.Content.Parts[0].Text != "" {
+ fmt.Printf("Agent Response: %s\n", event.Content.Parts[0].Text)
+ }
+ }
+}
diff --git a/examples/go/snippets/tools-custom/order_status/order_status.go b/examples/go/snippets/tools-custom/order_status/order_status.go
new file mode 100644
index 00000000..26c22f66
--- /dev/null
+++ b/examples/go/snippets/tools-custom/order_status/order_status.go
@@ -0,0 +1,51 @@
+package main
+
+// --8<-- [start:snippet]
+import (
+ "fmt"
+
+ "google.golang.org/adk/tool"
+)
+
+type lookupOrderStatusArgs struct {
+ OrderID string `json:"order_id"`
+}
+
+type order struct {
+ State string `json:"state"`
+ TrackingNumber string `json:"tracking_number"`
+}
+
+type lookupOrderStatusResult struct {
+ Status string `json:"status"`
+ Order order `json:"order,omitempty"`
+ ErrorMessage string `json:"error_message,omitempty"`
+}
+
+func lookupOrderStatus(ctx tool.Context, args lookupOrderStatusArgs) lookupOrderStatusResult {
+ // ... function implementation to fetch status ...
+ if statusDetails, ok := fetchStatusFromBackend(args.OrderID); ok {
+ return lookupOrderStatusResult{
+ Status: "success",
+ Order: order{
+ State: statusDetails.State,
+ TrackingNumber: statusDetails.Tracking,
+ },
+ }
+ }
+ return lookupOrderStatusResult{Status: "error", ErrorMessage: fmt.Sprintf("Order ID %s not found.", args.OrderID)}
+}
+
+// --8<-- [end:snippet]
+
+type statusDetails struct {
+ State string
+ Tracking string
+}
+
+func fetchStatusFromBackend(orderID string) (statusDetails, bool) {
+ if orderID == "12345" {
+ return statusDetails{State: "shipped", Tracking: "1Z9..."}, true
+ }
+ return statusDetails{}, false
+}
diff --git a/examples/go/snippets/tools-custom/user_preference/main.go b/examples/go/snippets/tools-custom/user_preference/main.go
new file mode 100644
index 00000000..e51be9d5
--- /dev/null
+++ b/examples/go/snippets/tools-custom/user_preference/main.go
@@ -0,0 +1,87 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+func main() {
+ ctx := context.Background()
+ model, err := gemini.NewModel(ctx, "gemini-2.0-flash", &genai.ClientConfig{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ userPreferenceTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "update_user_preference",
+ Description: "Updates a user-specific preference.",
+ },
+ updateUserPreference,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ mainAgent, err := llmagent.New(llmagent.Config{
+ Name: "main_agent",
+ Model: model,
+ Instruction: "You are an agent that can update user preferences. When a user asks to set a preference, identify the preference key and the desired value. For example, in 'set my theme to dark', the key is 'theme' and the value is 'dark'. Then, call the 'update_user_preference' tool with these values.",
+ Tools: []tool.Tool{userPreferenceTool},
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sessionService := session.InMemoryService()
+ runner, err := runner.New(runner.Config{
+ AppName: "user_preference",
+ Agent: mainAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: "user_preference",
+ UserID: "user1234",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ run(ctx, runner, session.Session.ID(), "set my theme to dark")
+}
+
+func run(ctx context.Context, r *runner.Runner, sessionID string, prompt string) {
+ fmt.Printf("\n> %s\n", prompt)
+ events := r.Run(
+ ctx,
+ "user1234",
+ sessionID,
+ genai.NewContentFromText(prompt, genai.RoleUser),
+ agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ },
+ )
+ for event, err := range events {
+ if err != nil {
+ log.Fatalf("ERROR during agent execution: %v", err)
+ }
+
+ if event.Content.Parts[0].Text != "" {
+ fmt.Printf("Agent Response: %s\n", event.Content.Parts[0].Text)
+ }
+ }
+}
diff --git a/examples/go/snippets/tools-custom/user_preference/user_preference.go b/examples/go/snippets/tools-custom/user_preference/user_preference.go
new file mode 100644
index 00000000..da6a7a52
--- /dev/null
+++ b/examples/go/snippets/tools-custom/user_preference/user_preference.go
@@ -0,0 +1,42 @@
+package main
+
+// --8<-- [start:example]
+import (
+ "fmt"
+
+ "google.golang.org/adk/tool"
+)
+
+type updateUserPreferenceArgs struct {
+ Preference string `json:"preference"`
+ Value string `json:"value"`
+}
+
+type updateUserPreferenceResult struct {
+ Status string `json:"status"`
+ UpdatedPreference string `json:"updated_preference"`
+}
+
+func updateUserPreference(ctx tool.Context, args updateUserPreferenceArgs) updateUserPreferenceResult {
+ userPrefsKey := "user:preferences"
+ val, err := ctx.State().Get(userPrefsKey)
+ if err != nil {
+ val = make(map[string]any)
+ }
+
+ preferencesMap, ok := val.(map[string]any)
+ if !ok {
+ preferencesMap = make(map[string]any)
+ }
+
+ preferencesMap[args.Preference] = args.Value
+
+ if err := ctx.State().Set(userPrefsKey, preferencesMap); err != nil {
+ return updateUserPreferenceResult{Status: "error"}
+ }
+
+ fmt.Printf("Tool: Updated user preference '%s' to '%s'\n", args.Preference, args.Value)
+ return updateUserPreferenceResult{Status: "success", UpdatedPreference: args.Preference}
+}
+
+// --8<-- [end:example]
diff --git a/examples/go/snippets/tools-custom/weather_sentiment/main.go b/examples/go/snippets/tools-custom/weather_sentiment/main.go
new file mode 100644
index 00000000..1154aaa2
--- /dev/null
+++ b/examples/go/snippets/tools-custom/weather_sentiment/main.go
@@ -0,0 +1,139 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+ "google.golang.org/genai"
+)
+
+type getWeatherReportArgs struct {
+ City string `json:"city"`
+}
+
+type getWeatherReportResult struct {
+ Status string `json:"status"`
+ Report string `json:"report,omitempty"`
+ ErrorMessage string `json:"error_message,omitempty"`
+}
+
+func getWeatherReport(ctx tool.Context, args getWeatherReportArgs) getWeatherReportResult {
+ if strings.ToLower(args.City) == "london" {
+ return getWeatherReportResult{Status: "success", Report: "The current weather in London is cloudy with a temperature of 18 degrees Celsius and a chance of rain."}
+ }
+ if strings.ToLower(args.City) == "paris" {
+ return getWeatherReportResult{Status: "success", Report: "The weather in Paris is sunny with a temperature of 25 degrees Celsius."}
+ }
+ return getWeatherReportResult{Status: "error", ErrorMessage: fmt.Sprintf("Weather information for '%s' is not available.", args.City)}
+}
+
+type analyzeSentimentArgs struct {
+ Text string `json:"text"`
+}
+
+type analyzeSentimentResult struct {
+ Sentiment string `json:"sentiment"`
+ Confidence float64 `json:"confidence"`
+}
+
+func analyzeSentiment(ctx tool.Context, args analyzeSentimentArgs) analyzeSentimentResult {
+ if strings.Contains(strings.ToLower(args.Text), "good") || strings.Contains(strings.ToLower(args.Text), "sunny") {
+ return analyzeSentimentResult{Sentiment: "positive", Confidence: 0.8}
+ }
+ if strings.Contains(strings.ToLower(args.Text), "rain") || strings.Contains(strings.ToLower(args.Text), "bad") {
+ return analyzeSentimentResult{Sentiment: "negative", Confidence: 0.7}
+ }
+ return analyzeSentimentResult{Sentiment: "neutral", Confidence: 0.6}
+}
+
+func main() {
+ ctx := context.Background()
+ model, err := gemini.NewModel(ctx, "gemini-2.0-flash", &genai.ClientConfig{})
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ weatherTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "get_weather_report",
+ Description: "Retrieves the current weather report for a specified city.",
+ },
+ getWeatherReport,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sentimentTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "analyze_sentiment",
+ Description: "Analyzes the sentiment of the given text.",
+ },
+ analyzeSentiment,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ weatherSentimentAgent, err := llmagent.New(llmagent.Config{
+ Name: "weather_sentiment_agent",
+ Model: model,
+ Instruction: "You are a helpful assistant that provides weather information and analyzes the sentiment of user feedback. **If the user asks about the weather in a specific city, use the 'get_weather_report' tool to retrieve the weather details.** **If the 'get_weather_report' tool returns a 'success' status, provide the weather report to the user.** **If the 'get_weather_report' tool returns an 'error' status, inform the user that the weather information for the specified city is not available and ask if they have another city in mind.** **After providing a weather report, if the user gives feedback on the weather (e.g., 'That's good' or 'I don't like rain'), use the 'analyze_sentiment' tool to understand their sentiment.** Then, briefly acknowledge their sentiment. You can handle these tasks sequentially if needed.",
+ Tools: []tool.Tool{weatherTool, sentimentTool},
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sessionService := session.InMemoryService()
+ runner, err := runner.New(runner.Config{
+ AppName: "weather_sentiment_agent",
+ Agent: weatherSentimentAgent,
+ SessionService: sessionService,
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: "weather_sentiment_agent",
+ UserID: "user1234",
+ })
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ run(ctx, runner, session.Session.ID(), "weather in london?")
+ run(ctx, runner, session.Session.ID(), "I don't like rain.")
+}
+
+func run(ctx context.Context, r *runner.Runner, sessionID string, prompt string) {
+ fmt.Printf("\n> %s\n", prompt)
+ events := r.Run(
+ ctx,
+ "user1234",
+ sessionID,
+ genai.NewContentFromText(prompt, genai.RoleUser),
+ agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ },
+ )
+ for event, err := range events {
+ if err != nil {
+ log.Fatalf("ERROR during agent execution: %v", err)
+ }
+
+ if event.Content.Parts[0].Text != "" {
+ fmt.Printf("Agent Response: %s\n", event.Content.Parts[0].Text)
+ }
+ }
+}
diff --git a/examples/go/snippets/tools/built-in-tools/google_search.go b/examples/go/snippets/tools/built-in-tools/google_search.go
new file mode 100644
index 00000000..000c75a9
--- /dev/null
+++ b/examples/go/snippets/tools/built-in-tools/google_search.go
@@ -0,0 +1,92 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/geminitool"
+ "google.golang.org/genai"
+)
+
+func createSearchAgent(ctx context.Context) (agent.Agent, error) {
+ model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
+ if err != nil {
+ return nil, fmt.Errorf("failed to create model: %v", err)
+ }
+
+ return llmagent.New(llmagent.Config{
+ Name: "basic_search_agent",
+ Model: model,
+ Description: "Agent to answer questions using Google Search.",
+ Instruction: "I can answer your questions by searching the web. Just ask me anything!",
+ Tools: []tool.Tool{geminitool.GoogleSearch{}},
+ })
+}
+
+const (
+ userID = "user1234"
+ appName = "Google Search_agent"
+)
+
+func callAgent(ctx context.Context, a agent.Agent, prompt string) error {
+ sessionService := session.InMemoryService()
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to create the session service: %v", err)
+ }
+
+ config := runner.Config{
+ AppName: appName,
+ Agent: a,
+ SessionService: sessionService,
+ }
+ r, err := runner.New(config)
+ if err != nil {
+ return fmt.Errorf("failed to create the runner: %v", err)
+ }
+
+ sessionID := session.Session.ID()
+ userMsg := &genai.Content{
+ Parts: []*genai.Part{{Text: prompt}},
+ Role: string(genai.RoleUser),
+ }
+
+ // The r.Run method streams events and errors.
+ // The loop iterates over the results, handling them as they arrive.
+ for event, err := range r.Run(ctx, userID, sessionID, userMsg, agent.RunConfig{
+ StreamingMode: agent.StreamingModeSSE,
+ }) {
+ if err != nil {
+ fmt.Printf("\nAGENT_ERROR: %v\n", err)
+ } else if event.Partial {
+ for _, p := range event.LLMResponse.Content.Parts {
+ fmt.Print(p.Text)
+ }
+ }
+ }
+ return nil
+}
+
+func main() {
+ agent, err := createSearchAgent(context.Background())
+ if err != nil {
+ log.Fatalf("Failed to create agent: %v", err)
+ }
+ fmt.Println("Agent created:", agent.Name())
+ prompt := "what's the latest ai news?"
+ fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
+ if err := callAgent(context.Background(), agent, prompt); err != nil {
+ log.Fatalf("Error calling agent: %v", err)
+ }
+ fmt.Println("\n---")
+}
diff --git a/examples/go/snippets/tools/function-tools/func_tool.go b/examples/go/snippets/tools/function-tools/func_tool.go
new file mode 100644
index 00000000..4238b710
--- /dev/null
+++ b/examples/go/snippets/tools/function-tools/func_tool.go
@@ -0,0 +1,249 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/agenttool"
+ "google.golang.org/adk/tool/functiontool"
+
+ "google.golang.org/genai"
+)
+
+// mockStockPrices provides a simple in-memory database of stock prices
+// to simulate a real-world stock data API. This allows the example to
+// demonstrate tool functionality without making external network calls.
+var mockStockPrices = map[string]float64{
+ "GOOG": 300.6,
+ "AAPL": 123.4,
+ "MSFT": 234.5,
+}
+
+// getStockPriceArgs defines the schema for the arguments passed to the getStockPrice tool.
+// Using a struct is the recommended approach in the Go ADK as it provides strong
+// typing and clear validation for the expected inputs.
+type getStockPriceArgs struct {
+ Symbol string
+}
+
+// getStockPriceResults defines the output schema for the getStockPrice tool.
+type getStockPriceResults struct {
+ Symbol string `json:"symbol"`
+ Price float64 `json:"price,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+// getStockPrice is a tool that retrieves the stock price for a given ticker symbol
+// from the mockStockPrices map. It demonstrates how a function can be used as a
+// tool by an agent. If the symbol is found, it returns a struct containing the
+// symbol and its price. Otherwise, it returns a struct with an error message.
+func getStockPrice(ctx tool.Context, input getStockPriceArgs) getStockPriceResults {
+ symbolUpper := strings.ToUpper(input.Symbol)
+ if price, ok := mockStockPrices[symbolUpper]; ok {
+ fmt.Printf("Tool: Found price for %s: %f\n", input.Symbol, price)
+ return getStockPriceResults{Symbol: input.Symbol, Price: price}
+ }
+ return getStockPriceResults{Symbol: input.Symbol, Error: "No data found for symbol"}
+}
+
+// createStockAgent initializes and configures an LlmAgent.
+// This agent is equipped with the getStockPrice tool and is instructed
+// on how to respond to user queries about stock prices. It uses the
+// Gemini model to understand user intent and decide when to use its tools.
+func createStockAgent(ctx context.Context) (agent.Agent, error) {
+ stockPriceTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "get_stock_price",
+ Description: "Retrieves the current stock price for a given symbol.",
+ },
+ getStockPrice)
+ if err != nil {
+ return nil, err
+ }
+
+ model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
+
+ if err != nil {
+ log.Fatalf("Failed to create model: %v", err)
+ }
+
+ return llmagent.New(llmagent.Config{
+ Name: "stock_agent",
+ Model: model,
+ Instruction: "You are an agent who retrieves stock prices. If a ticker symbol is provided, fetch the current price. If only a company name is given, first perform a Google search to find the correct ticker symbol before retrieving the stock price. If the provided ticker symbol is invalid or data cannot be retrieved, inform the user that the stock price could not be found.",
+ Description: "This agent specializes in retrieving real-time stock prices. Given a stock ticker symbol (e.g., AAPL, GOOG, MSFT) or the stock name, use the tools and reliable data sources to provide the most up-to-date price.",
+ Tools: []tool.Tool{
+ stockPriceTool,
+ },
+ })
+}
+
+// userID and appName are constants used to identify the user and application
+// throughout the session. These values are important for logging, tracking,
+// and managing state across different agent interactions.
+const (
+ userID = "example_user_id"
+ appName = "example_app"
+)
+
+// callAgent orchestrates the execution of the agent for a given prompt.
+// It sets up the necessary services, creates a session, and uses a runner
+// to manage the agent's lifecycle. It streams the agent's responses and
+// prints them to the console, handling any potential errors during the run.
+func callAgent(ctx context.Context, a agent.Agent, prompt string) {
+ sessionService := session.InMemoryService()
+ // Create a new session for the agent interactions.
+ session, err := sessionService.Create(ctx, &session.CreateRequest{
+ AppName: appName,
+ UserID: userID,
+ })
+ if err != nil {
+ log.Fatalf("Failed to create the session service: %v", err)
+ }
+ config := runner.Config{
+ AppName: appName,
+ Agent: a,
+ SessionService: sessionService,
+ }
+
+ // Create the runner to manage the agent execution.
+ r, err := runner.New(config)
+
+ if err != nil {
+ log.Fatalf("Failed to create the runner: %v", err)
+ }
+
+ sessionID := session.Session.ID()
+
+ userMsg := &genai.Content{
+ Parts: []*genai.Part{
+ genai.NewPartFromText(prompt),
+ },
+ Role: string(genai.RoleUser),
+ }
+
+ for event, err := range r.Run(ctx, userID, sessionID, userMsg, agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ }) {
+ if err != nil {
+ fmt.Printf("\nAGENT_ERROR: %v\n", err)
+ } else {
+ for _, p := range event.Content.Parts {
+ fmt.Print(p.Text)
+ }
+ }
+ }
+}
+
+// RunAgentSimulation serves as the entry point for this example.
+// It creates the stock agent and then simulates a series of user interactions
+// by sending different prompts to the agent. This function showcases how the
+// agent responds to various queries, including both successful and unsuccessful
+// attempts to retrieve stock prices.
+func RunAgentSimulation() {
+ // Create the stock agent
+ agent, err := createStockAgent(context.Background())
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("Agent created:", agent.Name())
+
+ prompts := []string{
+ "stock price of GOOG",
+ "What's the price of MSFT?",
+ "Can you find the stock price for an unknown company XYZ?",
+ }
+
+ // Simulate running the agent with different prompts
+ for _, prompt := range prompts {
+ fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
+ callAgent(context.Background(), agent, prompt)
+ fmt.Println("\n---")
+ }
+}
+
+// --8<-- [start:agent_tool_example]
+// createSummarizerAgent creates an agent whose sole purpose is to summarize text.
+func createSummarizerAgent(ctx context.Context) (agent.Agent, error) {
+ model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
+ if err != nil {
+ return nil, err
+ }
+ return llmagent.New(llmagent.Config{
+ Name: "SummarizerAgent",
+ Model: model,
+ Instruction: "You are an expert at summarizing text. Take the user's input and provide a concise summary.",
+ Description: "An agent that summarizes text.",
+ })
+}
+
+// createMainAgent creates the primary agent that will use the summarizer agent as a tool.
+func createMainAgent(ctx context.Context, tools ...tool.Tool) (agent.Agent, error) {
+ model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
+ if err != nil {
+ return nil, err
+ }
+ return llmagent.New(llmagent.Config{
+ Name: "MainAgent",
+ Model: model,
+ Instruction: "You are a helpful assistant. If you are asked to summarize a long text, use the 'summarize' tool. " +
+ "After getting the summary, present it to the user by saying 'Here is a summary of the text:'.",
+ Description: "The main agent that can delegate tasks.",
+ Tools: tools,
+ })
+}
+
+func RunAgentAsToolSimulation() {
+ ctx := context.Background()
+
+ // 1. Create the Tool Agent (Summarizer)
+ summarizerAgent, err := createSummarizerAgent(ctx)
+ if err != nil {
+ log.Fatalf("Failed to create summarizer agent: %v", err)
+ }
+
+ // 2. Wrap the Tool Agent in an AgentTool
+ summarizeTool := agenttool.New(summarizerAgent, &agenttool.Config{
+ SkipSummarization: true,
+ })
+
+ // 3. Create the Main Agent and provide it with the AgentTool
+ mainAgent, err := createMainAgent(ctx, summarizeTool)
+ if err != nil {
+ log.Fatalf("Failed to create main agent: %v", err)
+ }
+
+ // 4. Run the main agent
+ prompt := `
+ Please summarize this text for me:
+ Quantum computing represents a fundamentally different approach to computation,
+ leveraging the bizarre principles of quantum mechanics to process information. Unlike classical computers
+ that rely on bits representing either 0 or 1, quantum computers use qubits which can exist in a state of superposition - effectively
+ being 0, 1, or a combination of both simultaneously. Furthermore, qubits can become entangled,
+ meaning their fates are intertwined regardless of distance, allowing for complex correlations. This parallelism and
+ interconnectedness grant quantum computers the potential to solve specific types of incredibly complex problems - such
+ as drug discovery, materials science, complex system optimization, and breaking certain types of cryptography - far
+ faster than even the most powerful classical supercomputers could ever achieve, although the technology is still largely in its developmental stages.
+ `
+ fmt.Printf("\nPrompt: %s\nResponse: ", prompt)
+ callAgent(context.Background(), mainAgent, prompt)
+ fmt.Println("\n---")
+}
+
+// --8<-- [end:agent_tool_example]
+
+func main() {
+ fmt.Println("Attempting to run the agent simulation...")
+ RunAgentSimulation()
+ fmt.Println("\nAttempting to run the agent-as-a-tool simulation...")
+ RunAgentAsToolSimulation()
+}
diff --git a/examples/go/snippets/tools/function-tools/long-running-tool/long_running_tool.go b/examples/go/snippets/tools/function-tools/long-running-tool/long_running_tool.go
new file mode 100644
index 00000000..b484f9bf
--- /dev/null
+++ b/examples/go/snippets/tools/function-tools/long-running-tool/long_running_tool.go
@@ -0,0 +1,174 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "sync/atomic"
+
+ "google.golang.org/adk/agent"
+ "google.golang.org/adk/agent/llmagent"
+ "google.golang.org/adk/model/gemini"
+ "google.golang.org/adk/runner"
+ "google.golang.org/adk/session"
+ "google.golang.org/adk/tool"
+ "google.golang.org/adk/tool/functiontool"
+
+ "google.golang.org/genai"
+)
+
+// --8<-- [start:create_long_running_tool]
+// CreateTicketArgs defines the arguments for our long-running tool.
+type CreateTicketArgs struct {
+ Urgency string `json:"urgency"`
+}
+
+// CreateTicketResults defines the *initial* output of our long-running tool.
+type CreateTicketResults struct {
+ Status string `json:"status"`
+ TicketId string `json:"ticket_id"`
+}
+
+// createTicketAsync simulates the *initiation* of a long-running ticket creation task.
+func createTicketAsync(ctx tool.Context, args CreateTicketArgs) CreateTicketResults {
+ log.Printf("TOOL_EXEC: 'create_ticket_long_running' called with urgency: %s (Call ID: %s)\n", args.Urgency, ctx.FunctionCallID())
+
+ // "Generate" a ticket ID and return it in the initial response.
+ ticketID := "TICKET-ABC-123"
+ log.Printf("ACTION: Generated Ticket ID: %s for Call ID: %s\n", ticketID, ctx.FunctionCallID())
+
+ // In a real application, you would save the association between the
+ // FunctionCallID and the ticketID to handle the async response later.
+ return CreateTicketResults{
+ Status: "started",
+ TicketId: ticketID,
+ }
+}
+
+func createTicketAgent(ctx context.Context) (agent.Agent, error) {
+ ticketTool, err := functiontool.New(
+ functiontool.Config{
+ Name: "create_ticket_long_running",
+ Description: "Creates a new support ticket with a specified urgency level.",
+ },
+ createTicketAsync,
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to create long running tool: %w", err)
+ }
+
+ model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
+ if err != nil {
+ return nil, fmt.Errorf("failed to create model: %v", err)
+ }
+
+ return llmagent.New(llmagent.Config{
+ Name: "ticket_agent",
+ Model: model,
+ Instruction: "You are a helpful assistant for creating support tickets. Provide the status of the ticket at each interaction.",
+ Tools: []tool.Tool{ticketTool},
+ })
+}
+
+// --8<-- [end:create_long_running_tool]
+
+const (
+ userID = "example_user_id"
+ appName = "example_app"
+)
+
+// --8<-- [start:run_long_running_tool]
+// runTurn executes a single turn with the agent and returns the captured function call ID.
+func runTurn(ctx context.Context, r *runner.Runner, sessionID, turnLabel string, content *genai.Content) string {
+ var funcCallID atomic.Value // Safely store the found ID.
+
+ fmt.Printf("\n--- %s ---\n", turnLabel)
+ for event, err := range r.Run(ctx, userID, sessionID, content, agent.RunConfig{
+ StreamingMode: agent.StreamingModeNone,
+ }) {
+ if err != nil {
+ fmt.Printf("\nAGENT_ERROR: %v\n", err)
+ continue
+ }
+ // Print a summary of the event for clarity.
+ printEventSummary(event, turnLabel)
+
+ // Capture the function call ID from the event.
+ for _, part := range event.Content.Parts {
+ if fc := part.FunctionCall; fc != nil {
+ if fc.Name == "create_ticket_long_running" {
+ funcCallID.Store(fc.ID)
+ }
+ }
+ }
+ }
+
+ if id, ok := funcCallID.Load().(string); ok {
+ return id
+ }
+ return ""
+}
+
+func main() {
+ ctx := context.Background()
+ ticketAgent, err := createTicketAgent(ctx)
+ if err != nil {
+ log.Fatalf("Failed to create agent: %v", err)
+ }
+
+ // Setup the runner and session.
+ sessionService := session.InMemoryService()
+ session, err := sessionService.Create(ctx, &session.CreateRequest{AppName: appName, UserID: userID})
+ if err != nil {
+ log.Fatalf("Failed to create session: %v", err)
+ }
+ r, err := runner.New(runner.Config{AppName: appName, Agent: ticketAgent, SessionService: sessionService})
+ if err != nil {
+ log.Fatalf("Failed to create runner: %v", err)
+ }
+
+ // --- Turn 1: User requests to create a ticket. ---
+ initialUserMessage := genai.NewContentFromText("Create a high urgency ticket for me.", genai.RoleUser)
+ funcCallID := runTurn(ctx, r, session.Session.ID(), "Turn 1: User Request", initialUserMessage)
+ if funcCallID == "" {
+ log.Fatal("ERROR: Tool 'create_ticket_long_running' not called in Turn 1.")
+ }
+ fmt.Printf("ACTION: Captured FunctionCall ID: %s\n", funcCallID)
+
+ // --- Turn 2: App provides the final status of the ticket. ---
+ // In a real application, the ticketID would be retrieved from a database
+ // using the funcCallID. For this example, we'll use the same ID.
+ ticketID := "TICKET-ABC-123"
+ willContinue := false // Signal that this is the final response.
+ ticketStatusResponse := &genai.FunctionResponse{
+ Name: "create_ticket_long_running",
+ ID: funcCallID,
+ Response: map[string]any{
+ "status": "approved",
+ "ticket_id": ticketID,
+ },
+ WillContinue: &willContinue,
+ }
+ appResponseWithStatus := &genai.Content{
+ Role: string(genai.RoleUser),
+ Parts: []*genai.Part{{FunctionResponse: ticketStatusResponse}},
+ }
+ runTurn(ctx, r, session.Session.ID(), "Turn 2: App provides ticket status", appResponseWithStatus)
+ fmt.Println("Long running function completed successfully.")
+}
+
+// printEventSummary provides a readable log of agent and LLM interactions.
+func printEventSummary(event *session.Event, turnLabel string) {
+ for _, part := range event.Content.Parts {
+ // Check for a text part.
+ if part.Text != "" {
+ fmt.Printf("[%s][%s_TEXT]: %s\n", turnLabel, event.Author, part.Text)
+ }
+ // Check for a function call part.
+ if fc := part.FunctionCall; fc != nil {
+ fmt.Printf("[%s][%s_CALL]: %s(%v) ID: %s\n", turnLabel, event.Author, fc.Name, fc.Args, fc.ID)
+ }
+ }
+}
+
+// --8<-- [end:run_long_running_tool]