nue

package module
v0.0.0-...-3f386c8 Latest Latest
Warning

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

Go to latest
Published: Nov 27, 2025 License: MIT Imports: 12 Imported by: 0

README

nue

A modern Go framework for building AI agents with LLM providers.

Go Version License

Features

  • 🤖 Agent Framework - ReAct loop with automatic tool orchestration
  • 🔌 Provider Abstraction - Clean interface for LLM providers (Bedrock support included)
  • 🛠️ Type-Safe Tools - Generic tool definitions with automatic JSON schema generation
  • 🖼️ Multimodal Support - Vision capabilities with multiple image input methods
  • 📄 Document Understanding - Process PDFs, Word, Excel, CSV, HTML, Markdown, and text files
  • 🧠 Extended Thinking - Support for Claude's extended thinking mode
  • 📊 OpenTelemetry - Built-in observability with GenAI semantic conventions
  • 🌊 Streaming - Real-time event streaming for agent execution
  • 💾 Flexible Memory - Pluggable conversation history storage

Installation

go get github.com/yimsk/nue

Quick Start

Simple Chat
package main

import (
    "context"
    "fmt"
    "log"

    "github.com/yimsk/nue/provider/bedrock"
)

func main() {
    // Create Bedrock provider
    provider, err := bedrock.New("us.anthropic.claude-3-5-sonnet-20241022-v2:0")
    if err != nil {
        log.Fatal(err)
    }

    // Build and send request
    req := provider.NewRequest("Hello! How are you?")
    resp, err := provider.Chat(context.Background(), req)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(resp.Text())
}
With Conversation Memory
memory := &nue.inMemory{} // or your custom Memory implementation

req := provider.NewRequest("What did I just ask you?").
    WithMemory(memory).
    WithSystem("You are a helpful assistant")

resp, err := provider.Chat(ctx, req)
Request Builder API

Chain multiple options with the fluent builder pattern:

req := provider.NewRequest("Analyze this data").
    WithMemory(memory).
    WithSystem("You are a data analyst").
    WithThinking(8000).              // Extended Thinking with 8k budget
    WithMaxTokens(16000).
    WithTemperature(0.7)
Multimodal (Vision)

Multiple ways to add images to your requests:

// From raw bytes
req := provider.NewRequest("Describe this image").
    WithImageData(imageBytes, "png")

// From file
req, err := provider.NewRequest("What's in this photo?").
    WithImageFile("/path/to/image.jpg")

// From base64
req, err := provider.NewRequest("Analyze this diagram").
    WithImageBase64(base64String, "png")

// From S3 (Bedrock native support)
req := provider.NewRequest("この画像を説明して").
    WithImageS3("s3://my-bucket/image.png", "png")
Document Understanding

Process various document formats including PDFs, Word documents, Excel spreadsheets, and more:

// From file (format auto-detected from extension)
req, err := provider.NewRequest("Summarize this contract").
    WithDocumentFile("/path/to/contract.pdf")

// From raw bytes
req := provider.NewRequest("Analyze this spreadsheet").
    WithDocumentData(excelBytes, "xlsx", "sales-report.xlsx")

// From base64
req, err := provider.NewRequest("Extract key points from this document").
    WithDocumentBase64(base64String, "pdf", "presentation.pdf")

// From S3 (Bedrock native support)
req := provider.NewRequest("この契約書を分析して").
    WithDocumentS3("s3://my-bucket/contract.pdf", "pdf", "contract.pdf")

Supported formats: pdf, csv, doc, docx, xls, xlsx, html, txt, md

Agent with Tools

Define Type-Safe Tools
import "github.com/yimsk/nue/tool"

type CalculatorInput struct {
    Operation string  `json:"operation" nue:"desc=add/subtract/multiply/divide,required"`
    A         float64 `json:"a" nue:"desc=First number,required"`
    B         float64 `json:"b" nue:"desc=Second number,required"`
}

type CalculatorOutput struct {
    Result float64 `json:"result"`
}

func calculatorHandler(ctx context.Context, input CalculatorInput) (CalculatorOutput, error) {
    var result float64
    switch input.Operation {
    case "add":
        result = input.A + input.B
    case "subtract":
        result = input.A - input.B
    case "multiply":
        result = input.A * input.B
    case "divide":
        if input.B == 0 {
            return CalculatorOutput{}, fmt.Errorf("division by zero")
        }
        result = input.A / input.B
    default:
        return CalculatorOutput{}, fmt.Errorf("unknown operation: %s", input.Operation)
    }
    return CalculatorOutput{Result: result}, nil
}

// Create tool with auto-generated schema
calcTool, err := tool.NewTool(
    "calculator",
    "Performs basic arithmetic operations",
    calculatorHandler,
)
Run Agent
import "github.com/yimsk/nue"

// Create agent with tools
agent := nue.NewAgent(
    nue.WithProvider(provider),
    nue.WithTools(calcTool, weatherTool),  // nue.Tool alias
    nue.WithSystem("You are a helpful assistant with access to tools"),
    nue.WithMaxIterations(10),
    nue.WithMaxTokens(4096),
)

// Execute agent
result, err := agent.Run(ctx, "What is 15 multiplied by 23?")
if err != nil {
    log.Fatal(err)
}

fmt.Println(result.Text())                    // Get text response
fmt.Printf("Tokens used: %d\n", result.Usage.TotalTokens())
Streaming Execution
events, err := agent.RunStream(ctx, "Calculate 42 + 17 and tell me about the weather")
if err != nil {
    log.Fatal(err)
}

for event := range events {
    switch event.Type {
    case nue.StreamEventIterationStart:
        fmt.Printf("Iteration %d started\n", event.Iteration)

    case nue.StreamEventLLMChunk:
        if event.LLMEvent.Type == provider.EventTypeText {
            fmt.Print(event.LLMEvent.Content)
        }

    case nue.StreamEventToolStart:
        fmt.Printf("\nCalling tool: %s\n", event.ToolName)

    case nue.StreamEventToolResult:
        fmt.Printf("Tool result: %s\n", event.ToolOutput)

    case nue.StreamEventDone:
        fmt.Printf("\n\nFinal result: %s\n", event.Result.Text())
        fmt.Printf("Total tokens: %d\n", event.Result.Usage.TotalTokens())

    case nue.StreamEventError:
        log.Printf("Error: %v\n", event.Error)
    }
}

OpenTelemetry Integration

nue includes built-in OpenTelemetry instrumentation following GenAI semantic conventions:

import (
    "github.com/yimsk/nue"
    "github.com/yimsk/nue/observability"
    "go.opentelemetry.io/otel"
)

// Configure OTel providers (your setup)
tp := trace.NewTracerProvider(...)
mp := metrics.NewMeterProvider(...)
otel.SetTracerProvider(tp)
otel.SetMeterProvider(mp)

// Enable instrumentation
agent := nue.NewAgent(
    nue.WithProvider(provider),
    nue.WithObservability(&observability.Config{
        TracerProvider: tp,
        MeterProvider:  mp,
        Enabled:        true,
    }),
)
Metrics Collected
  • gen_ai.client.operation.duration - LLM call latency
  • gen_ai.client.token.usage.input - Input tokens consumed
  • gen_ai.client.token.usage.output - Output tokens generated
  • gen_ai.tool.call.count - Tool invocation count
  • gen_ai.tool.error.count - Tool error count
  • nue.agent.iterations - ReAct loop iterations per run
Spans Created
  • agent.run - Complete agent execution
  • agent.iteration - Individual ReAct iteration
  • tool.execute - Tool invocation

Extended Thinking

Enable Claude's extended thinking mode for complex reasoning tasks:

agent := nue.NewAgent(
    nue.WithProvider(provider),
    nue.WithThinking(8000),  // 8k token thinking budget
)

result, err := agent.Run(ctx, "Solve this complex math problem...")

// Access thinking content
if result.Usage.ThinkingTokens > 0 {
    fmt.Printf("Model used %d thinking tokens\n", result.Usage.ThinkingTokens)
}

Memory Implementations

Built-in In-Memory
// Default memory (automatic)
agent := nue.NewAgent(nue.WithProvider(provider))

// Or explicitly
memory := &nue.inMemory{}
agent := nue.NewAgent(
    nue.WithProvider(provider),
    nue.WithMemory(memory),
)
Custom Memory

Implement the Memory interface for custom storage:

type Memory interface {
    Add(msg nue.Message) error
    Messages() []nue.Message
    Clear() error
}

// Example: Redis-backed memory
type RedisMemory struct {
    client *redis.Client
    key    string
}

func (m *RedisMemory) Add(msg nue.Message) error {
    data, _ := json.Marshal(msg)
    return m.client.RPush(context.Background(), m.key, data).Err()
}

func (m *RedisMemory) Messages() []nue.Message {
    // Retrieve and deserialize messages
}

func (m *RedisMemory) Clear() error {
    return m.client.Del(context.Background(), m.key).Err()
}

Development

Prerequisites
  • Go 1.25+
  • Task (optional, for build automation)
  • AWS credentials configured (for Bedrock integration tests)
Build & Test
# Install git hooks
task install-hooks

# Run all checks (fmt, vet, build, test)
task check

# Run only unit tests
task test

# Run integration tests (requires AWS credentials)
task test:integration

# Format code
task fmt

# Build
task build
Project Structure
nue/
├── agent.go              # Main Agent struct + ReAct loop
├── options.go            # Functional options pattern
├── types.go              # Type aliases + Memory interface
├── core/
│   └── types.go          # Shared types (Message, ContentBlock, Usage)
├── provider/
│   ├── provider.go       # Provider interface + Request builder
│   └── bedrock/          # AWS Bedrock implementation
├── tool/
│   ├── tool.go           # TypedTool with generics
│   └── schema.go         # JSON Schema generation
├── memory/               # Memory implementations
├── mcp/                  # MCP client integration
├── a2a/                  # A2A protocol (JSON-RPC 2.0)
└── observability/        # OpenTelemetry instrumentation

Security & Limits

nue includes several built-in protections to ensure safe and reliable operation:

Input Size Limits

Tool Input Protection

  • Maximum tool input size: 1 MB (configurable via tool.MaxToolInputSize)
  • Prevents DoS attacks from malicious or oversized LLM-generated tool inputs
  • Configurable limit for custom requirements
// Custom tool input size limit
const CustomMaxToolInputSize = 512 * 1024 // 512 KB
Memory Limits

Conversation History Protection

  • Default maximum messages: 10,000 (prevents OOM in long-running agents)
  • Configurable per memory instance
  • Set to 0 for unlimited (not recommended for production)
// Create memory with custom limit
mem := memory.NewInMemory(memory.WithMaxSize(5000))

// Create memory with unlimited size (use with caution)
mem := memory.NewInMemory(memory.WithMaxSize(0))
S3 URI Validation

SSRF Prevention

  • S3 URIs are validated for correct format: s3://bucket/key
  • Prevents malicious URIs in WithImageS3 and WithDocumentS3 methods
  • Returns error for invalid formats
// Valid S3 URIs
req, err := req.WithImageS3("s3://my-bucket/image.jpg", "jpeg")
req, err := req.WithDocumentS3("s3://my-bucket/doc.pdf", "pdf", "doc.pdf")

// Invalid URIs will return errors
_, err := req.WithImageS3("http://example.com/image.jpg", "jpeg")  // Error
_, err := req.WithImageS3("s3://bucket", "jpeg")                     // Error
_, err := req.WithImageS3("s3:///key", "jpeg")                       // Error
Best Practices
  1. Context Timeouts: Always use context with timeout to prevent hanging requests

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    result, err := agent.Run(ctx, "query")
    
  2. Memory Management: Use appropriate memory size limits for production

  3. Tool Input Validation: Consider additional validation in tool handlers

  4. Error Logging: Observability errors are logged but don't block execution

Roadmap

  • Additional provider implementations (OpenAI, Anthropic direct, etc.)
  • Message persistence layer
  • Retry logic with exponential backoff
  • Circuit breaker for tool execution
  • Rate limiting for tools
  • Examples repository

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

License

MIT License - see LICENSE for details.

Acknowledgments

  • Built for dt - AI-powered development assistant
  • Inspired by modern agent frameworks and Go best practices
  • Uses AWS Bedrock for Claude model access
  • OpenTelemetry integration following GenAI semantic conventions

Documentation

Index

Constants

View Source
const (
	RoleUser      = core.RoleUser
	RoleAssistant = core.RoleAssistant

	StopReasonEndTurn   = core.StopReasonEndTurn
	StopReasonToolUse   = core.StopReasonToolUse
	StopReasonMaxTokens = core.StopReasonMaxTokens
	StopReasonStopSeq   = core.StopReasonStopSeq
)

Re-export constants.

Variables

View Source
var Logger *slog.Logger = slog.Default()

Logger is the default logger for nue. Set to a custom logger if needed.

Functions

func SetLogger

func SetLogger(l *slog.Logger)

SetLogger sets the global logger for nue.

Types

type Agent

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

Agent orchestrates LLM interactions with tools using a ReAct loop.

func NewAgent

func NewAgent(opts ...Option) *Agent

NewAgent creates a new agent with the given options.

func (*Agent) AddTool

func (a *Agent) AddTool(t tool.Tool)

AddTool adds a tool to the agent dynamically.

func (*Agent) ClearMemory

func (a *Agent) ClearMemory() error

ClearMemory clears the conversation history.

func (*Agent) Run

func (a *Agent) Run(ctx context.Context, userMessage string) (*Result, error)

Run executes the agent with a user message using a ReAct loop.

func (*Agent) RunStream

func (a *Agent) RunStream(ctx context.Context, userMessage string) (<-chan StreamEvent, error)

RunStream executes the agent with streaming events. Returns a channel that emits events as the agent progresses.

type ContentBlock

type ContentBlock = core.ContentBlock

Re-export core types for convenience.

type CumulativeUsage

type CumulativeUsage = core.CumulativeUsage

Re-export core types for convenience.

type DocumentContent

type DocumentContent = core.DocumentContent

Re-export core types for convenience.

type ImageContent

type ImageContent = core.ImageContent

Re-export core types for convenience.

type Memory

type Memory interface {
	// Add appends a message to the history.
	Add(msg Message) error

	// Messages returns all messages in order.
	Messages() []Message

	// Clear removes all messages.
	Clear() error
}

Memory is the interface for conversation history storage.

type Message

type Message = core.Message

Re-export core types for convenience.

type Option

type Option func(*Agent)

Option configures an Agent.

func WithInstrumentation

func WithInstrumentation(inst *observability.Instrumentation) Option

WithInstrumentation sets a pre-configured instrumentation instance. Use this when sharing instrumentation across multiple agents.

func WithMaxIterations

func WithMaxIterations(n int) Option

WithMaxIterations sets the maximum ReAct loop iterations.

func WithMaxTokens

func WithMaxTokens(n int32) Option

WithMaxTokens sets the maximum response tokens.

func WithMemory

func WithMemory(m Memory) Option

WithMemory sets the memory implementation.

func WithObservability

func WithObservability(cfg *observability.Config) Option

WithObservability enables OpenTelemetry instrumentation with the given config. If config is nil, default configuration using global providers is used.

func WithProvider

func WithProvider(p provider.Provider) Option

WithProvider sets the LLM provider.

func WithSystem

func WithSystem(system string) Option

WithSystem sets the system prompt.

func WithThinking

func WithThinking(budget int) Option

WithThinking enables Extended Thinking with the given budget. Budget of 0 disables thinking, >0 sets the token budget.

func WithTools

func WithTools(tools ...tool.Tool) Option

WithTools sets the available tools.

type Result

type Result struct {
	Content string
	Usage   *CumulativeUsage
}

Result represents the outcome of an agent run.

func (*Result) Text

func (r *Result) Text() string

Text returns the content string for API consistency with Response.Text().

type Role

type Role = core.Role

Re-export core types for convenience.

type StopReason

type StopReason = core.StopReason

Re-export core types for convenience.

type StreamEvent

type StreamEvent struct {
	Type StreamEventType

	// Iteration number (for iteration_start, iteration_end).
	Iteration int

	// LLM streaming event (for llm_chunk).
	LLMEvent *provider.Event

	// Tool information (for tool_start, tool_result).
	ToolName   string
	ToolID     string
	ToolInput  string
	ToolOutput string

	// Final result (for done).
	Result *Result

	// Error (for error).
	Error error
}

StreamEvent represents an event during agent execution.

type StreamEventType

type StreamEventType string

StreamEventType identifies the type of agent streaming event.

const (
	// StreamEventIterationStart signals the start of a ReAct iteration.
	StreamEventIterationStart StreamEventType = "iteration_start"
	// StreamEventLLMChunk is a chunk from the LLM (text, thinking, tool_use).
	StreamEventLLMChunk StreamEventType = "llm_chunk"
	// StreamEventToolStart signals the start of a tool execution.
	StreamEventToolStart StreamEventType = "tool_start"
	// StreamEventToolResult contains the result of a tool execution.
	StreamEventToolResult StreamEventType = "tool_result"
	// StreamEventIterationEnd signals the end of a ReAct iteration.
	StreamEventIterationEnd StreamEventType = "iteration_end"
	// StreamEventDone signals completion with final result.
	StreamEventDone StreamEventType = "done"
	// StreamEventError signals an error occurred.
	StreamEventError StreamEventType = "error"
)

type Tool

type Tool = tool.Tool

Tool is the interface for agent tools.

type ToolResultContent

type ToolResultContent = core.ToolResultContent

Re-export core types for convenience.

type ToolUseContent

type ToolUseContent = core.ToolUseContent

Re-export core types for convenience.

type Usage

type Usage = core.Usage

Re-export core types for convenience.

Directories

Path Synopsis
Package core provides core types shared across nue packages.
Package core provides core types shared across nue packages.
Package observability provides OpenTelemetry instrumentation for nue.
Package observability provides OpenTelemetry instrumentation for nue.

Jump to

Keyboard shortcuts

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