Soka
Ruby AI Agent Framework based on ReAct Pattern
Features • Installation • Quick Start • Advanced Features • API Documentation • Examples • Contributing
Soka is a Ruby AI Agent framework based on the ReAct (Reasoning and Acting) pattern, supporting multiple AI providers, offering an object-oriented tool system and intelligent memory management. It enables you to quickly build intelligent agents that handle complex reasoning and action tasks.
Features
- 🤖 Multi AI Provider Support: Google Gemini, OpenAI, Anthropic
- 🛠️ Object-Oriented Tool System: Grape API-like parameter definition and validation
- 🧠 Intelligent Memory Management: Conversation history and thought process recording
- 🔄 ReAct Reasoning Pattern: Tagged thought-action-observation loop
- ⚡ Flexible Configuration System: Global and instance-level configuration options
- 🔁 Error Handling and Retry: Built-in exponential backoff retry mechanism
- 🧪 Test Friendly: Complete test helper tools
- 📝 Full Type Support: Using dry-rb ecosystem
- 🚀 Modular Design: Easy to extend and maintain
- 💾 Built-in Caching Mechanism: Improve performance and save costs
Installation
Add the following to your Gemfile:
gem 'soka'
Then execute:
bundle install
Or install directly:
gem install soka
Quick Start
1. Set up API Key
# Method 1: Environment variable
export GEMINI_API_KEY="your-api-key"
# Method 2: Create .env file
echo "GEMINI_API_KEY=your-api-key" > .env
Get API Keys:
2. Basic Usage
require 'soka'
# Create a simple time tool
class TimeTool < Soka::AgentTool
desc "Get current time"
def call
Time.now.strftime('%Y-%m-%d %H:%M:%S')
end
end
# Create Agent
class SimpleAgent < Soka::Agent
tool TimeTool
end
# Execute
agent = SimpleAgent.new
result = agent.run("What time is it?")
puts result.final_answer
3. Run Examples
# Run full example (API key required)
ruby examples/1_basic.rb
Rails Integration
Soka Rails
For Rails applications, we provide a dedicated gem soka-rails
that offers seamless integration with Rails conventions:
# Gemfile
gem 'soka-rails'
Features
- 🚂 Native Rails Integration: Following Rails conventions and best practices
- 📁 Auto-loading Support: Automatically loads the
app/soka
directory - 🛠️ Generator Support: Quickly generate Agent and Tool templates
- ⚙️ Rails Configuration Integration: Uses Rails' configuration system
- 🧪 Rails Testing Integration: Seamless integration with RSpec
- 🔄 Rails Lifecycle Hooks: Integrates with Rails logging and error tracking
Quick Setup
# Install the gem
bundle add soka-rails
# Run the installation generator
rails generate soka:install
# Generate an agent
rails generate soka:agent customer_support
# Generate a tool
rails generate soka:tool order_lookup order_id:string
Basic Usage in Rails
# app/soka/agents/customer_support_agent.rb
class CustomerSupportAgent < ApplicationAgent
tool OrderLookupTool
tool UserInfoTool
end
# app/controllers/conversations_controller.rb
class ConversationsController < ApplicationController
def create
agent = CustomerSupportAgent.new
result = agent.run(params[:message])
render json: {
answer: result.final_answer,
status: result.status
}
end
end
For more details, visit the soka-rails repository.
Core Concepts
Global Configuration
Soka.setup do |config|
# AI Configuration
config.ai do |ai|
ai.provider = :gemini # :gemini, :openai, :anthropic
ai.model = 'gemini-2.5-flash-lite'
ai.api_key = ENV['GEMINI_API_KEY']
end
# Performance Configuration
config.performance do |perf|
perf.max_iterations = 10 # ReAct max iterations
end
# Default tools
config.tools = [SearchTool, TimeTool]
end
Defining Tools
Tools are functional modules that Agents can use:
class SearchTool < Soka::AgentTool
desc "Search the web for information"
params do
requires :query, String, desc: "The query to search for"
optional :location, String, desc: "Location context", default: "Global"
# Parameter validation
validates :query, presence: true, length: { minimum: 1, maximum: 500 }
validates :location, inclusion: { in: %w[Global US Europe Asia] }, allow_nil: true
end
def call(query:, location: "Global")
# Actual search logic
perform_search(query, location)
rescue => e
{ error: e.message, tool: self.class.name }
end
private
def perform_search(query, location)
# Here you can call real search APIs
"Search results for #{query} in #{location}..."
end
end
Defining Agents
Agents are the entities that perform ReAct reasoning:
class WeatherAgent < Soka::Agent
# AI settings (override global settings)
provider :gemini
model 'gemini-2.5-flash-lite'
max_iterations 10
# Register tools
tool SearchTool
tool TimeTool
# Conditional tool registration
tool CalculatorTool, if: -> { ENV['ENABLE_CALCULATOR'] == 'true' }
# Batch registration
tools SearchTool, TimeTool, WeatherTool
# Custom tool (functional) - requires description as second parameter
tool :get_weather, "Get weather for a location"
# Custom instructions (optional)
instructions "You are a weather expert. Provide detailed weather information."
# Thinking language (optional)
think_in 'en' # Default is 'en'
# Lifecycle hooks
before_action :track_action
after_action :update_metrics
on_error :handle_error
private
# Method implementation for functional tool
# Note: This is currently experimental and not fully implemented
def get_weather(location:)
"#{location} is currently sunny, temperature 25°C"
end
def track_action(action)
# Track action execution
@action_count ||= 0
@action_count += 1
end
def update_metrics(result)
# Update metrics
# metrics.record(result)
end
def handle_error(error, context)
# Handle errors
:continue # or :stop to interrupt execution
end
end
Using Agents
Block Mode (Real-time Feedback)
Suitable for scenarios that need to display the execution process:
agent = WeatherAgent.new
agent.run('What is the weather in Tokyo today?') do |event|
case event.type
when :thought
puts "💭 Thinking: #{event.content}"
when :action
puts "🔧 Action: Using tool #{event.content[:tool]}"
when :observation
puts "👀 Observation: #{event.content}"
when :final_answer
puts "✅ Answer: #{event.content}"
when :error
puts "❌ Error: #{event.content}"
end
end
Direct Mode (Get Result)
Suitable for scenarios that only need the final result:
agent = WeatherAgent.new
result = agent.run('What is the weather in Tokyo today?')
# Result object provides rich information
puts result.final_answer # Final answer
puts result.iterations # Number of iterations used
puts result.status # :success, :failed, :max_iterations_reached
puts result.execution_time # Execution time (if recorded)
# Check execution status
if result.successful?
puts "Success: #{result.final_answer}"
elsif result.failed?
puts "Failed: #{result.error}"
elsif result.max_iterations_reached?
puts "Max iterations reached"
end
Memory Management
Basic Conversation Memory
# Initialize Agent with history
memory = [
{ role: 'user', content: 'My name is John' },
{ role: 'assistant', content: 'Hello John! Nice to meet you.' }
]
agent = WeatherAgent.new(memory: memory)
result = agent.run('What is my name?')
# => "Your name is John."
# Memory updates automatically
puts agent.memory
# <Soka::Memory> [
# { role: 'user', content: 'My name is John' },
# { role: 'assistant', content: 'Hello John! Nice to meet you.' },
# { role: 'user', content: 'What is my name?' },
# { role: 'assistant', content: 'Your name is John.' }
# ]
Thought Process Memory
# View complete thought process
puts agent.thoughts_memory
# <Soka::ThoughtsMemory> (3 sessions, 2 successful, 1 failed, avg iterations: 2.3)
# Get detailed information for specific session
last_session = agent.thoughts_memory.last_session
puts last_session[:thoughts] # All thinking steps
puts last_session[:final_answer] # Final answer for that execution
Advanced Features
Custom Instructions
Customize your agent's personality and response style:
class FriendlyAgent < Soka::Agent
provider :gemini
# Method 1: Static string instructions
instructions <<~PROMPT
You are a friendly, helpful assistant.
Use casual language and be encouraging.
Add emojis when appropriate.
PROMPT
end
# Method 2: Dynamic instructions using method
class DynamicAgent < Soka::Agent
provider :gemini
# Reference a method for dynamic instructions
instructions :generate_instructions
private
def generate_instructions
hour = Time.now.hour
mood = hour < 12 ? 'cheerful morning' : 'relaxed afternoon'
<<~PROMPT
You are a #{mood} assistant.
Current Time: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}
Adjust your tone based on the time of day.
PROMPT
end
end
# Method 3: Override at runtime
agent = FriendlyAgent.new(
instructions: 'Be more formal and professional.'
)
result = agent.run("Help me with this task")
Multilingual Thinking (Think In)
Optimize reasoning for specific languages:
class GlobalAgent < Soka::Agent
provider :gemini
# Set default thinking language
think_in 'zh-TW' # Think in Traditional Chinese
end
# Or set dynamically
agent = GlobalAgent.new(think_in: 'ja-JP')
result = agent.run("幫我解決這個問題") # Input in Chinese
# Agent thinks in Japanese internally, responds in Chinese
Key Points:
- Thinking language affects internal reasoning only
- Responses adapt to user's input language
- Default is English (
'en'
) - No automatic language detection (explicit setting required)
- Improves reasoning quality for language-specific contexts
ReAct Flow Format
Soka uses a tagged ReAct format:
<Thought>I need to search for weather information in Tokyo</Thought>
<Action>
{"tool": "search", "parameters": {"query": "Tokyo weather", "location": "Japan"}}
</Action>
<Observation>Tokyo today: Sunny, temperature 28°C, humidity 65%</Observation>
<Thought>I have obtained the weather information and can answer the user now</Thought>
<FinalAnswer>Today in Tokyo it's sunny with a temperature of 28°C and humidity of 65%.</FinalAnswer>
Result Object Structure
# Result object attributes
result.input # User input
result.thoughts # Array of thinking steps
result.final_answer # Final answer
result.status # Status (:success, :failed, :max_iterations_reached)
result.error # Error message (if any)
result.execution_time # Execution time (seconds)
result.iterations # Number of iterations
# Complete structure
{
input: "User input",
thoughts: [
{
step: 1,
thought: "Thinking content",
action: { tool: "search", params: { query: "..." } },
observation: "Observation result"
}
],
final_answer: "Final answer",
status: :success, # :success, :failed, :max_iterations_reached
error: nil, # Error message (if any)
execution_time: 1.23, # Execution time (seconds)
iterations: 2, # Number of iterations
created_at: Time # Creation time
}
Test Support
Soka provides complete test helper tools:
RSpec.describe WeatherAgent do
include Soka::TestHelpers
it "answers weather questions" do
# Mock AI response
mock_ai_response({
thoughts: [
{
step: 1,
thought: "Need to search for weather information",
action: { tool: "search", params: { query: "Tokyo weather" } },
observation: "Tokyo is sunny today"
}
],
final_answer: "Tokyo is sunny today."
})
# Mock tool response
mock_tool_response(SearchTool, "Tokyo is sunny today")
agent = described_class.new
result = agent.run("What's the weather in Tokyo?")
expect(result).to be_successful
expect(result.final_answer).to include("sunny")
expect(result).to have_thoughts_count(1)
expect(result.status).to eq(:success)
end
it "handles tool errors gracefully" do
allow_tool_to_fail(SearchTool, StandardError.new("API error"))
agent = described_class.new
result = agent.run("Search test")
expect(result).to be_failed
expect(result.error).to include("API error")
end
end
Custom Engines
You can implement your own reasoning engine:
class CustomEngine < Soka::Engines::Base
def reason(task, &block)
# Implement custom reasoning logic
context = Soka::Engines::ReasoningContext.new(
task: task,
event_handler: block,
max_iterations: max_iterations
)
# Use emit_event to send events
emit_event(:thought, "Starting reasoning...", &block)
# Perform reasoning...
# Return result (using Struct)
Soka::Engines::React::ReasonResult.new(
input: task,
thoughts: thoughts,
final_answer: answer,
status: :success
)
end
end
# Use custom engine
agent = MyAgent.new(engine: CustomEngine)
Examples
The examples/
directory contains several examples demonstrating different features of Soka, ordered from basic to advanced:
1. Basic Example (examples/1_basic.rb
)
Demonstrates the fundamental usage of Soka with simple tools:
- Creating basic tools (SearchTool, TimeTool)
- Setting up an agent
- Running queries with event handling
- Direct result access
2. Event Handling (examples/2_event_handling.rb
)
Shows how to handle real-time events during agent execution:
- Event-based response handling
- Different event types (thought, action, observation, final_answer)
- Multi-step task processing
- Direct result mode vs event mode
3. Memory Management (examples/3_memory.rb
)
Illustrates memory features and conversation context:
- Using Soka::Memory for conversation history
- Array format for initial memory
- Tool-based memory storage and recall
- Accessing complete conversation history
- Viewing thinking processes
4. Lifecycle Hooks (examples/4_hooks.rb
)
Demonstrates lifecycle hooks for monitoring and control:
-
before_action
for pre-processing -
after_action
for post-processing -
on_error
for error handling - Tracking agent activity and metrics
5. Error Handling (examples/5_error_handling.rb
)
Shows robust error handling mechanisms:
- Tool errors and agent-level errors
- Using
on_error
hooks - Continuing execution after errors
- Error result inspection
6. Retry Mechanisms (examples/6_retry.rb
)
Demonstrates retry strategies for reliability:
- Handling transient failures
- Exponential backoff
- Rate limiting scenarios
- Configuring retry behavior
7. Conditional Tools (examples/7_tool_conditional.rb
)
Shows dynamic tool loading based on conditions:
- Environment-based tool loading
- Role-based access control
- Feature flag integration
- Time-based availability
8. Multi-Provider Support (examples/8_multi_provider.rb
)
Demonstrates using different AI providers:
- Configuring Gemini, OpenAI, and Anthropic
- Provider-specific features
- Comparing outputs across models
- Cost optimization strategies
9. Custom Instructions (examples/9_custom_instructions.rb
)
Shows how to customize agent personality:
- Setting instructions at class level
- Runtime instruction override
- Creating different agent personalities
- Use cases for different domains
10. Dynamic Instructions (examples/10_dynamic_instructions.rb
)
Demonstrates dynamic instruction generation:
- Using methods to generate instructions dynamically
- Time-based instruction changes
- Environment-based configuration
- Session-based personality switching
11. Multilingual Thinking (examples/11_think_in_languages.rb
)
Demonstrates language-specific reasoning:
- Setting thinking language with
think_in
- Class-level vs instance-level configuration
- Performance comparison across languages
- Cultural context optimization
To run any example:
# Make sure you have the required API keys in your .env file
ruby examples/1_basic.rb
API Documentation
Supported AI Providers
Google Gemini
- Models:
gemini-2.5-pro
,gemini-2.5-flash
,gemini-2.5-flash-lite
- Environment variable:
GEMINI_API_KEY
- Features: Fast response, cost-effective
- Default model:
gemini-2.5-flash-lite
OpenAI
- Models:
gpt-5
,gpt-5-mini
,gpt-5-nano
- Environment variable:
OPENAI_API_KEY
- Features: Streaming support, powerful reasoning
Anthropic
- Models:
claude-opus-4-0
,claude-sonnet-4-0
,claude-3-5-haiku-latest
- Environment variable:
ANTHROPIC_API_KEY
- Features: Long context support, excellent code understanding
Model Testing Results
Based on extensive testing with the Soka framework, here are the recommended models for optimal performance:
Provider | ✅ Recommended Models | ⚠️ Not Recommended | Notes |
---|---|---|---|
Gemini |
gemini-2.5-flash-lite ⭐gemini-2.5-pro
|
gemini-2.5-flash |
gemini-2.5-flash-lite: Fast, cost-effective, with good performance Avoid gemini-2.5-flash: Often skips thinking process when using tools, directly jumps to tool usage |
OpenAI |
gpt-5 gpt-5-mini ⭐ |
gpt-5-nano |
gpt-5-mini: Good balance of price, speed, and effectiveness Avoid gpt-5-nano: Can enter infinite tool-calling loops, fails to complete tasks |
Claude | To be tested | - | Testing in progress |
⭐ = Best choice for most use cases
Testing Insights
- Thinking Process: Models marked as "Not Recommended" often fail to properly follow the ReAct pattern, either skipping the thinking phase or getting stuck in loops
-
Cost-Performance:
gemini-2.5-flash-lite
andgpt-5-mini
offer the best balance for most applications - Reliability: Recommended models consistently complete tasks without entering error states
Configuration Options
Option | Type | Default | Description |
---|---|---|---|
ai.provider |
Symbol | :gemini |
AI provider |
ai.model |
String | "gemini-2.5-flash-lite" |
Model to use |
ai.api_key |
String | nil | API key |
ai.instructions |
String | nil | Custom agent instructions |
ai.think_in |
String | "en" |
Thinking language |
performance.max_iterations |
Integer | 10 | Max iterations |
Tool Parameter Validation
Validator | Options | Description |
---|---|---|
presence |
true/false |
Value cannot be empty |
length |
minimum , maximum
|
String length limits |
inclusion |
in , allow_nil
|
Value must be in specified list |
format |
with |
Match regular expression |
Performance Optimization
-
Use appropriate models:
- Simple tasks:
gemini-2.5-flash-lite
orgpt-5-mini
orclaude-3-5-haiku-latest
- Complex reasoning:
gemini-2.5-pro
orgpt-5
orclaude-sonnet-4-0
- Simple tasks:
-
Control iterations:
agent = MyAgent.new(max_iterations: 5) # Limit iterations
Troubleshooting
Common Issues
-
API Key Error
Soka::LLMError: API key is required
Solution: Ensure correct environment variable is set or provide API key in configuration
-
Max Iterations Reached
Status: max_iterations_reached
Solution: Simplify the problem or increase
max_iterations
Debugging Tips
# Adjust max iterations
Soka.configure do |c|
c.performance.max_iterations = 20
end
# Use block mode to see execution process
agent.run(query) do |event|
p event # Print all events
end
# Inspect thought process
result = agent.run(query)
result.thoughts.each do |thought|
puts "Step #{thought[:step]}: #{thought[:thought]}"
puts "Action: #{thought[:action]}" if thought[:action]
puts "Observation: #{thought[:observation]}" if thought[:observation]
end
Development
# Install dependencies
bundle install
# Run tests
bundle exec rspec
# Run Rubocop
bundle exec rubocop
# Open interactive console
bin/console
# Create new version
# 1. Update lib/soka/version.rb
# 2. Update CHANGELOG.md
# 3. Commit changes
# 4. Create tag
bundle exec rake release
Contributing
We welcome all forms of contributions!
- Fork the project
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Please ensure:
- Add appropriate tests
- Update relevant documentation
- Follow existing code style
- Pass Rubocop checks
License
This project is licensed under the MIT License. See the LICENSE file for details.
Acknowledgments
- Thanks to the ReAct paper for the theoretical foundation
- Thanks to the Regent project for architectural inspiration
- Thanks to all contributors for their efforts
Made with ❤️ in Taiwan
Created by Claude Code