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

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
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:
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
-
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")
-
Memory Management: Use appropriate memory size limits for production
-
Tool Input Validation: Consider additional validation in tool handlers
-
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