mirror of
https://github.com/google/adk-docs.git
synced 2026-09-14 16:16:59 +08:00
317 lines
9.8 KiB
Go
317 lines
9.8 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/v2/agent"
|
|
"google.golang.org/adk/v2/agent/llmagent"
|
|
"google.golang.org/adk/v2/model"
|
|
"google.golang.org/adk/v2/model/gemini"
|
|
"google.golang.org/adk/v2/runner"
|
|
"google.golang.org/adk/v2/session"
|
|
"google.golang.org/adk/v2/tool"
|
|
"google.golang.org/adk/v2/tool/functiontool"
|
|
"google.golang.org/genai"
|
|
)
|
|
|
|
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(ctx, "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 `agent.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(
|
|
functiontool.Config{Name: "update_action_count", Description: "Updates the user action count in the state."},
|
|
func(actx agent.Context, args struct{}) (struct{}, error) {
|
|
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.Context.")
|
|
return struct{}{}, nil
|
|
},
|
|
)
|
|
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
|
|
}
|