soka 0.0.1.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +365 -0
- data/CHANGELOG.md +31 -0
- data/CLAUDE.md +213 -0
- data/LICENSE +21 -0
- data/README.md +650 -0
- data/Rakefile +10 -0
- data/examples/1_basic.rb +94 -0
- data/examples/2_event_handling.rb +120 -0
- data/examples/3_memory.rb +182 -0
- data/examples/4_hooks.rb +140 -0
- data/examples/5_error_handling.rb +85 -0
- data/examples/6_retry.rb +164 -0
- data/examples/7_tool_conditional.rb +180 -0
- data/examples/8_multi_provider.rb +112 -0
- data/lib/soka/agent.rb +130 -0
- data/lib/soka/agent_tool.rb +146 -0
- data/lib/soka/agent_tools/params_validator.rb +139 -0
- data/lib/soka/agents/dsl_methods.rb +140 -0
- data/lib/soka/agents/hook_manager.rb +68 -0
- data/lib/soka/agents/llm_builder.rb +32 -0
- data/lib/soka/agents/retry_handler.rb +74 -0
- data/lib/soka/agents/tool_builder.rb +78 -0
- data/lib/soka/configuration.rb +60 -0
- data/lib/soka/engines/base.rb +67 -0
- data/lib/soka/engines/concerns/prompt_template.rb +130 -0
- data/lib/soka/engines/concerns/response_processor.rb +103 -0
- data/lib/soka/engines/react.rb +136 -0
- data/lib/soka/engines/reasoning_context.rb +92 -0
- data/lib/soka/llm.rb +85 -0
- data/lib/soka/llms/anthropic.rb +124 -0
- data/lib/soka/llms/base.rb +114 -0
- data/lib/soka/llms/concerns/response_parser.rb +47 -0
- data/lib/soka/llms/concerns/streaming_handler.rb +78 -0
- data/lib/soka/llms/gemini.rb +106 -0
- data/lib/soka/llms/openai.rb +97 -0
- data/lib/soka/memory.rb +83 -0
- data/lib/soka/result.rb +136 -0
- data/lib/soka/test_helpers.rb +162 -0
- data/lib/soka/thoughts_memory.rb +112 -0
- data/lib/soka/version.rb +5 -0
- data/lib/soka.rb +49 -0
- data/sig/soka.rbs +4 -0
- metadata +158 -0
data/examples/1_basic.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'soka'
|
6
|
+
require 'dotenv/load'
|
7
|
+
|
8
|
+
# Configure Soka
|
9
|
+
Soka.setup do |config|
|
10
|
+
config.ai do |ai|
|
11
|
+
ai.provider = :gemini
|
12
|
+
ai.model = 'gemini-2.5-flash-lite'
|
13
|
+
ai.api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
config.performance do |perf|
|
17
|
+
perf.max_iterations = 5
|
18
|
+
perf.timeout = 30
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Define a simple search tool
|
23
|
+
class SearchTool < Soka::AgentTool
|
24
|
+
desc 'Search the web for information'
|
25
|
+
|
26
|
+
params do
|
27
|
+
requires :query, String, desc: 'The query to search for'
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(query:)
|
31
|
+
puts "SearchTool call: #{query}"
|
32
|
+
# This is a simulated search result
|
33
|
+
case query.downcase
|
34
|
+
when /weather/
|
35
|
+
'Today in Taipei: Sunny, Temperature 28°C, Humidity 65%'
|
36
|
+
when /news/
|
37
|
+
'Today\'s headline: AI technology reaches new milestone'
|
38
|
+
else
|
39
|
+
"Searching for information about '#{query}'..."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Define time tool
|
45
|
+
class TimeTool < Soka::AgentTool
|
46
|
+
desc 'Get current time and date'
|
47
|
+
|
48
|
+
def call
|
49
|
+
puts 'TimeTool call'
|
50
|
+
Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Define Agent
|
55
|
+
class DemoAgent < Soka::Agent
|
56
|
+
tool SearchTool
|
57
|
+
tool TimeTool
|
58
|
+
end
|
59
|
+
|
60
|
+
# Use Agent
|
61
|
+
agent = DemoAgent.new
|
62
|
+
|
63
|
+
puts '=== Soka Demo Agent ==='
|
64
|
+
puts
|
65
|
+
|
66
|
+
# Example 1: Ask about weather
|
67
|
+
puts 'Question: What\'s the weather like in Taipei today?'
|
68
|
+
puts '-' * 50
|
69
|
+
|
70
|
+
agent.run('What\'s the weather like in Taipei today?') do |event|
|
71
|
+
case event.type
|
72
|
+
when :thought
|
73
|
+
puts "š Thinking: #{event.content}"
|
74
|
+
when :action
|
75
|
+
puts "š§ Action: Using tool #{event.content[:tool]}"
|
76
|
+
when :observation
|
77
|
+
puts "š Observation: #{event.content}"
|
78
|
+
when :final_answer
|
79
|
+
puts "ā
Answer: #{event.content}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
puts
|
84
|
+
puts '=' * 50
|
85
|
+
puts
|
86
|
+
|
87
|
+
# Example 2: Ask about time
|
88
|
+
puts 'Question: What time is it now?'
|
89
|
+
puts '-' * 50
|
90
|
+
|
91
|
+
result = agent.run('What time is it now?')
|
92
|
+
puts "ā
Answer: #{result.final_answer}"
|
93
|
+
puts "š Confidence: #{(result.confidence_score * 100).round(1)}%"
|
94
|
+
puts "ā±ļø Iterations: #{result.iterations}"
|
@@ -0,0 +1,120 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'soka'
|
6
|
+
require 'dotenv/load'
|
7
|
+
|
8
|
+
# Configure Soka
|
9
|
+
Soka.setup do |config|
|
10
|
+
config.ai do |ai|
|
11
|
+
ai.provider = :gemini
|
12
|
+
ai.model = 'gemini-2.5-flash-lite'
|
13
|
+
ai.api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
config.performance do |perf|
|
17
|
+
perf.max_iterations = 5
|
18
|
+
perf.timeout = 30
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Simple calculation tool
|
23
|
+
class CalculatorTool < Soka::AgentTool
|
24
|
+
desc 'Perform calculations'
|
25
|
+
|
26
|
+
params do
|
27
|
+
requires :expression, String, desc: 'Mathematical expression to calculate'
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(expression:)
|
31
|
+
result = eval(expression) # rubocop:disable Security/Eval
|
32
|
+
"Result: #{expression} = #{result}"
|
33
|
+
rescue StandardError => e
|
34
|
+
"Error: #{e.message}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Weather tool
|
39
|
+
class WeatherTool < Soka::AgentTool
|
40
|
+
desc 'Get weather information'
|
41
|
+
|
42
|
+
params do
|
43
|
+
requires :location, String, desc: 'Location to get weather for'
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(location:)
|
47
|
+
# Simulated weather data
|
48
|
+
"Weather in #{location}: Sunny, 22°C"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Define streaming agent
|
53
|
+
class StreamingAgent < Soka::Agent
|
54
|
+
tool CalculatorTool
|
55
|
+
tool WeatherTool
|
56
|
+
end
|
57
|
+
|
58
|
+
# Main program
|
59
|
+
puts '=== Soka Event Handling Example ==='
|
60
|
+
puts "Demonstrating event-based responses\n\n"
|
61
|
+
puts "NOTE: Streaming is not yet fully integrated with the event system\n"
|
62
|
+
|
63
|
+
agent = StreamingAgent.new
|
64
|
+
|
65
|
+
# Example 1: Event-based response handling
|
66
|
+
puts 'Example 1: Simple calculation with event handling'
|
67
|
+
puts '-' * 50
|
68
|
+
|
69
|
+
agent.run('Calculate 123 * 456') do |event|
|
70
|
+
case event.type
|
71
|
+
when :thought
|
72
|
+
puts "š Thinking: #{event.content}"
|
73
|
+
when :action
|
74
|
+
puts "š§ Action: Using #{event.content[:tool]}"
|
75
|
+
when :observation
|
76
|
+
puts "š Result: #{event.content}"
|
77
|
+
when :final_answer
|
78
|
+
puts "ā
Final: #{event.content}"
|
79
|
+
when :error
|
80
|
+
puts "ā Error: #{event.content}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
puts "\n" + '=' * 50 + "\n"
|
85
|
+
|
86
|
+
# Example 2: Multi-step task with event handling
|
87
|
+
puts 'Example 2: Multi-step task with event handling'
|
88
|
+
puts '-' * 50
|
89
|
+
|
90
|
+
agent.run('What is the weather in Tokyo? Also calculate 15% tip on $85.50') do |event|
|
91
|
+
case event.type
|
92
|
+
when :thought
|
93
|
+
puts "š Thinking: #{event.content}"
|
94
|
+
when :action
|
95
|
+
puts "š§ Action: #{event.content[:tool]}"
|
96
|
+
puts " Input: #{event.content[:params]}" if event.content[:params]
|
97
|
+
when :observation
|
98
|
+
puts "š Result: #{event.content}"
|
99
|
+
when :final_answer
|
100
|
+
puts "ā
Complete: #{event.content}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
puts "\n" + '=' * 50 + "\n"
|
105
|
+
|
106
|
+
# Example 3: Without event handling (direct result)
|
107
|
+
puts 'Example 3: Without event handling (direct result)'
|
108
|
+
puts '-' * 50
|
109
|
+
|
110
|
+
result = agent.run('Calculate the area of a circle with radius 5')
|
111
|
+
puts "Direct result: #{result.final_answer}"
|
112
|
+
puts "Confidence: #{(result.confidence_score * 100).round(1)}%"
|
113
|
+
puts "Iterations: #{result.iterations}"
|
114
|
+
|
115
|
+
puts "\n=== Event Handling Benefits ==="
|
116
|
+
puts "1. Real-time feedback on agent's thinking process"
|
117
|
+
puts "2. Visibility into tool usage and results"
|
118
|
+
puts "3. Can track progress during complex tasks"
|
119
|
+
puts "4. Debugging support with detailed event logs"
|
120
|
+
puts "\nNote: True streaming of LLM responses is not yet integrated with events"
|
@@ -0,0 +1,182 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'soka'
|
6
|
+
require 'dotenv/load'
|
7
|
+
require 'singleton'
|
8
|
+
|
9
|
+
# Configure Soka
|
10
|
+
Soka.setup do |config|
|
11
|
+
config.ai do |ai|
|
12
|
+
ai.provider = :gemini
|
13
|
+
ai.model = 'gemini-2.5-flash-lite'
|
14
|
+
ai.api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
config.performance do |perf|
|
18
|
+
perf.max_iterations = 10
|
19
|
+
perf.timeout = 30
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Calculator tool - Simple math calculations
|
24
|
+
class CalculatorTool < Soka::AgentTool
|
25
|
+
desc 'Perform basic math operations'
|
26
|
+
|
27
|
+
params do
|
28
|
+
requires :expression, String, desc: "Mathematical expression, e.g., '2 + 2' or '10 * 5'"
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(expression:)
|
32
|
+
# Simple math expression evaluation (production should use safer methods)
|
33
|
+
result = eval(expression) # rubocop:disable Security/Eval
|
34
|
+
"Calculation result: #{expression} = #{result}"
|
35
|
+
rescue StandardError => e
|
36
|
+
"Calculation error: #{e.message}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Shared memory manager
|
41
|
+
class SharedMemory
|
42
|
+
include Singleton
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
@storage = {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def store(key, value)
|
49
|
+
@storage[key] = value
|
50
|
+
end
|
51
|
+
|
52
|
+
def retrieve(key)
|
53
|
+
@storage[key]
|
54
|
+
end
|
55
|
+
|
56
|
+
def exists?(key)
|
57
|
+
@storage.key?(key)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Memory tool - Store and retrieve information
|
62
|
+
class MemoryTool < Soka::AgentTool
|
63
|
+
desc 'Remember important information'
|
64
|
+
|
65
|
+
params do
|
66
|
+
requires :key, String, desc: 'Information key'
|
67
|
+
requires :value, String, desc: 'Content to remember'
|
68
|
+
end
|
69
|
+
|
70
|
+
def call(key:, value:)
|
71
|
+
SharedMemory.instance.store(key, value)
|
72
|
+
"Remembered: #{key} = #{value}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Recall tool - Retrieve previously remembered information
|
77
|
+
class RecallTool < Soka::AgentTool
|
78
|
+
desc 'Recall previously remembered information'
|
79
|
+
|
80
|
+
params do
|
81
|
+
requires :key, String, desc: 'Information key to recall'
|
82
|
+
end
|
83
|
+
|
84
|
+
def call(key:)
|
85
|
+
if SharedMemory.instance.exists?(key)
|
86
|
+
value = SharedMemory.instance.retrieve(key)
|
87
|
+
"Recalled: #{key} = #{value}"
|
88
|
+
else
|
89
|
+
"No memory found for '#{key}'"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Define Agent
|
95
|
+
class MemoryAgent < Soka::Agent
|
96
|
+
tool CalculatorTool
|
97
|
+
tool MemoryTool
|
98
|
+
tool RecallTool
|
99
|
+
end
|
100
|
+
|
101
|
+
# Main program
|
102
|
+
puts '=== Soka Memory Example ==='
|
103
|
+
puts "Testing Agent memory functionality\n\n"
|
104
|
+
|
105
|
+
# Program starts
|
106
|
+
|
107
|
+
# Method 1: Using Soka::Memory class
|
108
|
+
puts '>>> Method 1: Using Soka::Memory class'
|
109
|
+
soka_memory = Soka::Memory.new
|
110
|
+
soka_memory.add(role: 'user', content: 'My name is Xiao Ming, I am 25 years old')
|
111
|
+
soka_memory.add(role: 'assistant', content: 'Nice to meet you, Xiao Ming! I will remember that you are 25 years old.')
|
112
|
+
|
113
|
+
# Method 2: Using Array (direct format)
|
114
|
+
puts '>>> Method 2: Using Array (direct format)'
|
115
|
+
memory_array = [{
|
116
|
+
role: 'user',
|
117
|
+
content: 'My name is Xiao Ming, I am 25 years old'
|
118
|
+
}, {
|
119
|
+
role: 'assistant',
|
120
|
+
content: 'Nice to meet you, Xiao Ming! I will remember that you are 25 years old.'
|
121
|
+
}]
|
122
|
+
|
123
|
+
# Both methods work - Soka will convert Array to Memory object automatically
|
124
|
+
memory = soka_memory # You can use either initial_memory_obj or initial_memory_array
|
125
|
+
|
126
|
+
# Example of using array format (uncomment to use this instead)
|
127
|
+
# memory = memory_array
|
128
|
+
|
129
|
+
# Test case 1: Basic calculation and memory
|
130
|
+
puts '\nTest 1: Basic calculation and memory'
|
131
|
+
# Agent with initial memory
|
132
|
+
agent_with_memory = MemoryAgent.new(memory: memory)
|
133
|
+
result = agent_with_memory.run(
|
134
|
+
"Please calculate 15 * 8, and remember this calculation result with key 'first_calc'"
|
135
|
+
)
|
136
|
+
puts "Agent: #{result.final_answer}"
|
137
|
+
puts "Confidence: #{(result.confidence_score * 100).round(1)}%"
|
138
|
+
puts "Iterations: #{result.iterations}"
|
139
|
+
puts '-' * 50
|
140
|
+
|
141
|
+
# Test case 2: Recall previous information
|
142
|
+
puts "\nTest 2: Recall previous calculation"
|
143
|
+
# Continue using the same agent, as it already has memory
|
144
|
+
result = agent_with_memory.run(
|
145
|
+
"Please recall what the value of 'first_calc' is?"
|
146
|
+
)
|
147
|
+
puts "Agent: #{result.final_answer}"
|
148
|
+
puts '-' * 50
|
149
|
+
|
150
|
+
# Test case 3: Use remembered information for new calculations
|
151
|
+
puts "\nTest 3: Complex memory operations"
|
152
|
+
result = agent_with_memory.run(
|
153
|
+
"Please remember that pi is 3.14159, then calculate the area of a circle with radius 5 (using pi * r * r), and remember the result as 'circle_area'"
|
154
|
+
)
|
155
|
+
puts "Agent: #{result.final_answer}"
|
156
|
+
puts '-' * 50
|
157
|
+
|
158
|
+
# Test case 4: Recall information from initial memory
|
159
|
+
puts "\nTest 4: Recall initial information"
|
160
|
+
result = agent_with_memory.run(
|
161
|
+
'Do you remember my name and age?'
|
162
|
+
)
|
163
|
+
puts "Agent: #{result.final_answer}"
|
164
|
+
puts '-' * 50
|
165
|
+
|
166
|
+
# Display complete conversation history
|
167
|
+
puts "\n=== Complete Conversation History ==="
|
168
|
+
agent_with_memory.memory.messages.each_with_index do |msg, idx|
|
169
|
+
puts "#{idx + 1}. [#{msg[:role]}]: #{msg[:content][0..100]}#{'ā¦' if msg[:content].length > 100}"
|
170
|
+
end
|
171
|
+
|
172
|
+
# Display the last thinking process
|
173
|
+
puts "\n=== Last Thinking Process ==="
|
174
|
+
if result.thoughts&.any?
|
175
|
+
result.thoughts.each_with_index do |thought_item, idx|
|
176
|
+
step = thought_item[:step] || (idx + 1)
|
177
|
+
content = thought_item[:thought] || thought_item[:content] || ''
|
178
|
+
puts "#{step}. #{content[0..200]}#{'ā¦' if content.length > 200}"
|
179
|
+
end
|
180
|
+
else
|
181
|
+
puts 'No thinking process recorded'
|
182
|
+
end
|
data/examples/4_hooks.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'soka'
|
6
|
+
require 'dotenv/load'
|
7
|
+
|
8
|
+
# Configure Soka
|
9
|
+
Soka.setup do |config|
|
10
|
+
config.ai do |ai|
|
11
|
+
ai.provider = :gemini
|
12
|
+
ai.model = 'gemini-2.5-flash-lite'
|
13
|
+
ai.api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Simple database tool
|
18
|
+
class DatabaseTool < Soka::AgentTool
|
19
|
+
desc 'Query database'
|
20
|
+
|
21
|
+
params do
|
22
|
+
requires :query, String, desc: 'SQL query to execute'
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(query:)
|
26
|
+
# Simulated database query
|
27
|
+
if query.downcase.include?('select')
|
28
|
+
"Found 5 records matching your query"
|
29
|
+
else
|
30
|
+
"Query executed successfully"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Agent with hooks
|
36
|
+
class HookedAgent < Soka::Agent
|
37
|
+
tool DatabaseTool
|
38
|
+
|
39
|
+
# Hook called before action
|
40
|
+
before_action :log_request
|
41
|
+
|
42
|
+
# Hook called after action
|
43
|
+
after_action :log_response
|
44
|
+
|
45
|
+
# Hook called on error
|
46
|
+
on_error :handle_error
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def log_request(input)
|
51
|
+
puts "šµ [BEFORE] Starting request: #{input}"
|
52
|
+
puts "šµ [BEFORE] Memory size: #{@memory.messages.size} messages"
|
53
|
+
puts "šµ [BEFORE] Available tools: #{@tools.map { |t| t.class.name }.join(', ')}"
|
54
|
+
puts '-' * 50
|
55
|
+
end
|
56
|
+
|
57
|
+
def log_response(result)
|
58
|
+
puts '-' * 50
|
59
|
+
puts "š¢ [AFTER] Request completed"
|
60
|
+
puts "š¢ [AFTER] Final answer: #{result.final_answer[0..100]}..."
|
61
|
+
puts "š¢ [AFTER] Iterations: #{result.iterations}"
|
62
|
+
puts "š¢ [AFTER] Confidence: #{(result.confidence_score * 100).round(1)}%"
|
63
|
+
puts "š¢ [AFTER] Memory size: #{@memory.messages.size} messages"
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_error(error, input)
|
67
|
+
puts "š“ [ERROR] An error occurred!"
|
68
|
+
puts "š“ [ERROR] Input: #{input}"
|
69
|
+
puts "š“ [ERROR] Error: #{error.message}"
|
70
|
+
puts "š“ [ERROR] Backtrace: #{error.backtrace.first(3).join("\n")}"
|
71
|
+
|
72
|
+
# Return :continue to continue execution with error result
|
73
|
+
# Return :stop to re-raise the error
|
74
|
+
:continue
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Main program
|
79
|
+
puts '=== Soka Hooks Example ==='
|
80
|
+
puts "Demonstrating lifecycle hooks\n\n"
|
81
|
+
|
82
|
+
agent = HookedAgent.new
|
83
|
+
|
84
|
+
# Example 1: Normal execution with hooks
|
85
|
+
puts 'Example 1: Normal execution showing all hooks'
|
86
|
+
puts '=' * 50
|
87
|
+
|
88
|
+
result = agent.run('Query the database for user statistics')
|
89
|
+
puts "\nResult: #{result.final_answer}" if result.respond_to?(:final_answer)
|
90
|
+
|
91
|
+
puts "\n" + '=' * 50 + "\n"
|
92
|
+
|
93
|
+
# Example 2: Multiple calls showing memory growth
|
94
|
+
puts 'Example 2: Multiple calls showing memory tracking'
|
95
|
+
puts '=' * 50
|
96
|
+
|
97
|
+
queries = [
|
98
|
+
'Select all active users',
|
99
|
+
'Count total orders this month',
|
100
|
+
'Find top selling products'
|
101
|
+
]
|
102
|
+
|
103
|
+
queries.each_with_index do |query, index|
|
104
|
+
puts "\n--- Query #{index + 1} ---"
|
105
|
+
agent.run(query)
|
106
|
+
sleep(0.5) # Small delay for visibility
|
107
|
+
end
|
108
|
+
|
109
|
+
puts "\n" + '=' * 50 + "\n"
|
110
|
+
|
111
|
+
# Example 3: Error handling
|
112
|
+
puts 'Example 3: Error handling with hooks'
|
113
|
+
puts '=' * 50
|
114
|
+
|
115
|
+
# Create a tool that will trigger an error
|
116
|
+
class ErrorTool < Soka::AgentTool
|
117
|
+
desc 'This tool simulates errors'
|
118
|
+
|
119
|
+
def call
|
120
|
+
raise StandardError, 'This is a simulated tool error!'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class ErrorHandlingAgent < HookedAgent
|
125
|
+
tool ErrorTool
|
126
|
+
end
|
127
|
+
|
128
|
+
error_agent = ErrorHandlingAgent.new
|
129
|
+
result = error_agent.run('Use the error tool to test error handling')
|
130
|
+
puts "\nError handled gracefully: #{result.failed? ? 'Yes' : 'No'}"
|
131
|
+
puts "Final answer: #{result.final_answer}" if result.final_answer
|
132
|
+
puts "Error message: #{result.error}" if result.error
|
133
|
+
|
134
|
+
puts "\n=== Hook Benefits ==="
|
135
|
+
puts "1. Logging and monitoring of agent activity"
|
136
|
+
puts "2. Pre-processing of inputs before execution"
|
137
|
+
puts "3. Post-processing of results"
|
138
|
+
puts "4. Custom error handling and recovery"
|
139
|
+
puts "5. Performance tracking and metrics"
|
140
|
+
puts "6. Integration with external systems"
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'soka'
|
6
|
+
require 'dotenv/load'
|
7
|
+
|
8
|
+
# Configure Soka
|
9
|
+
Soka.setup do |config|
|
10
|
+
config.ai do |ai|
|
11
|
+
ai.provider = :gemini
|
12
|
+
ai.model = 'gemini-2.5-flash-lite'
|
13
|
+
ai.api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Tool that can demonstrate different scenarios
|
18
|
+
class DemoTool < Soka::AgentTool
|
19
|
+
desc 'A tool that can succeed or fail based on input'
|
20
|
+
|
21
|
+
params do
|
22
|
+
requires :action, String, inclusion: %w[success fail],
|
23
|
+
desc: 'Whether to succeed or fail'
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(action:)
|
27
|
+
case action
|
28
|
+
when 'success'
|
29
|
+
'Operation completed successfully!'
|
30
|
+
when 'fail'
|
31
|
+
raise StandardError, 'Tool execution failed as requested!'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Agent with error handling
|
37
|
+
class ErrorDemoAgent < Soka::Agent
|
38
|
+
tool DemoTool
|
39
|
+
|
40
|
+
# This hook now handles ALL errors (both Tool and Agent-level)
|
41
|
+
on_error :handle_errors
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def handle_errors(error, input)
|
46
|
+
puts "\nš“ Error Handler Triggered!"
|
47
|
+
puts " Error Type: #{error.class}"
|
48
|
+
puts " Error Message: #{error.message}"
|
49
|
+
puts " Original Input: #{input}"
|
50
|
+
|
51
|
+
# Return :continue to get an error result but continue execution
|
52
|
+
# Return :stop to re-raise the error and halt
|
53
|
+
:continue
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Run demonstrations
|
58
|
+
puts "=== Soka Error Handling Example ==="
|
59
|
+
puts "Demonstrating that ALL errors now trigger the on_error hook\n\n"
|
60
|
+
|
61
|
+
agent = ErrorDemoAgent.new
|
62
|
+
|
63
|
+
# Example 1: Successful operation
|
64
|
+
puts "1. Successful Operation:"
|
65
|
+
puts "-" * 40
|
66
|
+
result = agent.run('Use the demo tool with action=success')
|
67
|
+
puts "Status: #{result.status}"
|
68
|
+
puts "Success?: #{result.successful?}"
|
69
|
+
puts "Answer: #{result.final_answer}"
|
70
|
+
|
71
|
+
# Example 2: Tool error (NOW triggers on_error)
|
72
|
+
puts "\n2. Tool Error (triggers on_error hook):"
|
73
|
+
puts "-" * 40
|
74
|
+
result = agent.run('Use the demo tool with action=fail')
|
75
|
+
puts "\nResult after error handling:"
|
76
|
+
puts "Status: #{result.status}"
|
77
|
+
puts "Failed?: #{result.failed?}"
|
78
|
+
puts "Error: #{result.error}"
|
79
|
+
puts "Answer: #{result.final_answer || 'No answer due to error'}"
|
80
|
+
|
81
|
+
puts "\n=== Key Points ==="
|
82
|
+
puts "ā
ALL errors now trigger the on_error hook (Tool and Agent errors)"
|
83
|
+
puts "ā
Tool errors are wrapped as Soka::ToolError"
|
84
|
+
puts "ā
Return :continue from on_error to continue with error result"
|
85
|
+
puts "ā
Return :stop from on_error to re-raise and halt execution"
|