Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ profile.cov
# Go workspace file
go.work
go.work.sum
.DS_Store
examples/tools/confirmation/confirmation
45 changes: 45 additions & 0 deletions GEMINI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Gemini Code Assist Context: `adk-go`

This document provides context for the `adk-go` project, the Go implementation of the Agent Development Kit (ADK).

## Project Overview

`adk-go` is a flexible and modular framework for building, deploying, and orchestrating sophisticated AI agents using Go. It is designed to be model-agnostic but is optimized for use with Google's Gemini models. The framework emphasizes a code-first approach, allowing developers to define agent logic, tools, and orchestration directly in Go.

The core concepts are:
- **Agent:** The fundamental building block, representing an entity that can perform tasks. Agents can have sub-agents, creating a hierarchy for complex workflows.
- **Tool:** A capability that an agent can use, such as a Go function, to interact with the world or perform a specific task.
- **Session:** Represents a single conversation or interaction with an agent.
- **Memory:** Provides a mechanism for agents to retain information across sessions.

## Building and Running

The project uses standard Go tooling for building and testing. The primary commands are defined in the `.github/workflows/go.yml` file.

* **Build the project:**
```bash
go build -mod=readonly -v ./...
```

* **Run tests:**
```bash
go test -mod=readonly -v ./...
```

* **Run linter:**
The project uses `golangci-lint` for linting.
```bash
golangci-lint run
```

## Development Conventions

* **Go Style:** All code should adhere to the [Google Go Style Guide](https://google.github.io/styleguide/go/index).
* **Testing:**
* Unit tests are mandatory for all new features and bug fixes.
* Manual End-to-End (E2E) tests with verifiable evidence (screenshots, logs) are required for pull requests.
* **Contribution:**
* All contributions are made via GitHub Pull Requests and require a signed Contributor License Agreement (CLA).
* For significant changes, it is recommended to open an issue first to discuss the proposal.
* **Documentation:** User-facing documentation changes should be submitted to the [adk-docs](https://github.com/google/adk-docs) repository.
* **Python ADK Parity:** The [Python ADK](https://github.com/google/adk-python) is considered the reference implementation. New features or changes should align with the Python version where applicable.
40 changes: 40 additions & 0 deletions PR_RESPONSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Updates Made

@verdverm I've addressed all the issues you mentioned:

### 1. ✅ Merge Conflict Resolved
Merged `upstream/main` into the branch successfully. All conflicts are now resolved.

### 2. ✅ Fixed Example Imports and API Usage
The example now uses the correct, current API:
- Changed from `google.golang.org/adk/llm` → `google.golang.org/adk/agent/llmagent`
- Changed from `google.golang.org/adk/runner/full` → `google.golang.org/adk/cmd/launcher/full`
- Updated to use `llmagent.New()` instead of deprecated `agent.NewLLMAgent()`
- Updated to use `launcher.Config` with the new API
- Added proper Gemini model initialization with API key

The example should now build and run correctly with `go mod tidy` and `go run main.go`.

### 3. ✅ Removed .DS_Store
- Removed `.DS_Store` from the repository
- Added it to `.gitignore` to prevent future commits

### Regarding Your Questions

**Per-session confirmation overrides**: That's a great feature idea! The current implementation provides the foundation for this. To implement "don't ask again for this session", we could:
- Add a session-level confirmation cache
- Store approved tool+payload combinations
- Check the cache before requesting confirmation

This could be a follow-up enhancement. Would you like me to open a separate issue for tracking this feature?

**Return payload from confirmation**: You're absolutely right that this is more powerful than just yes/no. The current `ConfirmationRequest` includes an arbitrary `Payload` field that could be used for:
- Presenting options (e.g., flight choices)
- Showing diffs for file writes
- Allowing partial selections

The `ConfirmationResponse` could be extended to include user selections/modifications. This would make it a general-purpose "user interrupt" mechanism rather than just confirmation.

Should we expand the scope of this PR to include response payloads, or handle that in a follow-up?

All changes have been pushed. The PR should now be ready for final review.
36 changes: 36 additions & 0 deletions examples/tools/confirmation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Confirmation Example

This example demonstrates how to use the confirmation feature in FunctionTools.

## Features Demonstrated

1. **Static Confirmation**: Using `RequireConfirmation: true` in the tool config to always require confirmation
2. **Dynamic Confirmation**: Using `ctx.RequestConfirmation()` within the tool function to request confirmation at runtime

## Prerequisites

Set your Google API key:
```bash
export GOOGLE_API_KEY=your_api_key_here
```

## Running the Example

```bash
cd examples/tools/confirmation
go run main.go
```

## How It Works

1. The example creates two types of tools that perform "file write" operations:
- A tool that uses static confirmation (defined in config)
- A tool that requests confirmation dynamically (in the function)

2. When either tool is called by the LLM:
- The static confirmation tool will always show a note in its description that it requires confirmation
- The dynamic confirmation tool will pause execution when `RequestConfirmation` is called

3. The confirmation request includes the tool name, a hint, and any associated payload data

This is useful for safety-critical operations like file system modifications or system commands that should require user approval before execution.
110 changes: 110 additions & 0 deletions examples/tools/confirmation/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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 demonstrates the use of confirmation in FunctionTools.
package main

import (
"context"
"fmt"
"log"
"os"

"google.golang.org/genai"

"google.golang.org/adk/agent"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/cmd/launcher"
"google.golang.org/adk/cmd/launcher/full"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/tool"
"google.golang.org/adk/tool/functiontool"
)

type WriteFileArgs struct {
Filename string `json:"filename"`
Content string `json:"content"`
}

// writeWithConfirmation simulates a file write operation that requires confirmation
func writeWithConfirmation(ctx tool.Context, args WriteFileArgs) (string, error) {
// Request confirmation before writing the file
err := ctx.RequestConfirmation("Writing file: "+args.Filename, map[string]any{
"filename": args.Filename,
"content": args.Content,
})
if err != nil {
// If confirmation was required, the flow will pause and wait for confirmation
// In this example, we would normally resume after receiving confirmation
return "", err
}

// After confirmation is granted, we would write the file
// For this example, we'll just simulate it
return fmt.Sprintf("File %s written successfully", args.Filename), nil
}

// staticConfirmationTool is a file operation that always requires confirmation
func staticConfirmationTool(ctx tool.Context, args WriteFileArgs) (string, error) {
return fmt.Sprintf("Static confirmation tool executed for file: %s", args.Filename), nil
}

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)
}

// Create a function tool that requests confirmation dynamically
dynamicTool, err := functiontool.New(functiontool.Config{
Name: "write_file_dynamic",
Description: "Write content to a file, with dynamic confirmation",
}, writeWithConfirmation)
if err != nil {
log.Fatalf("Failed to create dynamic confirmation tool: %v", err)
}

// Create a function tool that always requires confirmation via config
staticTool, err := functiontool.New(functiontool.Config{
Name: "write_file_static",
Description: "Write content to a file, with static confirmation requirement",
RequireConfirmation: true,
}, staticConfirmationTool)
if err != nil {
log.Fatalf("Failed to create static confirmation tool: %v", err)
}

a, err := llmagent.New(llmagent.Config{
Name: "file_operator",
Model: model,
Description: "Agent that writes files with confirmation",
Tools: []tool.Tool{dynamicTool, staticTool},
})
if err != nil {
log.Fatalf("Failed to create agent: %v", err)
}

config := &launcher.Config{
AgentLoader: agent.NewSingleLoader(a),
}

l := full.NewLauncher()
if err = l.Execute(ctx, config, os.Args[1:]); err != nil {
log.Fatalf("Run failed: %v\n\n%s", err, l.CommandLineSyntax())
}
}
53 changes: 37 additions & 16 deletions internal/llminternal/base_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ var (
DefaultResponseProcessors = []func(ctx agent.InvocationContext, req *model.LLMRequest, resp *model.LLMResponse) error{
nlPlanningResponseProcessor,
codeExecutionResponseProcessor,
confirmationRequestProcessor,
}
)

Expand Down Expand Up @@ -229,7 +230,7 @@ func toolPreprocess(ctx agent.InvocationContext, req *model.LLMRequest, tools []
return fmt.Errorf("tool %q does not implement RequestProcessor() method", t.Name())
}
// TODO: how to prevent mutation on this?
toolCtx := toolinternal.NewToolContext(ctx, "", &session.EventActions{})
toolCtx := toolinternal.NewToolContextWithToolName(ctx, "", &session.EventActions{}, t.Name())
if err := requestProcessor.ProcessRequest(toolCtx, req); err != nil {
return err
}
Expand Down Expand Up @@ -381,26 +382,46 @@ func (f *Flow) handleFunctionCalls(ctx agent.InvocationContext, toolsDict map[st

result := f.callTool(funcTool, fnCall.Args, toolCtx)

// TODO: agent.canonical_after_tool_callbacks
// TODO: handle long-running tool.
// Check if confirmation was requested
ev := session.NewEvent(ctx.InvocationID())
ev.LLMResponse = model.LLMResponse{
Content: &genai.Content{
Role: "user",
Parts: []*genai.Part{
{
FunctionResponse: &genai.FunctionResponse{
ID: fnCall.ID,
Name: fnCall.Name,
Response: result,
ev.Author = ctx.Agent().Name()
ev.Branch = ctx.Branch()
ev.Actions = *toolCtx.Actions()
if toolCtx.Actions().ConfirmationRequest != nil {
// If confirmation is requested, we need to return an event with the confirmation request
// Set a special status to indicate that confirmation is required
ev.LLMResponse = model.LLMResponse{
Content: &genai.Content{
Role: "user",
Parts: []*genai.Part{
{
Text: fmt.Sprintf("Confirmation required for tool %s: %s", fnCall.Name, toolCtx.Actions().ConfirmationRequest.Hint),
},
},
},
// Add custom metadata to indicate this is a confirmation request
CustomMetadata: map[string]any{
"confirmation_required": true,
"confirmation_request": toolCtx.Actions().ConfirmationRequest,
},
}
} else {
// Normal response when no confirmation needed
ev.LLMResponse = model.LLMResponse{
Content: &genai.Content{
Role: "user",
Parts: []*genai.Part{
{
FunctionResponse: &genai.FunctionResponse{
ID: fnCall.ID,
Name: fnCall.Name,
Response: result,
},
},
},
},
},
}
}
ev.Author = ctx.Agent().Name()
ev.Branch = ctx.Branch()
ev.Actions = *toolCtx.Actions()
telemetry.TraceToolCall(spans, curTool, fnCall.Args, ev)
fnResponseEvents = append(fnResponseEvents, ev)
}
Expand Down
13 changes: 13 additions & 0 deletions internal/llminternal/other_processors.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,16 @@ func codeExecutionResponseProcessor(ctx agent.InvocationContext, req *model.LLMR
// TODO: implement (adk-python src/google/adk_code_execution.py)
return nil
}

func confirmationRequestProcessor(ctx agent.InvocationContext, req *model.LLMRequest, resp *model.LLMResponse) error {
// This processor is a placeholder for handling confirmation requests that may originate
// from the LLM response. The primary logic for handling tool-initiated confirmation
// requests is handled in the base_flow.
if _, ok := resp.CustomMetadata["confirmation_request"]; ok {
if req.Tools != nil {
// The confirmation request would be handled at a higher level.
// Currently, this is a no-op.
}
}
return nil
}
Loading