soka 0.0.3 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b99b5c66b841d01fa4ac0955e3924d5057787027827bdd9794094304ad218fae
4
- data.tar.gz: 4343a63fd6057865f9fd03ac7de05e3a12ca1e2ef6e8b4fe2b35e4c088e7cb1a
3
+ metadata.gz: eb5c0a25160f948f0514c8297166706adef7b1d1921854e95cedef2466b561f0
4
+ data.tar.gz: afe45e214ea6077f81d3d21a56474213db820124d1bc46652a960d6a8c524e59
5
5
  SHA512:
6
- metadata.gz: 633bf8b6bece348f71a53ca68229b710cbb0437372eaac0ca8d7b3e1a83baea6f9a31ed3d4cc68267f6cafcd6bbb5aefc28327212cacbc74d1d15437c0faf5ec
7
- data.tar.gz: 3c5e8939f02d9ad9804e053b57ae22227c00662a4a57d35b4685e63a980534412c81441d75a46939d94e288b122084f7f612022c95e20ed506c1162114c25d1b
6
+ metadata.gz: 687b999469d40246dd5c224ec433d634089b81ee8f46d3ba85b6fa469317f2e1d1b65fba7e196905bee7060f41ab73423d054bce86ee432a3126f6868121dbe4
7
+ data.tar.gz: fc52a74769adc232e881019aad8b3d4edc9f72e60a42bc2ecf3178f06591f24a7f9a38830f4c98a0f0048783a8b42ac5854a1e8caf0aac6a5baadbb8357d2d77
data/.rubocop.yml CHANGED
@@ -10,7 +10,7 @@ plugins:
10
10
  - rubocop-rspec
11
11
 
12
12
  AllCops:
13
- TargetRubyVersion: 3.4
13
+ TargetRubyVersion: 3.1
14
14
  SuggestExtensions: false
15
15
 
16
16
  Exclude:
data/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.5] - 2025-08-05
11
+
12
+ ### Bug Fixes
13
+ - fix: remove confidence_score from examples and hook manager (12d9e8b)
14
+
15
+ ### Tests
16
+ - test: fix Hash#inspect format compatibility for Ruby 3.4 (c9f1a31)
17
+
18
+ ### Chores
19
+ - chore: update CI workflows to test multiple Ruby versions (d2e1b9a)
20
+ - chore: downgrade minimum Ruby version to 3.1 (8a3d7f0)
21
+
22
+ ## [0.0.4] - 2025-08-04
23
+
24
+ ### Features
25
+ - feat: add dynamic instructions via method support (e428631)
26
+
27
+ ### Documentation
28
+ - docs: update README with dynamic instructions feature (b3f2d81)
29
+
10
30
  ## [0.0.3] - 2025-08-03
11
31
 
12
32
  ### Features
data/README.md CHANGED
@@ -148,7 +148,7 @@ class ConversationsController < ApplicationController
148
148
  def create
149
149
  agent = CustomerSupportAgent.new
150
150
  result = agent.run(params[:message])
151
-
151
+
152
152
  render json: {
153
153
  answer: result.final_answer,
154
154
  status: result.status
@@ -240,10 +240,10 @@ class WeatherAgent < Soka::Agent
240
240
 
241
241
  # Custom tool (functional) - requires description as second parameter
242
242
  tool :get_weather, "Get weather for a location"
243
-
243
+
244
244
  # Custom instructions (optional)
245
245
  instructions "You are a weather expert. Provide detailed weather information."
246
-
246
+
247
247
  # Thinking language (optional)
248
248
  think_in 'en' # Default is 'en'
249
249
 
@@ -376,8 +376,8 @@ Customize your agent's personality and response style:
376
376
  ```ruby
377
377
  class FriendlyAgent < Soka::Agent
378
378
  provider :gemini
379
-
380
- # Define personality at class level
379
+
380
+ # Method 1: Static string instructions
381
381
  instructions <<~PROMPT
382
382
  You are a friendly, helpful assistant.
383
383
  Use casual language and be encouraging.
@@ -385,7 +385,28 @@ class FriendlyAgent < Soka::Agent
385
385
  PROMPT
386
386
  end
387
387
 
388
- # Or override at runtime
388
+ # Method 2: Dynamic instructions using method
389
+ class DynamicAgent < Soka::Agent
390
+ provider :gemini
391
+
392
+ # Reference a method for dynamic instructions
393
+ instructions :generate_instructions
394
+
395
+ private
396
+
397
+ def generate_instructions
398
+ hour = Time.now.hour
399
+ mood = hour < 12 ? 'cheerful morning' : 'relaxed afternoon'
400
+
401
+ <<~PROMPT
402
+ You are a #{mood} assistant.
403
+ Current Time: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}
404
+ Adjust your tone based on the time of day.
405
+ PROMPT
406
+ end
407
+ end
408
+
409
+ # Method 3: Override at runtime
389
410
  agent = FriendlyAgent.new(
390
411
  instructions: 'Be more formal and professional.'
391
412
  )
@@ -399,7 +420,7 @@ Optimize reasoning for specific languages:
399
420
  ```ruby
400
421
  class GlobalAgent < Soka::Agent
401
422
  provider :gemini
402
-
423
+
403
424
  # Set default thinking language
404
425
  think_in 'zh-TW' # Think in Traditional Chinese
405
426
  end
@@ -611,7 +632,14 @@ Shows how to customize agent personality:
611
632
  - Creating different agent personalities
612
633
  - Use cases for different domains
613
634
 
614
- ### 10. Multilingual Thinking (`examples/10_think_in_languages.rb`)
635
+ ### 10. Dynamic Instructions (`examples/10_dynamic_instructions.rb`)
636
+ Demonstrates dynamic instruction generation:
637
+ - Using methods to generate instructions dynamically
638
+ - Time-based instruction changes
639
+ - Environment-based configuration
640
+ - Session-based personality switching
641
+
642
+ ### 11. Multilingual Thinking (`examples/11_think_in_languages.rb`)
615
643
  Demonstrates language-specific reasoning:
616
644
  - Setting thinking language with `think_in`
617
645
  - Class-level vs instance-level configuration
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'soka'
6
+ require 'dotenv/load'
7
+ require 'dentaku'
8
+
9
+ # Example 10: Dynamic Instructions via Method
10
+ #
11
+ # This example demonstrates how to use a method to generate instructions dynamically.
12
+ # Instead of static string instructions, you can define a method that returns
13
+ # instructions based on runtime conditions.
14
+ #
15
+ # Key concepts:
16
+ # - Using a Symbol to reference an instructions method
17
+ # - Dynamic instruction generation based on context
18
+ # - Maintaining compatibility with string-based instructions
19
+
20
+ # Example 1: Time-aware Assistant
21
+ # Instructions change based on time of day
22
+ class TimeAwareAssistant < Soka::Agent
23
+ provider :gemini
24
+ model 'gemini-2.5-flash-lite'
25
+
26
+ # Use a method to generate instructions
27
+ instructions :generate_time_based_instructions
28
+
29
+ class GreetingTool < Soka::AgentTool
30
+ desc 'Generate appropriate greetings'
31
+
32
+ params do
33
+ requires :name, String, desc: 'Name of the person to greet'
34
+ end
35
+
36
+ def call(name:)
37
+ "Greeting generated for #{name}"
38
+ end
39
+ end
40
+
41
+ tool GreetingTool
42
+
43
+ private
44
+
45
+ def generate_time_based_instructions
46
+ hour = Time.now.hour
47
+
48
+ base_instructions = if hour < 12
49
+ 'You are a cheerful morning assistant who is energetic and motivating.'
50
+ elsif hour < 17
51
+ 'You are a focused afternoon assistant who is professional and efficient.'
52
+ else
53
+ 'You are a relaxed evening assistant who is calm and supportive.'
54
+ end
55
+
56
+ <<~INSTRUCTIONS
57
+ #{base_instructions}
58
+ Current time: #{Time.now.strftime('%H:%M')}
59
+
60
+ You must:
61
+ - Acknowledge the time of day in your responses
62
+ - Adjust your tone based on the time period
63
+ - Be helpful while matching the appropriate energy level
64
+ INSTRUCTIONS
65
+ end
66
+ end
67
+
68
+ # Example 2: Environment-based Assistant
69
+ # Instructions change based on environment variables
70
+ class EnvironmentAwareAssistant < Soka::Agent
71
+ provider :gemini
72
+ model 'gemini-2.5-flash-lite'
73
+
74
+ # Use a method for environment-based instructions
75
+ instructions :build_environment_instructions
76
+
77
+ class CalculatorTool < Soka::AgentTool
78
+ desc 'Performs calculations'
79
+
80
+ params do
81
+ requires :expression, String, desc: 'Math expression'
82
+ end
83
+
84
+ def call(expression:)
85
+ calculator = Dentaku::Calculator.new
86
+ result = calculator.evaluate(expression)
87
+ "Result: #{expression} = #{result}"
88
+ rescue StandardError => e
89
+ "Error: #{e.message}"
90
+ end
91
+ end
92
+
93
+ tool CalculatorTool
94
+
95
+ private
96
+
97
+ def build_environment_instructions
98
+ user_level = ENV['USER_LEVEL'] || 'beginner'
99
+ debug_mode = ENV['DEBUG'] == 'true'
100
+
101
+ style = case user_level
102
+ when 'expert'
103
+ 'Use technical terminology and assume deep knowledge. Be concise.'
104
+ when 'intermediate'
105
+ 'Balance technical accuracy with clear explanations. Provide context when needed.'
106
+ else
107
+ 'Use simple language and explain concepts thoroughly. Avoid jargon.'
108
+ end
109
+
110
+ debug_info = if debug_mode
111
+ "\n- Include detailed technical information in responses"
112
+ else
113
+ "\n- Keep responses focused on the essential information"
114
+ end
115
+
116
+ <<~INSTRUCTIONS
117
+ You are an AI assistant adapting to the user's expertise level.
118
+
119
+ Communication style: #{style}
120
+ User level: #{user_level}
121
+ Debug mode: #{debug_mode}
122
+
123
+ Guidelines:#{debug_info}
124
+ - Adapt your responses to the #{user_level} level
125
+ - Maintain accuracy while adjusting complexity
126
+ INSTRUCTIONS
127
+ end
128
+ end
129
+
130
+ # Example 3: Session-based Assistant
131
+ # Instructions change based on a session ID
132
+ class SessionAssistant < Soka::Agent
133
+ provider :gemini
134
+ model 'gemini-2.5-flash-lite'
135
+
136
+ # Use method for session-based instructions
137
+ instructions :generate_session_instructions
138
+
139
+ class InfoTool < Soka::AgentTool
140
+ desc 'Provides information'
141
+
142
+ params do
143
+ requires :query, String, desc: 'Information query'
144
+ end
145
+
146
+ def call(query:)
147
+ "Information about: #{query}"
148
+ end
149
+ end
150
+
151
+ tool InfoTool
152
+
153
+ private
154
+
155
+ def generate_session_instructions
156
+ # Simulate different session types based on time
157
+ session_type = Time.now.min % 3
158
+
159
+ personality = case session_type
160
+ when 0
161
+ 'You are a formal, professional assistant.'
162
+ when 1
163
+ 'You are a friendly, casual assistant.'
164
+ else
165
+ 'You are an educational, patient assistant.'
166
+ end
167
+
168
+ <<~INSTRUCTIONS
169
+ #{personality}
170
+ Session ID: #{Time.now.to_i}
171
+
172
+ Guidelines:
173
+ - Maintain consistent personality throughout the session
174
+ - Provide helpful and accurate information
175
+ - Adapt your tone to match the session style
176
+ INSTRUCTIONS
177
+ end
178
+ end
179
+
180
+ # Run examples
181
+ puts '=== Time-Aware Assistant Example ==='
182
+ time_assistant = TimeAwareAssistant.new
183
+
184
+ time_assistant.run('Please greet John appropriately for the current time') do |event|
185
+ case event.type
186
+ when :thought
187
+ puts "⏰ Thinking: #{event.content}"
188
+ when :final_answer
189
+ puts "📝 Response: #{event.content}"
190
+ end
191
+ end
192
+
193
+ puts "\n=== Environment-based Assistant (Beginner) Example ==="
194
+ ENV['USER_LEVEL'] = 'beginner'
195
+ ENV['DEBUG'] = 'false'
196
+
197
+ beginner_assistant = EnvironmentAwareAssistant.new
198
+
199
+ beginner_assistant.run('Calculate 15% of 200') do |event|
200
+ case event.type
201
+ when :thought
202
+ puts "🎓 Thinking: #{event.content}"
203
+ when :final_answer
204
+ puts "📚 Response: #{event.content}"
205
+ end
206
+ end
207
+
208
+ puts "\n=== Environment-based Assistant (Expert) Example ==="
209
+ ENV['USER_LEVEL'] = 'expert'
210
+ ENV['DEBUG'] = 'true'
211
+
212
+ expert_assistant = EnvironmentAwareAssistant.new
213
+
214
+ expert_assistant.run('Calculate 15% of 200') do |event|
215
+ case event.type
216
+ when :final_answer
217
+ puts "💼 Response: #{event.content}"
218
+ end
219
+ end
220
+
221
+ puts "\n=== Session Assistant Example ==="
222
+ session_assistant = SessionAssistant.new
223
+
224
+ session_assistant.run('Tell me about Ruby') do |event|
225
+ case event.type
226
+ when :thought
227
+ puts "🔧 Thinking: #{event.content}"
228
+ when :final_answer
229
+ puts "🖥️ Response: #{event.content}"
230
+ end
231
+ end
232
+
233
+ puts "\n=== Mixed: String and Method Instructions ==="
234
+ # You can still use string instructions
235
+ class StringInstructionsAgent < Soka::Agent
236
+ provider :gemini
237
+ model 'gemini-2.5-flash-lite'
238
+
239
+ instructions 'You are a helpful assistant that always responds concisely.'
240
+ end
241
+
242
+ puts "String instructions work as before."
243
+
244
+ # And override at runtime
245
+ TimeAwareAssistant.new(
246
+ instructions: 'Override with a custom string instruction at runtime.'
247
+ )
248
+ puts "Runtime overrides still work with both string and method-based instructions."
data/examples/1_basic.rb CHANGED
@@ -90,5 +90,4 @@ puts '-' * 50
90
90
 
91
91
  result = agent.run('What time is it now?')
92
92
  puts "✅ Answer: #{result.final_answer}"
93
- puts "📊 Confidence: #{(result.confidence_score * 100).round(1)}%"
94
93
  puts "⏱️ Iterations: #{result.iterations}"
data/examples/3_memory.rb CHANGED
@@ -136,7 +136,6 @@ result = agent_with_memory.run(
136
136
  "Please calculate 15 * 8, and remember this calculation result with key 'first_calc'"
137
137
  )
138
138
  puts "Agent: #{result.final_answer}"
139
- puts "Confidence: #{(result.confidence_score * 100).round(1)}%"
140
139
  puts "Iterations: #{result.iterations}"
141
140
  puts '-' * 50
142
141
 
data/lib/soka/agent.rb CHANGED
@@ -51,7 +51,7 @@ module Soka
51
51
  # Apply behavior-related configuration
52
52
  # @param options [Hash] Configuration options
53
53
  def apply_behavior_config(options)
54
- @instructions = options.fetch(:instructions) { self.class._instructions }
54
+ @instructions = options.fetch(:instructions) { resolve_instructions }
55
55
  @think_in = options.fetch(:think_in) { self.class._think_in } || 'en'
56
56
  end
57
57
 
@@ -86,6 +86,35 @@ module Soka
86
86
  end
87
87
  end
88
88
 
89
+ # Resolve instructions from class configuration
90
+ # @return [String, nil] The resolved instructions
91
+ def resolve_instructions
92
+ return nil unless self.class._instructions
93
+
94
+ case self.class._instructions
95
+ when Symbol
96
+ # Call the method if it's a symbol
97
+ send(self.class._instructions) if respond_to?(self.class._instructions, true)
98
+ else
99
+ # Return string or any other value directly
100
+ self.class._instructions
101
+ end
102
+ end
103
+
104
+ # Resolve current instructions (may be dynamic)
105
+ # @return [String, nil] The current instructions
106
+ def resolve_current_instructions
107
+ case @instructions
108
+ when Symbol
109
+ send(@instructions) if respond_to?(@instructions, true)
110
+ when Proc
111
+ @instructions.call
112
+ else
113
+ # Return string or any other value directly
114
+ @instructions
115
+ end
116
+ end
117
+
89
118
  # Validate the input is not empty
90
119
  # @param input [String] The input to validate
91
120
  # @raise [ArgumentError] If input is empty
@@ -115,7 +144,7 @@ module Soka
115
144
  engine_instance = @engine.new(self, @tools,
116
145
  llm: @llm,
117
146
  max_iterations: @max_iterations,
118
- custom_instructions: @instructions,
147
+ custom_instructions: resolve_current_instructions,
119
148
  think_in: @think_in)
120
149
  with_retry { engine_instance.reason(input, &) }
121
150
  end
@@ -51,9 +51,13 @@ module Soka
51
51
  end
52
52
 
53
53
  # Define custom instructions (system prompt) for the agent
54
- # @param text [String] The custom instructions/system prompt
55
- def instructions(text)
56
- @_instructions = text
54
+ # @param text_or_method [String, Symbol] The custom instructions/system prompt or method name
55
+ # @example Using a string
56
+ # instructions "You are a helpful assistant"
57
+ # @example Using a method
58
+ # instructions :generate_instructions
59
+ def instructions(text_or_method)
60
+ @_instructions = text_or_method
57
61
  end
58
62
 
59
63
  # Define thinking language for the agent
@@ -10,12 +10,12 @@ module Soka
10
10
  # @param hook_type [Symbol] The type of hook (:before_action, :after_action, :on_error)
11
11
  # @param args [Array] Arguments to pass to the hook methods
12
12
  # @return [Object, nil] The result from on_error hooks, or nil
13
- def run_hooks(hook_type, *)
13
+ def run_hooks(hook_type, ...)
14
14
  return unless self.class._hooks && self.class._hooks[hook_type]
15
15
 
16
16
  self.class._hooks[hook_type].each do |method_name|
17
17
  if respond_to?(method_name, true)
18
- result = send(method_name, *)
18
+ result = send(method_name, ...)
19
19
  return result if hook_type == :on_error && result
20
20
  end
21
21
  end
@@ -32,8 +32,7 @@ module Soka
32
32
  thoughts: engine_result.thoughts,
33
33
  final_answer: engine_result.final_answer,
34
34
  status: engine_result.status,
35
- error: engine_result.error,
36
- confidence_score: engine_result.confidence_score
35
+ error: engine_result.error
37
36
  )
38
37
  end
39
38
 
@@ -59,8 +58,7 @@ module Soka
59
58
  thoughts: [],
60
59
  final_answer: nil,
61
60
  status: :failed,
62
- error: error.message,
63
- confidence_score: 0.0
61
+ error: error.message
64
62
  )
65
63
  end
66
64
  end
@@ -11,7 +11,7 @@ module Soka
11
11
  include Concerns::ResponseParser
12
12
  include Concerns::ResultBuilder
13
13
 
14
- ReasonResult = Struct.new(:input, :thoughts, :final_answer, :status, :error, :confidence_score,
14
+ ReasonResult = Struct.new(:input, :thoughts, :final_answer, :status, :error,
15
15
  keyword_init: true) do
16
16
  def successful?
17
17
  status == :success
data/lib/soka/llm.rb CHANGED
@@ -69,14 +69,14 @@ module Soka
69
69
  # @param provider_name [Symbol] Provider type
70
70
  # @param options [Hash] Provider options
71
71
  # @return [LLMs::Base] Provider instance
72
- def create_provider(provider_name, **)
72
+ def create_provider(provider_name, ...)
73
73
  case provider_name.to_sym
74
74
  when :gemini
75
- LLMs::Gemini.new(**)
75
+ LLMs::Gemini.new(...)
76
76
  when :openai
77
- LLMs::OpenAI.new(**)
77
+ LLMs::OpenAI.new(...)
78
78
  when :anthropic
79
- LLMs::Anthropic.new(**)
79
+ LLMs::Anthropic.new(...)
80
80
  else
81
81
  raise LLMError, "Unknown LLM provider: #{provider_name}"
82
82
  end
data/lib/soka/result.rb CHANGED
@@ -65,8 +65,8 @@ module Soka
65
65
 
66
66
  # Convert to JSON string
67
67
  # @return [String]
68
- def to_json(*)
69
- to_h.to_json(*)
68
+ def to_json(...)
69
+ to_h.to_json(...)
70
70
  end
71
71
 
72
72
  # Get a summary of the result
data/lib/soka/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Soka
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.5'
5
5
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - jiunjiun
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2025-08-05 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: faraday
@@ -53,7 +54,8 @@ files:
53
54
  - LICENSE
54
55
  - README.md
55
56
  - Rakefile
56
- - examples/10_think_in_languages.rb
57
+ - examples/10_dynamic_instructions.rb
58
+ - examples/11_think_in_languages.rb
57
59
  - examples/1_basic.rb
58
60
  - examples/2_event_handling.rb
59
61
  - examples/3_memory.rb
@@ -97,6 +99,7 @@ metadata:
97
99
  source_code_uri: https://github.com/jiunjiun/soka
98
100
  changelog_uri: https://github.com/jiunjiun/soka/blob/main/CHANGELOG.md
99
101
  rubygems_mfa_required: 'true'
102
+ post_install_message:
100
103
  rdoc_options: []
101
104
  require_paths:
102
105
  - lib
@@ -104,14 +107,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
104
107
  requirements:
105
108
  - - ">="
106
109
  - !ruby/object:Gem::Version
107
- version: '3.4'
110
+ version: '3.1'
108
111
  required_rubygems_version: !ruby/object:Gem::Requirement
109
112
  requirements:
110
113
  - - ">="
111
114
  - !ruby/object:Gem::Version
112
115
  version: '0'
113
116
  requirements: []
114
- rubygems_version: 3.6.9
117
+ rubygems_version: 3.3.27
118
+ signing_key:
115
119
  specification_version: 4
116
120
  summary: A Ruby ReAct Agent Framework with multi-LLM support
117
121
  test_files: []