Skip to main content

Overview

MoFA provides native Go bindings generated through Mozilla UniFFI. These bindings offer idiomatic Go APIs with goroutine support, standard error handling, and seamless integration with Go applications.

Installation

go get github.com/mofa-org/mofa-go

Quick Start

Basic LLM Agent

package main

import (
	"fmt"
	"os"

	mofa "github.com/mofa-org/mofa-go"
)

func main() {
	// Set your API key
	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		fmt.Println("Error: OPENAI_API_KEY not set")
		os.Exit(1)
	}

	// Create an agent using the builder pattern
	builder := mofa.NewLlmAgentBuilder()
	builder.SetId("my-agent")
	builder.SetName("Go Agent")
	builder.SetSystemPrompt("You are a helpful assistant.")
	builder.SetTemperature(0.7)
	builder.SetMaxTokens(1000)
	builder.SetOpenaiProvider(apiKey, os.Getenv("OPENAI_BASE_URL"), "gpt-3.5-turbo")

	agent, err := builder.Build()
	if err != nil {
		fmt.Printf("Error building agent: %v\n", err)
		os.Exit(1)
	}

	// Simple Q&A (no context retention)
	answer, err := agent.Ask("What is Go?")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
	} else {
		fmt.Printf("Answer: %s\n", answer)
	}

	// Multi-turn chat (with context)
	agent.Chat("My favorite language is Go.")
	response, err := agent.Chat("What did I just tell you?")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
	} else {
		fmt.Printf("Response: %s\n", response)
	}

	// Get conversation history
	history := agent.GetHistory()
	fmt.Printf("Total messages: %d\n", len(history))

	// Clear history
	agent.ClearHistory()
}

API Reference

Package Functions

GetVersion
() string
Get the MoFA SDK version string.
IsDoraAvailable
() bool
Check if Dora-rs distributed runtime is available.
NewLlmAgentBuilder
() *LLMAgentBuilder
Create a new LLM agent builder.

LLMAgentBuilder

SetId
(id string) *LLMAgentBuilder
Set the agent ID. If not set, a UUID will be generated.
SetName
(name string) *LLMAgentBuilder
Set the agent name for display purposes.
SetSystemPrompt
(prompt string) *LLMAgentBuilder
Set the system prompt that defines agent behavior.
SetTemperature
(temp float32) *LLMAgentBuilder
Set the LLM temperature (0.0 to 1.0). Higher values produce more random outputs.
SetMaxTokens
(tokens uint32) *LLMAgentBuilder
Set the maximum number of tokens to generate.
SetSessionId
(id string) *LLMAgentBuilder
Set the initial session ID for conversation tracking.
SetUserId
(id string) *LLMAgentBuilder
Set the user ID for multi-tenant scenarios.
SetTenantId
(id string) *LLMAgentBuilder
Set the tenant ID for multi-tenant isolation.
SetContextWindowSize
(size uint32) *LLMAgentBuilder
Set the sliding context window size (in conversation rounds).
SetOpenaiProvider
(apiKey, baseUrl, model string) *LLMAgentBuilder
Configure the OpenAI provider. Pass empty strings for defaults.
Build
() (*LLMAgent, error)
required
Build the agent. Returns error if configuration is invalid.

LLMAgent

AgentId
() (string, error)
Get the agent ID.
Name
() (string, error)
Get the agent name.
Ask
(question string) (string, error)
Simple Q&A without context retention. Each call is independent.
Chat
(message string) (string, error)
Multi-turn chat with context retention. Maintains conversation history.
ClearHistory
()
Clear the conversation history.
GetHistory
() []ChatMessage
Get the full conversation history as a slice of messages.
GetLastOutput
() (*AgentOutputInfo, error)
Get structured output from the last execution (tools used, token usage, etc.).

Examples

Example 1: Concurrent Goroutines

package main

import (
	"fmt"
	"os"
	"sync"

	mofa "github.com/mofa-org/mofa-go"
)

func main() {
	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		fmt.Println("OPENAI_API_KEY not set")
		return
	}

	// Create agent
	builder := mofa.NewLlmAgentBuilder()
	builder.SetName("Concurrent Agent")
	builder.SetOpenaiProvider(apiKey, "", "gpt-3.5-turbo")

	agent, err := builder.Build()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Concurrent queries using goroutines
	questions := []string{
		"What is Go?",
		"What is Rust?",
		"What is Python?",
	}

	var wg sync.WaitGroup
	results := make([]string, len(questions))

	for i, question := range questions {
		wg.Add(1)
		go func(idx int, q string) {
			defer wg.Done()
			answer, err := agent.Ask(q)
			if err != nil {
				fmt.Printf("Error for question %d: %v\n", idx, err)
				return
			}
			results[idx] = answer
		}(i, question)
	}

	wg.Wait()

	// Print results
	for i, result := range results {
		if len(result) > 100 {
			result = result[:100] + "..."
		}
		fmt.Printf("Result %d: %s\n", i+1, result)
	}
}

Example 2: Multi-Provider Support

package main

import (
	"fmt"
	"os"

	mofa "github.com/mofa-org/mofa-go"
)

func createOpenAIAgent() (*mofa.LLMAgent, error) {
	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		return nil, fmt.Errorf("OPENAI_API_KEY not set")
	}

	builder := mofa.NewLlmAgentBuilder()
	builder.SetName("OpenAI Agent")
	builder.SetOpenaiProvider(apiKey, "", "gpt-4")

	return builder.Build()
}

func main() {
	agent, err := createOpenAIAgent()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	response, _ := agent.Ask("What is Go?")
	fmt.Println(response)
}

Example 3: Session Management

package main

import (
	"fmt"

	mofa "github.com/mofa-org/mofa-go"
)

func main() {
	// Create in-memory session manager
	manager := mofa.NewSessionManagerInMemory()

	// Get or create a session
	session, err := manager.GetOrCreate("user-123")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Add messages
	session.AddMessage("user", "Hello!")
	session.AddMessage("assistant", "Hi there! How can I help?")
	session.AddMessage("user", "What's the weather like?")

	// Retrieve history
	history, err := session.GetHistory(10)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Println("Conversation history:")
	for _, msg := range history {
		fmt.Printf("%s: %s\n", msg.Role, msg.Content)
	}

	// Store metadata
	err = session.SetMetadata("user_name", `"Alice"`)
	if err != nil {
		fmt.Printf("Error setting metadata: %v\n", err)
	}

	err = session.SetMetadata("preferences", `{"theme": "dark"}`)
	if err != nil {
		fmt.Printf("Error setting metadata: %v\n", err)
	}

	// Retrieve metadata
	userName := session.GetMetadata("user_name")
	fmt.Printf("User name: %s\n", userName)

	// Save session
	err = manager.SaveSession(session)
	if err != nil {
		fmt.Printf("Error saving session: %v\n", err)
	}

	// List all sessions
	allSessions, err := manager.ListSessions()
	if err != nil {
		fmt.Printf("Error listing sessions: %v\n", err)
	}
	fmt.Printf("Total sessions: %d\n", len(allSessions))

	// Delete session
	deleted, err := manager.DeleteSession("user-123")
	if err != nil {
		fmt.Printf("Error deleting session: %v\n", err)
	}
	fmt.Printf("Session deleted: %v\n", deleted)
}

Example 4: Custom Tool Registration

package main

import (
	"encoding/json"
	"fmt"

	mofa "github.com/mofa-org/mofa-go"
)

// CalculatorTool implements FfiToolCallback
type CalculatorTool struct{}

func (t *CalculatorTool) Name() string {
	return "calculator"
}

func (t *CalculatorTool) Description() string {
	return "Perform basic arithmetic operations"
}

func (t *CalculatorTool) ParametersSchemaJson() string {
	return `{
		"type": "object",
		"properties": {
			"operation": {
				"type": "string",
				"enum": ["add", "subtract", "multiply", "divide"]
			},
			"a": {"type": "number"},
			"b": {"type": "number"}
		},
		"required": ["operation", "a", "b"]
	}`
}

func (t *CalculatorTool) Execute(argumentsJson string) mofa.FfiToolResult {
	var args struct {
		Operation string  `json:"operation"`
		A         float64 `json:"a"`
		B         float64 `json:"b"`
	}

	err := json.Unmarshal([]byte(argumentsJson), &args)
	if err != nil {
		return mofa.FfiToolResult{
			Success:    false,
			OutputJson: "null",
			Error:      mofa.StringPtr(err.Error()),
		}
	}

	var result float64
	switch args.Operation {
	case "add":
		result = args.A + args.B
	case "subtract":
		result = args.A - args.B
	case "multiply":
		result = args.A * args.B
	case "divide":
		if args.B == 0 {
			return mofa.FfiToolResult{
				Success:    false,
				OutputJson: "null",
				Error:      mofa.StringPtr("Division by zero"),
			}
		}
		result = args.A / args.B
	default:
		return mofa.FfiToolResult{
			Success:    false,
			OutputJson: "null",
			Error:      mofa.StringPtr(fmt.Sprintf("Unknown operation: %s", args.Operation)),
		}
	}

	output := map[string]float64{"result": result}
	outputJson, _ := json.Marshal(output)

	return mofa.FfiToolResult{
		Success:    true,
		OutputJson: string(outputJson),
		Error:      nil,
	}
}

func main() {
	// Create registry and register tool
	registry := mofa.NewToolRegistry()
	err := registry.RegisterTool(&CalculatorTool{})
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// List registered tools
	fmt.Println("Registered tools:")
	tools := registry.ListTools()
	for _, tool := range tools {
		fmt.Printf("  - %s: %s\n", tool.Name, tool.Description)
	}

	// Execute the tool
	result, err := registry.ExecuteTool(
		"calculator",
		`{"operation": "add", "a": 3, "b": 7}`,
	)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Success: %v\n", result.Success)
	fmt.Printf("Output: %s\n", result.OutputJson)
}

Example 5: Context and Cancellation

package main

import (
	"context"
	"fmt"
	"os"
	"time"

	mofa "github.com/mofa-org/mofa-go"
)

func askWithTimeout(agent *mofa.LLMAgent, question string, timeout time.Duration) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	type result struct {
		answer string
		err    error
	}

	resultChan := make(chan result, 1)

	go func() {
		answer, err := agent.Ask(question)
		resultChan <- result{answer, err}
	}()

	select {
	case <-ctx.Done():
		return "", fmt.Errorf("request timeout: %w", ctx.Err())
	case res := <-resultChan:
		return res.answer, res.err
	}
}

func main() {
	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		fmt.Println("OPENAI_API_KEY not set")
		return
	}

	builder := mofa.NewLlmAgentBuilder()
	builder.SetOpenaiProvider(apiKey, "", "gpt-3.5-turbo")

	agent, err := builder.Build()
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	// Ask with 5-second timeout
	answer, err := askWithTimeout(agent, "What is Go?", 5*time.Second)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	fmt.Printf("Answer: %s\n", answer)
}

Example 6: Error Handling Patterns

package main

import (
	"errors"
	"fmt"
	"os"

	mofa "github.com/mofa-org/mofa-go"
)

type AppError struct {
	Kind    string
	Message string
	Err     error
}

func (e *AppError) Error() string {
	return fmt.Sprintf("%s: %s", e.Kind, e.Message)
}

func (e *AppError) Unwrap() error {
	return e.Err
}

func createAgent(apiKey string) (*mofa.LLMAgent, error) {
	if apiKey == "" {
		return nil, &AppError{
			Kind:    "ConfigError",
			Message: "API key is required",
		}
	}

	builder := mofa.NewLlmAgentBuilder()
	builder.SetOpenaiProvider(apiKey, "", "gpt-3.5-turbo")

	agent, err := builder.Build()
	if err != nil {
		return nil, &AppError{
			Kind:    "RuntimeError",
			Message: "Failed to build agent",
			Err:     err,
		}
	}

	return agent, nil
}

func askQuestion(agent *mofa.LLMAgent, question string) (string, error) {
	answer, err := agent.Ask(question)
	if err != nil {
		return "", &AppError{
			Kind:    "LLMError",
			Message: "Failed to get answer",
			Err:     err,
		}
	}
	return answer, nil
}

func main() {
	apiKey := os.Getenv("OPENAI_API_KEY")

	agent, err := createAgent(apiKey)
	if err != nil {
		var appErr *AppError
		if errors.As(err, &appErr) {
			fmt.Printf("%s: %s\n", appErr.Kind, appErr.Message)
			if appErr.Err != nil {
				fmt.Printf("Underlying error: %v\n", appErr.Err)
			}
		} else {
			fmt.Printf("Error: %v\n", err)
		}
		return
	}

	answer, err := askQuestion(agent, "What is Go?")
	if err != nil {
		var appErr *AppError
		if errors.As(err, &appErr) {
			fmt.Printf("%s: %s\n", appErr.Kind, appErr.Message)
		} else {
			fmt.Printf("Error: %v\n", err)
		}
		return
	}

	fmt.Printf("Answer: %s\n", answer)
}

Error Handling

Standard Error Handling

package main

import (
	"fmt"
	"os"
	"strings"

	mofa "github.com/mofa-org/mofa-go"
)

func handleMoFaError(err error) {
	if err == nil {
		return
	}

	errorMsg := err.Error()

	switch {
	case strings.Contains(errorMsg, "ConfigError"):
		fmt.Printf("Configuration error: %v\n", err)
	case strings.Contains(errorMsg, "RuntimeError"):
		fmt.Printf("Runtime error: %v\n", err)
	case strings.Contains(errorMsg, "LLMError"):
		fmt.Printf("LLM provider error: %v\n", err)
	case strings.Contains(errorMsg, "IoError"):
		fmt.Printf("I/O error: %v\n", err)
	case strings.Contains(errorMsg, "InvalidArgument"):
		fmt.Printf("Invalid argument: %v\n", err)
	case strings.Contains(errorMsg, "ToolError"):
		fmt.Printf("Tool execution error: %v\n", err)
	case strings.Contains(errorMsg, "SessionError"):
		fmt.Printf("Session management error: %v\n", err)
	default:
		fmt.Printf("Unknown error: %v\n", err)
	}
}

func main() {
	builder := mofa.NewLlmAgentBuilder()
	builder.SetOpenaiProvider(
		os.Getenv("OPENAI_API_KEY"),
		"",
		"gpt-3.5-turbo",
	)

	agent, err := builder.Build()
	if err != nil {
		handleMoFaError(err)
		return
	}

	response, err := agent.Ask("Hello!")
	if err != nil {
		handleMoFaError(err)
		return
	}

	fmt.Println(response)
}

Retry Logic

package main

import (
	"fmt"
	"time"

	mofa "github.com/mofa-org/mofa-go"
)

func askWithRetry(agent *mofa.LLMAgent, question string, maxRetries int) (string, error) {
	var lastErr error

	for i := 0; i < maxRetries; i++ {
		answer, err := agent.Ask(question)
		if err == nil {
			return answer, nil
		}

		lastErr = err
		fmt.Printf("Attempt %d failed: %v\n", i+1, err)

		if i < maxRetries-1 {
			backoff := time.Duration(1<<uint(i)) * time.Second
			fmt.Printf("Retrying in %v...\n", backoff)
			time.Sleep(backoff)
		}
	}

	return "", fmt.Errorf("failed after %d retries: %w", maxRetries, lastErr)
}

Best Practices

1. Use Environment Variables

package main

import (
	"fmt"
	"os"
)

func getAPIKey() (string, error) {
	apiKey := os.Getenv("OPENAI_API_KEY")
	if apiKey == "" {
		return "", fmt.Errorf("OPENAI_API_KEY environment variable not set")
	}
	return apiKey, nil
}

2. Builder Pattern Helper

type AgentConfig struct {
	ID               string
	Name             string
	SystemPrompt     string
	Temperature      float32
	MaxTokens        uint32
	ContextWindowSize *uint32
}

func buildAgent(config AgentConfig, apiKey string) (*mofa.LLMAgent, error) {
	builder := mofa.NewLlmAgentBuilder()
	builder.SetId(config.ID)
	builder.SetName(config.Name)
	builder.SetSystemPrompt(config.SystemPrompt)
	builder.SetTemperature(config.Temperature)
	builder.SetMaxTokens(config.MaxTokens)

	if config.ContextWindowSize != nil {
		builder.SetContextWindowSize(*config.ContextWindowSize)
	}

	builder.SetOpenaiProvider(apiKey, "", "gpt-3.5-turbo")

	return builder.Build()
}

3. Resource Pool

type AgentPool struct {
	agents []*mofa.LLMAgent
	current int
	mu      sync.Mutex
}

func NewAgentPool(size int, apiKey string) (*AgentPool, error) {
	agents := make([]*mofa.LLMAgent, size)

	for i := 0; i < size; i++ {
		builder := mofa.NewLlmAgentBuilder()
		builder.SetId(fmt.Sprintf("agent-%d", i))
		builder.SetOpenaiProvider(apiKey, "", "gpt-3.5-turbo")

		agent, err := builder.Build()
		if err != nil {
			return nil, err
		}
		agents[i] = agent
	}

	return &AgentPool{agents: agents}, nil
}

func (p *AgentPool) Get() *mofa.LLMAgent {
	p.mu.Lock()
	defer p.mu.Unlock()

	agent := p.agents[p.current]
	p.current = (p.current + 1) % len(p.agents)
	return agent
}

Go Module Setup

go.mod Example

module github.com/example/mofa-app

go 1.21

require github.com/mofa-org/mofa-go v0.1.0

CGO Configuration

# Set library path for CGO
export CGO_LDFLAGS="-L/path/to/mofa/target/release"
export LD_LIBRARY_PATH="/path/to/mofa/target/release:$LD_LIBRARY_PATH"

# Build
go build -o app main.go

# Run
./app

Troubleshooting

Library Not Found

# If you get: error while loading shared libraries: libmofa_ffi.so

# Build the library
cd mofa
cargo build --release --features uniffi -p mofa-ffi

# Set library path
export LD_LIBRARY_PATH=/path/to/mofa/target/release:$LD_LIBRARY_PATH

# Or copy to system library directory
sudo cp target/release/libmofa_ffi.so /usr/local/lib/
sudo ldconfig

CGO Issues

# If you get CGO errors
export CGO_ENABLED=1
export CC=gcc

# Verify CGO is working
go env CGO_ENABLED

API Key Issues

import "os"

apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
	panic("OPENAI_API_KEY not set. Set it with: export OPENAI_API_KEY=your-key")
}

Next Steps

Python Bindings

Python integration guide

Java Bindings

Java integration with Maven/Gradle

Examples

Browse full Go examples

API Reference

Complete API documentation

Build docs developers (and LLMs) love