adk

module
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 20, 2026 License: Apache-2.0

README

ADK — Agent Development Kit

简体中文

A lightweight, idiomatic Go library for building AI agents. ADK decouples agent logic from LLM providers, session storage, and tool integrations so you can compose exactly the pieces you need.

Early Development Notice: This project is under active development and is not yet recommended for production use. APIs may change without notice.

Module path: github.com/soasurs/adk
Go version: 1.26+


Features

  • Provider-agnostic LLM interface — swap OpenAI, Gemini, or Anthropic without touching agent code
  • Stateless Agent + Stateful Runner — clean separation of concerns
  • Automatic tool-call loopLlmAgent drives the model until a stop response
  • Agent composition — chain agents sequentially or run them in parallel
  • Agent as Tool — delegate tasks to sub-agents via function calling
  • Pluggable session backends — in-memory (zero config) or SQLite (persistent)
  • Message history compaction — soft-archive old messages without deleting them
  • MCP tool integration — connect any Model Context Protocol server
  • Snowflake IDs — distributed, time-ordered message identifiers
  • Streaming via Go iterators — native iter.Seq2 for incremental output

Installation

go get github.com/soasurs/adk

Architecture

┌──────────────────────────────────────────────────────┐
│                       Runner                         │
│  - loads session history                             │
│  - appends & persists every message                  │
│  - drives the Agent per user turn                    │
└───────────────┬──────────────────────────────────────┘
                │ []model.Message
                ▼
┌──────────────────────────────────────────────────────┐
│                    LlmAgent  (stateless)            │
│  - prepends system prompt                            │
│  - calls model.LLM.Generate in a loop                │
│  - executes tool calls, yields each message          │
└───────────────┬──────────────────────────────────────┘
                │
        ┌───────┴────────┐
        ▼                ▼
  model.LLM          tool.Tool
  (OpenAI, …)    (builtin / MCP / custom)

The Runner is stateful — it owns the session and persists every message.
The Agent is stateless — it only sees the messages passed to it and yields results; it has no memory of previous turns.


Package Layout

Package Purpose
agent Agent interface
agent/llmagent LLM-backed agent with tool-call loop
agent/sequentialagent Sequential agent composition (pipeline)
agent/parallelagent Parallel agent composition (fan-out)
agent/agentool Wrap agents as tools for delegation
model Provider-agnostic LLM interface and message types
model/openai OpenAI adapter
model/gemini Google Gemini adapter (Gemini API & Vertex AI)
model/anthropic Anthropic Claude adapter
model/retry Retry wrapper with exponential backoff
session Session and SessionService interfaces
session/memory In-memory session backend
session/database SQLite session backend
session/message Persisted message type
session/compaction Sliding-window message compaction utilities
tool Tool interface and Definition
tool/builtin Built-in tools (echo, …)
tool/mcp MCP server bridge
runner Wires Agent + SessionService together
internal/snowflake Snowflake node factory

Quick Start

1. Create an LLM

OpenAI:

import "github.com/soasurs/adk/model/openai"

llm := openai.New(os.Getenv("OPENAI_API_KEY"), "", "gpt-4o-mini")

Google Gemini:

import "github.com/soasurs/adk/model/gemini"

llm, err := gemini.New(ctx, os.Getenv("GEMINI_API_KEY"), "gemini-2.0-flash")
// Or use Vertex AI:
// llm, err := gemini.NewVertexAI(ctx, "my-project", "us-central1", "gemini-2.0-flash")

Anthropic Claude:

import "github.com/soasurs/adk/model/anthropic"

llm := anthropic.New(os.Getenv("ANTHROPIC_API_KEY"), "claude-sonnet-4-5")
2. Build an Agent
import (
    "github.com/soasurs/adk/agent/llmagent"
    "github.com/soasurs/adk/model"
)

agent := llmagent.New(llmagent.Config{
    Name:         "my-agent",
    Description:  "A helpful assistant",
    Model:        llm,
    Instruction:  "You are a helpful assistant.",
    Stream:       true, // enable real-time streaming
})
3. Choose a Session Backend

In-memory (great for testing or single-process use):

import "github.com/soasurs/adk/session/memory"

svc := memory.NewSessionService()

SQLite (persistent across restarts):

import "github.com/soasurs/adk/session/database"

svc, err := database.NewSessionService("sessions.db")
4. Create a Runner and Run
import (
    "github.com/soasurs/adk/runner"
)

r, err := runner.New(agent, svc)
if err != nil { /* … */ }

ctx := context.Background()
sessionID := int64(1)

// Create the session once
_, _ = svc.CreateSession(ctx, sessionID)

// Send a user message and iterate over the results
for event, err := range r.Run(ctx, sessionID, "Hello!") {
    if err != nil { /* … */ }
    if event.Partial {
        // Streaming fragment — display in real-time
        fmt.Print(event.Message.Content)
    } else {
        // Complete message — persist or process
        fmt.Println(event.Message.Role, event.Message.Content)
    }
}

Core Concepts

model.LLM

The central interface every LLM adapter must implement:

type LLM interface {
    Name() string
    GenerateContent(ctx context.Context, req *LLMRequest, cfg *GenerateConfig, stream bool) iter.Seq2[*LLMResponse, error]
}

GenerateContent returns a Go iterator. When stream is false, exactly one complete *LLMResponse is yielded. When stream is true, zero or more partial responses (streaming chunks) are yielded first, followed by a single complete response.

GenerateConfig lets you tune temperature, reasoning effort, thinking budget, and service tier in a provider-agnostic way.

model.Message

A single message in a conversation. Key fields:

Field Description
Role system / user / assistant / tool
Content Plain-text content
Parts Multi-modal content (text + images)
ReasoningContent Chain-of-thought from reasoning models (informational only)
ToolCalls Tool invocations requested by the assistant
ToolCallID Links a tool-result message back to its call
Usage Token consumption for this generation step
agent.Agent
type Agent interface {
    Name() string
    Description() string
    Run(ctx context.Context, messages []model.Message) iter.Seq2[*model.Event, error]
}

Run returns a Go iterator that yields each produced event — assistant replies, tool results, and intermediate steps — allowing the caller to stream output incrementally.

Events carry a Partial flag: when true, the event is a streaming fragment for real-time display; when false, it is a complete, persistable message.

tool.Tool
type Tool interface {
    Definition() Definition   // name, description, JSON Schema
    Run(ctx context.Context, toolCallID string, arguments string) (string, error)
}

LlmAgent dispatches tool calls automatically during its generation loop.

Session & Message Compaction

A Session holds a conversation's message history. The two storage backends both support soft compaction: old messages are archived (marked with a CompactedAt timestamp) rather than deleted.

import "github.com/soasurs/adk/session/compaction"

// Build a sliding-window compactor
cfg := compaction.Config{MaxTokens: 8000, KeepRecentRounds: 3}
compactor, _ := compaction.NewSlidingWindowCompactor(cfg,
    func(ctx context.Context, msgs []*message.Message) (string, error) {
        return summarise(msgs), nil // your summarisation logic
    },
)

// Check whether compaction is needed, then run it
msgs, _ := sess.ListMessages(ctx)
if compaction.ShouldCompact(msgs, cfg) {
    splitID, summaryMsg, _ := compactor(ctx, msgs)
    if splitID > 0 {
        _ = sess.CompactMessages(ctx, splitID, summaryMsg)
    }
}

// Active messages (post-compaction)
active, _ := sess.ListMessages(ctx)

MCP Tools

Connect any MCP server and expose all its tools to your agent:

import (
    "github.com/soasurs/adk/tool/mcp"
    sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
)

transport := sdkmcp.NewStdioTransport("my-mcp-server", []string{"--flag"}, nil)
ts := mcp.NewToolSet(transport)
if err := ts.Connect(ctx); err != nil { /* … */ }
defer ts.Close()

tools, err := ts.Tools(ctx)

agent := llmagent.New(llmagent.Config{
    // …
    Tools: tools,
})

Agent Composition

Sequential Agent

Chain multiple agents into a pipeline where each agent sees the output of all previous agents:

import "github.com/soasurs/adk/agent/sequentialagent"

pipeline, err := sequentialagent.New(sequentialagent.Config{
    Name:        "research-pipeline",
    Description: "Research, draft, and review",
    Agents: []agent.Agent{
        researchAgent,  // Step 1: gather information
        draftAgent,     // Step 2: write draft
        reviewAgent,    // Step 3: review and polish
    },
})
Parallel Agent

Run multiple agents concurrently and merge their outputs:

import "github.com/soasurs/adk/agent/parallelagent"

ensemble, err := parallelagent.New(parallelagent.Config{
    Name:        "multi-model-ensemble",
    Description: "Get answers from multiple models",
    Agents: []agent.Agent{
        gpt4Agent,
        claudeAgent,
        geminiAgent,
    },
    // Optional: custom merge function
    MergeFunc: func(results []parallelagent.AgentOutput) model.Message {
        // Combine outputs from all agents
    },
})
Agent as Tool

Delegate tasks to sub-agents via the LLM's function-calling mechanism:

import "github.com/soasurs/adk/agent/agentool"

// Wrap an agent as a tool
calculatorTool := agentool.New(calculatorAgent)

// Give it to another agent
orchestrator := llmagent.New(llmagent.Config{
    Name:        "orchestrator",
    Description: "Delegates calculations",
    Model:       llm,
    Tools:       []tool.Tool{calculatorTool},
    Instruction: "Use the calculator tool for math problems.",
})

Multi-modal Input

Pass images alongside text using model.ContentPart:

msg := model.Message{
    Role: model.RoleUser,
    Parts: []model.ContentPart{
        {Type: model.ContentPartTypeText, Text: "What is in this image?"},
        {
            Type:        model.ContentPartTypeImageURL,
            ImageURL:    "https://example.com/photo.jpg",
            ImageDetail: model.ImageDetailHigh,
        },
    },
}

Dependencies

Library Purpose
github.com/openai/openai-go/v3 OpenAI API client
google.golang.org/genai Google Gemini API client
github.com/anthropics/anthropic-sdk-go Anthropic Claude API client
github.com/modelcontextprotocol/go-sdk MCP client
github.com/google/jsonschema-go JSON Schema for tool definitions
github.com/jmoiron/sqlx SQL query helpers
github.com/mattn/go-sqlite3 SQLite driver
github.com/bwmarrin/snowflake Distributed ID generation
github.com/stretchr/testify Test assertions

License

Apache 2.0

Directories

Path Synopsis
examples
chat command
Package main demonstrates a Chat agent that uses LlmAgent with Exa MCP search.
Package main demonstrates a Chat agent that uses LlmAgent with Exa MCP search.
internal
retry
Package retry provides generic retry logic with exponential backoff for iter.Seq2-based operations such as LLM provider calls.
Package retry provides generic retry logic with exponential backoff for iter.Seq2-based operations such as LLM provider calls.
compaction
Package compaction provides types for manual context management.
Package compaction provides types for manual context management.
mcp

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL