Files
Ivan Cheung 8834593806 [ADK Go] Merging all Go docs from the private repo (#885)
* docs: Add Go language quickstart

* updates based on review comments

* fix tab stops

* update based on review comments

* sync with main

* update to tested version

* Added google_search snippet

* Fix: Address Go style guide issues in google_search.go

* Migrated to new API

* Fixed tool info in md file

* Added partial check

* Added function-tools sample

* Migrated to new API

* Added partial check

* Ran linter

* feat: add go fmt check

* Run go fmt only on changed files

* Added google_search snippet

* Fix: Address Go style guide issues in google_search.go

* Migrated to new API

* Added partial check

* Added function-tools sample

* Migrated to new API

* Added partial check

* Ran linter

* feat: add go fmt check

* Run go fmt only on changed files

* Feat: Add Golang example for LLM agents

* Added schema agent

* Added session support

* Migrated to new API

* Added back OutputKey

* Added missing snippets

* Moved snipped to its own package

* Minor cleanup

* Reverted llm-agents.md

* Added back correct changes to llm-agents.md

* Updated to newest API

* Ran linter

* WIP

* WIP: Added the rest of the agents

* Added sequential agent

* Migrated agents to new API

* Fixed snippet tags

* Updated parallel and sequential with OutputKey

* Updated loop agent

* Cleanup

* Fixed and ran linter

* Removed comment

* Ran linter

* Added custom agent snippets

* Migrated to new API

* Fixed incorrect info about channels since run returns an Iterator, not Channel

* Cleanup and renaming

* Ran linter

* Fixed md

* Added multi-agent docs

* Updated to use AgentTool

* Split snippets

* Added missing file

* Ran linter and fixed API

* Added Models snippets

* Migrated to new API

* Embedded imports

* Ran linter

* Added long running tool

* Ran linter and fixed API

* Switched to two-turn style

* Ran linter

* Added callback snippets

* Added back imports

* Added types of callbacks snippets

* feat(go): align callback snippets with other languages

* Ran linter and updated API

* Added tool callbacks

* Added artifact snippets

* Use story.pdf instead of random bytes

* Fixed issue with artifact loading

* Cleanup unneeded fixes

* Added back inadvertently removed GCS section

* Addressed PR comments

* Ran linter

* feat: Added context snippets

* Fixed callback signature

* Fixed additional callbacks

* Added remaining snippets

* Added conceptual runner example

* Removed duplicate code

* Clean up tags and imports

* Updated API

* Twaked text

* Ran linter

* Fixed broken API issues

* Updated to latest Go ADK API

* Added func param info

* Interleaved Py and Go

* Revert mistakes

* Removed extra newline

* Added example cloud run code and update md with Go (#41)

* Added example cloud run code and update md with Go

* Added a2a_agent_url and fixed future tense

* Moved Go before Java

---------

Co-authored-by: ivanmkc <ivanmkc@google.com>

* adding go snippets to runconfig docs

* realigning runconfig go examples with current state of adk-go.

* adding language support tags to individual runtime config fields

* removing unsupported supportcfc go examples

* removing unsupported streamingmode.bidi go snippets

* Added AgentTool examples (#31)

* Added AgentTool example

* Ran formatter

---------

Co-authored-by: ivanmkc <ivanmkc@google.com>

* adding initial go sessions.sessions snippets

* updating go session docs + session examples

* update go session snippets

* updating DatabaseSessionService language support tag

* cleaning up go vertex ai comments

* removing go.mod for session_management_example

* docs: add language support tags for Go

* initial go runtime sample updates

* fixing runtime go snippet formatting

* formatting updates to runtime go snippets

* updating runtime go examples to streamline to newer go features

* adding go snippets for events

* updating go snippets for events

* adding artifactdelta snippet for go

* minor java fixes, cleaning up go event.content

* Custom tools: Merging again  (#44)

* Added missing custom-tools snippets

* Split user_preference runner code

* Split more files

* Tweaks

* Split session into 1 and 2 and added artifact saving

* Fixed conflict

---------

Co-authored-by: ivanmkc <ivanmkc@google.com>

* Fixed tab issue with cloud-run md (#45)

Co-authored-by: ivanmkc <ivanmkc@google.com>

* adding initial go snippets for memory services

* updating go memory sample, adding python formatting in docs to clarify current support.

* removing go replace for memory example

* removing go.sum from memory_example

* fixing go formatting for memory example

* fixing go memory snippets to streamline examples in docs

* removing unnecessary go.mod for memory_example.go and snippet cleanup

* adding go session state snippets."

* adding additional go state examples for state injection

* fixing go formatting

* fixing language ordering on session/state.md; removing unnecessary go.mod files

* reworking error functions in go session state samples

* fixing go formatting on session state samples

* initial go snippets for safety tools

* fixing go snippet formatting in safety/index.md

* temp storing safety example

* cleaning up go snippets in safety docs; deferring safety go full example

* cleaning up go safety snippets

* Added link to Go API docs (#47)

Co-authored-by: ivanmkc <ivanmkc@google.com>

* Added a2a_basic sample (#46)

* Added a2a_basic sample

* Removed dummy LLMs

* Added md

Fix inaccuracies in md

Ran formatter

* Cleaned up index

---------

Co-authored-by: ivanmkc <ivanmkc@google.com>

* Reverted to proper ToC and added missing links (#48)

Co-authored-by: ivanmkc <ivanmkc@google.com>

* Added missing get-started/go.md (#50)

Co-authored-by: ivanmkc <ivanmkc@google.com>

* Added missing JSON tags for Go structs (#51)

* Added json tags

* Added jsonschema and json tags

---------

Co-authored-by: ivanmkc <ivanmkc@google.com>

* Renamed Golang to Go (#52)

Co-authored-by: ivanmkc <ivanmkc@google.com>

---------

Co-authored-by: Joe Fernandez <joefernandez@google.com>
Co-authored-by: ivanmkc <ivanmkc@google.com>
Co-authored-by: Toni Klopfenstein <tklopfenstein@google.com>
2025-11-06 14:13:13 -05:00

321 lines
9.9 KiB
Go

// 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"
"time"
"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"
)
const (
appName = "state_example_app"
userID = "user1234"
sessionID = "1234"
modelID = "gemini-2.0-flash" // Replace with a valid model name
)
// --8<-- [start:greeting]
// 1. GreetingAgent demonstrates using `OutputKey` to save an agent's
// final text response directly into the session state.
func greetingAgentExample(sessionService session.Service) {
fmt.Println("--- Running GreetingAgent (output_key) Example ---")
ctx := context.Background()
modelGreeting, err := gemini.NewModel(ctx, modelID, nil)
if err != nil {
log.Fatalf("Failed to create Gemini model for greeting agent: %v", err)
}
greetingAgent, err := llmagent.New(llmagent.Config{
Name: "Greeter",
Model: modelGreeting,
Instruction: "Generate a short, friendly greeting.",
OutputKey: "last_greeting",
})
if err != nil {
log.Fatalf("Failed to create greeting agent: %v", err)
}
r, err := runner.New(runner.Config{
AppName: appName,
Agent: agent.Agent(greetingAgent),
SessionService: sessionService,
})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
// Run the agent
userMessage := genai.NewContentFromText("Hello", "user")
for event, err := range r.Run(ctx, userID, sessionID, userMessage, agent.RunConfig{}) {
if err != nil {
log.Printf("Agent Error: %v", err)
continue
}
if isFinalResponse(event) {
if event.LLMResponse.Content != nil {
fmt.Printf("Agent responded with: %q\n", textParts(event.LLMResponse.Content))
} else {
fmt.Println("Agent responded.")
}
}
}
// Check the updated state
resp, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
lastGreeting, _ := resp.Session.State().Get("last_greeting")
fmt.Printf("State after agent run: last_greeting = %q\n\n", lastGreeting)
}
// --8<-- [end:greeting]
// --8<-- [start:manual]
// 2. manualStateUpdateExample demonstrates creating an event with explicit
// state changes (a "state_delta") to update multiple keys, including
// those with user- and temp- prefixes.
func manualStateUpdateExample(sessionService session.Service) {
fmt.Println("--- Running Manual State Update (EventActions) Example ---")
ctx := context.Background()
s, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
retrievedSession := s.Session
// Define state changes
loginCount, _ := retrievedSession.State().Get("user:login_count")
newLoginCount := 1
if lc, ok := loginCount.(int); ok {
newLoginCount = lc + 1
}
stateChanges := map[string]any{
"task_status": "active",
"user:login_count": newLoginCount,
"user:last_login_ts": time.Now().Unix(),
"temp:validation_needed": true,
}
// Create an event with the state changes
systemEvent := session.NewEvent("inv_login_update")
systemEvent.Author = "system"
systemEvent.Actions.StateDelta = stateChanges
// Append the event to update the state
if err := sessionService.AppendEvent(ctx, retrievedSession, systemEvent); err != nil {
log.Fatalf("Failed to append event: %v", err)
}
fmt.Println("`append_event` called with explicit state delta.")
// Check the updated state
updatedResp, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
taskStatus, _ := updatedResp.Session.State().Get("task_status")
loginCount, _ = updatedResp.Session.State().Get("user:login_count")
lastLogin, _ := updatedResp.Session.State().Get("user:last_login_ts")
temp, err := updatedResp.Session.State().Get("temp:validation_needed") // This should fail or be nil
fmt.Printf("State after event: task_status=%q, user:login_count=%v, user:last_login_ts=%v\n", taskStatus, loginCount, lastLogin)
if err != nil {
fmt.Printf("As expected, temp state was not persisted: %v\n\n", err)
} else {
fmt.Printf("Unexpected temp state value: %v\n\n", temp)
}
}
// --8<-- [end:manual]
// --8<-- [start:context]
// 3. contextStateUpdateExample demonstrates the recommended way to modify state
// from within a tool function using the provided `tool.Context`.
func contextStateUpdateExample(sessionService session.Service) {
fmt.Println("--- Running Context State Update (ToolContext) Example ---")
ctx := context.Background()
// Define the tool that modifies state
updateActionCountTool, err := functiontool.New[struct{}, struct{}](
functiontool.Config{Name: "update_action_count", Description: "Updates the user action count in the state."},
func(tctx tool.Context, args struct{}) struct{} {
actx, ok := tctx.(agent.CallbackContext)
if !ok {
log.Fatalf("tool.Context is not of type agent.CallbackContext")
}
s, err := actx.State().Get("user_action_count")
if err != nil {
log.Printf("could not get user_action_count: %v", err)
}
newCount := 1
if c, ok := s.(int); ok {
newCount = c + 1
}
if err := actx.State().Set("user_action_count", newCount); err != nil {
log.Printf("could not set user_action_count: %v", err)
}
if err := actx.State().Set("temp:last_operation_status", "success from tool"); err != nil {
log.Printf("could not set temp:last_operation_status: %v", err)
}
fmt.Println("Tool: Updated state via agent.CallbackContext.")
return struct{}{}
},
)
if err != nil {
log.Fatalf("Failed to create tool: %v", err)
}
// Define an agent that uses the tool
modelTool, err := gemini.NewModel(ctx, modelID, nil)
if err != nil {
log.Fatalf("Failed to create Gemini model for tool agent: %v", err)
}
toolAgent, err := llmagent.New(llmagent.Config{
Name: "ToolAgent",
Model: modelTool,
Instruction: "Use the update_action_count tool.",
Tools: []tool.Tool{updateActionCountTool},
})
if err != nil {
log.Fatalf("Failed to create tool agent: %v", err)
}
r, err := runner.New(runner.Config{
AppName: appName,
Agent: agent.Agent(toolAgent),
SessionService: sessionService,
})
if err != nil {
log.Fatalf("Failed to create runner: %v", err)
}
// Run the agent to trigger the tool
userMessage := genai.NewContentFromText("Please update the action count.", "user")
for _, err := range r.Run(ctx, userID, sessionID, userMessage, agent.RunConfig{}) {
if err != nil {
log.Printf("Agent Error: %v", err)
}
}
// Check the updated state
resp, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
actionCount, _ := resp.Session.State().Get("user_action_count")
fmt.Printf("State after tool run: user_action_count = %v\n", actionCount)
}
// --8<-- [end:context]
func main() {
ctx := context.Background()
sessionService := session.InMemoryService()
// Initialize session with some state
_, err := sessionService.Create(ctx, &session.CreateRequest{
AppName: appName,
UserID: userID,
SessionID: sessionID,
State: map[string]any{
"user:login_count": 0,
"task_status": "idle",
},
})
if err != nil {
log.Fatalf("Failed to create session: %v", err)
}
s, err := sessionService.Get(ctx, &session.GetRequest{AppName: appName, UserID: userID, SessionID: sessionID})
if err != nil {
log.Fatalf("Failed to get session: %v", err)
}
taskStatus, _ := s.Session.State().Get("task_status")
loginCount, _ := s.Session.State().Get("user:login_count")
fmt.Printf("Initial state: task_status=%q, user:login_count=%v\n\n", taskStatus, loginCount)
greetingAgentExample(sessionService)
manualStateUpdateExample(sessionService)
contextStateUpdateExample(sessionService)
}
// --- Helper Functions ---
func isFinalResponse(ev *session.Event) bool {
if ev.Actions.SkipSummarization || len(ev.LongRunningToolIDs) > 0 {
return true
}
if ev.LLMResponse.Content == nil {
return true
}
return !hasFunctionCalls(&ev.LLMResponse) && !hasFunctionResponses(&ev.LLMResponse) && !ev.LLMResponse.Partial && !hasTrailingCodeExecutionResult(&ev.LLMResponse)
}
func hasFunctionCalls(resp *model.LLMResponse) bool {
if resp == nil || resp.Content == nil {
return false
}
for _, part := range resp.Content.Parts {
if part.FunctionCall != nil {
return true
}
}
return false
}
func hasFunctionResponses(resp *model.LLMResponse) bool {
if resp == nil || resp.Content == nil {
return false
}
for _, part := range resp.Content.Parts {
if part.FunctionResponse != nil {
return true
}
}
return false
}
func hasTrailingCodeExecutionResult(resp *model.LLMResponse) bool {
if resp == nil || resp.Content == nil || len(resp.Content.Parts) == 0 {
return false
}
lastPart := resp.Content.Parts[len(resp.Content.Parts)-1]
return lastPart.CodeExecutionResult != nil
}
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
}