adk-go-pkg

Extension library for Google's ADK-Go.
adk-go-pkg provides production-ready building blocks that complement
ADK-Go with capabilities it does not ship
out of the box.
Features
| Feature |
Description |
| OpenAI Model Provider |
Drop-in model.LLM adapter for any OpenAI-compatible API (OpenAI, Ollama, LiteLLM, OpenRouter, vLLM, Together AI). |
| Generic AG-UI Server |
Framework-agnostic AG-UI protocol server (agui/) with event emitter, state management, tool orchestration, middleware, and SSE handler. Zero ADK dependency. |
| ADK-Go AG-UI Bridge |
Translates ADK-Go session events to AG-UI events (aguiadk/). Thread-to-session mapping, state/message snapshots, and client tool proxy. |
| Planners |
Structured plan generation (ReAct JSON and free-form Thinking) that separates reasoning from execution. |
| File Artifact Service |
Filesystem-backed artifact.Service with automatic versioning and metadata sidecars. |
| Session Rewind |
Roll a session back to any prior event, recalculating state from replayed deltas. |
| Config Agent Loader |
Declare entire agent trees in YAML/JSON and build them at runtime via a factory registry. Now includes Agent Skills support. |
| Agent Skills Config |
Declarative skill integration via YAML/JSON. Supports filesystem sources with preload optimization and specific skill loading (wildcard or filtered by name). |
| Test Utilities |
Complete fake implementations of all ADK-Go interfaces for deterministic testing without external LLM providers. Includes FakeLLM, FakeAgent, FakeSession, and RunnerBuilder. |
Installation
go get github.com/ieshan/adk-go-pkg
Quick Start
OpenAI Model Provider
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/ieshan/adk-go-pkg/model/openai"
"google.golang.org/adk/model"
"google.golang.org/genai"
)
func main() {
m, err := openai.New(openai.Config{
Model: "gpt-4o",
APIKey: os.Getenv("OPENAI_API_KEY"),
})
if err != nil {
log.Fatal(err)
}
req := &model.LLMRequest{
Contents: []*genai.Content{
genai.NewContentFromText("Hello!", "user"),
},
}
for resp, err := range m.GenerateContent(context.Background(), req, false) {
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Content.Parts[0].Text)
}
}
Detailed docs →
Generic AG-UI Server (agui/)
package main
import (
"context"
"iter"
"log"
"net/http"
"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/core/events"
"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/core/types"
"github.com/ieshan/adk-go-pkg/agui"
)
func main() {
agent := agui.AgentFunc(func(ctx context.Context, input types.RunAgentInput) iter.Seq2[events.Event, error] {
ch := make(chan events.Event, 64)
emitter := agui.NewEventEmitter(ch)
go func() {
defer close(ch)
emitter.RunStarted(input.ThreadID, input.RunID)
msgID := emitter.GenerateMessageID()
role := "assistant"
emitter.TextMessageStart(msgID, &role)
emitter.TextMessageContent(msgID, "Hello from AG-UI!")
emitter.TextMessageEnd(msgID)
emitter.RunFinished(input.ThreadID, input.RunID)
}()
return agui.ChanToIter(ctx, ch)
})
handler, err := agui.Handler(agui.Config{Agent: agent})
if err != nil {
log.Fatal(err)
}
log.Fatal(http.ListenAndServe(":8080", handler))
}
Detailed docs →
ADK-Go AG-UI Bridge (aguiadk/)
package main
import (
"iter"
"log"
"net/http"
"github.com/ieshan/adk-go-pkg/agui"
"github.com/ieshan/adk-go-pkg/aguiadk"
"google.golang.org/adk/agent"
"google.golang.org/adk/session"
"google.golang.org/genai"
)
func main() {
myAgent, err := agent.New(agent.Config{
Name: "greeter",
Run: func(ctx agent.InvocationContext) iter.Seq2[*session.Event, error] {
return func(yield func(*session.Event, error) bool) {
content := genai.NewContentFromText("Hello from ADK!", genai.RoleModel)
yield(&session.Event{Author: "greeter", Content: content}, nil)
}
},
})
if err != nil {
log.Fatal(err)
}
handler, err := aguiadk.Handler(
aguiadk.Config{
Agent: myAgent,
AppName: "my-chatbot",
UserID: "default-user",
},
agui.Config{},
)
if err != nil {
log.Fatal(err)
}
http.Handle("/api/agent", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
Detailed docs →
Planners
package main
import (
"context"
"fmt"
"log"
"github.com/ieshan/adk-go-pkg/planner"
"google.golang.org/adk/model"
)
func main() {
var myLLM model.LLM // your model
p := planner.NewPlanReAct(planner.PlanReActConfig{
Model: myLLM,
MaxSteps: 5,
})
plan, err := p.GeneratePlan(context.Background(), &planner.PlanRequest{
UserMessage: "Book a flight and send a confirmation email",
ToolDescriptions: []planner.ToolDescription{
{Name: "book_flight", Description: "Books a flight"},
{Name: "send_email", Description: "Sends an email"},
},
})
if err != nil {
log.Fatal(err)
}
for i, step := range plan.Steps {
fmt.Printf("Step %d: %s\n", i+1, step.Description)
}
}
Detailed docs →
File Artifact Service
package main
import (
"context"
"fmt"
"log"
"github.com/ieshan/adk-go-pkg/artifact/file"
"google.golang.org/adk/artifact"
"google.golang.org/genai"
)
func main() {
svc, err := file.New(file.Config{RootDir: "/tmp/artifacts"})
if err != nil {
log.Fatal(err)
}
resp, err := svc.Save(context.Background(), &artifact.SaveRequest{
AppName: "myapp",
UserID: "alice",
SessionID: "session-1",
FileName: "report.txt",
Part: &genai.Part{Text: "quarterly report"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println("saved version:", resp.Version)
// Get artifact version metadata without loading content
versionResp, err := svc.GetArtifactVersion(context.Background(), &artifact.GetArtifactVersionRequest{
AppName: "myapp",
UserID: "alice",
SessionID: "session-1",
FileName: "report.txt",
Version: 0, // 0 means latest
})
if err != nil {
log.Fatal(err)
}
fmt.Println("MIME type:", versionResp.ArtifactVersion.MimeType)
}
Detailed docs →
Session Rewind
package main
import (
"context"
"fmt"
"log"
"github.com/ieshan/adk-go-pkg/session/rewind"
"google.golang.org/adk/session"
)
func main() {
ctx := context.Background()
svc := session.InMemoryService()
// ... create session, append events ...
rewound, err := rewind.RewindToIndex(ctx, svc, "my-app", "user-1", "session-abc", 2)
if err != nil {
log.Fatal(err)
}
fmt.Println("events remaining:", rewound.Events().Len())
}
Detailed docs →
Config Agent Loader
package main
import (
"context"
"log"
"github.com/ieshan/adk-go-pkg/config"
"google.golang.org/adk/model"
"google.golang.org/adk/tool"
)
func main() {
reg := config.NewRegistry()
reg.RegisterModel("openai", func(cfg map[string]any) (model.LLM, error) {
// build your model from cfg["model"] and other keys
return nil, nil
})
reg.RegisterTool("search", func(cfg map[string]any) (tool.Tool, error) {
// build your tool
return nil, nil
})
agent, err := config.LoadAndBuild(context.Background(), "agents/root.yaml", reg)
if err != nil {
log.Fatal(err)
}
_ = agent
}
Detailed docs →
Agent Skills Config
package main
import (
"context"
"log"
"github.com/ieshan/adk-go-pkg/config"
)
func main() {
reg := config.NewRegistry()
// Filesystem skill factory is built-in, no registration needed
// Load agent with skills from YAML
agent, err := config.LoadAndBuild(context.Background(), "agents/skills-agent.yaml", reg)
if err != nil {
log.Fatal(err)
}
// Agent now has access to skills defined in ./skills/
}
Example YAML configuration:
name: skills-agent
type: llm
model: gemini/gemini-2.5-flash
instruction: "You are a helpful assistant with access to specialized skills."
skillsets:
- name: filesystem
config:
path: "./skills"
preload: complete
# Optional: load only specific skills instead of all
# names: ["weather", "cooking"]
Detailed docs →
Test Utilities
package main
import (
"testing"
"github.com/ieshan/adk-go-pkg/testutil"
"google.golang.org/adk/agent"
"google.golang.org/adk/llmagent"
"google.golang.org/adk/runner"
"google.golang.org/genai"
)
func TestMyAgent(t *testing.T) {
// Create fake LLM with preconfigured responses
llm := testutil.NewFakeLLM(
testutil.NewTextResponse("I'll help you!"),
)
// Build agent with fake LLM
ag, _ := llmagent.New(llmagent.Config{
Name: "test-agent",
Model: llm,
Instruction: "You are helpful.",
})
// Use RunnerBuilder for end-to-end testing
r, fakes, _ := testutil.NewRunnerBuilder().
WithAgent(ag).
BuildWithFakes()
// Run and collect events
events, _ := testutil.CollectEvents(r.Run(ctx, "user-1", "session-1",
genai.NewContentFromText("Hello", "user"), agent.RunConfig{}))
// Assert on results and calls
if len(events) == 0 {
t.Error("expected events")
}
if fakes.SessionService.AppendEventCount() == 0 {
t.Error("expected events to be appended")
}
}
Detailed docs →
Compatibility
- Go 1.26+ — Uses
iter.Seq2 and range-over-func.
- ADK-Go v1.2.0+ (
google.golang.org/adk) — Required for Agent Skills support
- GenAI v1.54.0 (
google.golang.org/genai)
Recent Changes
- Test Utilities: New
testutil package with fake implementations of all ADK-Go interfaces (FakeLLM, FakeAgent, FakeSession, FakeArtifactService, FakeMemoryService, FakeSessionService, RunnerBuilder). Enables fast, deterministic testing without external LLM providers. See docs/testutil.md.
- Agent Skills Config: New skillset support in config loader. Define skills in YAML/JSON with filesystem sources, preload optimization, and specific skill loading (wildcard or filtered by name). Requires ADK-Go v1.2.0+.
- OpenAI Model Provider: Updated to support genai v1.54.0
FunctionResponse.Parts structure for function calling compatibility
- File Artifact Service: Added
GetArtifactVersion method for ADK-Go v1.1.0 compatibility, enabling metadata retrieval without loading full content
Dependencies
Beyond ADK-Go and google.golang.org/genai, the only additional direct dependencies are:
License
TBD