ox-ai-workers 0.5.8 → 0.6.2
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 +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +8 -1
- data/README.md +50 -2
- data/lib/ox-ai-workers.rb +4 -1
- data/lib/oxaiworkers/delayed_request.rb +1 -1
- data/lib/oxaiworkers/engine.rb +11 -0
- data/lib/oxaiworkers/iterator.rb +34 -5
- data/lib/oxaiworkers/load_i18n.rb +4 -2
- data/lib/oxaiworkers/state_tools.rb +1 -1
- data/lib/oxaiworkers/tool/converter.rb +35 -0
- data/lib/oxaiworkers/tool_definition.rb +178 -159
- data/lib/oxaiworkers/version.rb +1 -1
- metadata +11 -8
- /data/{locales/en.assistant.yml → config/locales/en.oxaiworkers.assistant.yml} +0 -0
- /data/{locales/en.iterator.yml → config/locales/en.oxaiworkers.iterator.yml} +0 -0
- /data/{locales/en.tool.yml → config/locales/en.oxaiworkers.tool.yml} +0 -0
- /data/{locales/ru.assistant.yml → config/locales/ru.oxaiworkers.assistant.yml} +0 -0
- /data/{locales/ru.iterator.yml → config/locales/ru.oxaiworkers.iterator.yml} +0 -0
- /data/{locales/ru.tool.yml → config/locales/ru.oxaiworkers.tool.yml} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 811df931f990a04c2764d1e5dc952e69e1dfc409bfe942ff87822ef8b755a6c9
|
4
|
+
data.tar.gz: 1d591cc02bec476f76707de96acadd58bb0cda60a1af0bf6657150e9360e02e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4629630ea656bb07ab2da027852ebc262901e7dfb09d27b73071727eed149c850a3b4f40c03e094792c630f32642f9dc2c8c5a6786b5283e4a03916b8c47f8b
|
7
|
+
data.tar.gz: c57849c113e05f41fad7a51ab17b86878a27b61b4f7abff08dbac478114efe2ba697fce94ebfff23551931e982d07ae7e2d808d54adb3f28424bc29381b574d7
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.3.4
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -86,9 +86,10 @@ For a more robust setup, you can configure the gem with your API keys, for examp
|
|
86
86
|
OxAiWorkers.configure do |config|
|
87
87
|
config.access_token = ENV.fetch("OPENAI")
|
88
88
|
config.model = "gpt-4o"
|
89
|
-
config.max_tokens = 4096
|
90
|
-
config.temperature = 0.7
|
89
|
+
config.max_tokens = 4096 # Default
|
90
|
+
config.temperature = 0.7 # Default
|
91
91
|
config.auto_execute = true # Default
|
92
|
+
config.wait_for_complete = true # Default
|
92
93
|
end
|
93
94
|
```
|
94
95
|
|
@@ -178,6 +179,53 @@ As a worker, you can use different classes depending on your needs:
|
|
178
179
|
|
179
180
|
- `OxAiWorkers::DelayedRequest`: This class is used for batch API requests, ideal for operations that do not require immediate execution. Using `DelayedRequest` can save up to 50% on costs as requests are executed when the remote server is less busy, but no later than within 24 hours.
|
180
181
|
|
182
|
+
### Rails Projects with DelayedRequest
|
183
|
+
|
184
|
+
Generate your model to store the `batch_id` in the database:
|
185
|
+
|
186
|
+
```bash
|
187
|
+
rails generate model MyRequestWithStore batch_id:string
|
188
|
+
```
|
189
|
+
|
190
|
+
In your `app/models/my_request_with_store.rb` file, add the following code:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
class MyRequestWithStore < ApplicationRecord
|
194
|
+
def delayed_request
|
195
|
+
@worker ||= OxAiWorkers::DelayedRequest.new(batch_id: self.batch_id)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
Then you can use the iterator like this:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
# Fetch the first stored batch
|
204
|
+
my_store = MyRequestWithStore.first
|
205
|
+
|
206
|
+
# Get the worker
|
207
|
+
my_worker = my_store.delayed_request
|
208
|
+
|
209
|
+
# Create the iterator
|
210
|
+
iterator = OxAiWorkers::Iterator.new(worker: my_worker)
|
211
|
+
# ... use the iterator
|
212
|
+
|
213
|
+
# Destroy the store after completion
|
214
|
+
my_store.destroy if my_worker.completed?
|
215
|
+
```
|
216
|
+
|
217
|
+
To store your batches in the database, use the following code:
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
# Get the worker from the iterator
|
221
|
+
my_worker = iterator.worker
|
222
|
+
|
223
|
+
# Store the batch_id if it's not completed
|
224
|
+
unless my_worker.completed?
|
225
|
+
my_store = MyRequestWithStore.create!(batch_id: my_worker.batch_id)
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
181
229
|
## Command Line Interface (CLI)
|
182
230
|
|
183
231
|
1. Navigate to the required directory.
|
data/lib/ox-ai-workers.rb
CHANGED
@@ -31,6 +31,8 @@ require_relative 'oxaiworkers/assistant/sysop'
|
|
31
31
|
require_relative 'oxaiworkers/assistant/coder'
|
32
32
|
require_relative 'oxaiworkers/assistant/localizer'
|
33
33
|
|
34
|
+
require_relative 'oxaiworkers/engine' if defined?(Rails)
|
35
|
+
|
34
36
|
module OxAiWorkers
|
35
37
|
DEFAULT_MODEL = 'gpt-4o-mini'
|
36
38
|
DEFAULT_MAX_TOKEN = 4096
|
@@ -40,7 +42,7 @@ module OxAiWorkers
|
|
40
42
|
class ConfigurationError < Error; end
|
41
43
|
|
42
44
|
class Configuration
|
43
|
-
attr_accessor :model, :max_tokens, :temperature, :access_token, :auto_execute
|
45
|
+
attr_accessor :model, :max_tokens, :temperature, :access_token, :auto_execute, :wait_for_complete
|
44
46
|
|
45
47
|
def initialize
|
46
48
|
@access_token = nil
|
@@ -48,6 +50,7 @@ module OxAiWorkers
|
|
48
50
|
@max_tokens = DEFAULT_MAX_TOKEN
|
49
51
|
@temperature = DEFAULT_TEMPERATURE
|
50
52
|
@auto_execute = true
|
53
|
+
@wait_for_complete = true
|
51
54
|
|
52
55
|
[Array, NilClass, String, Symbol, Hash].each do |c|
|
53
56
|
c.send(:include, OxAiWorkers::PresentCompat) unless c.method_defined?(:present?)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module OxAiWorkers
|
4
4
|
class DelayedRequest < OxAiWorkers::StateBatch
|
5
5
|
def initialize(batch_id: nil, model: nil, max_tokens: nil, temperature: nil)
|
6
|
-
initialize_requests(model
|
6
|
+
initialize_requests(model:, max_tokens:, temperature:)
|
7
7
|
@custom_id = nil if batch_id.present?
|
8
8
|
@batch_id = batch_id
|
9
9
|
@file_id = nil
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module OxAiWorkers
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace OxAiWorkers # this is generally recommended
|
4
|
+
|
5
|
+
# no other special configuration needed. But maybe you want
|
6
|
+
# an initializer to be added to the app? Easy!
|
7
|
+
# initializer 'yourgem.boot_stuff_up' do
|
8
|
+
# OxAiWorkers.boot_something_up!
|
9
|
+
# end
|
10
|
+
end
|
11
|
+
end
|
data/lib/oxaiworkers/iterator.rb
CHANGED
@@ -52,8 +52,15 @@ module OxAiWorkers
|
|
52
52
|
cleanup
|
53
53
|
|
54
54
|
super()
|
55
|
+
|
56
|
+
tick_or_wait if requested?
|
55
57
|
end
|
56
58
|
|
59
|
+
#
|
60
|
+
# Resets the state of the object by setting all instance variables to their initial values.
|
61
|
+
#
|
62
|
+
# Returns nothing.
|
63
|
+
#
|
57
64
|
def cleanup
|
58
65
|
@result = nil
|
59
66
|
@queue = []
|
@@ -63,6 +70,11 @@ module OxAiWorkers
|
|
63
70
|
complete_iteration
|
64
71
|
end
|
65
72
|
|
73
|
+
# Updates the internal state of the iterator by adding the given `speach` to the `@queue` and calling the `@on_inner_monologue` callback with the `speach` text.
|
74
|
+
#
|
75
|
+
# @param speach [String] The text to be added to the `@queue` and passed to the `@on_inner_monologue` callback.
|
76
|
+
#
|
77
|
+
# @return [nil] This method does not return a value.
|
66
78
|
def inner_monologue(speach:)
|
67
79
|
# @queue.pop
|
68
80
|
@queue << { role: :assistant, content: speach.to_s }
|
@@ -74,7 +86,7 @@ module OxAiWorkers
|
|
74
86
|
# @queue.pop
|
75
87
|
@queue << { role: :assistant, content: text.to_s }
|
76
88
|
complete! unless available_defs.include?(:action_request)
|
77
|
-
@on_outer_voice&.call(text:
|
89
|
+
@on_outer_voice&.call(text:)
|
78
90
|
nil
|
79
91
|
end
|
80
92
|
|
@@ -96,7 +108,7 @@ module OxAiWorkers
|
|
96
108
|
@worker.finish
|
97
109
|
rebuild_worker
|
98
110
|
complete! if can_complete?
|
99
|
-
@on_summarize&.call(text:
|
111
|
+
@on_summarize&.call(text:)
|
100
112
|
nil
|
101
113
|
end
|
102
114
|
|
@@ -158,11 +170,28 @@ module OxAiWorkers
|
|
158
170
|
|
159
171
|
def external_request
|
160
172
|
@worker.request!
|
161
|
-
|
173
|
+
tick_or_wait
|
174
|
+
end
|
175
|
+
|
176
|
+
def tick_or_wait
|
177
|
+
if OxAiWorkers.configuration.wait_for_complete
|
178
|
+
wait_for_complete
|
179
|
+
else
|
180
|
+
ticker
|
181
|
+
end
|
162
182
|
end
|
163
183
|
|
164
184
|
def ticker
|
165
|
-
|
185
|
+
return false unless @worker.completed?
|
186
|
+
|
187
|
+
analyze!
|
188
|
+
true
|
189
|
+
end
|
190
|
+
|
191
|
+
def wait_for_complete
|
192
|
+
return unless requested?
|
193
|
+
|
194
|
+
sleep(60) unless ticker
|
166
195
|
analyze!
|
167
196
|
end
|
168
197
|
|
@@ -199,7 +228,7 @@ module OxAiWorkers
|
|
199
228
|
end
|
200
229
|
|
201
230
|
def add_context(text, role: :system)
|
202
|
-
@context << { role
|
231
|
+
@context << { role:, content: text }
|
203
232
|
end
|
204
233
|
|
205
234
|
def execute
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
unless defined?(Rails)
|
4
|
+
require 'i18n'
|
5
|
+
I18n.load_path += Dir["#{File.expand_path('../../config/locales', __dir__)}/*.yml"]
|
6
|
+
end
|
5
7
|
|
6
8
|
module OxAiWorkers
|
7
9
|
module LoadI18n
|
@@ -7,7 +7,7 @@ module OxAiWorkers
|
|
7
7
|
include OxAiWorkers::StateHelper
|
8
8
|
extend StateMachines::MacroMethods
|
9
9
|
|
10
|
-
state_machine :state, initial: :idle do
|
10
|
+
state_machine :state, initial: ->(t) { t.worker.requested? ? :requested : :idle } do
|
11
11
|
before_transition from: any, do: :log_me
|
12
12
|
|
13
13
|
after_transition on: :iterate, do: :next_iteration
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# def getDocument doc1, &block
|
2
|
+
# document = getFileBin(doc1)
|
3
|
+
# content = nil
|
4
|
+
# unless document.nil?
|
5
|
+
# extension = doc1['file_name'].split('.').last.downcase
|
6
|
+
# if extension == "docx"
|
7
|
+
# docx = Docx::Document.open document#.read
|
8
|
+
# content = docx.instance_variable_get("@doc").xpath('//w:document//w:body').children.map{ |c|
|
9
|
+
# if c.name == 'p' # paragraph
|
10
|
+
# paragraph = docx.send(:parse_paragraph_from, c)
|
11
|
+
# ReverseMarkdown.convert(paragraph.to_html).strip
|
12
|
+
# elsif c.name = 'tbl' # table
|
13
|
+
# table = docx.send(:parse_table_from, c)
|
14
|
+
# table.rows.map { |row| row.cells.map { |cell| cell.paragraphs.map(&:text).reject { |c| c.empty? }.join("\\n ") }.join(" | ") }.join("\n")
|
15
|
+
# else # other types?
|
16
|
+
# c.content
|
17
|
+
# end
|
18
|
+
# }.reject { |c| c.empty? }.join("\n\n")
|
19
|
+
# #content = ReverseMarkdown.convert(docx.to_html)
|
20
|
+
# makeTempFileWithContent(content, ["file_#{@current_user.id}", ".txt"]) do |file|
|
21
|
+
# yield(content, doc1['file_name'], file)
|
22
|
+
# end
|
23
|
+
# elsif %w[xlsx xls ods].include?(extension)
|
24
|
+
# xlsx = Roo::Spreadsheet.open document, extension: extension.to_sym
|
25
|
+
# content = xlsx.to_csv
|
26
|
+
# makeTempFileWithContent(content, ["file_#{@current_user.id}", ".csv"]) do |file|
|
27
|
+
# yield(content, doc1['file_name'], file)
|
28
|
+
# end
|
29
|
+
# elsif %w[csv txt ini asc log kicad_sch cpp h rb c hpp erb md yml].include?(extension)
|
30
|
+
# yield(document.read.force_encoding("UTF-8"), doc1['file_name'], nil)
|
31
|
+
# else
|
32
|
+
# yield(nil, doc1['file_name'], nil)
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require 'json'
|
4
4
|
|
5
5
|
#
|
6
6
|
# Extends a class to be used as a tool in the assistant.
|
@@ -36,201 +36,220 @@ require "json"
|
|
36
36
|
# end
|
37
37
|
# end
|
38
38
|
#
|
39
|
-
module OxAiWorkers
|
40
|
-
|
39
|
+
module OxAiWorkers
|
40
|
+
module ToolDefinition
|
41
|
+
attr_accessor :white_list
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# @param method_name [Symbol] Name of the method to define
|
49
|
-
# @param description [String] Description of the function
|
50
|
-
# @yield Block that defines the parameters for the function
|
51
|
-
def define_function(method_name, description:, &)
|
52
|
-
if @white_list.nil? || @white_list == method_name || @white_list.include?(method_name)
|
53
|
-
function_schemas.add_function(method_name:, description:, &)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Returns the FunctionSchemas instance for this tool
|
58
|
-
#
|
59
|
-
# @return [FunctionSchemas] The FunctionSchemas instance
|
60
|
-
def function_schemas
|
61
|
-
@function_schemas ||= FunctionSchemas.new(tool_name)
|
62
|
-
end
|
63
|
-
|
64
|
-
# Returns the snake_case version of the class name as the tool's name
|
65
|
-
#
|
66
|
-
# @return [String] The snake_case version of the class name
|
67
|
-
def tool_name
|
68
|
-
@tool_name ||= (self.respond_to?(:name) ? name : self.class.name)
|
69
|
-
.gsub("::", "_")
|
70
|
-
.gsub(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_")
|
71
|
-
.downcase
|
72
|
-
end
|
73
|
-
|
74
|
-
def full_function_name(fun)
|
75
|
-
function_schemas.function_name(fun)
|
76
|
-
end
|
77
|
-
|
78
|
-
# Manages schemas for functions
|
79
|
-
class FunctionSchemas
|
80
|
-
def initialize(tool_name)
|
81
|
-
@schemas = {}
|
82
|
-
@tool_name = tool_name
|
83
|
-
end
|
84
|
-
|
85
|
-
def function_name method_name
|
86
|
-
"#{@tool_name}__#{method_name}"
|
43
|
+
# Initializes the white list with the given `only` parameter.
|
44
|
+
#
|
45
|
+
# @param only [Object, Array] The object or array to initialize the white list with.
|
46
|
+
# @return [Array] The initialized white list.
|
47
|
+
def init_white_list_with(only)
|
48
|
+
@white_list = only.is_a?(Array) ? only : [only]
|
87
49
|
end
|
88
50
|
|
89
|
-
#
|
51
|
+
# Defines a function for the tool
|
90
52
|
#
|
91
|
-
# @param method_name [Symbol] Name of the method to
|
53
|
+
# @param method_name [Symbol] Name of the method to define
|
92
54
|
# @param description [String] Description of the function
|
93
55
|
# @yield Block that defines the parameters for the function
|
94
|
-
|
95
|
-
|
96
|
-
name = function_name(method_name)
|
56
|
+
def define_function(method_name, description:, &)
|
57
|
+
return unless @white_list.nil? || @white_list == method_name || @white_list.include?(method_name)
|
97
58
|
|
98
|
-
|
99
|
-
|
59
|
+
function_schemas.add_function(method_name:, description:, &)
|
60
|
+
end
|
100
61
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
62
|
+
# Returns the FunctionSchemas instance for this tool
|
63
|
+
#
|
64
|
+
# @return [FunctionSchemas] The FunctionSchemas instance
|
65
|
+
def function_schemas
|
66
|
+
@function_schemas ||= FunctionSchemas.new(tool_name)
|
67
|
+
end
|
105
68
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
69
|
+
# Returns the snake_case version of the class name as the tool's name
|
70
|
+
#
|
71
|
+
# @return [String] The snake_case version of the class name
|
72
|
+
def tool_name
|
73
|
+
@tool_name ||= (respond_to?(:name) ? name : self.class.name)
|
74
|
+
.gsub('::', '_')
|
75
|
+
.gsub(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, '_')
|
76
|
+
.downcase
|
110
77
|
end
|
111
78
|
|
112
|
-
#
|
79
|
+
# Returns the full function name for the given function.
|
113
80
|
#
|
114
|
-
# @
|
115
|
-
|
116
|
-
|
81
|
+
# @param fun [Symbol] The function name.
|
82
|
+
# @return [String] The full function name, which is the tool name concatenated with the function name.
|
83
|
+
def full_function_name(fun)
|
84
|
+
function_schemas.function_name(fun)
|
117
85
|
end
|
118
86
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
@
|
87
|
+
# Manages schemas for functions
|
88
|
+
class FunctionSchemas
|
89
|
+
def initialize(tool_name)
|
90
|
+
@schemas = {}
|
91
|
+
@tool_name = tool_name
|
124
92
|
end
|
125
|
-
end
|
126
93
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
94
|
+
# Returns the full function name for the given method name.
|
95
|
+
#
|
96
|
+
# @param method_name [Symbol] The name of the method.
|
97
|
+
# @return [String] The full function name, which is the tool name concatenated with the method name.
|
98
|
+
def function_name(method_name)
|
99
|
+
"#{@tool_name}__#{method_name}"
|
133
100
|
end
|
134
|
-
end
|
135
101
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
102
|
+
# Adds a function to the schemas
|
103
|
+
#
|
104
|
+
# @param method_name [Symbol] Name of the method to add
|
105
|
+
# @param description [String] Description of the function
|
106
|
+
# @yield Block that defines the parameters for the function
|
107
|
+
# @raise [ArgumentError] If a block is defined and no parameters are specified for the function
|
108
|
+
def add_function(method_name:, description:, &)
|
109
|
+
name = function_name(method_name)
|
110
|
+
|
111
|
+
if block_given?
|
112
|
+
parameters = ParameterBuilder.new(parent_type: 'object').build(&)
|
113
|
+
|
114
|
+
if parameters[:properties].empty?
|
115
|
+
raise ArgumentError,
|
116
|
+
'Function parameters must have at least one property defined within it, if a block is provided'
|
117
|
+
end
|
118
|
+
end
|
143
119
|
|
144
|
-
|
145
|
-
|
146
|
-
|
120
|
+
@schemas[method_name] = {
|
121
|
+
type: 'function',
|
122
|
+
function: { name:, description:, parameters: }.compact
|
123
|
+
}
|
124
|
+
end
|
147
125
|
|
148
|
-
|
149
|
-
|
150
|
-
@
|
151
|
-
|
126
|
+
# Converts schemas to OpenAI-compatible format
|
127
|
+
#
|
128
|
+
# @return [String] JSON string of schemas in OpenAI format
|
129
|
+
def to_openai_format(only: nil)
|
130
|
+
valid_schemas(only:).values
|
131
|
+
end
|
152
132
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
133
|
+
# Returns a subset of schemas based on the provided filter.
|
134
|
+
#
|
135
|
+
# @param only [Array<Symbol>] An optional array of schema names to filter by.
|
136
|
+
# @return [Hash<Symbol, Hash>] A hash of schemas with their corresponding names as keys.
|
137
|
+
def valid_schemas(only: nil)
|
138
|
+
if only.nil?
|
139
|
+
@schemas
|
140
|
+
else
|
141
|
+
@schemas.select { |name, _schema| only.include?(name) }
|
142
|
+
end
|
143
|
+
end
|
161
144
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
# @param required [Boolean] Whether the property is required
|
169
|
-
# @yield [Block] Block for nested properties (only for object and array types)
|
170
|
-
# @raise [ArgumentError] If any parameter is invalid
|
171
|
-
def property(name = nil, type:, description: nil, enum: nil, required: false, &)
|
172
|
-
validate_parameters(name:, type:, enum:, required:)
|
173
|
-
|
174
|
-
prop = {type:, description:, enum:}.compact
|
175
|
-
|
176
|
-
if block_given?
|
177
|
-
nested_schema = ParameterBuilder.new(parent_type: type).build(&)
|
178
|
-
|
179
|
-
case type
|
180
|
-
when "object"
|
181
|
-
if nested_schema[:properties].empty?
|
182
|
-
raise ArgumentError, "Object properties must have at least one property defined within it"
|
183
|
-
end
|
184
|
-
prop = nested_schema
|
185
|
-
when "array"
|
186
|
-
if nested_schema.empty?
|
187
|
-
raise ArgumentError, "Array properties must have at least one item defined within it"
|
188
|
-
end
|
189
|
-
prop[:items] = nested_schema
|
145
|
+
# Converts schemas to Anthropic-compatible format
|
146
|
+
#
|
147
|
+
# @return [String] JSON string of schemas in Anthropic format
|
148
|
+
def to_anthropic_format(only: nil)
|
149
|
+
valid_schemas(only:).values.map do |schema|
|
150
|
+
schema[:function].transform_keys('parameters' => 'input_schema')
|
190
151
|
end
|
191
152
|
end
|
192
153
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
154
|
+
# Converts schemas to Google Gemini-compatible format
|
155
|
+
#
|
156
|
+
# @return [String] JSON string of schemas in Google Gemini format
|
157
|
+
def to_google_gemini_format(only: nil)
|
158
|
+
valid_schemas(only:).values.map { |schema| schema[:function] }
|
198
159
|
end
|
199
160
|
end
|
200
161
|
|
201
|
-
#
|
202
|
-
|
162
|
+
# Builds parameter schemas for functions
|
163
|
+
class ParameterBuilder
|
164
|
+
VALID_TYPES = %w[object array string number integer boolean].freeze
|
203
165
|
|
204
|
-
|
166
|
+
def initialize(parent_type:)
|
167
|
+
@schema = parent_type == 'object' ? { type: 'object', properties: {}, required: [] } : {}
|
168
|
+
@parent_type = parent_type
|
169
|
+
end
|
205
170
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
171
|
+
# Builds the parameter schema
|
172
|
+
#
|
173
|
+
# @yield Block that defines the properties of the schema
|
174
|
+
# @return [Hash] The built schema
|
175
|
+
def build(&)
|
176
|
+
instance_eval(&)
|
177
|
+
@schema
|
178
|
+
end
|
179
|
+
|
180
|
+
# Defines a property in the schema
|
181
|
+
#
|
182
|
+
# @param name [Symbol] Name of the property (required only for a parent of type object)
|
183
|
+
# @param type [String] Type of the property
|
184
|
+
# @param description [String] Description of the property
|
185
|
+
# @param enum [Array] Array of allowed values
|
186
|
+
# @param required [Boolean] Whether the property is required
|
187
|
+
# @yield [Block] Block for nested properties (only for object and array types)
|
188
|
+
# @raise [ArgumentError] If any parameter is invalid
|
189
|
+
def property(name = nil, type:, description: nil, enum: nil, required: false, &)
|
190
|
+
validate_parameters(name:, type:, enum:, required:)
|
191
|
+
|
192
|
+
prop = { type:, description:, enum: }.compact
|
193
|
+
|
194
|
+
if block_given?
|
195
|
+
nested_schema = ParameterBuilder.new(parent_type: type).build(&)
|
196
|
+
|
197
|
+
case type
|
198
|
+
when 'object'
|
199
|
+
if nested_schema[:properties].empty?
|
200
|
+
raise ArgumentError, 'Object properties must have at least one property defined within it'
|
201
|
+
end
|
202
|
+
|
203
|
+
prop = nested_schema
|
204
|
+
when 'array'
|
205
|
+
if nested_schema.empty?
|
206
|
+
raise ArgumentError,
|
207
|
+
'Array properties must have at least one item defined within it'
|
208
|
+
end
|
209
|
+
|
210
|
+
prop[:items] = nested_schema
|
211
|
+
end
|
217
212
|
end
|
218
|
-
|
219
|
-
|
213
|
+
|
214
|
+
if @parent_type == 'object'
|
215
|
+
@schema[:properties][name] = prop
|
216
|
+
@schema[:required] << name.to_s if required
|
217
|
+
else
|
218
|
+
@schema = prop
|
220
219
|
end
|
221
220
|
end
|
222
221
|
|
223
|
-
|
224
|
-
|
225
|
-
|
222
|
+
# Alias for property method, used for defining array items
|
223
|
+
alias item property
|
224
|
+
|
225
|
+
private
|
226
|
+
|
227
|
+
# Validates the parameters for a property
|
228
|
+
#
|
229
|
+
# @param name [Symbol] Name of the property
|
230
|
+
# @param type [String] Type of the property
|
231
|
+
# @param enum [Array] Array of allowed values
|
232
|
+
# @param required [Boolean] Whether the property is required
|
233
|
+
# @raise [ArgumentError] If any parameter is invalid
|
234
|
+
def validate_parameters(name:, type:, enum:, required:)
|
235
|
+
if @parent_type == 'object'
|
236
|
+
raise ArgumentError, 'Name must be provided for properties of an object' if name.nil?
|
237
|
+
raise ArgumentError, "Invalid name '#{name}'. Name must be a symbol" unless name.is_a?(Symbol)
|
238
|
+
end
|
226
239
|
|
227
|
-
|
228
|
-
|
229
|
-
|
240
|
+
unless VALID_TYPES.include?(type)
|
241
|
+
raise ArgumentError, "Invalid type '#{type}'. Valid types are: #{VALID_TYPES.join(', ')}"
|
242
|
+
end
|
243
|
+
|
244
|
+
unless enum.nil? || enum.is_a?(Array)
|
245
|
+
raise ArgumentError,
|
246
|
+
"Invalid enum '#{enum}'. Enum must be nil or an array"
|
247
|
+
end
|
248
|
+
|
249
|
+
return if [true, false].include?(required)
|
230
250
|
|
231
|
-
unless [true, false].include?(required)
|
232
251
|
raise ArgumentError, "Invalid required '#{required}'. Required must be a boolean"
|
233
252
|
end
|
234
253
|
end
|
235
254
|
end
|
236
|
-
end
|
255
|
+
end
|
data/lib/oxaiworkers/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ox-ai-workers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Smolev
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -124,11 +124,18 @@ executables:
|
|
124
124
|
extensions: []
|
125
125
|
extra_rdoc_files: []
|
126
126
|
files:
|
127
|
+
- ".ruby-version"
|
127
128
|
- CHANGELOG.md
|
128
129
|
- CODE_OF_CONDUCT.md
|
129
130
|
- LICENSE
|
130
131
|
- README.md
|
131
132
|
- Rakefile
|
133
|
+
- config/locales/en.oxaiworkers.assistant.yml
|
134
|
+
- config/locales/en.oxaiworkers.iterator.yml
|
135
|
+
- config/locales/en.oxaiworkers.tool.yml
|
136
|
+
- config/locales/ru.oxaiworkers.assistant.yml
|
137
|
+
- config/locales/ru.oxaiworkers.iterator.yml
|
138
|
+
- config/locales/ru.oxaiworkers.tool.yml
|
132
139
|
- exe/oxaiworkers
|
133
140
|
- exe/start
|
134
141
|
- lib/ox-ai-workers.rb
|
@@ -140,6 +147,7 @@ files:
|
|
140
147
|
- lib/oxaiworkers/contextual_logger.rb
|
141
148
|
- lib/oxaiworkers/delayed_request.rb
|
142
149
|
- lib/oxaiworkers/dependency_helper.rb
|
150
|
+
- lib/oxaiworkers/engine.rb
|
143
151
|
- lib/oxaiworkers/iterator.rb
|
144
152
|
- lib/oxaiworkers/load_i18n.rb
|
145
153
|
- lib/oxaiworkers/module_request.rb
|
@@ -148,18 +156,13 @@ files:
|
|
148
156
|
- lib/oxaiworkers/state_batch.rb
|
149
157
|
- lib/oxaiworkers/state_helper.rb
|
150
158
|
- lib/oxaiworkers/state_tools.rb
|
159
|
+
- lib/oxaiworkers/tool/converter.rb
|
151
160
|
- lib/oxaiworkers/tool/database.rb
|
152
161
|
- lib/oxaiworkers/tool/eval.rb
|
153
162
|
- lib/oxaiworkers/tool/file_system.rb
|
154
163
|
- lib/oxaiworkers/tool_definition.rb
|
155
164
|
- lib/oxaiworkers/version.rb
|
156
165
|
- lib/ruby/ox-ai-workers.rb
|
157
|
-
- locales/en.assistant.yml
|
158
|
-
- locales/en.iterator.yml
|
159
|
-
- locales/en.tool.yml
|
160
|
-
- locales/ru.assistant.yml
|
161
|
-
- locales/ru.iterator.yml
|
162
|
-
- locales/ru.tool.yml
|
163
166
|
- template/my_assistant.rb
|
164
167
|
- template/start
|
165
168
|
- template/tools/my_tool.rb
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|