soka 0.0.5 → 0.0.6

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: eb5c0a25160f948f0514c8297166706adef7b1d1921854e95cedef2466b561f0
4
- data.tar.gz: afe45e214ea6077f81d3d21a56474213db820124d1bc46652a960d6a8c524e59
3
+ metadata.gz: 07c69024bab6c364c20dfdea14e2a9b2dddc6248c6c3c741c1d917fdad0e31b2
4
+ data.tar.gz: f89d4e6f71c833f340dda97dbb281d21b7ff586aa8df3de0ebcda419dc947576
5
5
  SHA512:
6
- metadata.gz: 687b999469d40246dd5c224ec433d634089b81ee8f46d3ba85b6fa469317f2e1d1b65fba7e196905bee7060f41ab73423d054bce86ee432a3126f6868121dbe4
7
- data.tar.gz: fc52a74769adc232e881019aad8b3d4edc9f72e60a42bc2ecf3178f06591f24a7f9a38830f4c98a0f0048783a8b42ac5854a1e8caf0aac6a5baadbb8357d2d77
6
+ metadata.gz: e10b7c4eb7428b4eec647d26c767af0a9fd87a38f639b5925c12784a3feaad4f9ef2abb907e2eb73e46a271305c14a8d75dca37c0d559758bf5f64d43faf5c1e
7
+ data.tar.gz: b34810a3566fd70bddbd0b0922f321c2a7a9aecf2b914b2cf08d613e0f3c5ee9aedb0a8e2ae4d8b4cf8b8551db4b2f2d1ff14d4d47b10f0f0b9b825a595e5854
data/CHANGELOG.md CHANGED
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.6] - 2025-08-12
11
+
12
+ ### Features
13
+ - feat: add OpenAI Responses API support with reasoning (8c81be8)
14
+ - feat: enhance ReAct prompt template with clearer instructions (d371c12)
15
+ - feat(gemini): add thinkingConfig to generationConfig (8e44e63)
16
+
17
+ ### Bug Fixes
18
+ - fix(openai): update default model name from gpt-5-nano to gpt-5-mini (1e5971f)
19
+ - fix: update Final_Answer tag to FinalAnswer across codebase (feb6071)
20
+
21
+ ### Documentation
22
+ - docs: add model testing results and update OpenAI model names (f83b987)
23
+ - docs: add problem solving guidelines for AI assistants (0e99e5f)
24
+
25
+ ### Code Refactoring
26
+ - refactor: reorganize prompt templates into modular structure (0c10e21)
27
+ - refactor: improve format reminder in response processor (a3de6a5)
28
+ - refactor: remove configurable timeout option (7e7e7c3)
29
+ - refactor: reorganize Anthropic LLM class structure (4fc4f90)
30
+ - refactor: extract format helpers from prompt template (d371c12)
31
+ - refactor: simplify AgentTool type mapping with constant (c609949)
32
+ - refactor: standardize Action tag to use single-line JSON format (5ac06f0)
33
+ - refactor(context): simplify iteration handling and improve logging (3e65643)
34
+ - refactor(llms): remove max_tokens parameter from configurations (d7a1fc8)
35
+
36
+ ### Performance
37
+ - perf: replace standard JSON parser with Oj for better performance (3fc969f)
38
+
39
+ ### Chores
40
+ - chore: update gitignore and documentation (8dc5102)
41
+ - chore: adjust HTTP client timeout settings (38f47a7)
42
+ - chore: update .gitignore to include .serena (90e3d41)
43
+
10
44
  ## [0.0.5] - 2025-08-05
11
45
 
12
46
  ### Bug Fixes
data/CLAUDE.md CHANGED
@@ -65,7 +65,6 @@ class MyAgent < Soka::Agent
65
65
  provider :gemini # Choose AI provider
66
66
  model 'gemini-2.5-flash-lite' # Specify model
67
67
  max_iterations 10 # Limit reasoning cycles
68
- timeout 30 # Request timeout
69
68
 
70
69
  # Define tools
71
70
  tool :my_tool, 'Description' do
@@ -256,7 +255,6 @@ Soka.setup do |config|
256
255
 
257
256
  config.performance do |perf|
258
257
  perf.max_iterations = 10
259
- perf.timeout = 30
260
258
  end
261
259
  end
262
260
  ```
@@ -274,7 +272,6 @@ ANTHROPIC_API_KEY=your_api_key
274
272
 
275
273
  ### Performance Tips
276
274
  - Set appropriate `max_iterations` to prevent infinite loops
277
- - Use `timeout` to handle slow API responses
278
275
  - Implement caching for frequently used tools
279
276
  - Consider memory limits for long conversations
280
277
 
@@ -306,7 +303,7 @@ end
306
303
  ```ruby
307
304
  allow(agent).to receive(:llm).and_return(mock_llm)
308
305
  allow(mock_llm).to receive(:chat).and_return(
309
- double(content: '<Thought>Test</Thought><Final_Answer>Done</Final_Answer>')
306
+ double(content: '<Thought>Test</Thought><FinalAnswer>Done</FinalAnswer>')
310
307
  )
311
308
  ```
312
309
 
@@ -352,6 +349,13 @@ The `examples/` directory contains 10 comprehensive examples:
352
349
  - Remove duplicate code
353
350
  - Avoid over-abstraction
354
351
 
352
+ ### Ruby Code Organization (Important for AI Assistants)
353
+ - **Always organize Ruby class methods with public methods first, then private methods**
354
+ - Place all public methods at the beginning of the class
355
+ - Place all private methods at the end after a single `private` keyword
356
+ - Never mix public and private sections
357
+ - Avoid using `public` keyword explicitly unless switching back from private
358
+
355
359
  ### Code Documentation
356
360
  - **When adjusting code, add comments and documentation to methods**
357
361
  - Use YARD format comments
@@ -363,6 +367,17 @@ The `examples/` directory contains 10 comprehensive examples:
363
367
  - Document all public APIs
364
368
  - Keep README and CLAUDE.md updated
365
369
 
370
+ ### Problem Solving Approach (Important for AI Assistants)
371
+ - **Always solve problems directly - never avoid issues or use incorrect workarounds**
372
+ - Face technical challenges head-on without shortcuts or workarounds
373
+ - Ensure solutions align with architecture design and best practices
374
+ - Avoid temporary patches; pursue fundamental solutions
375
+ - **When facing difficult problems or needing more information, provide current status and issues, then pause for discussion before implementation**
376
+ - Analyze and explain the situation when encountering complex problems
377
+ - Clearly describe difficulties encountered and information needed
378
+ - Wait for confirmation and discussion before proceeding with implementation
379
+ - Maintain transparent communication without assuming or guessing requirements
380
+
366
381
  ## 🤝 Contributing
367
382
 
368
383
  [Contributing Guidelines]
data/README.md CHANGED
@@ -175,7 +175,6 @@ Soka.setup do |config|
175
175
  # Performance Configuration
176
176
  config.performance do |perf|
177
177
  perf.max_iterations = 10 # ReAct max iterations
178
- perf.timeout = 30 # API call timeout (seconds)
179
178
  end
180
179
 
181
180
  # Default tools
@@ -226,7 +225,6 @@ class WeatherAgent < Soka::Agent
226
225
  provider :gemini
227
226
  model 'gemini-2.5-flash-lite'
228
227
  max_iterations 10
229
- timeout 30
230
228
 
231
229
  # Register tools
232
230
  tool SearchTool
@@ -314,7 +312,7 @@ result = agent.run('What is the weather in Tokyo today?')
314
312
  # Result object provides rich information
315
313
  puts result.final_answer # Final answer
316
314
  puts result.iterations # Number of iterations used
317
- puts result.status # :success, :failed, :timeout, :max_iterations_reached
315
+ puts result.status # :success, :failed, :max_iterations_reached
318
316
  puts result.execution_time # Execution time (if recorded)
319
317
 
320
318
  # Check execution status
@@ -322,8 +320,6 @@ if result.successful?
322
320
  puts "Success: #{result.final_answer}"
323
321
  elsif result.failed?
324
322
  puts "Failed: #{result.error}"
325
- elsif result.timeout?
326
- puts "Execution timeout"
327
323
  elsif result.max_iterations_reached?
328
324
  puts "Max iterations reached"
329
325
  end
@@ -445,12 +441,11 @@ Soka uses a tagged ReAct format:
445
441
  ```xml
446
442
  <Thought>I need to search for weather information in Tokyo</Thought>
447
443
  <Action>
448
- Tool: search
449
- Parameters: {"query": "Tokyo weather", "location": "Japan"}
444
+ {"tool": "search", "parameters": {"query": "Tokyo weather", "location": "Japan"}}
450
445
  </Action>
451
446
  <Observation>Tokyo today: Sunny, temperature 28°C, humidity 65%</Observation>
452
447
  <Thought>I have obtained the weather information and can answer the user now</Thought>
453
- <Final_Answer>Today in Tokyo it's sunny with a temperature of 28°C and humidity of 65%.</Final_Answer>
448
+ <FinalAnswer>Today in Tokyo it's sunny with a temperature of 28°C and humidity of 65%.</FinalAnswer>
454
449
  ```
455
450
 
456
451
  ### Result Object Structure
@@ -460,7 +455,7 @@ Parameters: {"query": "Tokyo weather", "location": "Japan"}
460
455
  result.input # User input
461
456
  result.thoughts # Array of thinking steps
462
457
  result.final_answer # Final answer
463
- result.status # Status (:success, :failed, :timeout, :max_iterations_reached)
458
+ result.status # Status (:success, :failed, :max_iterations_reached)
464
459
  result.error # Error message (if any)
465
460
  result.execution_time # Execution time (seconds)
466
461
  result.iterations # Number of iterations
@@ -477,7 +472,7 @@ result.iterations # Number of iterations
477
472
  }
478
473
  ],
479
474
  final_answer: "Final answer",
480
- status: :success, # :success, :failed, :timeout, :max_iterations_reached
475
+ status: :success, # :success, :failed, :max_iterations_reached
481
476
  error: nil, # Error message (if any)
482
477
  execution_time: 1.23, # Execution time (seconds)
483
478
  iterations: 2, # Number of iterations
@@ -663,7 +658,7 @@ ruby examples/1_basic.rb
663
658
  - Default model: `gemini-2.5-flash-lite`
664
659
 
665
660
  #### OpenAI
666
- - Models: `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`
661
+ - Models: `gpt-5`, `gpt-5-mini`, `gpt-5-nano`
667
662
  - Environment variable: `OPENAI_API_KEY`
668
663
  - Features: Streaming support, powerful reasoning
669
664
 
@@ -672,6 +667,24 @@ ruby examples/1_basic.rb
672
667
  - Environment variable: `ANTHROPIC_API_KEY`
673
668
  - Features: Long context support, excellent code understanding
674
669
 
670
+ ### Model Testing Results
671
+
672
+ Based on extensive testing with the Soka framework, here are the recommended models for optimal performance:
673
+
674
+ | Provider | ✅ Recommended Models | ⚠️ Not Recommended | Notes |
675
+ |----------|----------------------|-------------------|--------|
676
+ | **Gemini** | `gemini-2.5-flash-lite` ⭐<br>`gemini-2.5-pro` | `gemini-2.5-flash` | **gemini-2.5-flash-lite**: Fast, cost-effective, with good performance<br>**Avoid gemini-2.5-flash**: Often skips thinking process when using tools, directly jumps to tool usage |
677
+ | **OpenAI** | `gpt-5` <br>`gpt-5-mini` ⭐ | `gpt-5-nano` | **gpt-5-mini**: Good balance of price, speed, and effectiveness<br>**Avoid gpt-5-nano**: Can enter infinite tool-calling loops, fails to complete tasks |
678
+ | **Claude** | *To be tested* | - | Testing in progress |
679
+
680
+ ⭐ = Best choice for most use cases
681
+
682
+ #### Testing Insights
683
+
684
+ - **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
685
+ - **Cost-Performance**: `gemini-2.5-flash-lite` and `gpt-5-mini` offer the best balance for most applications
686
+ - **Reliability**: Recommended models consistently complete tasks without entering error states
687
+
675
688
  ### Configuration Options
676
689
 
677
690
  | Option | Type | Default | Description |
@@ -682,7 +695,6 @@ ruby examples/1_basic.rb
682
695
  | `ai.instructions` | String | nil | Custom agent instructions |
683
696
  | `ai.think_in` | String | `"en"` | Thinking language |
684
697
  | `performance.max_iterations` | Integer | 10 | Max iterations |
685
- | `performance.timeout` | Integer | 30 | Timeout (seconds) |
686
698
 
687
699
  ### Tool Parameter Validation
688
700
 
@@ -696,8 +708,8 @@ ruby examples/1_basic.rb
696
708
  ## Performance Optimization
697
709
 
698
710
  1. **Use appropriate models**:
699
- - Simple tasks: `gemini-2.5-flash-lite` or `gpt-4.1-mini` or `claude-3-5-haiku-latest`
700
- - Complex reasoning: `gemini-2.5-pro` or `gpt-4.1` or `claude-sonnet-4-0`
711
+ - Simple tasks: `gemini-2.5-flash-lite` or `gpt-5-mini` or `claude-3-5-haiku-latest`
712
+ - Complex reasoning: `gemini-2.5-pro` or `gpt-5` or `claude-sonnet-4-0`
701
713
 
702
714
  2. **Control iterations**:
703
715
  ```ruby
@@ -715,13 +727,7 @@ ruby examples/1_basic.rb
715
727
  ```
716
728
  Solution: Ensure correct environment variable is set or provide API key in configuration
717
729
 
718
- 2. **Timeout Error**
719
- ```
720
- Soka::LLMError: Request timed out
721
- ```
722
- Solution: Increase timeout or use a faster model
723
-
724
- 3. **Max Iterations Reached**
730
+ 2. **Max Iterations Reached**
725
731
  ```
726
732
  Status: max_iterations_reached
727
733
  ```
@@ -795,7 +801,7 @@ This project is licensed under the MIT License. See the [LICENSE](LICENSE) file
795
801
  ## Acknowledgments
796
802
 
797
803
  - Thanks to the [ReAct paper](https://arxiv.org/abs/2210.03629) for the theoretical foundation
798
- - Thanks to the [Regent](https://github.com/alextwoods/regent) project for architectural inspiration
804
+ - Thanks to the [Regent](https://github.com/alchaplinsky/regent) project for architectural inspiration
799
805
  - Thanks to all contributors for their efforts
800
806
 
801
807
  ---
data/examples/1_basic.rb CHANGED
@@ -15,7 +15,6 @@ Soka.setup do |config|
15
15
 
16
16
  config.performance do |perf|
17
17
  perf.max_iterations = 5
18
- perf.timeout = 30
19
18
  end
20
19
  end
21
20
 
@@ -16,7 +16,6 @@ Soka.setup do |config|
16
16
 
17
17
  config.performance do |perf|
18
18
  perf.max_iterations = 5
19
- perf.timeout = 30
20
19
  end
21
20
  end
22
21
 
data/examples/3_memory.rb CHANGED
@@ -17,7 +17,6 @@ Soka.setup do |config|
17
17
 
18
18
  config.performance do |perf|
19
19
  perf.max_iterations = 10
20
- perf.timeout = 30
21
20
  end
22
21
  end
23
22
 
data/lib/soka/agent.rb CHANGED
@@ -17,7 +17,6 @@ module Soka
17
17
  # @param engine [Class] The engine class to use (defaults to Engine::React)
18
18
  # @param options [Hash] Configuration options
19
19
  # @option options [Integer] :max_iterations Maximum iterations for reasoning
20
- # @option options [Integer] :timeout Timeout in seconds for operations
21
20
  # @option options [Symbol] :provider LLM provider override
22
21
  # @option options [String] :model LLM model override
23
22
  # @option options [String] :api_key LLM API key override
@@ -44,8 +43,9 @@ module Soka
44
43
  # Apply performance-related configuration
45
44
  # @param options [Hash] Configuration options
46
45
  def apply_performance_config(options)
47
- @max_iterations = options.fetch(:max_iterations) { self.class._max_iterations } || 10
48
- @timeout = options.fetch(:timeout) { self.class._timeout } || 30
46
+ @max_iterations = options.fetch(:max_iterations) do
47
+ self.class._max_iterations || Soka.configuration.performance.max_iterations
48
+ end
49
49
  end
50
50
 
51
51
  # Apply behavior-related configuration
@@ -5,6 +5,15 @@ module Soka
5
5
  class AgentTool
6
6
  include AgentTools::ParamsValidator
7
7
 
8
+ TYPE_MAPPING = {
9
+ 'String' => 'string',
10
+ 'Integer' => 'integer', 'Fixnum' => 'integer', 'Bignum' => 'integer',
11
+ 'Float' => 'number', 'Numeric' => 'number',
12
+ 'TrueClass' => 'boolean', 'FalseClass' => 'boolean', 'Boolean' => 'boolean',
13
+ 'Array' => 'array',
14
+ 'Hash' => 'object', 'Object' => 'object'
15
+ }.freeze
16
+
8
17
  # Handles parameter definitions for tools
9
18
  class ParamsDefinition
10
19
  attr_reader :required_params, :optional_params, :validations
@@ -109,22 +118,7 @@ module Soka
109
118
  end
110
119
 
111
120
  def type_to_json_type(ruby_type)
112
- type_mapping[ruby_type.to_s] || 'string'
113
- end
114
-
115
- def type_mapping
116
- @type_mapping ||= build_type_mapping
117
- end
118
-
119
- def build_type_mapping
120
- {
121
- 'String' => 'string',
122
- 'Integer' => 'integer', 'Fixnum' => 'integer', 'Bignum' => 'integer',
123
- 'Float' => 'number', 'Numeric' => 'number',
124
- 'TrueClass' => 'boolean', 'FalseClass' => 'boolean', 'Boolean' => 'boolean',
125
- 'Array' => 'array',
126
- 'Hash' => 'object', 'Object' => 'object'
127
- }.freeze
121
+ TYPE_MAPPING[ruby_type.to_s] || 'string'
128
122
  end
129
123
  end
130
124
 
@@ -10,7 +10,7 @@ module Soka
10
10
 
11
11
  # Class methods for DSL
12
12
  module ClassMethods
13
- attr_accessor :_provider, :_model, :_api_key, :_max_iterations, :_timeout, :_tools, :_retry_config, :_hooks,
13
+ attr_accessor :_provider, :_model, :_api_key, :_max_iterations, :_tools, :_retry_config, :_hooks,
14
14
  :_instructions, :_think_in
15
15
 
16
16
  def inherited(subclass)
@@ -44,12 +44,6 @@ module Soka
44
44
  @_max_iterations = num
45
45
  end
46
46
 
47
- # Define timeout for the agent
48
- # @param duration [Integer] The timeout duration in seconds
49
- def timeout(duration)
50
- @_timeout = duration
51
- end
52
-
53
47
  # Define custom instructions (system prompt) for the agent
54
48
  # @param text_or_method [String, Symbol] The custom instructions/system prompt or method name
55
49
  # @example Using a string
@@ -5,7 +5,7 @@ module Soka
5
5
  class Configuration
6
6
  # AI provider configuration
7
7
  class AIConfig
8
- attr_accessor :provider, :model, :api_key, :fallback_provider, :fallback_model, :fallback_api_key
8
+ attr_accessor :provider, :model, :api_key
9
9
 
10
10
  def initialize
11
11
  @provider = :gemini
@@ -15,11 +15,10 @@ module Soka
15
15
 
16
16
  # Performance-related configuration
17
17
  class PerformanceConfig
18
- attr_accessor :max_iterations, :timeout
18
+ attr_accessor :max_iterations
19
19
 
20
20
  def initialize
21
21
  @max_iterations = 10
22
- @timeout = 30
23
22
  end
24
23
  end
25
24
 
@@ -15,7 +15,7 @@ module Soka
15
15
  {
16
16
  thoughts: extract_tagged_content(text, 'Thought'),
17
17
  actions: extract_actions(text),
18
- final_answer: extract_tagged_content(text, 'Final_Answer').first
18
+ final_answer: extract_tagged_content(text, 'FinalAnswer').first
19
19
  }
20
20
  end
21
21
 
@@ -29,30 +29,18 @@ module Soka
29
29
  action_blocks.filter_map { |block| parse_action_block(block[0]) }
30
30
  end
31
31
 
32
+ # Parse action block from LLM response in JSON format
33
+ # @param content [String] The action block content containing JSON
34
+ # @return [Hash, nil] The parsed action with tool and params
32
35
  def parse_action_block(content)
33
36
  content = content.strip
34
- tool_match = content.match(/Tool:\s*(.+)/)
35
- params_match = content.match(/Parameters:\s*(.+)/m)
37
+ action_json = Oj.load(content, symbol_keys: true)
36
38
 
37
- return unless tool_match && params_match
39
+ return nil unless action_json[:tool]
38
40
 
39
- tool_name = tool_match[1].strip
40
- params_json = params_match[1].strip
41
- params = parse_json_params(params_json)
42
-
43
- { tool: tool_name, params: params }
44
- end
45
-
46
- # Parse JSON parameters from action block
47
- # @param params_json [String] The JSON string to parse
48
- # @return [Hash] The parsed parameters as a hash with symbol keys
49
- def parse_json_params(params_json)
50
- # Clean up the JSON string - remove any trailing commas or whitespace
51
- cleaned_json = params_json.strip.gsub(/,\s*}/, '}').gsub(/,\s*\]/, ']')
52
- JSON.parse(cleaned_json, symbolize_names: true)
53
- rescue JSON::ParserError
54
- # Return empty hash to continue when JSON parsing fails
55
- {}
41
+ { tool: action_json[:tool], params: action_json[:parameters] || {} }
42
+ rescue Oj::ParseError
43
+ nil
56
44
  end
57
45
  end
58
46
  end
@@ -86,16 +86,27 @@ module Soka
86
86
  # @param context [ReasoningContext] The reasoning context
87
87
  # @param content [String] The raw response content
88
88
  def handle_no_action(context, content)
89
+ # Important: We still need to add the message to maintain conversation flow
90
+ # but we should NOT add a new thought since the response was incomplete
89
91
  context.add_message(role: 'assistant', content: content)
90
92
  context.add_message(
91
93
  role: 'user',
92
- content: 'Please follow the exact format with <Thought>, <Action>, and <Final_Answer> tags.'
94
+ content: format_reminder
93
95
  )
94
96
  end
95
97
 
96
98
  def format_observation(observation)
97
99
  "<Observation>#{observation}</Observation>"
98
100
  end
101
+
102
+ def format_reminder
103
+ # Strict mode (default)
104
+ 'CRITICAL: Only responses with proper <Thought>, <Action>, and <FinalAnswer> tags will be processed. ' \
105
+ 'Other formats will fail.'
106
+ # Normal mode (alternative)
107
+ # 'Structure your response using these required tags: <Thought> for reasoning, ' \
108
+ # '<Action> for tool usage, and <FinalAnswer> for conclusions.'
109
+ end
99
110
  end
100
111
  end
101
112
  end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Engines
5
+ module Prompts
6
+ # Base module for prompt generation
7
+ module Base
8
+ private
9
+
10
+ def system_prompt
11
+ # Use custom instructions if provided, otherwise use default ReAct prompt
12
+ if custom_instructions
13
+ combine_with_react_format(custom_instructions).strip
14
+ else
15
+ default_react_prompt.strip
16
+ end
17
+ end
18
+
19
+ def default_react_prompt
20
+ tools_description = format_tools_description(tools)
21
+
22
+ <<~PROMPT
23
+ You are an AI assistant that uses the ReAct (Reasoning and Acting) framework to solve problems step by step.
24
+
25
+ You have access to the following tools:
26
+ #{tools_description}
27
+
28
+ #{format_instructions}
29
+ PROMPT
30
+ end
31
+
32
+ def combine_with_react_format(instructions)
33
+ tools_description = format_tools_description(tools)
34
+
35
+ <<~PROMPT
36
+ #{react_base_prompt}
37
+
38
+ #{react_framework_structure}
39
+
40
+ You have access to the following tools:
41
+ #{tools_description}
42
+
43
+ #{format_instructions}
44
+
45
+ #{custom_instructions_section(instructions)}
46
+ PROMPT
47
+ end
48
+
49
+ def react_base_prompt
50
+ 'You are an AI assistant that uses the ReAct (Reasoning and Acting) framework to solve problems step by step.'
51
+ end
52
+
53
+ def react_framework_structure
54
+ <<~STRUCTURE
55
+ 🔧 REACT FRAMEWORK STRUCTURE:
56
+ You MUST use XML-style tags to structure your response. Each tag has a specific purpose:
57
+ - <Thought>: Your first-person reasoning (max 30 words)
58
+ - <Action>: Tool invocation with JSON parameters
59
+ - <Observation>: Tool results (provided by system)
60
+ - <FinalAnswer>: Your complete solution (direct & concise)
61
+
62
+ These tags are MANDATORY and define the ReAct workflow. The system parses these tags to understand your reasoning process.
63
+ STRUCTURE
64
+ end
65
+
66
+ def custom_instructions_section(instructions)
67
+ <<~SECTION
68
+ 📋 CUSTOM BEHAVIOR INSTRUCTIONS:
69
+ ═══════════════════════════════════════════
70
+ The following instructions apply to the CONTENT within your XML tags, NOT the ReAct structure itself:
71
+
72
+ #{instructions}
73
+
74
+ ⚠️ IMPORTANT: These custom instructions modify HOW you think and respond within the tags,
75
+ but DO NOT change the requirement to use the XML tag structure for ReAct.
76
+ ═══════════════════════════════════════════
77
+ SECTION
78
+ end
79
+
80
+ def format_instructions
81
+ thinking_instruction = build_thinking_instruction(think_in)
82
+ iteration_limit = build_iteration_limit_warning
83
+
84
+ <<~INSTRUCTIONS
85
+ #{iteration_limit}
86
+
87
+ 🎯 MANDATORY XML TAG STRUCTURE:
88
+ You MUST use XML-style tags to wrap your content. This is NOT optional.
89
+ The system relies on these tags to parse and understand your reasoning.
90
+
91
+ #{thinking_instruction}
92
+
93
+ #{step_format_example}
94
+
95
+ #{action_format_rules}
96
+
97
+ #{final_answer_critical_format}
98
+ INSTRUCTIONS
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Engines
5
+ module Prompts
6
+ # Helper methods for formatting prompt components
7
+ module FormatHelpers
8
+ private
9
+
10
+ # Format tools description for prompt
11
+ # @param tools [Array] The tools available
12
+ # @return [String] Formatted tools description
13
+ def format_tools_description(tools)
14
+ return 'No tools available.' if tools.empty?
15
+
16
+ tools.map do |tool|
17
+ schema = tool.class.to_h
18
+ params_desc = format_parameters(schema[:parameters])
19
+
20
+ "- #{schema[:name]}: #{schema[:description]}\n Parameters: \n#{params_desc}"
21
+ end.join("\n")
22
+ end
23
+
24
+ # Format parameters for tool description
25
+ # @param params_schema [Hash] The parameters schema
26
+ # @return [String] Formatted parameters description
27
+ def format_parameters(params_schema)
28
+ return 'none' if params_schema[:properties].empty?
29
+
30
+ properties = params_schema[:properties].map do |name, config|
31
+ required = params_schema[:required].include?(name.to_s) ? '(required)' : '(optional)'
32
+ type = config[:type]
33
+ desc = config[:description]
34
+
35
+ " - #{name} #{required} [#{type}] - #{desc}"
36
+ end
37
+
38
+ properties.join("\n")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Engines
5
+ module Prompts
6
+ # Module for building instruction components
7
+ module Instructions
8
+ private
9
+
10
+ # Build iteration limit warning
11
+ # @return [String] The iteration limit warning
12
+ def build_iteration_limit_warning
13
+ return '' unless respond_to?(:max_iterations) && max_iterations
14
+
15
+ <<~WARNING
16
+ ⏰ ITERATION LIMIT: You have a maximum of #{max_iterations} iterations to complete this task.
17
+ - Each Thought/Action cycle counts as one iteration
18
+ - Plan your approach efficiently to stay within this limit
19
+ - If you cannot complete the task within #{max_iterations} iterations, provide your best answer with what you have
20
+ WARNING
21
+ end
22
+
23
+ # Build thinking instruction based on language
24
+ # @param language [String, nil] The language to use for thinking
25
+ # @return [String] The thinking instruction
26
+ def build_thinking_instruction(language)
27
+ return '' unless language
28
+
29
+ <<~INSTRUCTION
30
+ 🌐 THINKING LANGUAGE:
31
+ Use #{language} for your reasoning WITHIN the <Thought> XML tags.
32
+ This affects the content inside tags, not the tag structure itself.
33
+ INSTRUCTION
34
+ end
35
+
36
+ # Step format example
37
+ # @return [String] The step format example
38
+ def step_format_example
39
+ <<~FORMAT
40
+ 📝 XML TAG FORMAT FOR EACH STEP:
41
+
42
+ 1️⃣ THINKING PHASE (Required):
43
+ <Thought>
44
+ MAXIMUM 30 WORDS - Be extremely concise!
45
+ Use first-person perspective (I, me, my).
46
+ NEVER mention: "LLM", "AI", "formatting for", "organizing for someone".
47
+ NEVER say: "I will act as", "I will play the role of", "as an expert".
48
+ BE DIRECT: "I'll check", "Let me see", "Looking at this".
49
+ Keep it light and witty within the word limit.
50
+ </Thought>
51
+
52
+ 2️⃣ ACTION PHASE (When needed):
53
+ <Action>
54
+ {"tool": "tool_name", "parameters": {"param1": "value1", "param2": "value2"}}
55
+ </Action>
56
+
57
+ ⚠️ CRITICAL: Each tag MUST have both opening <TagName> and closing </TagName>.
58
+ The content between tags follows any custom instructions provided.
59
+ FORMAT
60
+ end
61
+
62
+ # Critical format for final answer
63
+ # @return [String] The critical format instructions
64
+ def final_answer_critical_format
65
+ <<~CRITICAL
66
+ ⚠️ CRITICAL - FINAL ANSWER XML TAG FORMAT:
67
+ When you have the complete answer, YOU MUST use XML-style tags:
68
+
69
+ <FinalAnswer>
70
+ State the result directly without explanation.
71
+ No justification or reasoning needed - just the answer.
72
+ Maximum 300 words. Be direct and concise.
73
+ </FinalAnswer>
74
+
75
+ 🚨 PARSER REQUIREMENTS:
76
+ - The system parser REQUIRES both opening <FinalAnswer> and closing </FinalAnswer> tags
77
+ - Without proper XML tags, the system CANNOT detect task completion
78
+ - The tags are case-sensitive and must match exactly
79
+ - Final answer MUST NOT exceed 300 words
80
+ - NO over-explanation - direct results only
81
+ CRITICAL
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Engines
5
+ module Prompts
6
+ # Module for ReAct workflow rules
7
+ module WorkflowRules
8
+ private
9
+
10
+ # Action format rules
11
+ # @return [String] The action format rules
12
+ def action_format_rules
13
+ <<~RULES
14
+ 🔄 REACT WORKFLOW WITH XML TAGS:
15
+
16
+ STOP HERE after each Action. Do NOT include <Observation> in your response.
17
+ The system will execute the tool and provide the observation wrapped in <Observation> tags.
18
+
19
+ After receiving the observation, you can continue with more Thought/Action cycles or provide a final answer.
20
+
21
+ #{react_flow_example}
22
+
23
+ #{xml_tag_requirements}
24
+ RULES
25
+ end
26
+
27
+ # ReAct flow example
28
+ # @return [String] The ReAct flow example
29
+ def react_flow_example
30
+ <<~EXAMPLE
31
+ 📝 EXAMPLE OF COMPLETE REACT FLOW WITH XML TAGS:
32
+
33
+ Step 1: Your response
34
+ <Thought>Math problem! I'll use the calculator tool for 2+2.</Thought>
35
+ <Action>{"tool": "calculator", "parameters": {"expression": "2+2"}}</Action>
36
+
37
+ Step 2: System provides
38
+ <Observation>4</Observation>
39
+
40
+ Step 3: Your response
41
+ <Thought>Got it - the answer is 4!</Thought>
42
+ <FinalAnswer>4</FinalAnswer>
43
+ EXAMPLE
44
+ end
45
+
46
+ # XML tag requirements
47
+ # @return [String] The XML tag requirements
48
+ def xml_tag_requirements
49
+ <<~REQUIREMENTS
50
+ 🚨 XML TAG REQUIREMENTS AND RULES:
51
+
52
+ 1. 🏷️ MANDATORY XML STRUCTURE:
53
+ - Always wrap content in appropriate XML tags
54
+ - Tags: <Thought>, <Action>, <FinalAnswer>
55
+ - Each tag MUST have matching closing tag
56
+
57
+ 2. 💭 THINKING PHASE:
58
+ - Always start with <Thought>...</Thought>
59
+ - MAXIMUM 30 WORDS per thought
60
+ - Use first-person perspective (I, me, my)
61
+ - AVOID: "LLM", "AI", "formatting for", "organizing for"
62
+ - AVOID: "act as", "play role of", "as an expert", "I will be"
63
+ - BE DIRECT: Use natural language like "I'll check", "Let me see"
64
+ - Be concise and witty
65
+ - Custom instructions affect HOW you think, not WHETHER you use tags
66
+
67
+ 3. 🔧 ACTION FORMAT:
68
+ - <Action> content MUST be valid JSON on a single line
69
+ - Format: {"tool": "name", "parameters": {...}}
70
+ - Empty parameters: {"tool": "name", "parameters": {}}
71
+
72
+ 4. 👁️ OBSERVATION:
73
+ - NEVER create <Observation> tags yourself
74
+ - System provides these automatically
75
+
76
+ 5. ✅ FINAL ANSWER:
77
+ - MUST use <FinalAnswer>...</FinalAnswer> tags
78
+ - Both opening AND closing tags REQUIRED
79
+ - System cannot detect completion without these tags
80
+ - No text after closing </FinalAnswer> tag
81
+ - Maximum 300 words
82
+ - Direct results only - NO explanations or justifications
83
+
84
+ 6. 🎯 EFFICIENCY:
85
+ - Limited iterations available
86
+ - Think harder upfront to minimize tool calls
87
+ - Prioritize essential actions
88
+
89
+ 7. 📋 CUSTOM INSTRUCTIONS SCOPE:
90
+ - Custom instructions modify content WITHIN tags
91
+ - They DO NOT change the XML tag structure requirement
92
+ - ReAct workflow with XML tags is ALWAYS required
93
+ REQUIREMENTS
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Engines
5
+ # Main module that combines all prompt components
6
+ module Prompts
7
+ include Base
8
+ include Instructions
9
+ include WorkflowRules
10
+ include FormatHelpers
11
+ end
12
+ end
13
+ end
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Soka
6
4
  module Engines
7
5
  # ReAct (Reasoning and Acting) engine implementation
8
6
  class React < Base
7
+ include Prompts
9
8
  include Concerns::ResponseProcessor
10
- include Concerns::PromptTemplate
11
9
  include Concerns::ResponseParser
12
10
  include Concerns::ResultBuilder
13
11
 
@@ -35,12 +33,13 @@ module Soka
35
33
  # @param context [ReasoningContext] The reasoning context
36
34
  # @return [ReasonResult, nil] The result if found, nil otherwise
37
35
  def iterate_reasoning(context)
38
- max_iterations.times do
36
+ max_iterations.times do |index|
37
+ context.iteration = index # Set current iteration number
39
38
  result = process_iteration(context)
40
39
  return result if result
41
-
42
- context.increment_iteration!
43
40
  end
41
+ # When max iterations reached, ensure iteration count is correct
42
+ context.iteration = max_iterations
44
43
  nil
45
44
  end
46
45
 
@@ -37,18 +37,6 @@ module Soka
37
37
  @event_handler.call(event)
38
38
  end
39
39
 
40
- # Check if we've reached the maximum number of iterations
41
- # @return [Boolean] true if max iterations reached
42
- def max_iterations_reached?
43
- @iteration >= @max_iterations
44
- end
45
-
46
- # Increment the iteration counter
47
- # @return [Integer] The new iteration count
48
- def increment_iteration!
49
- @iteration += 1
50
- end
51
-
52
40
  # Get the current iteration number (1-based for display)
53
41
  # @return [Integer] The current iteration number for display
54
42
  def current_step
@@ -6,6 +6,21 @@ module Soka
6
6
  class Anthropic < Base
7
7
  ENV_KEY = 'ANTHROPIC_API_KEY'
8
8
 
9
+ def chat(messages, **params)
10
+ request_params = build_request_params(messages, params)
11
+
12
+ response = connection.post do |req|
13
+ req.url '/v1/messages'
14
+ req.headers['x-api-key'] = api_key
15
+ req.headers['anthropic-version'] = options[:anthropic_version]
16
+ req.body = request_params
17
+ end
18
+
19
+ parse_response(response)
20
+ rescue Faraday::Error => e
21
+ handle_error(e)
22
+ end
23
+
9
24
  private
10
25
 
11
26
  def default_model
@@ -21,30 +36,11 @@ module Soka
21
36
  temperature: 0.7,
22
37
  top_p: 1.0,
23
38
  top_k: 1,
24
- max_tokens: 2048,
25
- anthropic_version: '2023-06-01'
39
+ anthropic_version: '2023-06-01',
40
+ max_tokens: 2048
26
41
  }
27
42
  end
28
43
 
29
- public
30
-
31
- def chat(messages, **params)
32
- request_params = build_request_params(messages, params)
33
-
34
- response = connection.post do |req|
35
- req.url '/v1/messages'
36
- req.headers['x-api-key'] = api_key
37
- req.headers['anthropic-version'] = options[:anthropic_version]
38
- req.body = request_params
39
- end
40
-
41
- parse_response(response)
42
- rescue Faraday::Error => e
43
- handle_error(e)
44
- end
45
-
46
- private
47
-
48
44
  def build_request_params(messages, params)
49
45
  formatted_messages, system_prompt = extract_system_prompt(messages)
50
46
  request = build_base_request(formatted_messages, params)
@@ -57,8 +57,7 @@ module Soka
57
57
  faraday.request :json
58
58
  faraday.response :json
59
59
  faraday.adapter Faraday.default_adapter
60
- faraday.options.timeout = options[:timeout] || 30
61
- faraday.options.open_timeout = options[:open_timeout] || 10
60
+ faraday.options.timeout = 60
62
61
  end
63
62
  end
64
63
 
@@ -20,8 +20,7 @@ module Soka
20
20
  {
21
21
  temperature: 0.7,
22
22
  top_p: 1.0,
23
- top_k: 1,
24
- max_output_tokens: 2048
23
+ top_k: 1
25
24
  }
26
25
  end
27
26
 
@@ -50,7 +49,7 @@ module Soka
50
49
  temperature: params[:temperature] || options[:temperature],
51
50
  topP: params[:top_p] || options[:top_p],
52
51
  topK: params[:top_k] || options[:top_k],
53
- maxOutputTokens: params[:max_output_tokens] || options[:max_output_tokens]
52
+ thinkingConfig: { thinkingBudget: 512 }
54
53
  }
55
54
  }
56
55
  end
@@ -6,33 +6,11 @@ module Soka
6
6
  class OpenAI < Base
7
7
  ENV_KEY = 'OPENAI_API_KEY'
8
8
 
9
- private
10
-
11
- def default_model
12
- 'gpt-4.1-mini'
13
- end
14
-
15
- def base_url
16
- 'https://api.openai.com'
17
- end
18
-
19
- def default_options
20
- {
21
- temperature: 0.7,
22
- top_p: 1.0,
23
- frequency_penalty: 0,
24
- presence_penalty: 0,
25
- max_tokens: 2048
26
- }
27
- end
28
-
29
- public
30
-
31
9
  def chat(messages, **params)
32
10
  request_params = build_request_params(messages, params)
33
11
 
34
12
  response = connection.post do |req|
35
- req.url '/v1/chat/completions'
13
+ req.url '/v1/responses'
36
14
  req.headers['Authorization'] = "Bearer #{api_key}"
37
15
  req.body = request_params
38
16
  end
@@ -44,16 +22,35 @@ module Soka
44
22
 
45
23
  private
46
24
 
25
+ def default_model
26
+ 'gpt-5-mini'
27
+ end
28
+
29
+ def base_url
30
+ 'https://api.openai.com'
31
+ end
32
+
47
33
  def build_request_params(messages, params)
48
- {
34
+ request_params = {
49
35
  model: model,
50
- messages: messages,
51
- temperature: params[:temperature] || options[:temperature],
52
- top_p: params[:top_p] || options[:top_p],
53
- frequency_penalty: params[:frequency_penalty] || options[:frequency_penalty],
54
- presence_penalty: params[:presence_penalty] || options[:presence_penalty],
55
- max_tokens: params[:max_tokens] || options[:max_tokens]
36
+ input: messages
56
37
  }
38
+
39
+ # Add max_output_tokens if provided (Responses API uses max_output_tokens)
40
+ request_params[:max_output_tokens] = params[:max_tokens] if params[:max_tokens]
41
+ add_reasoning_effort(request_params)
42
+
43
+ request_params
44
+ end
45
+
46
+ def add_reasoning_effort(request_params)
47
+ return unless allowed_reasoning_prefix_models?
48
+
49
+ request_params[:reasoning] = { effort: 'minimal', summary: 'auto' }
50
+ end
51
+
52
+ def allowed_reasoning_prefix_models?
53
+ model.start_with?('gpt-5') && !model.start_with?('gpt-5-chat-latest')
57
54
  end
58
55
 
59
56
  def parse_response(response)
@@ -70,18 +67,27 @@ module Soka
70
67
  end
71
68
 
72
69
  def build_result_from_response(body)
73
- choice = body.dig('choices', 0)
74
- message = choice['message']
70
+ # Extract text from the Responses API format
71
+ output_text = extract_output_text(body['output'])
72
+
73
+ # Get the status to determine finish reason
74
+ finish_reason = body['status'] == 'completed' ? 'stop' : body['status']
75
75
 
76
76
  Result.new(
77
77
  model: body['model'],
78
- content: message['content'],
79
- input_tokens: body.dig('usage', 'prompt_tokens'),
80
- output_tokens: body.dig('usage', 'completion_tokens'),
81
- finish_reason: choice['finish_reason'],
78
+ content: output_text,
79
+ input_tokens: body.dig('usage', 'input_tokens'),
80
+ output_tokens: body.dig('usage', 'output_tokens'),
81
+ finish_reason: finish_reason,
82
82
  raw_response: body
83
83
  )
84
84
  end
85
+
86
+ def extract_output_text(output_items)
87
+ message = output_items.find { |item| item['type'] == 'message' }
88
+ content = message['content'].find { |content| content['type'] == 'output_text' }
89
+ content['text']
90
+ end
85
91
  end
86
92
  end
87
93
  end
data/lib/soka/result.rb CHANGED
@@ -37,12 +37,6 @@ module Soka
37
37
  status == :failed
38
38
  end
39
39
 
40
- # Check if the result timed out
41
- # @return [Boolean]
42
- def timeout?
43
- status == :timeout
44
- end
45
-
46
40
  # Check if max iterations were reached
47
41
  # @return [Boolean]
48
42
  def max_iterations_reached?
@@ -65,8 +59,8 @@ module Soka
65
59
 
66
60
  # Convert to JSON string
67
61
  # @return [String]
68
- def to_json(...)
69
- to_h.to_json(...)
62
+ def to_json(*)
63
+ Oj.dump(to_h, mode: :compat)
70
64
  end
71
65
 
72
66
  # Get a summary of the result
@@ -117,7 +111,6 @@ module Soka
117
111
  {
118
112
  success: "Success: #{truncate(final_answer)}",
119
113
  failed: "Failed: #{error}",
120
- timeout: 'Timeout: Execution exceeded time limit',
121
114
  max_iterations_reached: "Max iterations reached: #{iterations} iterations"
122
115
  }
123
116
  end
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.5'
4
+ VERSION = '0.0.6'
5
5
  end
data/lib/soka.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'zeitwerk'
4
4
  require 'faraday'
5
+ require 'oj'
5
6
 
6
7
  # Main module for the Soka ReAct Agent Framework
7
8
  # Provides AI agent capabilities with multiple LLM providers support
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soka
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - jiunjiun
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-05 00:00:00.000000000 Z
11
+ date: 2025-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.16'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.16'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: zeitwerk
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -76,10 +90,14 @@ files:
76
90
  - lib/soka/agents/tool_builder.rb
77
91
  - lib/soka/configuration.rb
78
92
  - lib/soka/engines/base.rb
79
- - lib/soka/engines/concerns/prompt_template.rb
80
93
  - lib/soka/engines/concerns/response_parser.rb
81
94
  - lib/soka/engines/concerns/response_processor.rb
82
95
  - lib/soka/engines/concerns/result_builder.rb
96
+ - lib/soka/engines/prompts.rb
97
+ - lib/soka/engines/prompts/base.rb
98
+ - lib/soka/engines/prompts/format_helpers.rb
99
+ - lib/soka/engines/prompts/instructions.rb
100
+ - lib/soka/engines/prompts/workflow_rules.rb
83
101
  - lib/soka/engines/react.rb
84
102
  - lib/soka/engines/reasoning_context.rb
85
103
  - lib/soka/llm.rb
@@ -1,121 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Soka
4
- module Engines
5
- module Concerns
6
- # Module for handling prompt templates in ReAct engine
7
- module PromptTemplate
8
- private
9
-
10
- def system_prompt
11
- # Use custom instructions if provided, otherwise use default ReAct prompt
12
- if custom_instructions
13
- combine_with_react_format(custom_instructions)
14
- else
15
- default_react_prompt
16
- end
17
- end
18
-
19
- def default_react_prompt
20
- tools_description = format_tools_description(tools)
21
-
22
- <<~PROMPT
23
- You are an AI assistant that uses the ReAct (Reasoning and Acting) framework to solve problems step by step.
24
-
25
- You have access to the following tools:
26
- #{tools_description}
27
-
28
- #{format_instructions}
29
- PROMPT
30
- end
31
-
32
- def combine_with_react_format(instructions)
33
- tools_description = format_tools_description(tools)
34
-
35
- <<~PROMPT
36
- #{instructions}
37
-
38
- You have access to the following tools:
39
- #{tools_description}
40
-
41
- #{format_instructions}
42
- PROMPT
43
- end
44
-
45
- def format_instructions
46
- thinking_instruction = build_thinking_instruction(think_in)
47
-
48
- <<~INSTRUCTIONS
49
- You must follow this exact format for each step:
50
-
51
- #{thinking_instruction}
52
-
53
- <Thought>Your reasoning about what to do next</Thought>
54
- <Action>
55
- Tool: tool_name
56
- Parameters: {"param1": "value1", "param2": "value2"}
57
- </Action>
58
-
59
- #{action_format_rules}
60
- INSTRUCTIONS
61
- end
62
-
63
- # Build thinking instruction based on language
64
- # @param language [String, nil] The language to use for thinking
65
- # @return [String] The thinking instruction
66
- def build_thinking_instruction(language)
67
- return '' unless language
68
-
69
- "Use #{language} for your reasoning in <Thought> tags."
70
- end
71
-
72
- # Action format rules
73
- # @return [String] The action format rules
74
- def action_format_rules
75
- <<~RULES
76
- STOP HERE after each Action. Do NOT include <Observation> in your response.
77
- The system will execute the tool and provide the observation.
78
-
79
- After receiving the observation, you can continue with more Thought/Action cycles or provide a final answer:
80
-
81
- <Final_Answer>Your complete answer to the user's question</Final_Answer>
82
-
83
- Important rules:
84
- 1. Always start with a <Thought> to analyze the problem
85
- 2. Use tools when you need information or to perform actions
86
- 3. Parameters MUST be valid JSON format (e.g., {"query": "weather"} not {query: "weather"})
87
- 4. For tools without parameters, use empty JSON object: {}
88
- 5. NEVER include <Observation> tags - wait for the system to provide them
89
- 6. Provide a clear and complete <Final_Answer> when done
90
- 7. If you cannot complete the task, explain why in the <Final_Answer>
91
- RULES
92
- end
93
-
94
- def format_tools_description(tools)
95
- return 'No tools available.' if tools.empty?
96
-
97
- tools.map do |tool|
98
- schema = tool.class.to_h
99
- params_desc = format_parameters(schema[:parameters])
100
-
101
- "- #{schema[:name]}: #{schema[:description]}\n Parameters: #{params_desc}"
102
- end.join("\n")
103
- end
104
-
105
- def format_parameters(params_schema)
106
- return 'none' if params_schema[:properties].empty?
107
-
108
- properties = params_schema[:properties].map do |name, config|
109
- required = params_schema[:required].include?(name.to_s) ? '(required)' : '(optional)'
110
- type = config[:type]
111
- desc = config[:description]
112
-
113
- "#{name} #{required} [#{type}] - #{desc}"
114
- end
115
-
116
- properties.join(', ')
117
- end
118
- end
119
- end
120
- end
121
- end