Skip to content

netologist/SamurAI

Repository files navigation

Screenshot 2025-11-24 at 18 50 39

SamurAI Agent Framework

An educational Rust framework for building autonomous AI agents with memory, planning, tool execution, and safety guardrails.

Overview

This framework demonstrates how to build production-quality AI agents in Rust, with clear architectural boundaries and incremental complexity. It's designed as a learning resource with a modular workspace structure where each component can be understood independently.

Motivation

Building AI agents requires orchestrating multiple complex systems: language models, memory management, tool execution, and safety constraints. This framework provides a clear, well-structured implementation that:

  • Teaches by Example: Each crate demonstrates a specific architectural pattern (traits, async execution, error handling)
  • Production-Ready Patterns: Uses industry-standard libraries and follows Rust best practices
  • Incremental Learning: Components can be studied independently, building from simple to complex
  • Safety First: Demonstrates how to build guardrails and validation into AI systems
  • Extensible Design: Easy to add new LLM providers, tools, and safety rules

This project serves as both a functional framework and a comprehensive tutorial on building AI systems in Rust.

Architecture

The framework is organized into three layers, with clear separation of concerns and minimal coupling:

┌─────────────────────────────────────────────────────────────────┐
│                        CLI & Examples                           │
│                    (User Interface Layer)                       │
└────────────────────────────┬────────────────────────────────────┘
                             │
┌────────────────────────────┴────────────────────────────────────┐
│                       Intelligence Layer                        │
│    ┌──────────┐  ┌──────────┐  ┌───────────┐  ┌──────────┐      │
│    │ Planner  │  │ Executor │  │Guardrails │  │  Rules   │      │
│    └──────────┘  └──────────┘  └───────────┘  └──────────┘      │
│         │              │               │              │         │
└────--───┼──────────────┼───────────────┼──────────────┼─────────┘
          │              │               │              │
┌──────--─┴──────────────┴───────────────┴──────────────┴─────────┐
│                      Capability Layer                           │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                       │
│  │   LLM    │  │  Memory  │  │  Tools   │                       │
│  └──────────┘  └──────────┘  └──────────┘                       │
│       │              │               │                          │
└───────┼──────────────┼───────────────┼──────────────────────────┘
        │              │               │
┌───────┴──────────────┴───────────────┴──────────────────────────┐
│                      Foundation Layer                           │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐                   │
│  │   Core   │  │  Config  │  │Communication │                   │
│  └──────────┘  └──────────┘  └──────────────┘                   │
└─────────────────────────────────────────────────────────────────┘

Foundation Layer

  • core - Fundamental types (Message, Role, AgentError) and error handling
  • config - Configuration management from files and environment variables
  • communication - HTTP client utilities with retry logic and timeout handling

Capability Layer

  • llm - LLM provider interfaces (OpenAI, Anthropic) with unified API
  • memory - Conversation storage with token-aware context management
  • tools - Tool system with registry and example implementations (Calculator, FileReader, WebSearch)

Intelligence Layer

  • planner - Task decomposition using LLM reasoning (ReAct pattern)
  • executor - Step-by-step execution of plans with tool invocation
  • guardrails - Safety validation before execution (file paths, rate limits)
  • rules - Behavior customization through prompt modification

Interface Layer

  • cli - Command-line interface (REPL and single-turn modes)
  • examples - Example agents demonstrating framework capabilities

Project Structure

ai-agent-framework/
├── Cargo.toml              # Workspace manifest
├── README.md               # This file
├── core/                   # Fundamental traits and types
├── config/                 # Configuration management
├── communication/          # HTTP client and API utilities
├── llm/                    # LLM provider implementations
├── memory/                 # Conversation storage
├── tools/                  # Tool trait and implementations
├── planner/                # Task decomposition
├── executor/               # Step execution
├── guardrails/             # Safety validation
├── rules/                  # Behavior customization
├── cli/                    # Command-line interface
└── examples/               # Example agents

Getting Started

Prerequisites

Quick Start

Option A: Using Ollama (Local, No API Key Required)

With Docker (Recommended):

git clone https://github.com/netologist/samurai ai-agent-framework
cd ai-agent-framework

# One-command setup (starts container + pulls model)
make ollama-setup
# Or: ./scripts/setup-ollama.sh

# Run the chatbot
make run-ollama

Native Installation:

# Install Ollama from https://ollama.ai
ollama pull llama2

# Clone and run
git clone https://github.com/netologist/samurai ai-agent-framework
cd ai-agent-framework
cargo build --workspace
cargo run --example ollama_chatbot

Option B: Using OpenAI or Anthropic

  1. Clone the repository:
git clone https://github.com/netologist/samurai ai-agent-framework
cd ai-agent-framework
  1. Set up your API key:
# For OpenAI (GPT-3.5, GPT-4)
export OPENAI_API_KEY="sk-..."

# OR for Anthropic (Claude)
export ANTHROPIC_API_KEY="sk-ant-..."
  1. Build the project:
cargo build --workspace
  1. Run your first agent:
cargo run --example chatbot

You should see an interactive prompt where you can chat with the AI agent!

Running Examples

The framework includes four example agents that demonstrate different capabilities:

1. Ollama Chatbot (No API Key Required!)

Conversational agent using local open-source models via Ollama.

cargo run --example ollama_chatbot

What it demonstrates:

  • Running agents completely locally
  • Using open-source models (llama2, mistral, etc.)
  • No API costs or internet dependency

Prerequisites: Ollama installed with a model pulled (e.g., ollama pull llama2)

2. Simple Chatbot

Basic conversational agent with memory but no tools.

cargo run --example chatbot

What it demonstrates:

  • LLM integration (OpenAI/Anthropic/Ollama)
  • Conversation memory
  • Multi-turn interactions

3. Research Assistant

Agent with web search and file reading capabilities.

cargo run --example research

What it demonstrates:

  • Tool integration and execution
  • Multi-step planning
  • Tool result handling

4. File Manager

Agent with file operations and safety guardrails.

cargo run --example file_manager

What it demonstrates:

  • Guardrail validation
  • File path restrictions
  • Safe tool execution

Basic Usage

Here's a minimal example of creating an agent:

use ai_agent_framework::*;

#[tokio::main]
async fn main() -> Result<()> {
    // Load configuration
    let config = config::load_from_file("config.yaml")?;
    
    // Initialize components
    let llm = llm::create_provider(&config.llm)?;
    let memory = memory::InMemoryStore::new();
    let tools = tools::ToolRegistry::new();
    
    // Create planner and executor
    let planner = planner::Planner::new(llm.clone(), memory.clone());
    let executor = executor::Executor::new(tools, memory);
    
    // Process a query
    let plan = planner.create_plan("What is 2 + 2?", &tools.list_tools()).await?;
    let result = executor.execute_plan(plan).await?;
    
    println!("Result: {}", result.final_response);
    Ok(())
}

Configuration

Create a config.yaml file:

llm:
  provider: openai  # or "anthropic"
  model: gpt-4
  api_key: ${OPENAI_API_KEY}
  temperature: 0.7
  max_tokens: 2000

memory:
  max_messages: 100
  token_budget: 4000

tools:
  - calculator
  - file_reader

guardrails:
  - file_path
  - rate_limit

Running Tests

# Run all tests
cargo test --workspace

# Run tests for a specific crate
cargo test -p core

# Run integration tests
cargo test --test agent_flow

# Run with output
cargo test -- --nocapture

Crate Documentation

Each crate has a specific responsibility and can be understood independently:

Core Crate (core/)

Purpose: Fundamental types and error handling used throughout the framework.

Key Types:

  • Message - Represents conversation turns with role, content, and timestamp
  • Role - Enum for System, User, and Assistant roles
  • AgentError - Common error type with structured error information using thiserror
  • Result<T> - Type alias for std::result::Result<T, AgentError>

Dependencies: serde, thiserror, chrono

When to use: Import core types when building any framework component.


Config Crate (config/)

Purpose: Configuration management from files and environment variables.

Key Functions:

  • load_from_file(path) - Parse YAML/TOML configuration files
  • from_env() - Build configuration from environment variables
  • merge(file, env) - Combine file and environment configs (env takes precedence)

Configuration Structure:

  • AgentConfig - Top-level configuration
  • LLMConfig - Provider settings (provider, model, api_key, temperature, max_tokens)
  • MemoryConfig - Memory settings (max_messages, token_budget)

Dependencies: serde, serde_yaml, core

When to use: Load configuration at application startup before initializing other components.


Communication Crate (communication/)

Purpose: HTTP client utilities with retry logic and error handling.

Key Types:

  • ApiClient - Wrapper around reqwest with timeout and retry support
  • with_retry() - Exponential backoff retry function (max 3 attempts)

Features:

  • 30-second default timeout
  • Automatic retry on network errors and 5xx responses
  • JSON serialization/deserialization
  • Structured error conversion

Dependencies: reqwest, tokio, serde_json, core

When to use: Use for all HTTP API calls to LLM providers.


LLM Crate (llm/)

Purpose: Unified interface for multiple LLM providers.

Key Trait:

  • LLMProvider - Async trait with send_message(&self, messages: &[Message]) -> Result<String>

Implementations:

  • OpenAIProvider - OpenAI API (GPT-3.5, GPT-4)
  • AnthropicProvider - Anthropic API (Claude models)
  • OllamaProvider - Local Ollama server (llama2, mistral, phi, etc.)

Factory:

  • create_provider(config) - Creates provider instance from configuration

Supported Providers:

  • OpenAI: Cloud-based, requires API key, supports GPT models
  • Anthropic: Cloud-based, requires API key, supports Claude models
  • Ollama: Local execution, no API key needed, supports open-source models

Dependencies: async-trait, communication, config, core

When to use: Initialize at startup and use for all LLM interactions.


Memory Crate (memory/)

Purpose: Conversation storage with token-aware context management.

Key Trait:

  • MemoryStore - Trait for different storage backends

Implementations:

  • InMemoryStore - Vec-based storage for MVP
  • ConversationHistory - Wrapper with helper methods

Key Methods:

  • add_message(message) - Append to conversation history
  • get_recent(limit) - Retrieve last N messages
  • get_within_budget(tokens) - Token-aware retrieval
  • clear() - Reset conversation

Dependencies: tiktoken-rs, core

When to use: Store all conversation turns and retrieve context for LLM calls.


Tools Crate (tools/)

Purpose: Extensible tool system for agent capabilities.

Key Trait:

  • Tool - Async trait with name(), description(), parameters_schema(), execute(params)

Registry:

  • ToolRegistry - HashMap-based tool storage and lookup

Built-in Tools:

  • Calculator - Arithmetic operations (add, subtract, multiply, divide)
  • FileReader - Read file contents with error handling
  • WebSearchStub - Mock web search for demonstration

Dependencies: async-trait, serde_json, core

When to use: Register tools at startup; executor invokes them during plan execution.


Planner Crate (planner/)

Purpose: LLM-based task decomposition using ReAct pattern.

Key Types:

  • Plan - Sequence of steps with reasoning
  • Step - Enum: ToolCall, Reasoning, Response
  • ToolCall - Structured tool invocation (name + parameters)
  • Planner - Orchestrates plan generation

Key Methods:

  • create_plan(goal, tools) - Generate plan from user goal
  • validate_plan(plan, registry) - Ensure all tools exist

Dependencies: llm, tools, memory, core

When to use: Convert user queries into executable plans before execution.


Executor Crate (executor/)

Purpose: Sequential execution of plans with tool invocation.

Key Types:

  • Executor - Stateful executor with tool registry and memory
  • ExecutionResult - Outcome with success status and final response
  • StepResult - Individual step execution result

Key Methods:

  • execute_plan(plan) - Run all steps sequentially
  • execute_step(step) - Run single step
  • handle_tool_call(tool_call) - Invoke tool with parameters

Dependencies: planner, tools, memory, core

When to use: Execute validated plans after guardrail checks.


Guardrails Crate (guardrails/)

Purpose: Safety validation before plan execution.

Key Trait:

  • Guardrail - Trait with validate(plan) -> Result<()>

Registry:

  • GuardrailRegistry - Collection of active guardrails

Built-in Guardrails:

  • FilePathGuardrail - Restrict file operations to allowed directories
  • RateLimitGuardrail - Enforce API call limits per minute

Dependencies: planner, core

When to use: Validate plans before execution to prevent unauthorized actions.


Rules Crate (rules/)

Purpose: Customize agent behavior through prompt modification.

Key Trait:

  • Rule - Trait with apply(context) and priority()

Engine:

  • RuleEngine - Ordered collection of rules

Built-in Rules:

  • ResponseLengthRule - Limit response word count
  • ToneRule - Guide response style (Formal, Casual, Technical)

Dependencies: core

When to use: Apply rules before planning to modify LLM behavior.


CLI Crate (cli/)

Purpose: Command-line interface for agent interaction.

Modes:

  • REPL Mode - Interactive conversation with history
  • Single-Turn Mode - One query, one response

Features:

  • Colored output (errors in red, success in green)
  • Line editing with rustyline
  • Conversation history display
  • Verbose logging option

Dependencies: clap, rustyline, colored, all framework crates

When to use: Run as binary for testing and demonstration.


Examples Crate (examples/)

Purpose: Demonstrate framework usage patterns.

Examples:

  1. chatbot.rs - Basic conversation (LLM + memory only)
  2. research.rs - Tool-enabled agent (web search, file reading)
  3. file_manager.rs - Guardrail demonstration (safe file operations)

Configuration: Each example has a corresponding YAML file in examples/configs/

When to use: Study examples to learn framework patterns and best practices.

Development

Building

# Build all crates
cargo build --workspace

# Build in release mode
cargo build --release --workspace

Testing

# Run all tests
cargo test --workspace

# Run tests for a specific crate
cargo test -p core

# Run integration tests
cargo test --test agent_flow

Documentation

# Generate and open documentation
cargo doc --open --no-deps

# Check documentation
cargo doc --workspace --no-deps

Code Quality

# Run clippy for linting
cargo clippy --workspace -- -D warnings

# Format code
cargo fmt --all

# Check formatting
cargo fmt --all -- --check

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines on:

  • Adding new LLM providers
  • Creating custom tools
  • Implementing guardrails and rules
  • Code style and testing requirements

License

This project is licensed under the MIT License - see the LICENSE file for details.

Extending the Framework

Adding a New LLM Provider

  1. Create a new module in llm/src/your_provider/
  2. Define request/response types matching the provider's API
  3. Implement the LLMProvider trait
  4. Add to the factory in llm/src/factory.rs

Example:

// llm/src/my_provider/mod.rs
use async_trait::async_trait;
use crate::provider::LLMProvider;

pub struct MyProvider {
    api_key: String,
    client: ApiClient,
}

#[async_trait]
impl LLMProvider for MyProvider {
    async fn send_message(&self, messages: &[Message]) -> Result<String> {
        // Implementation
    }
}

Creating a Custom Tool

  1. Create a struct for your tool
  2. Implement the Tool trait
  3. Define parameter schema using JSON Schema
  4. Register with ToolRegistry

Example:

use async_trait::async_trait;
use tools::Tool;

pub struct WeatherTool;

#[async_trait]
impl Tool for WeatherTool {
    fn name(&self) -> &str { "weather" }
    
    fn description(&self) -> &str {
        "Get current weather for a location"
    }
    
    fn parameters_schema(&self) -> Value {
        json!({
            "type": "object",
            "properties": {
                "location": {"type": "string"}
            },
            "required": ["location"]
        })
    }
    
    async fn execute(&self, params: Value) -> Result<Value> {
        let location = params["location"].as_str().unwrap();
        // Call weather API
        Ok(json!({"temperature": 72, "condition": "sunny"}))
    }
}

Implementing a Custom Guardrail

  1. Create a struct for your guardrail
  2. Implement the Guardrail trait
  3. Add validation logic in the validate method
  4. Register with GuardrailRegistry

Example:

use guardrails::Guardrail;

pub struct TokenLimitGuardrail {
    max_tokens: usize,
}

impl Guardrail for TokenLimitGuardrail {
    fn name(&self) -> &str { "token_limit" }
    
    fn validate(&self, plan: &Plan) -> Result<()> {
        let total_tokens = estimate_plan_tokens(plan);
        if total_tokens > self.max_tokens {
            return Err(AgentError::GuardrailViolation(
                format!("Plan exceeds token limit: {} > {}", 
                    total_tokens, self.max_tokens)
            ));
        }
        Ok(())
    }
}

Blog Series

This framework was developed as part of a comprehensive blog series on building AI agents in Rust. Each day of development corresponds to a blog post that explains the concepts, design decisions, and implementation details.

Blog Series: Building AI Agents in Rust - A 14-Day Journey

Topics covered:

  • Day 1-3: Foundation (workspace setup, configuration, HTTP communication)
  • Day 4-7: Capabilities (LLM integration, memory, tools)
  • Day 8-11: Intelligence (planning, execution, guardrails, rules)
  • Day 12-14: Interface (CLI, examples, testing, documentation)

Resources

Rust Learning

AI Agent Architecture

Crate Documentation

Troubleshooting

API Key Issues

# Verify your API key is set
echo $OPENAI_API_KEY

# If empty, set it
export OPENAI_API_KEY="sk-..."

Build Errors

# Clean and rebuild
cargo clean
cargo build --workspace

# Update dependencies
cargo update

Test Failures

# Run tests with output
cargo test -- --nocapture

# Run specific test
cargo test test_name -- --nocapture

Integration Test API Costs

Integration tests that call real LLM APIs are marked with #[ignore]. Run them explicitly:

cargo test --test openai_integration -- --ignored

Performance Considerations

This framework prioritizes clarity and learning over performance. For production use, consider:

  • Async Runtime: Uses tokio with full features; minimize features for smaller binaries
  • Token Counting: tiktoken-rs is OpenAI-specific; implement provider-specific counting
  • Memory Storage: InMemoryStore is not persistent; implement file or database backends
  • Error Handling: Detailed errors are helpful for debugging but may expose sensitive info
  • Retry Logic: Fixed exponential backoff; consider adaptive strategies for production

Acknowledgments

This framework was built as an educational resource to demonstrate production-quality AI agent architecture in Rust. It prioritizes clarity and learning over performance optimization.

Special thanks to:

  • The Rust community for excellent documentation and libraries
  • OpenAI and Anthropic for accessible LLM APIs
  • Contributors and learners who provide feedback and improvements

About

Simple Agentic AI Framework

Resources

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages