llmagent

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Jun 9, 2026 License: MIT Imports: 17 Imported by: 0

README ¶

llmagent — A Simple Golang Agent SDK Framework

llmagent is a lightweight, modular, and extensible AI Agent development framework (SDK) written in Go. It is designed to help developers quickly build, run, and visualize intelligent agents with support for complex toolchains, Human-in-the-Loop (HITL) workflows, and real-time execution state monitoring.


🚀 Key Features

  • 🛠 Declarative Tool Registration: Automatic parameter inference using Go generics. When registering a tool using NewTool[T], the framework automatically generates the corresponding JSON Schema definition based on the struct T using jsonschema-go, eliminating the need to write complex schemas manually.
  • 🔄 Modular Loop Control: Built-in standard ReactLoop (ReAct pattern) and full support for completely custom Loop logic to satisfy diverse decision-making scenarios.
  • 🔌 Powerful Interceptors: Middleware support at three levels: Tasks, LLM completions, and Tool calls. Easily implement unified logging, auditing, performance metrics, retries, rate limiting, or authorization.
  • 💾 Flexible Session & Event Persistence: Built-in in-memory storage (MemStorage) and PostgreSQL storage (PGStorage), supporting full session state lifecycle management and automatic history compression.
  • 👤 Human-in-the-Loop (HITL): Native support for human interaction events. When a tool requires human approval or extra inputs, the execution can be suspended, waiting for an external response.
  • 📊 Built-in Web Console: Integrated visual admin dashboard. Simply enable the console option in the HTTP server to visualize the agent's event stream, chat logs, session states, and tool execution details.

📦 Installation

Add the framework to your Go project:

go get github.com/xucx/llmagent

Note: This project depends on github.com/xucx/llmapi as the underlying LLM adapter layer.


💡 Quick Start

Here is a quick example demonstrating how to define a custom tool, initialize an Agent, and start the HTTP server with the built-in Web Console enabled.

package main

import (
	"context"
	"log"

	"github.com/xucx/llmagent"
	"github.com/xucx/llmagent/session"
	"github.com/xucx/llmapi"
)

// 1. Define the tool parameters struct (use tags to describe fields for automatic JSON Schema generation)
type AddArgs struct {
	A float64 `json:"a" jsonschema:"description=The first number to add"`
	B float64 `json:"b" jsonschema:"description=The second number to add"`
}

func main() {
	// 2. Initialize the LLM client
	models := llmapi.DefaultModels // Ensure your environment variables (e.g. OPENAI_API_KEY) are configured

	// 3. Register a custom addition tool
	addTool := llmagent.NewTool("add_tool", "Used to calculate the sum of two numbers", func(ctx llmagent.ToolContext, args *AddArgs) (any, error) {
		result := args.A + args.B
		return map[string]any{"result": result}, nil
	})

	// 4. Create an Agent instance
	agent, err := llmagent.NewAgent(
		"math_assistant",                      // Agent Name
		"An agent skilled in math calculations", // Agent Description
		session.NewMemStorage(),               // Storage: in-memory storage
		llmagent.ReactLoop,                    // Execution Loop: standard ReAct loop
		llmagent.WithModel(models, "gpt-4o"),  // Bind client and model
		llmagent.WithInstruction("You are a helpful math assistant. For any addition, you must call add_tool and report the result."),
		llmagent.WithTools(addTool),           // Inject tools
	)
	if err != nil {
		log.Fatalf("Failed to create Agent: %v", err)
	}

	// 5.1 Option A: Run a task directly in code
	ctx := context.Background()
	task, err := agent.RunTask(ctx, "session-001", &llmagent.TaskPrompt{
		Text: "Please calculate 1234.56 + 7890.12 for me",
	})
	if err != nil {
		log.Fatalf("Failed to run task: %v", err)
	}
	log.Printf("Task completed with %d events generated", len(task.Events))

	// 5.2 Option B: Start HTTP server with Web Console enabled
	// Access http://localhost:8080/ in your browser to view the Agent console
	server := llmagent.NewHTTPServer(
		[]*llmagent.Agent{agent},
		llmagent.HTTPWithEnableConsole(true), // Enable the built-in web console
		llmagent.HTTPWithPrefix("/api"),       // API prefix
	)

	log.Println("HTTP Server starting on :8080 ...")
	log.Println("Please open http://localhost:8080/ in your browser")
	if err := server.Start(":8080"); err != nil {
		log.Fatalf("Server failed to start: %v", err)
	}
}

🧱 Directory Structure

llmagent/
├── agent.go        # Core Agent definition & lifecycle (RunTask, createTaskSession)
├── loop.go         # Core agent state machine runner (ReactLoop)
├── options.go      # Agent configuration options and interceptors definition
├── task.go         # Task instance and turn execution runner
├── tool.go         # Declarative tool definitions (NewTool) and parallel call logic
├── http.go         # HTTP Server serving REST APIs
├── session/        # Session and Storage implementations
│   ├── session.go     # Session model and state management
│   ├── storage_mem.go # In-memory session storage (MemStorage)
│   └── storage_pg.go  # PostgreSQL session storage (PGStorage)
├── console/        # Built-in visual Web Console (React frontend static assets & embedding)
└── types/          # Shared type definitions (Event, Session, Tool, etc.)

🛠 Advanced Usage

1. Interceptors

Interceptors allow you to hook into and inspect the Agent's execution flow. The framework provides three levels of interceptors:

  • TaskInterceptor: Intercepts the entire lifecycle of a Task.
  • LLMInterceptor: Intercepts every LLM Completion request (great for token tracking or prompt injection).
  • ToolCallInterceptor: Intercepts the execution of individual tools (great for validation or error handling).
// Example: A simple LLM interceptor to log request latency
var logLLMInterceptor = func(next llmagent.LLMHandler) llmagent.LLMHandler {
	return func(ctx context.Context, turn *llmagent.TaskTurn) (*llmtypes.Completion, error) {
		start := time.Now()
		completion, err := next(ctx, turn)
		log.Printf("[LLM] Duration: %v, Error: %v", time.Since(start), err)
		return completion, err
	}
}

// Register interceptor via agent options
llmagent.NewAgent(..., llmagent.LLMInterceptors(logLLMInterceptor))
2. Human-in-the-Loop (HITL)

If human approval or additional inputs are required during a tool call, you can suspend execution using the ToolContext. The underlying event stream will generate a EventTypeHumanRequest event and block until a corresponding EventTypeHumanResponse is received.


📄 License

This project is licensed under the MIT License.

Documentation ¶

Index ¶

Constants ¶

This section is empty.

Variables ¶

This section is empty.

Functions ¶

func ReactLoop ¶

func ReactLoop(ctx context.Context, task *Task) error

Types ¶

type Agent ¶

type Agent struct {
	Name        string
	Description string
	// contains filtered or unexported fields
}

func NewAgent ¶

func NewAgent(name, desc string, storage types.SessionStorage, loop Loop, opts ...AgentOption) (*Agent, error)

func (*Agent) ListSessions ¶

func (a *Agent) ListSessions(ctx context.Context) ([]*types.Session, error)

func (*Agent) RunTask ¶

func (a *Agent) RunTask(ctx context.Context, sessionId string, prompt *TaskPrompt, opts ...AgentOption) (*Task, error)

type AgentOption ¶

type AgentOption func(*AgentOptions) *AgentOptions

func WithHistoryCompressor ¶

func WithHistoryCompressor(compressor HistoryCompressor) AgentOption

func WithInstruction ¶

func WithInstruction(instruction string) AgentOption

func WithLLMInterceptor ¶

func WithLLMInterceptor(interceptor LLMInterceptor) AgentOption

func WithMaxTurn ¶

func WithMaxTurn(maxTurn int) AgentOption

func WithModel ¶

func WithModel(models *llmapi.Models, model string) AgentOption

func WithNewTools ¶

func WithNewTools(tools ...*Tool) AgentOption

func WithOnEventUpdate ¶

func WithOnEventUpdate(handler EventUpdateHandler) AgentOption

func WithReasonLevel ¶

func WithReasonLevel(level int) AgentOption

func WithStream ¶

func WithStream(stream bool) AgentOption

func WithTaskInterceptor ¶

func WithTaskInterceptor(interceptor TaskInterceptor) AgentOption

func WithToolCallInterceptor ¶

func WithToolCallInterceptor(interceptor ToolCallInterceptor) AgentOption

func WithTools ¶

func WithTools(tools ...*Tool) AgentOption

type AgentOptions ¶

type AgentOptions struct {
	Models               *llmapi.Models
	Model                string
	Instruction          string
	ReasonLevel          int
	Tools                []*Tool
	MaxTurn              int
	Stream               bool
	OnEventUpdate        EventUpdateHandler
	HistoryCompressor    HistoryCompressor
	TaskInterceptors     []TaskInterceptor
	LLMInterceptors      []LLMInterceptor
	ToolCallInterceptors []ToolCallInterceptor
}

type EventUpdateHandler ¶

type EventUpdateHandler func(*types.Event)

type HTTPOption ¶

type HTTPOption func(*HTTPServer)

func HTTPWithEnableConsole ¶

func HTTPWithEnableConsole(enable bool) HTTPOption

func HTTPWithPrefix ¶

func HTTPWithPrefix(prefix string) HTTPOption

type HTTPServer ¶

type HTTPServer struct {
	// contains filtered or unexported fields
}

func NewHTTPServer ¶

func NewHTTPServer(agents []*Agent, opts ...HTTPOption) *HTTPServer

func (*HTTPServer) Handler ¶

func (s *HTTPServer) Handler() http.Handler

func (*HTTPServer) ServeHTTP ¶

func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*HTTPServer) Start ¶

func (s *HTTPServer) Start(addr string) error

type HistoryCompressor ¶

type HistoryCompressor func(context.Context, *Task) (*types.SessionCompress, error)

type LLMHandler ¶

type LLMHandler func(context.Context, *TaskTurn) (*llmtypes.Completion, error)

type LLMInterceptor ¶

type LLMInterceptor func(LLMHandler) LLMHandler

type Loop ¶

type Loop func(ctx context.Context, task *Task) error

type RunResult ¶

type RunResult struct {
	AgentName string         `json:"agentName,omitempty" yaml:"agentName,omitempty"`
	SessionID string         `json:"sessionId,omitempty" yaml:"sessionId,omitempty"`
	TaskID    string         `json:"taskId,omitempty" yaml:"taskId,omitempty"`
	Events    []*types.Event `json:"events,omitempty" yaml:"events,omitempty"`
	Error     string         `json:"error,omitempty" yaml:"error,omitempty"`
}

type Task ¶

type Task struct {
	ID      string
	Session *session.Session
	Events  []*types.Event

	Models               *llmapi.Models
	Model                string
	Instruction          string
	Tools                map[string]*Tool
	ReasonLevel          int
	History              []*llmtypes.Message
	Prompt               *TaskPrompt
	Stream               bool
	MaxTurn              int
	HistoryCompressor    HistoryCompressor
	OnEventUpdate        EventUpdateHandler
	LLMInterceptors      []LLMInterceptor
	ToolCallInterceptors []ToolCallInterceptor
	// contains filtered or unexported fields
}

func NewTask ¶

func NewTask(agent *Agent, session *session.Session, options *AgentOptions, prompt *TaskPrompt) (*Task, error)

func (*Task) AddEvent ¶

func (t *Task) AddEvent(event *types.Event) error

func (*Task) AddMessage ¶

func (t *Task) AddMessage(msg *llmtypes.Message) (*types.Event, error)

func (*Task) AddToolResults ¶

func (t *Task) AddToolResults(toolRuns []*ToolRun) ([]*types.Event, error)

func (*Task) Done ¶

func (t *Task) Done(err error) error

func (*Task) RunTurn ¶

func (t *Task) RunTurn(ctx context.Context, turnIndex int) ([]*types.Event, error)

func (*Task) Start ¶

func (t *Task) Start() error

type TaskHandler ¶

type TaskHandler func(context.Context, *Task) error

type TaskInterceptor ¶

type TaskInterceptor func(TaskHandler) TaskHandler

type TaskPrompt ¶

type TaskPrompt struct {
	Text         string                   `json:"text,omitempty"`
	Tools        []*TaskPromptTool        `json:"tools,omitempty"`
	ToolResults  []*TaskPromptToolResult  `json:"toolResults,omitempty"`
	HumanResults []*TaskPromptHumanResult `json:"humanResults,omitempty"`
	State        map[string]any           `json:"state,omitempty"`
	StateDelta   map[string]any           `json:"stateDelta,omitempty"`
}

type TaskPromptHumanResult ¶

type TaskPromptHumanResult struct {
	ID     string         `json:"id,omitempty" yaml:"id,omitempty"`
	Result map[string]any `json:"result,omitempty" yaml:"result,omitempty"`
}

type TaskPromptTool ¶

type TaskPromptTool struct {
	Name  string         `json:"name,omitempty"`
	Desc  string         `json:"desc,omitempty"`
	Param map[string]any `json:"param,omitempty"`
}

type TaskPromptToolResult ¶

type TaskPromptToolResult struct {
	ID     string         `json:"id,omitempty" yaml:"id,omitempty"`
	Result map[string]any `json:"result,omitempty" yaml:"result,omitempty"`
}

type TaskTurn ¶

type TaskTurn struct {
	Task        *Task
	Index       int
	Model       string
	Instruction string
	History     []*llmtypes.Message
}

type Tool ¶

type Tool struct {
	Name        string
	Description string
	Parameters  map[string]any
	Invoker     ToolInvoker
}

func NewTool ¶

func NewTool[T any](name string, desc string, invoker func(ToolContext, *T) (any, error)) *Tool

type ToolCallInterceptor ¶

type ToolCallInterceptor func(ToolCallHandler) ToolCallHandler

type ToolContext ¶

type ToolContext interface {
	context.Context
	Session() types.Session
	SetState(state map[string]interface{}, replace bool) error
	HumanRequest() *types.EventHumanRequest
	HumanResponse() *types.EventHumanResponse
}

type ToolInvoker ¶

type ToolInvoker func(ToolContext, string) (any, error)

type ToolResultHuman ¶

type ToolResultHuman struct {
	Arguments map[string]any
}

type ToolResultNotify ¶

type ToolResultNotify struct{}

type ToolRun ¶

type ToolRun struct {
	Call   *llmtypes.MessageToolCall
	Result *llmtypes.MessageToolResult
}

Directories ¶

Path Synopsis

Jump to

Keyboard shortcuts

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