Skip to content

Repository files navigation

AgentKit

中文文档

A lightweight, event-stream-driven Agent toolkit built on top of CloudWeGo Eino ADK.

Inspired by pi-agent-core, AgentKit brings event streaming, message queuing, and human-in-the-loop (HITL) capabilities to the Go + Eino ecosystem.

Features

  • Event-stream architecture — Subscribe to fine-grained events (message deltas, tool calls, errors, etc.)
  • Steering & follow-up queues — Inject messages mid-execution to redirect the agent or append follow-up tasks
  • Human-in-the-loop (HITL) — Interrupt agent execution and resume with user-provided data
  • Streaming support — Real-time token-by-token output via Eino ADK streaming
  • Reasoning model support — First-class support for thinking/reasoning models (DeepSeek-R1, o1, etc.) with streaming reasoning output
  • Multimodal input — Send text, images, audio, video, and files via Send() with ergonomic constructors
  • Tool integration — Plug in any Eino-compatible tool with automatic tool-call handling
  • Type aliases — Use agentkit.ChatModel, agentkit.Tool, agentkit.ToolCall, etc. without importing eino packages directly

Installation

go get github.com/wsshow/agentkit

Quick Start

package main
import (
"context""fmt""log""github.com/cloudwego/eino-ext/components/model/openai""github.com/wsshow/agentkit"
)
funcmain() {
ctx:=context.Background()
chatModel, _:=openai.NewChatModel(ctx, &openai.ChatModelConfig{
APIKey: "your-api-key",
BaseURL: "https://api.openai.com/v1",
Model: "gpt-4o",
})
agent, err:=agentkit.New(ctx, &agentkit.Config{
Name: "assistant",
SystemPrompt: "You are a helpful assistant.",
Model: chatModel,
})
iferr!=nil {
log.Fatalln(err)
}
deferagent.Close()
agent.Subscribe(func(e agentkit.Event) {
switche.Type {
caseagentkit.EventReasoningDelta:
fmt.Print(e.Delta) // reasoning/thinking stream (for reasoning models)caseagentkit.EventMessageDelta:
fmt.Print(e.Delta)
caseagentkit.EventMessageEnd:
fmt.Println()
caseagentkit.EventError:
fmt.Printf("Error: %v\n", e.Error)
}
})
iferr:=agent.Prompt(ctx, "Hello!"); err!=nil {
log.Fatalln(err)
}
}

Event Types

EventDescription
EventAgentStartAgent begins processing
EventTurnStartNew turn starts before the next model request
EventMessageStartMessage begins (Event.Role identifies user, assistant, or tool)
EventReasoningDeltaReasoning/thinking stream delta (Event.Delta), for reasoning models
EventMessageDeltaIncremental streaming text (Event.Delta)
EventMessageEndMessage complete (Event.Role, Event.Content, Event.ResponseMeta)
EventToolStartTool call requested (Event.ToolCalls)
EventToolUpdateTool execution progress update (Event.ToolCallID, Event.Content)
EventToolEndTool call result returned (Event.ToolCallID, Event.ToolName, Event.Content)
EventTurnEndTurn complete after the assistant message and tool results
EventTransferAgent transfer (multi-agent)
EventInterruptedHITL interrupt (Event.Interrupt)
EventAgentEndAgent processing complete
EventErrorError occurred (Event.Error)

Event Struct

typeEventstruct {
TypeEventTypeAgentstring// source agent nameRoleRoleType// message role (message_start / message_end)Contentstring// full text (message_end / tool_end)Deltastring// streaming delta (message_delta / reasoning_delta)ReasoningContentstring// full reasoning content (message_end, reasoning models only)ResponseMeta*ResponseMeta// token usage, finish reason (message_end)ToolCalls []ToolCall// tool call list (tool_start)ToolCallIDstring// tool call ID (tool_update / tool_end)ToolNamestring// tool name (tool_update / tool_end)ToolArgumentsstring// tool arguments (tool_update / tool_end)Interrupt []InterruptPoint// interrupt points (interrupted)Errorerror// error details (error)
}

API Reference

Creating an Agent

agent, err:=agentkit.New(ctx, &agentkit.Config{
Name: "my-agent",
Description: "Agent description",
SystemPrompt: "System instructions",
Model: chatModel, // agentkit.ChatModelTools: []agentkit.Tool{myTool}, // optionalHistory: savedHistory, // optionalHandlers: []agentkit.ChatModelAgentMiddleware{myHandler}, // optionalModelRetryConfig: &agentkit.ModelRetryConfig{MaxRetries: 2}, // optionalModelFailoverConfig: failoverConfig, // optionalMaxIterations: 20, // max LLM call cycles (default: 20)CheckPointStore: store, // checkpoint store (optional)
})
deferagent.Close()

Core Methods

// Send user text input and drive agent execution (blocking, thread-safe)err:=agent.Prompt(ctx, "user message")
// Send multimodal input (text + images, audio, video, files)err:=agent.Send(ctx,
agentkit.Text("What is in this image?"),
agentkit.ImageURL("https://example.com/cat.jpg"),
)
// Resume from current state without new message (e.g. retry after error)err:=agent.Continue(ctx)
// Resume from a HITL interrupterr:=agent.Resume(ctx, map[string]any{"interruptID": data})
// Subscribe to events, returns unsubscribe functionunsubscribe:=agent.Subscribe(func(e agentkit.Event) { ... })
// Cancel current execution and wait for completionagent.Abort()
// Reset agent state (waits for completion, then clears history and queues)agent.Reset()
// Get full conversation history for debugging or persistence (returns a copy)history:=agent.History()
// Replace full conversation history and sync display stateagent.SetHistory(history)
// Get agent state (message records, streaming status)state:=agent.State()
// Close agent and release resources (implements io.Closer)agent.Close()

Prompt, Continue, and Resume are mutually exclusive — calling one while another is running returns an error.

Integration Tests

Use MockChatModel to run agents without calling a real model:

model:=agentkit.NewMockChatModel(
agentkit.MockModelStream("hel", "lo"),
)
agent, err:=agentkit.New(ctx, &agentkit.Config{
Name: "test-agent",
Model: model,
})
iferr!=nil {
t.Fatal(err)
}
deferagent.Close()
iferr:=agent.Prompt(ctx, "say hello"); err!=nil {
t.Fatal(err)
}
calls:=model.Calls()
ifcalls[0].Input[len(calls[0].Input)-1].Content!="say hello" {
t.Fatal("unexpected input")
}

Common response helpers:

agentkit.MockModelText("done")
agentkit.MockModelStream("part 1", "part 2")
agentkit.MockModelError(err)
agentkit.MockModelStreamError(err, "partial")

Tool calls can execute real functions:

weather:=agentkit.MustMockTool(
"get_weather",
"query weather",
func(ctx context.Context, input*WeatherInput) (*WeatherOutput, error) {
return&WeatherOutput{City: input.City, Condition: "sunny"}, nil
},
)
beijing:=weather.Call("beijing_weather", &WeatherInput{City: "Beijing"})
shanghai:=weather.Call("shanghai_weather", &WeatherInput{City: "Shanghai"})
model:=agentkit.NewMockChatModel(
agentkit.MockModelCalls(beijing),
agentkit.MockModelCallsAfter(beijing, shanghai),
agentkit.MockModelRespondsAfter(shanghai, func(out*WeatherOutput) agentkit.MockModelResponse {
returnagentkit.MockModelText(out.City+" is "+out.Condition)
}),
)
agent, err:=agentkit.New(ctx, &agentkit.Config{
Name: "test-agent",
Model: model,
Tools: agentkit.MockTools(weather),
})

Use MockModelCalls when one model response calls multiple tools:

beijing:=weather.Call("beijing_weather", &WeatherInput{City: "Beijing"})
shanghai:=weather.Call("shanghai_weather", &WeatherInput{City: "Shanghai"})
model:=agentkit.NewMockChatModel(
agentkit.MockModelCalls(beijing, shanghai),
agentkit.MockModelTextAfterAll("done", beijing, shanghai),
)

Steering & Follow-Up

// Inject a steering message during execution (checked after the current tool batch)agent.Steer("Please focus on topic X instead")
// Append a follow-up message (processed after current task completes)agent.FollowUp("Also check Y")
// Configure queue processing modeagent.SetSteeringMode(agentkit.QueueModeAll) // process all queued messages at onceagent.SetFollowUpMode(agentkit.QueueModeOneAtATime) // process one at a time (default)// Clear queuesagent.ClearSteeringQueue()
agent.ClearFollowUpQueue()
agent.ClearAllQueues()

HITL (Human-in-the-Loop)

// In a tool: trigger interruptreturn"", agentkit.Interrupt(ctx, "Need user confirmation")
// With state preservationreturn"", agentkit.StatefulInterrupt(ctx, "Confirm?", myState)
// In a resumed tool: check interrupt statewasInterrupted, hasState, state:= agentkit.GetInterruptState[MyState](ctx)
// Get resume data from userisTarget, hasData, data:= agentkit.GetResumeContext[bool](ctx)

Multimodal Input

Send accepts variadic ContentPart values built with constructor functions:

// Text + imageagent.Send(ctx,
agentkit.Text("What is in this image?"),
agentkit.ImageURL("https://example.com/cat.jpg"),
)
// Image with quality controlagent.Send(ctx,
agentkit.Text("Describe in detail"),
agentkit.ImageURL("https://example.com/photo.jpg", agentkit.ImageDetailHigh),
)
// Base64 encoded imageagent.Send(ctx,
agentkit.Text("Identify this"),
agentkit.ImageBase64(base64Data, "image/png"),
)
// Audio / Video / Fileagent.Send(ctx, agentkit.Text("Transcribe"), agentkit.AudioURL("https://example.com/speech.mp3"))
agent.Send(ctx, agentkit.Text("Summarize"), agentkit.VideoURL("https://example.com/clip.mp4"))
agent.Send(ctx, agentkit.Text("Analyze"), agentkit.FileURL("https://example.com/report.pdf"))

Available constructors:

ConstructorDescription
Text(s)Text content
ImageURL(url, detail...)Image from URL (optional quality)
ImageBase64(data, mime, detail...)Image from Base64
AudioURL(url)Audio from URL
AudioBase64(data, mime)Audio from Base64
VideoURL(url)Video from URL
VideoBase64(data, mime)Video from Base64
FileURL(url)File from URL
FileBase64(data, mime, name...)File from Base64 (optional filename)

Tool Progress Updates

Tools can emit progress events during execution:

funcmyTool(ctx context.Context, inputstring) (string, error) {
agentkit.EmitToolUpdate(ctx, "Processing step 1...")
// ... do work ...agentkit.EmitToolUpdate(ctx, "Processing step 2...")
return"result", nil
}

Type Aliases

AgentKit provides type aliases so consumers don't need to import eino packages directly:

AliasEino Type
ChatModelmodel.BaseChatModel
Tooltool.BaseTool
ToolCallschema.ToolCall
ResponseMetaschema.ResponseMeta
TokenUsageschema.TokenUsage
ContentPartschema.MessageInputPart
ImageURLDetailschema.ImageURLDetail

Examples

See the examples directory:

  • simple — Minimal multi-turn conversation (~60 lines)
  • tools — Tool calls with progress events
  • history — Export and restore conversation history
  • queues — Follow-up and steering queues
  • hitl — Human-in-the-loop interrupt and resume
  • multimodal — Text and image inputs

License

See LICENSE for details.

About

A lightweight, event-stream-driven Agent toolkit built on top of CloudWeGo Eino ADK.

Topics

Resources

Stars

16 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages