Phase 1 delivers the complete durability and storage layer:
- WAL with crash recovery: Append-only journal with BLAKE3 checksums,
fsync guarantees, and proper seek-to-EOF on reopen
- Storage engine: sled-backed KVStore with scan_prefix for range queries
- Content-addressed storage: H:{hash}, V:{hash}, E:{hash} key patterns
- Ingestor: Background worker tailing WAL, writing to KV with 8-byte
aligned record headers for rkyv zero-copy deserialization
- Comprehensive tests: 31 tests covering crash recovery, round-trips,
and multi-cycle durability
New crates: stemedb-wal, stemedb-storage, stemedb-ingest
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
837 lines
20 KiB
Markdown
837 lines
20 KiB
Markdown
# ADK-Go Developer Reference Guide
|
|
|
|
A practical guide for building AI agents with Google's Agent Development Kit for Go.
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
### Prerequisites
|
|
|
|
- Go 1.24.4 or later
|
|
- Google API key (AI Studio) or Google Cloud project (Vertex AI)
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
# Create new project
|
|
mkdir my-agent && cd my-agent
|
|
go mod init example.com/my-agent
|
|
|
|
# Install ADK
|
|
go get google.golang.org/adk
|
|
```
|
|
|
|
### Environment Setup
|
|
|
|
```bash
|
|
# Option 1: AI Studio (API Key)
|
|
export GOOGLE_API_KEY="your-api-key"
|
|
|
|
# Option 2: Vertex AI (Application Default Credentials)
|
|
export GOOGLE_CLOUD_PROJECT="your-project-id"
|
|
export GOOGLE_CLOUD_LOCATION="us-central1"
|
|
gcloud auth application-default login
|
|
```
|
|
|
|
### Minimal Agent
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
|
|
"google.golang.org/adk/agent/llmagent"
|
|
"google.golang.org/adk/cmd/launcher/adk"
|
|
"google.golang.org/adk/cmd/launcher/full"
|
|
"google.golang.org/adk/model/gemini"
|
|
"google.golang.org/adk/server/restapi/services"
|
|
"google.golang.org/genai"
|
|
)
|
|
|
|
func main() {
|
|
ctx := context.Background()
|
|
|
|
// Create model
|
|
model, err := gemini.NewModel(ctx, "gemini-3-flash-preview", &genai.ClientConfig{
|
|
APIKey: os.Getenv("GOOGLE_API_KEY"),
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Create agent
|
|
agent, err := llmagent.New(llmagent.Config{
|
|
Name: "assistant",
|
|
Model: model,
|
|
Description: "A helpful assistant",
|
|
Instruction: "You are a helpful assistant. Be concise and accurate.",
|
|
})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Launch
|
|
l := full.NewLauncher()
|
|
cfg := &adk.Config{AgentLoader: services.NewSingleAgentLoader(agent)}
|
|
if err := l.Execute(ctx, cfg, os.Args[1:]); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Running Modes
|
|
|
|
```bash
|
|
# Console mode (interactive terminal)
|
|
go run main.go
|
|
|
|
# Web UI + API
|
|
go run main.go web api webui
|
|
# Opens at http://localhost:8080
|
|
|
|
# Production (API + A2A protocol)
|
|
go run main.go web api a2a
|
|
```
|
|
|
|
---
|
|
|
|
## Core Imports
|
|
|
|
```go
|
|
import (
|
|
// Agents
|
|
"google.golang.org/adk/agent"
|
|
"google.golang.org/adk/agent/llmagent"
|
|
"google.golang.org/adk/agent/sequentialagent"
|
|
"google.golang.org/adk/agent/parallelagent"
|
|
"google.golang.org/adk/agent/loopagent"
|
|
|
|
// Models
|
|
"google.golang.org/adk/model/gemini"
|
|
"google.golang.org/genai"
|
|
|
|
// Tools
|
|
"google.golang.org/adk/tool"
|
|
"google.golang.org/adk/tool/functiontool"
|
|
"google.golang.org/adk/tool/geminitool"
|
|
"google.golang.org/adk/tool/mcptool"
|
|
|
|
// Session & Memory
|
|
"google.golang.org/adk/session"
|
|
"google.golang.org/adk/memory"
|
|
|
|
// Launcher
|
|
"google.golang.org/adk/cmd/launcher/adk"
|
|
"google.golang.org/adk/cmd/launcher/full"
|
|
"google.golang.org/adk/server/restapi/services"
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Creating Agents
|
|
|
|
### LLM Agent Configuration
|
|
|
|
```go
|
|
agent, err := llmagent.New(llmagent.Config{
|
|
// Required
|
|
Name: "my_agent",
|
|
Model: model,
|
|
|
|
// Recommended
|
|
Description: "What this agent does (used for routing)",
|
|
Instruction: "System prompt with behavior guidelines",
|
|
|
|
// Optional
|
|
Tools: []tool.Tool{...}, // Available tools
|
|
SubAgents: []agent.Agent{...}, // Agents this can delegate to
|
|
OutputKey: "result", // Save output to state["result"]
|
|
})
|
|
```
|
|
|
|
### Instruction Templates
|
|
|
|
Use curly braces to inject state values:
|
|
|
|
```go
|
|
llmagent.Config{
|
|
Instruction: `You are a travel assistant.
|
|
User preferences: {user:preferences}
|
|
Current itinerary: {itinerary}
|
|
Budget remaining: {budget}`,
|
|
}
|
|
```
|
|
|
|
### Available Models
|
|
|
|
```go
|
|
// Gemini models
|
|
"gemini-3-flash-preview" // Fast, efficient
|
|
"gemini-2.5-pro" // Most capable
|
|
"gemini-3-flash-preview" // Previous generation
|
|
"gemini-1.5-pro" // Long context
|
|
```
|
|
|
|
---
|
|
|
|
## Tools
|
|
|
|
### Built-in Tools
|
|
|
|
```go
|
|
import "google.golang.org/adk/tool/geminitool"
|
|
|
|
agent, _ := llmagent.New(llmagent.Config{
|
|
Tools: []tool.Tool{
|
|
geminitool.GoogleSearch{}, // Web search
|
|
geminitool.CodeExecution{}, // Run code in sandbox
|
|
},
|
|
})
|
|
```
|
|
|
|
### Custom Function Tools
|
|
|
|
```go
|
|
import "google.golang.org/adk/tool/functiontool"
|
|
|
|
// 1. Define input/output structs with JSON tags
|
|
type CalculateInput struct {
|
|
Expression string `json:"expression" jsonschema:"Math expression to evaluate"`
|
|
}
|
|
|
|
type CalculateOutput struct {
|
|
Result float64 `json:"result"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// 2. Implement the function
|
|
func calculate(ctx tool.Context, input CalculateInput) CalculateOutput {
|
|
// Your logic here
|
|
return CalculateOutput{Result: 42}
|
|
}
|
|
|
|
// 3. Create the tool
|
|
calcTool, err := functiontool.New(
|
|
functiontool.Config{
|
|
Name: "calculate",
|
|
Description: "Evaluates mathematical expressions",
|
|
},
|
|
calculate,
|
|
)
|
|
|
|
// 4. Add to agent
|
|
agent, _ := llmagent.New(llmagent.Config{
|
|
Tools: []tool.Tool{calcTool},
|
|
})
|
|
```
|
|
|
|
### Schema Tags Reference
|
|
|
|
```go
|
|
type Input struct {
|
|
// Required field with description
|
|
Query string `json:"query" jsonschema:"Search query to execute"`
|
|
|
|
// Optional field (omitempty makes it optional)
|
|
Limit int `json:"limit,omitempty" jsonschema:"Max results (default 10)"`
|
|
|
|
// Enum values
|
|
Format string `json:"format" jsonschema:"Output format" jsonschema_enum:"json,xml,csv"`
|
|
}
|
|
```
|
|
|
|
### Accessing Context in Tools
|
|
|
|
```go
|
|
func myTool(ctx tool.Context, input MyInput) MyOutput {
|
|
// Access session state
|
|
userID := ctx.Session().State().Get("user:id")
|
|
|
|
// Access artifacts
|
|
artifact, err := ctx.Artifacts().Load("document.pdf")
|
|
|
|
// Standard context operations
|
|
if ctx.Err() != nil {
|
|
return MyOutput{Error: "cancelled"}
|
|
}
|
|
|
|
return MyOutput{...}
|
|
}
|
|
```
|
|
|
|
### MCP (Model Context Protocol) Tools
|
|
|
|
```go
|
|
import "google.golang.org/adk/tool/mcptool"
|
|
|
|
// Stdio-based MCP server
|
|
params := mcptool.StdioServerParams{
|
|
Command: "npx",
|
|
Args: []string{"-y", "@modelcontextprotocol/server-filesystem", "/path"},
|
|
}
|
|
tools, closer, err := mcptool.FromServer(ctx, params)
|
|
defer closer.Close()
|
|
|
|
// HTTP/SSE-based MCP server
|
|
sseParams := mcptool.SseServerParams{
|
|
URL: "http://localhost:8090/sse",
|
|
Timeout: 5.0,
|
|
}
|
|
tools, closer, err := mcptool.FromServer(ctx, sseParams)
|
|
```
|
|
|
|
---
|
|
|
|
## Multi-Agent Patterns
|
|
|
|
### Sequential Agent (Pipeline)
|
|
|
|
Executes agents in order. Each agent's output is available to the next.
|
|
|
|
```go
|
|
import "google.golang.org/adk/agent/sequentialagent"
|
|
|
|
// Agent 1: Research
|
|
researcher, _ := llmagent.New(llmagent.Config{
|
|
Name: "researcher",
|
|
Model: model,
|
|
Instruction: "Research the given topic thoroughly.",
|
|
Tools: []tool.Tool{geminitool.GoogleSearch{}},
|
|
OutputKey: "research", // Saves to state["research"]
|
|
})
|
|
|
|
// Agent 2: Write (uses research output)
|
|
writer, _ := llmagent.New(llmagent.Config{
|
|
Name: "writer",
|
|
Model: model,
|
|
Instruction: "Write an article based on: {research}",
|
|
OutputKey: "draft",
|
|
})
|
|
|
|
// Agent 3: Edit
|
|
editor, _ := llmagent.New(llmagent.Config{
|
|
Name: "editor",
|
|
Model: model,
|
|
Instruction: "Edit and improve: {draft}",
|
|
OutputKey: "final",
|
|
})
|
|
|
|
// Combine into pipeline
|
|
pipeline, _ := sequentialagent.New(sequentialagent.Config{
|
|
AgentConfig: agent.Config{
|
|
Name: "content_pipeline",
|
|
Description: "Research, write, and edit content",
|
|
SubAgents: []agent.Agent{researcher, writer, editor},
|
|
},
|
|
})
|
|
```
|
|
|
|
### Parallel Agent (Concurrent Execution)
|
|
|
|
Executes agents simultaneously. Each runs in an isolated branch.
|
|
|
|
```go
|
|
import "google.golang.org/adk/agent/parallelagent"
|
|
|
|
// Create specialized agents
|
|
webSearcher, _ := llmagent.New(llmagent.Config{
|
|
Name: "web_searcher",
|
|
Model: model,
|
|
Tools: []tool.Tool{geminitool.GoogleSearch{}},
|
|
OutputKey: "web_results",
|
|
})
|
|
|
|
docAnalyzer, _ := llmagent.New(llmagent.Config{
|
|
Name: "doc_analyzer",
|
|
Model: model,
|
|
OutputKey: "doc_analysis",
|
|
})
|
|
|
|
// Run in parallel
|
|
parallel, _ := parallelagent.New(parallelagent.Config{
|
|
AgentConfig: agent.Config{
|
|
Name: "parallel_research",
|
|
SubAgents: []agent.Agent{webSearcher, docAnalyzer},
|
|
},
|
|
})
|
|
```
|
|
|
|
### Loop Agent (Iterative Refinement)
|
|
|
|
Repeats agents until max iterations or termination condition.
|
|
|
|
```go
|
|
import "google.golang.org/adk/agent/loopagent"
|
|
|
|
// Writer produces drafts
|
|
writer, _ := llmagent.New(llmagent.Config{
|
|
Name: "writer",
|
|
Model: model,
|
|
Instruction: "Write or improve the draft. Current: {draft}",
|
|
OutputKey: "draft",
|
|
})
|
|
|
|
// Critic evaluates and decides if done
|
|
critic, _ := llmagent.New(llmagent.Config{
|
|
Name: "critic",
|
|
Model: model,
|
|
Instruction: `Evaluate the draft: {draft}
|
|
If excellent, respond with exactly: APPROVED
|
|
Otherwise, provide specific improvement suggestions.`,
|
|
OutputKey: "feedback",
|
|
})
|
|
|
|
// Loop until approved or max iterations
|
|
refiner, _ := loopagent.New(loopagent.Config{
|
|
AgentConfig: agent.Config{
|
|
Name: "refiner",
|
|
SubAgents: []agent.Agent{writer, critic},
|
|
},
|
|
MaxIterations: 5,
|
|
})
|
|
```
|
|
|
|
### Hierarchical Delegation
|
|
|
|
Parent agent delegates to specialized sub-agents.
|
|
|
|
```go
|
|
// Specialist agents
|
|
mathAgent, _ := llmagent.New(llmagent.Config{
|
|
Name: "math_expert",
|
|
Description: "Handles mathematical calculations and problems",
|
|
Model: model,
|
|
})
|
|
|
|
codeAgent, _ := llmagent.New(llmagent.Config{
|
|
Name: "code_expert",
|
|
Description: "Writes and explains code",
|
|
Model: model,
|
|
Tools: []tool.Tool{geminitool.CodeExecution{}},
|
|
})
|
|
|
|
// Router agent delegates based on query type
|
|
router, _ := llmagent.New(llmagent.Config{
|
|
Name: "router",
|
|
Model: model,
|
|
Instruction: `Route queries to the appropriate specialist.
|
|
Use math_expert for calculations.
|
|
Use code_expert for programming questions.`,
|
|
SubAgents: []agent.Agent{mathAgent, codeAgent},
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Session & State Management
|
|
|
|
### State Prefixes
|
|
|
|
| Prefix | Scope | Persistence |
|
|
| ------- | ----------- | -------------------------- |
|
|
| (none) | App | Persists across sessions |
|
|
| `user:` | User | Persists for specific user |
|
|
| `app:` | Application | Global app state |
|
|
| `temp:` | Invocation | Cleared after each turn |
|
|
|
|
### Reading/Writing State
|
|
|
|
```go
|
|
// In agent instruction (template syntax)
|
|
Instruction: "User name: {user:name}, Preferences: {user:preferences}"
|
|
|
|
// In tool function
|
|
func myTool(ctx tool.Context, input Input) Output {
|
|
state := ctx.Session().State()
|
|
|
|
// Read
|
|
name := state.Get("user:name")
|
|
|
|
// Write
|
|
state.Set("user:preferences", "dark_mode=true")
|
|
|
|
// Delete
|
|
state.Delete("temp:scratch")
|
|
}
|
|
```
|
|
|
|
### Session Services
|
|
|
|
```go
|
|
import "google.golang.org/adk/session"
|
|
|
|
// In-memory (development)
|
|
sessionSvc := session.InMemoryService()
|
|
|
|
// Create session
|
|
sess, err := sessionSvc.Create(ctx, &session.CreateRequest{
|
|
AppName: "my_app",
|
|
UserID: "user123",
|
|
State: map[string]any{"user:name": "Alice"},
|
|
})
|
|
|
|
// Get existing session
|
|
sess, err := sessionSvc.Get(ctx, &session.GetRequest{
|
|
AppName: "my_app",
|
|
UserID: "user123",
|
|
SessionID: "session-id",
|
|
})
|
|
|
|
// List user sessions
|
|
sessions, err := sessionSvc.List(ctx, &session.ListRequest{
|
|
AppName: "my_app",
|
|
UserID: "user123",
|
|
})
|
|
```
|
|
|
|
### Memory Service (Long-term Knowledge)
|
|
|
|
```go
|
|
import "google.golang.org/adk/memory"
|
|
|
|
// In-memory for development
|
|
memorySvc := memory.InMemoryService()
|
|
|
|
// Store knowledge
|
|
err := memorySvc.Add(ctx, &memory.AddRequest{
|
|
AppName: "my_app",
|
|
UserID: "user123",
|
|
Content: "User prefers vegetarian restaurants",
|
|
})
|
|
|
|
// Search knowledge
|
|
results, err := memorySvc.Search(ctx, &memory.SearchRequest{
|
|
AppName: "my_app",
|
|
UserID: "user123",
|
|
Query: "food preferences",
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Running Agents Programmatically
|
|
|
|
### Using the Runner
|
|
|
|
```go
|
|
import "google.golang.org/adk/runner"
|
|
|
|
// Create runner
|
|
r := runner.New(runner.Config{
|
|
Agent: myAgent,
|
|
SessionService: session.InMemoryService(),
|
|
MemoryService: memory.InMemoryService(),
|
|
})
|
|
|
|
// Run agent
|
|
userMsg := genai.NewContentFromText(genai.RoleUser, "Hello!")
|
|
|
|
for event, err := range r.Run(ctx, "user123", "session123", userMsg, agent.RunConfig{}) {
|
|
if err != nil {
|
|
log.Printf("Error: %v", err)
|
|
continue
|
|
}
|
|
|
|
// Process events
|
|
switch {
|
|
case event.IsFinal():
|
|
fmt.Println("Final:", event.Content())
|
|
case event.Partial:
|
|
fmt.Print(event.Text()) // Streaming output
|
|
case event.ToolCall != nil:
|
|
fmt.Printf("Calling tool: %s\n", event.ToolCall.Name)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Streaming Modes
|
|
|
|
```go
|
|
// Streaming modes
|
|
agent.RunConfig{
|
|
StreamingMode: agent.StreamingModeNone, // Wait for complete response
|
|
StreamingMode: agent.StreamingModeSSE, // Server-sent events
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Callbacks
|
|
|
|
### Model Callbacks
|
|
|
|
```go
|
|
agent, _ := llmagent.New(llmagent.Config{
|
|
BeforeModelCallback: func(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMRequest, error) {
|
|
// Modify request before sending to model
|
|
log.Printf("Sending to model: %v", req)
|
|
|
|
// Block certain queries
|
|
if containsForbiddenContent(req) {
|
|
return nil, errors.New("request blocked")
|
|
}
|
|
|
|
return req, nil
|
|
},
|
|
|
|
AfterModelCallback: func(ctx agent.CallbackContext, resp *model.LLMResponse) (*model.LLMResponse, error) {
|
|
// Process/modify response
|
|
log.Printf("Received: %v", resp)
|
|
return resp, nil
|
|
},
|
|
})
|
|
```
|
|
|
|
### Tool Callbacks
|
|
|
|
```go
|
|
agent, _ := llmagent.New(llmagent.Config{
|
|
BeforeToolCallback: func(ctx agent.CallbackContext, call *tool.Call) (*tool.Call, error) {
|
|
// Validate tool arguments
|
|
log.Printf("Tool call: %s(%v)", call.Name, call.Args)
|
|
|
|
// Block dangerous operations
|
|
if call.Name == "delete_file" {
|
|
return nil, errors.New("file deletion not allowed")
|
|
}
|
|
|
|
return call, nil
|
|
},
|
|
|
|
AfterToolCallback: func(ctx agent.CallbackContext, call *tool.Call, result *tool.Result) (*tool.Result, error) {
|
|
// Log or modify results
|
|
log.Printf("Tool result: %v", result)
|
|
return result, nil
|
|
},
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
### Local Development
|
|
|
|
```bash
|
|
# Console mode
|
|
go run main.go
|
|
|
|
# Web UI for testing
|
|
go run main.go web api webui
|
|
```
|
|
|
|
### Cloud Run Deployment
|
|
|
|
```bash
|
|
# Build the CLI tool
|
|
go build -o adkgo ./cmd/adkgo
|
|
|
|
# Deploy
|
|
./adkgo deploy cloudrun \
|
|
-p $GOOGLE_CLOUD_PROJECT \
|
|
-r us-central1 \
|
|
-s my-agent-service \
|
|
-e ./main.go \
|
|
--a2a --api
|
|
```
|
|
|
|
### Manual Docker Deployment
|
|
|
|
```dockerfile
|
|
# Dockerfile
|
|
FROM golang:1.24-alpine AS builder
|
|
WORKDIR /app
|
|
COPY go.mod go.sum ./
|
|
RUN go mod download
|
|
COPY . .
|
|
RUN CGO_ENABLED=0 GOOS=linux go build -o agent main.go
|
|
|
|
FROM alpine:latest
|
|
RUN apk --no-cache add ca-certificates
|
|
WORKDIR /app
|
|
COPY --from=builder /app/agent .
|
|
EXPOSE 8080
|
|
ENV PORT=8080
|
|
CMD ["./agent", "web", "api", "a2a"]
|
|
```
|
|
|
|
```bash
|
|
# Build and run
|
|
docker build -t my-agent .
|
|
docker run -p 8080:8080 -e GOOGLE_API_KEY=$GOOGLE_API_KEY my-agent
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Purpose |
|
|
| ----------------------- | ------------------------------ |
|
|
| `GOOGLE_API_KEY` | AI Studio API key |
|
|
| `GOOGLE_CLOUD_PROJECT` | GCP project ID |
|
|
| `GOOGLE_CLOUD_LOCATION` | GCP region (e.g., us-central1) |
|
|
| `PORT` | Server port (default: 8080) |
|
|
|
|
---
|
|
|
|
## API Endpoints
|
|
|
|
When running with `web api`:
|
|
|
|
| Endpoint | Method | Purpose |
|
|
| ----------------------------------------------- | ------ | --------------- |
|
|
| `/apps/{app}/users/{user}/sessions` | POST | Create session |
|
|
| `/apps/{app}/users/{user}/sessions` | GET | List sessions |
|
|
| `/apps/{app}/users/{user}/sessions/{id}` | GET | Get session |
|
|
| `/apps/{app}/users/{user}/sessions/{id}:run` | POST | Send message |
|
|
| `/apps/{app}/users/{user}/sessions/{id}:runSse` | POST | Stream response |
|
|
| `/.well-known/agent-card.json` | GET | A2A agent card |
|
|
|
|
### Example API Call
|
|
|
|
```bash
|
|
# Create session
|
|
curl -X POST http://localhost:8080/apps/myapp/users/user1/sessions \
|
|
-H "Content-Type: application/json" \
|
|
-d '{}'
|
|
|
|
# Send message
|
|
curl -X POST http://localhost:8080/apps/myapp/users/user1/sessions/SESSION_ID:run \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"message": {"role": "user", "parts": [{"text": "Hello!"}]}}'
|
|
```
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Error Handling in Tools
|
|
|
|
```go
|
|
func myTool(ctx tool.Context, input Input) Output {
|
|
result, err := externalAPI(input.Query)
|
|
if err != nil {
|
|
// Return error message to the model
|
|
return Output{
|
|
Success: false,
|
|
Error: fmt.Sprintf("API error: %v", err),
|
|
}
|
|
}
|
|
return Output{Success: true, Data: result}
|
|
}
|
|
```
|
|
|
|
### Tool with HTTP Client
|
|
|
|
```go
|
|
func fetchURL(ctx tool.Context, input URLInput) FetchOutput {
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
|
|
req, _ := http.NewRequestWithContext(ctx, "GET", input.URL, nil)
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return FetchOutput{Error: err.Error()}
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
return FetchOutput{Content: string(body)}
|
|
}
|
|
```
|
|
|
|
### Conditional Tool Availability
|
|
|
|
```go
|
|
// Filter tools based on state
|
|
agent, _ := llmagent.New(llmagent.Config{
|
|
ToolFilter: func(ctx agent.InvocationContext, tools []tool.Tool) []tool.Tool {
|
|
userRole := ctx.Session().State().Get("user:role")
|
|
if userRole != "admin" {
|
|
// Remove admin-only tools
|
|
return filterOutAdminTools(tools)
|
|
}
|
|
return tools
|
|
},
|
|
})
|
|
```
|
|
|
|
### Graceful Shutdown
|
|
|
|
```go
|
|
func main() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// Handle shutdown signals
|
|
sigCh := make(chan os.Signal, 1)
|
|
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
|
go func() {
|
|
<-sigCh
|
|
log.Println("Shutting down...")
|
|
cancel()
|
|
}()
|
|
|
|
// Run agent
|
|
l := full.NewLauncher()
|
|
if err := l.Execute(ctx, cfg, os.Args[1:]); err != nil {
|
|
if ctx.Err() == nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issues
|
|
|
|
| Problem | Solution |
|
|
| --------------------------- | ----------------------------------------------- |
|
|
| `module not found` | Use `google.golang.org/adk`, not GitHub path |
|
|
| `API key invalid` | Check `GOOGLE_API_KEY` is set correctly |
|
|
| `context deadline exceeded` | Increase timeout or check network |
|
|
| `tool not called` | Improve tool description; make it clearer |
|
|
| `state not persisting` | Check state prefix; use `user:` for persistence |
|
|
|
|
### Debug Logging
|
|
|
|
```go
|
|
import "log"
|
|
|
|
agent, _ := llmagent.New(llmagent.Config{
|
|
BeforeModelCallback: func(ctx agent.CallbackContext, req *model.LLMRequest) (*model.LLMRequest, error) {
|
|
log.Printf("REQUEST: %+v", req)
|
|
return req, nil
|
|
},
|
|
AfterModelCallback: func(ctx agent.CallbackContext, resp *model.LLMResponse) (*model.LLMResponse, error) {
|
|
log.Printf("RESPONSE: %+v", resp)
|
|
return resp, nil
|
|
},
|
|
})
|
|
```
|
|
|
|
### Web UI Debugging
|
|
|
|
Access `http://localhost:8080` when running with `web api webui`:
|
|
|
|
- **Events tab**: See all events in the session
|
|
- **Request/Response tab**: Raw LLM communication
|
|
- **Graph tab**: Visualize agent flow
|
|
- **State tab**: Inspect session state
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- **GitHub**: https://github.com/google/adk-go
|
|
- **Documentation**: https://google.github.io/adk-docs/
|
|
- **Go Package**: https://pkg.go.dev/google.golang.org/adk
|
|
- **Examples**: https://github.com/google/adk-go/tree/main/examples
|
|
- **Python ADK**: https://github.com/google/adk-python
|
|
- **Java ADK**: https://github.com/google/adk-java
|