soka-rails 0.0.1.beta4

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.
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/named_base'
4
+
5
+ module Soka
6
+ module Generators
7
+ # Generator for creating new Soka tool classes with configurable parameters
8
+ class ToolGenerator < ::Rails::Generators::NamedBase
9
+ source_root File.expand_path('templates', __dir__)
10
+
11
+ argument :params, type: :array, default: [], banner: 'param1:type param2:type'
12
+
13
+ def create_tool_file
14
+ @tool_class_name = tool_class_name
15
+ @params_list = parse_params
16
+
17
+ template 'tool.rb.tt',
18
+ File.join('app/soka/tools', class_path, "#{tool_file_name}.rb")
19
+ end
20
+
21
+ def create_test_file
22
+ return unless rspec_installed?
23
+
24
+ @tool_class_name = tool_class_name
25
+ @params_list = parse_params
26
+
27
+ template 'tool_spec.rb.tt',
28
+ File.join('spec/soka/tools', class_path, "#{tool_file_name}_spec.rb")
29
+ end
30
+
31
+ private
32
+
33
+ # Normalize the tool file name to always end with _tool
34
+ def tool_file_name
35
+ base_name = file_name.to_s
36
+
37
+ # Remove existing _tool suffix if present to avoid duplication
38
+ base_name = base_name.sub(/_tool\z/, '')
39
+
40
+ # Add _tool suffix
41
+ "#{base_name}_tool"
42
+ end
43
+
44
+ # Normalize the tool class name to always end with Tool
45
+ def tool_class_name
46
+ base_class = class_name.to_s
47
+
48
+ # Remove existing Tool suffix if present to avoid duplication
49
+ base_class = base_class.sub(/Tool\z/, '')
50
+
51
+ # Add Tool suffix
52
+ "#{base_class}Tool"
53
+ end
54
+
55
+ def parse_params
56
+ params.map do |param|
57
+ name, type = param.split(':')
58
+ { name: name, type: type.capitalize || 'String' }
59
+ end
60
+ end
61
+
62
+ def rspec_installed?
63
+ File.exist?(::Rails.root.join('spec/spec_helper.rb')) ||
64
+ File.exist?(::Rails.root.join('spec/rails_helper.rb'))
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Rails
5
+ # Extensions for Soka::Agent to integrate with Rails configuration
6
+ module AgentExtensions
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ # Class methods for agent configuration
12
+ module ClassMethods
13
+ def inherited(subclass)
14
+ super
15
+ apply_rails_configuration(subclass)
16
+ end
17
+
18
+ private
19
+
20
+ def apply_rails_configuration(subclass)
21
+ return unless defined?(Soka::Rails.configuration)
22
+
23
+ rails_config = Soka::Rails.configuration
24
+ apply_configuration_values(subclass, rails_config)
25
+ end
26
+
27
+ def apply_configuration_values(subclass, config)
28
+ return unless config
29
+
30
+ subclass.provider config.ai_provider if config.ai_provider
31
+ subclass.model config.ai_model if config.ai_model
32
+ subclass.api_key config.ai_api_key if config.ai_api_key
33
+ subclass.max_iterations config.max_iterations if config.max_iterations
34
+ subclass.timeout config.timeout if config.timeout
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # Apply extensions to Soka::Agent
42
+ Soka::Agent.include(Soka::Rails::AgentExtensions) if defined?(Soka::Agent)
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Rails
5
+ # Main configuration class for Soka Rails settings
6
+ class Configuration
7
+ attr_accessor :ai_provider, :ai_model, :ai_api_key, :max_iterations, :timeout
8
+
9
+ def initialize
10
+ # Use ENV.fetch to set default values
11
+ @ai_provider = ENV.fetch('SOKA_PROVIDER', :gemini).to_sym
12
+ @ai_model = ENV.fetch('SOKA_MODEL', 'gemini-2.0-flash-exp')
13
+ @ai_api_key = ENV.fetch('SOKA_API_KEY', nil)
14
+
15
+ # Performance settings
16
+ @max_iterations = defined?(::Rails) && ::Rails.env.production? ? 10 : 5
17
+ @timeout = 30
18
+ end
19
+
20
+ # DSL configuration methods
21
+ def ai
22
+ yield(AIConfig.new(self)) if block_given?
23
+ end
24
+
25
+ def performance
26
+ yield(PerformanceConfig.new(self)) if block_given?
27
+ end
28
+
29
+ # Internal configuration classes
30
+ # DSL class for AI-specific configuration settings
31
+ class AIConfig
32
+ def initialize(config)
33
+ @config = config
34
+ end
35
+
36
+ def provider=(value)
37
+ @config.ai_provider = value
38
+ end
39
+
40
+ def model=(value)
41
+ @config.ai_model = value
42
+ end
43
+
44
+ def api_key=(value)
45
+ @config.ai_api_key = value
46
+ end
47
+ end
48
+
49
+ # DSL class for performance-related configuration settings
50
+ class PerformanceConfig
51
+ def initialize(config)
52
+ @config = config
53
+ end
54
+
55
+ def max_iterations=(value)
56
+ @config.max_iterations = value
57
+ end
58
+
59
+ def timeout=(value)
60
+ @config.timeout = value.is_a?(Numeric) ? value : value.to_i
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Rails
5
+ # Base error class for Soka Rails
6
+ class Error < StandardError; end
7
+
8
+ # Configuration related errors
9
+ # Raised when there are configuration-related issues
10
+ class ConfigurationError < Error; end
11
+
12
+ # Agent related errors
13
+ # Raised when agent execution encounters problems
14
+ class AgentError < Error; end
15
+
16
+ # Tool related errors
17
+ # Raised when tool execution or validation fails
18
+ class ToolError < Error; end
19
+
20
+ # Generator related errors
21
+ # Raised when generator operations fail
22
+ class GeneratorError < Error; end
23
+
24
+ # Specific error classes
25
+ # Raised when API key is missing for the configured provider
26
+ class MissingApiKeyError < ConfigurationError
27
+ def initialize(provider = nil)
28
+ message = if provider
29
+ "Missing API key for provider: #{provider}. " \
30
+ "Please set SOKA_API_KEY or SOKA_#{provider.to_s.upcase}_API_KEY environment variable."
31
+ else
32
+ 'Missing API key. Please set SOKA_API_KEY environment variable.'
33
+ end
34
+ super(message)
35
+ end
36
+ end
37
+
38
+ # Raised when an unsupported AI provider is specified
39
+ class InvalidProviderError < ConfigurationError
40
+ def initialize(provider)
41
+ super("Invalid AI provider: #{provider}. Supported providers: :gemini, :openai, :anthropic")
42
+ end
43
+ end
44
+
45
+ # Raised when agent execution exceeds the configured timeout
46
+ class AgentTimeoutError < AgentError
47
+ def initialize(timeout)
48
+ super("Agent execution timeout after #{timeout} seconds")
49
+ end
50
+ end
51
+
52
+ # Raised when agent exceeds maximum allowed iterations
53
+ class MaxIterationsExceededError < AgentError
54
+ def initialize(max_iterations)
55
+ super("Agent exceeded maximum iterations limit of #{max_iterations}")
56
+ end
57
+ end
58
+
59
+ # Raised when a requested tool cannot be found
60
+ class ToolNotFoundError < ToolError
61
+ def initialize(tool_name)
62
+ super("Tool not found: #{tool_name}")
63
+ end
64
+ end
65
+
66
+ # Raised when tool is called with invalid parameters
67
+ class InvalidToolParametersError < ToolError
68
+ def initialize(tool_name, errors)
69
+ super("Invalid parameters for tool #{tool_name}: #{errors.join(', ')}")
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Rails
5
+ # Rails integration for Soka, configuring autoloading and initializers
6
+ class Railtie < ::Rails::Railtie
7
+ # Configure autoloading - executed after Rails completes basic setup
8
+ initializer 'soka.setup_autoloading', before: :set_autoload_paths do |app|
9
+ # Add app/soka to autoload paths
10
+ # eager_load: true will automatically include all subdirectories
11
+ app.config.paths.add 'app/soka', eager_load: true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'soka/rails/test_helpers'
4
+
5
+ if defined?(RSpec)
6
+ RSpec.configure do |config|
7
+ config.include Soka::Rails::TestHelpers, type: :agent
8
+ config.include Soka::Rails::TestHelpers, type: :tool
9
+
10
+ config.before(:each, type: :agent) do
11
+ # Reset Soka configuration
12
+ Soka.configuration = Soka::Configuration.new if defined?(Soka) && Soka.respond_to?(:configuration=)
13
+ end
14
+
15
+ config.before(:each, type: :tool) do
16
+ # Ensure Rails environment is available
17
+ allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new('test')) if defined?(Rails)
18
+ end
19
+
20
+ # Automatically tag tests under spec/soka directory
21
+ config.define_derived_metadata(file_path: %r{/spec/soka/agents/}) do |metadata|
22
+ metadata[:type] = :agent
23
+ end
24
+
25
+ config.define_derived_metadata(file_path: %r{/spec/soka/tools/}) do |metadata|
26
+ metadata[:type] = :tool
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Rails
5
+ # Test helpers for RSpec tests with Soka agents and tools
6
+ module TestHelpers
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ let(:mock_llm) { instance_double('Soka::LLM') }
11
+ end
12
+
13
+ # Mock AI response
14
+ def mock_ai_response(response_attrs = {})
15
+ response = build_mock_response(response_attrs)
16
+ setup_llm_mock(response)
17
+ end
18
+
19
+ def build_mock_response(attrs)
20
+ default_response = {
21
+ final_answer: 'Mocked answer',
22
+ confidence_score: 0.95,
23
+ status: :completed,
24
+ iterations: 1,
25
+ thought_process: []
26
+ }
27
+ default_response.merge(attrs)
28
+ end
29
+
30
+ def setup_llm_mock(response)
31
+ return unless defined?(Soka::LLM)
32
+
33
+ allow(Soka::LLM).to receive(:new).and_return(mock_llm)
34
+ allow(mock_llm).to receive(:chat).and_return(
35
+ Struct.new(:content).new(build_react_response(response))
36
+ )
37
+ end
38
+
39
+ # Mock tool execution
40
+ def mock_tool_execution(tool_class, result)
41
+ allow_any_instance_of(tool_class).to receive(:call).and_return(result)
42
+ end
43
+
44
+ # Build ReAct format response
45
+ def build_react_response(attrs)
46
+ thoughts = attrs[:thought_process].presence ||
47
+ ['Analyzing the request']
48
+
49
+ response = thoughts.map { |t| "<Thought>#{t}</Thought>" }.join("\n")
50
+ response += "\n<Final_Answer>#{attrs[:final_answer]}</Final_Answer>"
51
+ response
52
+ end
53
+
54
+ # Agent test helper methods
55
+ def run_agent(agent, input, &)
56
+ result = if block_given?
57
+ agent.run(input, &)
58
+ else
59
+ agent.run(input)
60
+ end
61
+
62
+ expect(result).to be_a(Struct)
63
+ result
64
+ end
65
+
66
+ # Event collector
67
+ def collect_agent_events(agent, input)
68
+ events = []
69
+
70
+ agent.run(input) do |event|
71
+ events << event
72
+ end
73
+
74
+ events
75
+ end
76
+
77
+ # Test configuration
78
+ def with_test_configuration
79
+ original_config = Soka::Rails.configuration.dup
80
+
81
+ Soka::Rails.configure do |config|
82
+ config.ai_provider = :mock
83
+ config.max_iterations = 3
84
+ config.timeout = 5
85
+ yield config if block_given?
86
+ end
87
+
88
+ yield
89
+ ensure
90
+ Soka::Rails.configuration = original_config
91
+ end
92
+
93
+ # Helper method: create successful result
94
+ def successful_result(attrs = {})
95
+ default_attrs = {
96
+ status: :completed,
97
+ final_answer: 'Success',
98
+ confidence_score: 0.95,
99
+ iterations: 1
100
+ }
101
+
102
+ Struct.new(*default_attrs.keys).new(*default_attrs.merge(attrs).values)
103
+ end
104
+
105
+ # Helper method: check if result is successful
106
+ RSpec::Matchers.define :be_successful do
107
+ match do |actual|
108
+ actual.respond_to?(:status) && actual.status == :completed
109
+ end
110
+
111
+ failure_message do |actual|
112
+ "expected result to be successful, but status was #{actual.status}"
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Soka
4
+ module Rails
5
+ VERSION = '0.0.1.beta4'
6
+ end
7
+ end
data/lib/soka/rails.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rails/version'
4
+ require_relative 'rails/configuration'
5
+ require_relative 'rails/errors'
6
+ require_relative 'rails/railtie'
7
+ require_relative 'rails/agent_extensions'
8
+
9
+ module Soka
10
+ # Rails integration for Soka AI Agent Framework
11
+ module Rails
12
+ class << self
13
+ attr_writer :configuration
14
+
15
+ def configuration
16
+ @configuration ||= Configuration.new
17
+ end
18
+
19
+ def configure
20
+ yield(configuration)
21
+ end
22
+ end
23
+ end
24
+ end
data/lib/soka_rails.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file serves as the entry point for the soka-rails gem
4
+ # It ensures proper loading order for Rails integration
5
+
6
+ require 'rails'
7
+ require 'soka'
8
+ require 'zeitwerk'
9
+
10
+ # Load the main Soka::Rails module
11
+ require_relative 'soka/rails'
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: soka-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.beta4
5
+ platform: ruby
6
+ authors:
7
+ - jiunjiun
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: soka
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: 0.0.1
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: 0.0.1
46
+ - !ruby/object:Gem::Dependency
47
+ name: zeitwerk
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '2.6'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '2.6'
60
+ description: Soka Rails provides seamless integration between the Soka AI Agent Framework
61
+ and Ruby on Rails applications, following Rails conventions for easy adoption.
62
+ email:
63
+ - imjiunjiun@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - ".rspec"
69
+ - ".rubocop.yml"
70
+ - CHANGELOG.md
71
+ - CLAUDE.md
72
+ - DESIGN.md
73
+ - LICENSE
74
+ - README.md
75
+ - REQUIREMENT.md
76
+ - Rakefile
77
+ - SPEC.md
78
+ - app/soka/agents/application_agent.rb
79
+ - app/soka/tools/application_tool.rb
80
+ - lib/generators/soka/agent/agent_generator.rb
81
+ - lib/generators/soka/agent/templates/agent.rb.tt
82
+ - lib/generators/soka/agent/templates/agent_spec.rb.tt
83
+ - lib/generators/soka/install/install_generator.rb
84
+ - lib/generators/soka/install/templates/application_agent.rb
85
+ - lib/generators/soka/install/templates/application_tool.rb
86
+ - lib/generators/soka/install/templates/soka.rb
87
+ - lib/generators/soka/tool/templates/tool.rb.tt
88
+ - lib/generators/soka/tool/templates/tool_spec.rb.tt
89
+ - lib/generators/soka/tool/tool_generator.rb
90
+ - lib/soka/rails.rb
91
+ - lib/soka/rails/agent_extensions.rb
92
+ - lib/soka/rails/configuration.rb
93
+ - lib/soka/rails/errors.rb
94
+ - lib/soka/rails/railtie.rb
95
+ - lib/soka/rails/rspec.rb
96
+ - lib/soka/rails/test_helpers.rb
97
+ - lib/soka/rails/version.rb
98
+ - lib/soka_rails.rb
99
+ homepage: https://github.com/jiunjiun/soka-rails
100
+ licenses:
101
+ - MIT
102
+ metadata:
103
+ homepage_uri: https://github.com/jiunjiun/soka-rails
104
+ source_code_uri: https://github.com/jiunjiun/soka-rails
105
+ changelog_uri: https://github.com/jiunjiun/soka-rails/blob/main/CHANGELOG.md
106
+ rubygems_mfa_required: 'true'
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '3.4'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubygems_version: 3.6.9
122
+ specification_version: 4
123
+ summary: Rails integration for Soka AI Agent Framework
124
+ test_files: []