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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +367 -0
- data/CHANGELOG.md +41 -0
- data/CLAUDE.md +243 -0
- data/DESIGN.md +957 -0
- data/LICENSE +21 -0
- data/README.md +420 -0
- data/REQUIREMENT.md +308 -0
- data/Rakefile +12 -0
- data/SPEC.md +420 -0
- data/app/soka/agents/application_agent.rb +5 -0
- data/app/soka/tools/application_tool.rb +5 -0
- data/lib/generators/soka/agent/agent_generator.rb +60 -0
- data/lib/generators/soka/agent/templates/agent.rb.tt +25 -0
- data/lib/generators/soka/agent/templates/agent_spec.rb.tt +31 -0
- data/lib/generators/soka/install/install_generator.rb +39 -0
- data/lib/generators/soka/install/templates/application_agent.rb +5 -0
- data/lib/generators/soka/install/templates/application_tool.rb +5 -0
- data/lib/generators/soka/install/templates/soka.rb +31 -0
- data/lib/generators/soka/tool/templates/tool.rb.tt +26 -0
- data/lib/generators/soka/tool/templates/tool_spec.rb.tt +48 -0
- data/lib/generators/soka/tool/tool_generator.rb +68 -0
- data/lib/soka/rails/agent_extensions.rb +42 -0
- data/lib/soka/rails/configuration.rb +65 -0
- data/lib/soka/rails/errors.rb +73 -0
- data/lib/soka/rails/railtie.rb +15 -0
- data/lib/soka/rails/rspec.rb +29 -0
- data/lib/soka/rails/test_helpers.rb +117 -0
- data/lib/soka/rails/version.rb +7 -0
- data/lib/soka/rails.rb +24 -0
- data/lib/soka_rails.rb +11 -0
- metadata +124 -0
data/SPEC.md
ADDED
@@ -0,0 +1,420 @@
|
|
1
|
+
# Soka Rails - Rails Integration for Soka AI Agent Framework
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Soka Rails is the Rails integration package for the Soka AI Agent Framework, providing seamless integration with the Rails ecosystem, following Rails conventions, allowing developers to easily use AI Agent in Rails applications.
|
6
|
+
|
7
|
+
## Core Features
|
8
|
+
|
9
|
+
* **Native Rails Integration**: Following Rails conventions and best practices
|
10
|
+
* **Autoloading Support**: Automatically loads app/soka directory
|
11
|
+
* **Generator Support**: Quickly generate Agent and Tool templates
|
12
|
+
* **Rails Configuration Integration**: Uses Rails configuration system
|
13
|
+
* **Rails Testing Integration**: Seamless RSpec integration
|
14
|
+
|
15
|
+
## Directory Structure
|
16
|
+
|
17
|
+
```
|
18
|
+
rails-app/
|
19
|
+
├── app/
|
20
|
+
│ └── soka/
|
21
|
+
│ ├── agents/ # Agent definitions
|
22
|
+
│ │ ├── application_agent.rb # Base Agent class
|
23
|
+
│ │ ├── weather_agent.rb
|
24
|
+
│ │ └── support_agent.rb
|
25
|
+
│ └── tools/ # Tool definitions
|
26
|
+
│ ├── application_tool.rb # Base Tool class
|
27
|
+
│ └── rails_info_tool.rb # Rails environment info tool
|
28
|
+
└── config/
|
29
|
+
└── initializers/
|
30
|
+
└── soka.rb # Soka global configuration
|
31
|
+
```
|
32
|
+
|
33
|
+
## Installation and Setup
|
34
|
+
|
35
|
+
### 1. Install Gem
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# Gemfile
|
39
|
+
gem 'soka-rails'
|
40
|
+
```
|
41
|
+
|
42
|
+
### 2. Run Installation Generator
|
43
|
+
|
44
|
+
```bash
|
45
|
+
rails generate soka:install
|
46
|
+
```
|
47
|
+
|
48
|
+
This will generate:
|
49
|
+
- `config/initializers/soka.rb` - Main configuration file
|
50
|
+
- `app/soka/agents/application_agent.rb` - Base Agent class
|
51
|
+
- `app/soka/tools/application_tool.rb` - Base Tool class
|
52
|
+
|
53
|
+
## Configuration System
|
54
|
+
|
55
|
+
### Main Configuration File (config/initializers/soka.rb)
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# config/initializers/soka.rb
|
59
|
+
Soka::Rails.configure do |config|
|
60
|
+
# Use environment variables to manage API keys
|
61
|
+
config.ai do |ai|
|
62
|
+
ai.provider = ENV.fetch('SOKA_PROVIDER', :gemini)
|
63
|
+
ai.model = ENV.fetch('SOKA_MODEL', 'gemini-2.5-flash-lite')
|
64
|
+
ai.api_key = ENV['SOKA_API_KEY']
|
65
|
+
end
|
66
|
+
|
67
|
+
# Performance configuration
|
68
|
+
config.performance do |perf|
|
69
|
+
perf.max_iterations = Rails.env.production? ? 10 : 5
|
70
|
+
perf.timeout = 30.seconds
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
## Agent System
|
76
|
+
|
77
|
+
### ApplicationAgent Base Class
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
# app/soka/agents/application_agent.rb
|
81
|
+
class ApplicationAgent < Soka::Agent
|
82
|
+
# Default configuration for Rails environment
|
83
|
+
if Rails.env.development?
|
84
|
+
max_iterations 5
|
85
|
+
timeout 15.seconds
|
86
|
+
end
|
87
|
+
|
88
|
+
# Auto-register Rails related tools
|
89
|
+
tool RailsInfoTool
|
90
|
+
tool ApplicationTool
|
91
|
+
|
92
|
+
# Rails integration lifecycle hooks
|
93
|
+
before_action :log_to_rails
|
94
|
+
on_error :notify_error_tracking
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def log_to_rails(input)
|
99
|
+
Rails.logger.info "[Soka] Starting agent execution: #{input}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def notify_error_tracking(error, context)
|
103
|
+
# Integrate Rails error tracking services
|
104
|
+
Rails.error.report(error, context: { agent: self.class.name, input: context })
|
105
|
+
:continue
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
### Custom Agent Example
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# app/soka/agents/customer_support_agent.rb
|
114
|
+
class CustomerSupportAgent < ApplicationAgent
|
115
|
+
# Register business-related tools
|
116
|
+
tool OrderLookupTool
|
117
|
+
tool UserInfoTool
|
118
|
+
tool RefundTool, if: -> { Current.user&.admin? }
|
119
|
+
|
120
|
+
# Integrate Rails authentication
|
121
|
+
before_action :authenticate_user!
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def authenticate_user!
|
126
|
+
raise UnauthorizedError unless Current.user.present?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
## Tool System
|
132
|
+
|
133
|
+
### Rails-specific Tools
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# app/soka/tools/rails_info_tool.rb
|
137
|
+
class RailsInfoTool < ApplicationTool
|
138
|
+
desc "Get Rails application information"
|
139
|
+
|
140
|
+
params do
|
141
|
+
requires :info_type, String, desc: "Type of information",
|
142
|
+
validates: { inclusion: { in: %w[routes version environment config] } }
|
143
|
+
end
|
144
|
+
|
145
|
+
def call(info_type:)
|
146
|
+
case info_type
|
147
|
+
when 'routes'
|
148
|
+
Rails.application.routes.routes.map { |r| format_route(r) }.compact
|
149
|
+
when 'version'
|
150
|
+
{ rails: Rails.version, ruby: RUBY_VERSION, app: Rails.application.class.name }
|
151
|
+
when 'environment'
|
152
|
+
{ env: Rails.env, host: Rails.application.config.hosts.first }
|
153
|
+
when 'config'
|
154
|
+
safe_config_values
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def format_route(route)
|
161
|
+
return unless route.name
|
162
|
+
|
163
|
+
{
|
164
|
+
name: route.name,
|
165
|
+
verb: route.verb,
|
166
|
+
path: route.path.spec.to_s,
|
167
|
+
controller: route.defaults[:controller],
|
168
|
+
action: route.defaults[:action]
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def safe_config_values
|
173
|
+
# Only return safe configuration values
|
174
|
+
{
|
175
|
+
time_zone: Rails.application.config.time_zone,
|
176
|
+
locale: I18n.locale
|
177
|
+
}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
### Custom Tool Example
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
# app/soka/tools/order_lookup_tool.rb
|
186
|
+
class OrderLookupTool < ApplicationTool
|
187
|
+
desc "Look up order information"
|
188
|
+
|
189
|
+
params do
|
190
|
+
requires :order_id, String, desc: "Order ID"
|
191
|
+
optional :include_items, :boolean, desc: "Include order items", default: false
|
192
|
+
end
|
193
|
+
|
194
|
+
def call(order_id:, include_items: false)
|
195
|
+
# Mock order lookup logic
|
196
|
+
order = {
|
197
|
+
id: order_id,
|
198
|
+
status: "delivered",
|
199
|
+
total: "$99.99",
|
200
|
+
date: "2025-01-15"
|
201
|
+
}
|
202
|
+
|
203
|
+
if include_items
|
204
|
+
order[:items] = [
|
205
|
+
{ name: "Product A", quantity: 2, price: "$49.99" }
|
206
|
+
]
|
207
|
+
end
|
208
|
+
|
209
|
+
order
|
210
|
+
rescue => e
|
211
|
+
{ error: e.message }
|
212
|
+
end
|
213
|
+
end
|
214
|
+
```
|
215
|
+
|
216
|
+
## Generator Support
|
217
|
+
|
218
|
+
### Installation Generator
|
219
|
+
|
220
|
+
```bash
|
221
|
+
rails generate soka:install
|
222
|
+
```
|
223
|
+
|
224
|
+
Generated content:
|
225
|
+
- Configuration file (`config/initializers/soka.rb`)
|
226
|
+
- Base classes (`ApplicationAgent`, `ApplicationTool`)
|
227
|
+
- Example Agent and Tool
|
228
|
+
|
229
|
+
### Agent Generator
|
230
|
+
|
231
|
+
```bash
|
232
|
+
rails generate soka:agent weather
|
233
|
+
```
|
234
|
+
|
235
|
+
Generates `app/soka/agents/weather_agent.rb`:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
class WeatherAgent < ApplicationAgent
|
239
|
+
# Tool registration
|
240
|
+
# tool YourTool
|
241
|
+
|
242
|
+
# Configuration
|
243
|
+
# max_iterations 10
|
244
|
+
# timeout 30.seconds
|
245
|
+
|
246
|
+
# Hooks
|
247
|
+
# before_action :your_method
|
248
|
+
# after_action :your_method
|
249
|
+
# on_error :your_method
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
# Implement your private methods
|
254
|
+
end
|
255
|
+
```
|
256
|
+
|
257
|
+
### Tool Generator
|
258
|
+
|
259
|
+
```bash
|
260
|
+
rails generate soka:tool weather_api
|
261
|
+
```
|
262
|
+
|
263
|
+
Generates `app/soka/tools/weather_api_tool.rb`:
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
class WeatherApiTool < ApplicationTool
|
267
|
+
desc "Description of your tool"
|
268
|
+
|
269
|
+
params do
|
270
|
+
# requires :param_name, String, desc: "Parameter description"
|
271
|
+
# optional :param_name, String, desc: "Parameter description"
|
272
|
+
end
|
273
|
+
|
274
|
+
def call(**params)
|
275
|
+
# Implement your tool logic
|
276
|
+
"Tool result"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
```
|
280
|
+
|
281
|
+
## Usage Examples
|
282
|
+
|
283
|
+
### Basic Usage
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
# Using in Controller
|
287
|
+
class ConversationsController < ApplicationController
|
288
|
+
def create
|
289
|
+
agent = CustomerSupportAgent.new
|
290
|
+
result = agent.run(params[:message])
|
291
|
+
|
292
|
+
render json: {
|
293
|
+
answer: result.final_answer,
|
294
|
+
confidence: result.confidence_score,
|
295
|
+
status: result.status
|
296
|
+
}
|
297
|
+
end
|
298
|
+
end
|
299
|
+
```
|
300
|
+
|
301
|
+
### Using Memory
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
class ConversationsController < ApplicationController
|
305
|
+
def create
|
306
|
+
# Load memory from session
|
307
|
+
memory = session[:conversation_memory] || []
|
308
|
+
|
309
|
+
agent = CustomerSupportAgent.new(memory: memory)
|
310
|
+
result = agent.run(params[:message])
|
311
|
+
|
312
|
+
# Update memory
|
313
|
+
session[:conversation_memory] = agent.memory.to_a
|
314
|
+
|
315
|
+
render json: { answer: result.final_answer }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
```
|
319
|
+
|
320
|
+
### Event Handling
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
class ConversationsController < ApplicationController
|
324
|
+
include ActionController::Live
|
325
|
+
|
326
|
+
def stream
|
327
|
+
response.headers['Content-Type'] = 'text/event-stream'
|
328
|
+
agent = CustomerSupportAgent.new
|
329
|
+
|
330
|
+
agent.run(params[:message]) do |event|
|
331
|
+
response.stream.write("event: #{event.type}\n")
|
332
|
+
response.stream.write("data: #{event.content.to_json}\n\n")
|
333
|
+
end
|
334
|
+
ensure
|
335
|
+
response.stream.close
|
336
|
+
end
|
337
|
+
end
|
338
|
+
```
|
339
|
+
|
340
|
+
## Testing Support
|
341
|
+
|
342
|
+
### RSpec Integration
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
# spec/rails_helper.rb
|
346
|
+
require 'soka/rails/rspec'
|
347
|
+
|
348
|
+
RSpec.configure do |config|
|
349
|
+
config.include Soka::Rails::TestHelpers, type: :agent
|
350
|
+
config.include Soka::Rails::TestHelpers, type: :tool
|
351
|
+
end
|
352
|
+
|
353
|
+
# spec/soka/agents/weather_agent_spec.rb
|
354
|
+
require 'rails_helper'
|
355
|
+
|
356
|
+
RSpec.describe WeatherAgent, type: :agent do
|
357
|
+
let(:agent) { described_class.new }
|
358
|
+
|
359
|
+
before do
|
360
|
+
# Mock AI response
|
361
|
+
mock_ai_response(
|
362
|
+
final_answer: "Today in Taipei is sunny, temperature 28°C"
|
363
|
+
)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "responds to weather queries" do
|
367
|
+
result = agent.run("What's the weather in Taipei today?")
|
368
|
+
|
369
|
+
expect(result).to be_successful
|
370
|
+
expect(result.final_answer).to include("28°C")
|
371
|
+
end
|
372
|
+
|
373
|
+
it "handles multiple queries" do
|
374
|
+
# First query
|
375
|
+
result1 = agent.run("What's the weather today?")
|
376
|
+
expect(result1).to be_successful
|
377
|
+
|
378
|
+
# Second query
|
379
|
+
result2 = agent.run("What's the weather tomorrow?")
|
380
|
+
expect(result2).to be_successful
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# spec/soka/tools/rails_info_tool_spec.rb
|
385
|
+
RSpec.describe RailsInfoTool, type: :tool do
|
386
|
+
let(:tool) { described_class.new }
|
387
|
+
|
388
|
+
it "returns Rails version info" do
|
389
|
+
result = tool.call(info_type: "version")
|
390
|
+
|
391
|
+
expect(result).to include(:rails, :ruby, :app)
|
392
|
+
expect(result[:rails]).to eq(Rails.version)
|
393
|
+
end
|
394
|
+
|
395
|
+
it "returns safe config values only" do
|
396
|
+
result = tool.call(info_type: "config")
|
397
|
+
|
398
|
+
expect(result).to include(:time_zone, :locale)
|
399
|
+
expect(result).not_to include(:secret_key_base)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
```
|
403
|
+
|
404
|
+
## Version Compatibility
|
405
|
+
|
406
|
+
- Ruby: >= 3.0
|
407
|
+
- Rails: >= 7.0
|
408
|
+
- Soka: >= 1.0
|
409
|
+
|
410
|
+
## Contributing
|
411
|
+
|
412
|
+
1. Fork the project
|
413
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
414
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
415
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
416
|
+
5. Open a Pull Request
|
417
|
+
|
418
|
+
## License
|
419
|
+
|
420
|
+
MIT License
|
@@ -0,0 +1,60 @@
|
|
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 agent classes with their associated tests
|
8
|
+
class AgentGenerator < ::Rails::Generators::NamedBase
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
10
|
+
|
11
|
+
argument :tools, type: :array, default: [], banner: 'tool1 tool2'
|
12
|
+
|
13
|
+
def create_agent_file
|
14
|
+
@agent_class_name = agent_class_name
|
15
|
+
@tools_list = tools
|
16
|
+
|
17
|
+
template 'agent.rb.tt',
|
18
|
+
File.join('app/soka/agents', class_path, "#{agent_file_name}.rb")
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_test_file
|
22
|
+
return unless rspec_installed?
|
23
|
+
|
24
|
+
@agent_class_name = agent_class_name
|
25
|
+
|
26
|
+
template 'agent_spec.rb.tt',
|
27
|
+
File.join('spec/soka/agents', class_path, "#{agent_file_name}_spec.rb")
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Normalize the agent file name to always end with _agent
|
33
|
+
def agent_file_name
|
34
|
+
base_name = file_name.to_s
|
35
|
+
|
36
|
+
# Remove existing _agent suffix if present to avoid duplication
|
37
|
+
base_name = base_name.sub(/_agent\z/, '')
|
38
|
+
|
39
|
+
# Add _agent suffix
|
40
|
+
"#{base_name}_agent"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Normalize the agent class name to always end with Agent
|
44
|
+
def agent_class_name
|
45
|
+
base_class = class_name.to_s
|
46
|
+
|
47
|
+
# Remove existing Agent suffix if present to avoid duplication
|
48
|
+
base_class = base_class.sub(/Agent\z/, '')
|
49
|
+
|
50
|
+
# Add Agent suffix
|
51
|
+
"#{base_class}Agent"
|
52
|
+
end
|
53
|
+
|
54
|
+
def rspec_installed?
|
55
|
+
File.exist?(::Rails.root.join('spec/spec_helper.rb')) ||
|
56
|
+
File.exist?(::Rails.root.join('spec/rails_helper.rb'))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= @agent_class_name %> < ApplicationAgent
|
4
|
+
# Tool registration
|
5
|
+
<% if @tools_list.any? -%>
|
6
|
+
<% @tools_list.each do |tool| -%>
|
7
|
+
tool <%= tool.camelize %>Tool
|
8
|
+
<% end -%>
|
9
|
+
<% else -%>
|
10
|
+
# tool YourTool
|
11
|
+
<% end -%>
|
12
|
+
|
13
|
+
# Configuration
|
14
|
+
# max_iterations 10
|
15
|
+
# timeout 30
|
16
|
+
|
17
|
+
# Hooks
|
18
|
+
# before_action :your_method
|
19
|
+
# after_action :your_method
|
20
|
+
# on_error :your_method
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Implement your private methods here
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe <%= @agent_class_name %>Agent, type: :agent do
|
6
|
+
let(:agent) { described_class.new }
|
7
|
+
|
8
|
+
describe '#run' do
|
9
|
+
before do
|
10
|
+
# Mock AI response
|
11
|
+
mock_ai_response(
|
12
|
+
final_answer: 'Test response'
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'responds to queries' do
|
17
|
+
result = agent.run('Test query')
|
18
|
+
|
19
|
+
expect(result).to be_successful
|
20
|
+
expect(result.final_answer).to eq('Test response')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'handles errors gracefully' do
|
24
|
+
allow(agent).to receive(:execute).and_raise(StandardError, 'Test error')
|
25
|
+
|
26
|
+
expect {
|
27
|
+
agent.run('Test query')
|
28
|
+
}.not_to raise_error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators/base'
|
4
|
+
|
5
|
+
module Soka
|
6
|
+
module Generators
|
7
|
+
# Generator for installing Soka Rails configuration
|
8
|
+
class InstallGenerator < ::Rails::Generators::Base
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
10
|
+
|
11
|
+
def create_initializer
|
12
|
+
template 'soka.rb', 'config/initializers/soka.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_application_agent
|
16
|
+
template 'application_agent.rb', 'app/soka/agents/application_agent.rb'
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_application_tool
|
20
|
+
template 'application_tool.rb', 'app/soka/tools/application_tool.rb'
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_soka_directory
|
24
|
+
empty_directory 'app/soka'
|
25
|
+
empty_directory 'app/soka/agents'
|
26
|
+
empty_directory 'app/soka/tools'
|
27
|
+
end
|
28
|
+
|
29
|
+
def display_post_install_message
|
30
|
+
say "\nSoka Rails has been successfully installed!", :green
|
31
|
+
say "\nNext steps:"
|
32
|
+
say ' 1. Set your AI provider API key: GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY'
|
33
|
+
say ' 2. Create your first agent: rails generate soka:agent MyAgent'
|
34
|
+
say ' 3. Create your first tool: rails generate soka:tool MyTool'
|
35
|
+
say "\nFor more information, visit: https://github.com/jiunjiun/soka-rails"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Soka Rails configuration
|
4
|
+
Soka::Rails.configure do |config|
|
5
|
+
# AI Provider Configuration
|
6
|
+
config.ai do |ai|
|
7
|
+
# Setup Gemini AI Studio
|
8
|
+
ai.provider = :gemini
|
9
|
+
ai.model = 'gemini-2.5-flash-lite'
|
10
|
+
ai.api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
11
|
+
|
12
|
+
# Setup OpenAI
|
13
|
+
# ai.provider = :openai
|
14
|
+
# ai.model = 'gpt-4.1-mini'
|
15
|
+
# ai.api_key = ENV.fetch('OPENAI_API_KEY', nil)
|
16
|
+
|
17
|
+
# Setup Anthropic
|
18
|
+
# ai.provider = :anthropic
|
19
|
+
# ai.model = 'claude-sonnet-4-0'
|
20
|
+
# ai.api_key = ENV.fetch('ANTHROPIC_API_KEY', nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Performance Configuration
|
24
|
+
config.performance do |perf|
|
25
|
+
# Maximum iterations for ReAct loop
|
26
|
+
perf.max_iterations = Rails.env.production? ? 10 : 5
|
27
|
+
|
28
|
+
# Timeout for agent execution (in seconds)
|
29
|
+
perf.timeout = 30
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class <%= @tool_class_name %> < ApplicationTool
|
4
|
+
desc 'Description of your tool'
|
5
|
+
|
6
|
+
params do
|
7
|
+
<% if @params_list.any? -%>
|
8
|
+
<% @params_list.each do |param| -%>
|
9
|
+
requires :<%= param[:name] %>, <%= param[:type] %>, desc: '<%= param[:name].humanize %>'
|
10
|
+
<% end -%>
|
11
|
+
<% else -%>
|
12
|
+
# requires :param_name, String, desc: 'Parameter description'
|
13
|
+
# optional :param_name, String, desc: 'Parameter description'
|
14
|
+
<% end -%>
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(<%= @params_list.map { |param| param[:name] }.join(', ') %>)
|
18
|
+
# Implement your tool logic here
|
19
|
+
|
20
|
+
# Return a hash with your results
|
21
|
+
{
|
22
|
+
result: 'Tool execution result',
|
23
|
+
processed_at: Time.current
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe <%= @tool_class_name %>Tool, type: :tool do
|
6
|
+
let(:tool) { described_class.new }
|
7
|
+
|
8
|
+
describe '#call' do
|
9
|
+
<% if @params_list.any? -%>
|
10
|
+
let(:params) do
|
11
|
+
{
|
12
|
+
<% @params_list.each do |param| -%>
|
13
|
+
<%= param[:name] %>: <%= param[:type] == 'String' ? "'test_value'" : "test_value" %>,
|
14
|
+
<% end -%>
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'executes successfully with valid params' do
|
19
|
+
result = tool.call(**params)
|
20
|
+
|
21
|
+
expect(result).to be_a(Hash)
|
22
|
+
expect(result[:result]).to be_present
|
23
|
+
end
|
24
|
+
<% else -%>
|
25
|
+
it 'executes successfully' do
|
26
|
+
result = tool.call
|
27
|
+
|
28
|
+
expect(result).to be_a(Hash)
|
29
|
+
expect(result[:result]).to be_present
|
30
|
+
end
|
31
|
+
<% end -%>
|
32
|
+
|
33
|
+
it 'includes timestamp in response' do
|
34
|
+
result = tool.call<%= @params_list.any? ? '(**params)' : '' %>
|
35
|
+
|
36
|
+
expect(result[:processed_at]).to be_present
|
37
|
+
expect(result[:processed_at]).to be_within(1.second).of(Time.current)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'handles errors gracefully' do
|
41
|
+
allow(tool).to receive(:call).and_raise(StandardError, 'Test error')
|
42
|
+
|
43
|
+
expect {
|
44
|
+
tool.call<%= @params_list.any? ? '(**params)' : '' %>
|
45
|
+
}.to raise_error(StandardError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|