mirror of
https://github.com/google/adk-docs.git
synced 2026-09-14 16:16:59 +08:00
8834593806
* 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>
321 lines
9.9 KiB
Go
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
|
|
}
|