ox-ai-workers 0.5.3.1 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +30 -15
- data/exe/start +4 -0
- data/lib/ox-ai-workers.rb +3 -2
- data/lib/oxaiworkers/assistant/coder.rb +3 -1
- data/lib/oxaiworkers/assistant/localizer.rb +3 -1
- data/lib/oxaiworkers/assistant/module_base.rb +2 -0
- data/lib/oxaiworkers/assistant/sysop.rb +4 -2
- data/lib/oxaiworkers/iterator.rb +63 -30
- data/lib/oxaiworkers/load_i18n.rb +15 -0
- data/lib/oxaiworkers/tool/database.rb +27 -22
- data/lib/oxaiworkers/tool/eval.rb +17 -10
- data/lib/oxaiworkers/tool/file_system.rb +31 -26
- data/lib/oxaiworkers/tool_definition.rb +33 -9
- data/lib/oxaiworkers/version.rb +1 -1
- data/locales/en.iterator.yml +2 -2
- data/locales/ru.iterator.yml +2 -2
- data/template/my_assistant.rb +9 -3
- data/template/tools/my_tool.rb +13 -2
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca54c1ff9c1f4360ddfe986c4b5bc6fb6b7c0cde0484d1b2f88bfe4d8c5707b7
|
4
|
+
data.tar.gz: 2c54e55fedd99af36f7a510ab923c54581faecfc8b36caa4d228f14349e293a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8915a64826c118706328c89a87c01fdf49f13b63408c64d5af1ea53a2f92dbb326b9e537f36aa755297a5c919239d426da1f38a83b3cd5fdfbc5a9ebc4146f7
|
7
|
+
data.tar.gz: 8614ed16d7771b594bda52de36f87bbd211adfc8c579fff752634fc4ce44a7d554cd25cf7b32bfaa80954830f835a7b3c1f61d0e237c930bc043f433e98534c1
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.5] - 2024-08-02
|
4
|
+
|
5
|
+
- Added `only` for Tools
|
6
|
+
- Addded `locale` for `Iterator` and `Assistant`
|
7
|
+
|
8
|
+
## [0.5.4] - 2024-08-01
|
9
|
+
|
10
|
+
- def_except and def_only for `Iterator`
|
11
|
+
- Renamed on_pack_history to on_summarize
|
12
|
+
- Renamed all "pack_history" to "summarize"
|
13
|
+
|
3
14
|
## [0.5.3] - 2024-07-31
|
4
15
|
|
5
16
|
- Fixed summarize state
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
[](https://rubygems.org/gems/ox-ai-workers)
|
2
|
-
|
3
1
|
# OxAiWorkers (ox-ai-workers)
|
4
2
|
|
3
|
+
[](https://rubygems.org/gems/ox-ai-workers)
|
4
|
+
|
5
5
|
OxAiWorkers is a Ruby gem that implements a finite state machine (using the `state_machine` gem) to solve tasks using generative intelligence (with the `ruby-openai` gem). This approach enhances the final result by utilizing internal monologue and external tools.
|
6
6
|
|
7
7
|
## Installation
|
@@ -63,7 +63,7 @@ worker = OxAiWorkers::Request.new(
|
|
63
63
|
temperature: 0.7 )
|
64
64
|
|
65
65
|
# Initialize a tool
|
66
|
-
my_tool = OxAiWorkers::Tool::Eval.new()
|
66
|
+
my_tool = OxAiWorkers::Tool::Eval.new(only: :sh)
|
67
67
|
|
68
68
|
# Create an iterator with the worker and tool
|
69
69
|
iterator = OxAiWorkers::Iterator.new(
|
@@ -86,9 +86,9 @@ 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
|
91
|
-
config.auto_execute = true
|
89
|
+
config.max_tokens = 4096 # Default
|
90
|
+
config.temperature = 0.7 # Default
|
91
|
+
config.auto_execute = true # Default
|
92
92
|
end
|
93
93
|
```
|
94
94
|
|
@@ -96,17 +96,26 @@ Then you can create an assistant like this:
|
|
96
96
|
|
97
97
|
```ruby
|
98
98
|
assistant = OxAiWorkers::Assistant::Sysop.new()
|
99
|
-
assistant.task = "
|
99
|
+
assistant.task = "Remove all cron jobs."
|
100
100
|
|
101
101
|
# Provide a response to the assistant's question
|
102
102
|
assistant.add_response("blah-blah-blah")
|
103
103
|
```
|
104
104
|
|
105
|
+
Besides, you can create assistants with different locales
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
I18n.with_locale(:en) { @sysop_en = OxAiWorkers::Assistant::Sysop.new() }
|
109
|
+
|
110
|
+
# Assign tasks and responses in different languages
|
111
|
+
@sysop_en.task = "Remove all cron jobs."
|
112
|
+
```
|
113
|
+
|
105
114
|
Or you can create a lower-level iterator for more control:
|
106
115
|
|
107
116
|
```ruby
|
108
117
|
my_worker = OxAiWorkers::Request.new
|
109
|
-
my_tool = OxAiWorkers::Tool::Eval.new
|
118
|
+
my_tool = OxAiWorkers::Tool::Eval.new(only: [:sh])
|
110
119
|
|
111
120
|
iterator = OxAiWorkers::Iterator.new(
|
112
121
|
worker: my_worker,
|
@@ -115,7 +124,7 @@ iterator = OxAiWorkers::Iterator.new(
|
|
115
124
|
on_inner_monologue: ->(text:) { puts "monologue: #{text}".colorize(:yellow) },
|
116
125
|
on_outer_voice: ->(text:) { puts "voice: #{text}".colorize(:green) },
|
117
126
|
on_action_request: ->(text:) { puts "action: #{text}".colorize(:red) },
|
118
|
-
|
127
|
+
on_summarize: ->(text:) { puts "summary: #{text}".colorize(:blue) }
|
119
128
|
)
|
120
129
|
|
121
130
|
iterator.add_task("Show files in current directory.")
|
@@ -130,20 +139,26 @@ This way, you have the flexibility to choose between a higher-level assistant fo
|
|
130
139
|
```ruby
|
131
140
|
steps = []
|
132
141
|
steps << 'Step 1. Develop your own solution to the problem, taking initiative and making assumptions.'
|
133
|
-
steps <<
|
142
|
+
steps << "Step 2. Enclose all your developments from the previous step in the #{OxAiWorkers::Iterator.full_function_name(:inner_monologue)} function."
|
134
143
|
steps << 'Step 3. Call the necessary functions one after another until the desired result is achieved.'
|
135
|
-
steps <<
|
144
|
+
steps << "Step 4. When all intermediate steps are completed and the exact content of previous messages is no longer relevant, use the #{OxAiWorkers::Iterator.full_function_name(:summarize)} function."
|
136
145
|
steps << "Step 5. When the solution is ready, notify about it and wait for the user's response."
|
137
146
|
|
147
|
+
# To retain the locale if you have assistants in different languages in your project.
|
148
|
+
store_locale # Optional
|
149
|
+
|
138
150
|
@iterator = OxAiWorkers::Iterator.new(
|
139
151
|
worker: init_worker(delayed: delayed, model: model),
|
140
152
|
role: 'You are a software agent inside my computer',
|
141
153
|
tools: [MyTool.new],
|
154
|
+
locale: @locale || I18n.locale,
|
142
155
|
steps: steps,
|
156
|
+
# def_except: [:summarize], # It's except steps with that functions
|
157
|
+
# def_only: [:inner_monologue, :outer_voice], # Use it only with your steps
|
143
158
|
on_inner_monologue: ->(text:) { puts "monologue: #{text}".colorize(:yellow) },
|
144
159
|
on_outer_voice: ->(text:) { puts "voice: #{text}".colorize(:green) },
|
145
160
|
on_action_request: ->(text:) { puts "action: #{text}".colorize(:red) },
|
146
|
-
|
161
|
+
on_summarize: ->(text:) { puts "summary: #{text}".colorize(:blue) }
|
147
162
|
)
|
148
163
|
```
|
149
164
|
|
@@ -165,7 +180,7 @@ As a worker, you can use different classes depending on your needs:
|
|
165
180
|
oxaiworkers init
|
166
181
|
```
|
167
182
|
|
168
|
-
This will create a `.oxaiworkers-local` directory with the necessary initial source code.
|
183
|
+
This will create a `.oxaiworkers-local` directory with the necessary initial source code.
|
169
184
|
|
170
185
|
Additionally, you can initialize a more comprehensive example using the command:
|
171
186
|
|
@@ -199,7 +214,7 @@ OxAiWorkers.logger.level = :debug
|
|
199
214
|
|
200
215
|
## Contributing
|
201
216
|
|
202
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/neonix20b/ox-ai-workers
|
217
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/neonix20b/ox-ai-workers>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/neonix20b/ox-ai-workers/blob/main/CODE_OF_CONDUCT.md).
|
203
218
|
|
204
219
|
## License
|
205
220
|
|
@@ -207,4 +222,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
207
222
|
|
208
223
|
## Code of Conduct
|
209
224
|
|
210
|
-
Everyone interacting in the OxAiWorkers project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/neonix20b/ox-ai-workers/blob/main/CODE_OF_CONDUCT.md).
|
225
|
+
Everyone interacting in the OxAiWorkers project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/neonix20b/ox-ai-workers/blob/main/CODE_OF_CONDUCT.md).
|
data/exe/start
CHANGED
data/lib/ox-ai-workers.rb
CHANGED
@@ -11,13 +11,14 @@ require_relative 'oxaiworkers/version'
|
|
11
11
|
require_relative 'oxaiworkers/contextual_logger'
|
12
12
|
require_relative 'oxaiworkers/load_i18n'
|
13
13
|
require_relative 'oxaiworkers/present_compat'
|
14
|
+
require_relative 'oxaiworkers/tool_definition'
|
15
|
+
require_relative 'oxaiworkers/dependency_helper'
|
16
|
+
|
14
17
|
require_relative 'oxaiworkers/module_request'
|
15
18
|
require_relative 'oxaiworkers/state_helper'
|
16
19
|
require_relative 'oxaiworkers/state_batch'
|
17
20
|
require_relative 'oxaiworkers/state_tools'
|
18
|
-
require_relative 'oxaiworkers/tool_definition'
|
19
21
|
require_relative 'oxaiworkers/delayed_request'
|
20
|
-
require_relative 'oxaiworkers/dependency_helper'
|
21
22
|
require_relative 'oxaiworkers/iterator'
|
22
23
|
require_relative 'oxaiworkers/request'
|
23
24
|
|
@@ -6,14 +6,16 @@ module OxAiWorkers
|
|
6
6
|
include OxAiWorkers::Assistant::ModuleBase
|
7
7
|
|
8
8
|
def initialize(delayed: false, model: nil, language: 'ruby')
|
9
|
+
store_locale
|
9
10
|
@iterator = Iterator.new(
|
10
11
|
worker: init_worker(delayed: delayed, model: model),
|
11
12
|
role: format(I18n.t('oxaiworkers.assistant.coder.role'), language),
|
12
13
|
tools: [Tool::Eval.new, Tool::FileSystem.new],
|
14
|
+
locale: @locale,
|
13
15
|
on_inner_monologue: ->(text:) { puts "monologue: #{text}".colorize(:yellow) },
|
14
16
|
on_outer_voice: ->(text:) { puts "voice: #{text}".colorize(:green) },
|
15
17
|
on_action_request: ->(text:) { puts "action: #{text}".colorize(:red) },
|
16
|
-
|
18
|
+
on_summarize: ->(text:) { puts "summary: #{text}".colorize(:blue) }
|
17
19
|
)
|
18
20
|
end
|
19
21
|
end
|
@@ -6,14 +6,16 @@ module OxAiWorkers
|
|
6
6
|
include OxAiWorkers::Assistant::ModuleBase
|
7
7
|
|
8
8
|
def initialize(delayed: false, model: nil, language: 'русский', locale: :ru, source: 'english')
|
9
|
+
store_locale
|
9
10
|
@iterator = Iterator.new(
|
10
11
|
worker: init_worker(delayed: delayed, model: model),
|
11
12
|
role: format(I18n.t('oxaiworkers.assistant.localizer.role'), language),
|
12
13
|
tools: [Tool::Eval.new, Tool::FileSystem.new],
|
14
|
+
locale: @locale,
|
13
15
|
on_inner_monologue: ->(text:) { puts "monologue: #{text}".colorize(:yellow) },
|
14
16
|
on_outer_voice: ->(text:) { puts "voice: #{text}".colorize(:green) },
|
15
17
|
on_action_request: ->(text:) { puts "action: #{text}".colorize(:red) },
|
16
|
-
|
18
|
+
on_summarize: ->(text:) { puts "summary: #{text}".colorize(:blue) }
|
17
19
|
)
|
18
20
|
@iterator.add_context(format(I18n.t('oxaiworkers.assistant.localizer.source'), source))
|
19
21
|
|
@@ -6,14 +6,16 @@ module OxAiWorkers
|
|
6
6
|
include OxAiWorkers::Assistant::ModuleBase
|
7
7
|
|
8
8
|
def initialize(delayed: false, model: nil)
|
9
|
+
store_locale
|
9
10
|
@iterator = Iterator.new(
|
10
11
|
worker: init_worker(delayed: delayed, model: model),
|
11
12
|
role: I18n.t('oxaiworkers.assistant.sysop.role'),
|
12
|
-
tools: [Tool::Eval.new],
|
13
|
+
tools: [Tool::Eval.new(only: :sh), Tool::FileSystem.new(only: %i[read_file write_to_file])],
|
14
|
+
locale: @locale,
|
13
15
|
on_inner_monologue: ->(text:) { puts "monologue: #{text}".colorize(:yellow) },
|
14
16
|
on_outer_voice: ->(text:) { puts "voice: #{text}".colorize(:green) },
|
15
17
|
on_action_request: ->(text:) { puts "action: #{text}".colorize(:red) },
|
16
|
-
|
18
|
+
on_summarize: ->(text:) { puts "summary: #{text}".colorize(:blue) }
|
17
19
|
)
|
18
20
|
end
|
19
21
|
end
|
data/lib/oxaiworkers/iterator.rb
CHANGED
@@ -2,40 +2,52 @@
|
|
2
2
|
|
3
3
|
module OxAiWorkers
|
4
4
|
class Iterator < OxAiWorkers::StateTools
|
5
|
-
|
6
|
-
attr_accessor :worker, :role, :messages, :context, :result, :tools, :queue, :monologue, :tasks, :milestones
|
7
|
-
attr_accessor :on_inner_monologue, :on_outer_voice, :on_action_request, :on_pack_history
|
5
|
+
ITERATOR_FUNCTIONS = %i[inner_monologue outer_voice action_request summarize].freeze
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
required: true
|
12
|
-
end
|
7
|
+
include OxAiWorkers::ToolDefinition
|
8
|
+
include OxAiWorkers::LoadI18n
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
end
|
10
|
+
attr_accessor :worker, :role, :messages, :context, :result, :tools, :queue, :monologue, :tasks, :milestones,
|
11
|
+
:on_inner_monologue, :on_outer_voice, :on_action_request, :on_summarize, :def_except, :def_only
|
17
12
|
|
18
|
-
|
19
|
-
|
20
|
-
required: true
|
21
|
-
end
|
13
|
+
def initialize(worker:, role: nil, tools: [], on_inner_monologue: nil, on_outer_voice: nil, on_action_request: nil,
|
14
|
+
on_summarize: nil, steps: nil, def_except: [], def_only: nil, locale: nil)
|
22
15
|
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
@locale = locale || I18n.locale
|
17
|
+
|
18
|
+
with_locale do
|
19
|
+
define_function :inner_monologue, description: I18n.t('oxaiworkers.iterator.inner_monologue.description') do
|
20
|
+
property :speach, type: 'string', description: I18n.t('oxaiworkers.iterator.inner_monologue.speach'),
|
21
|
+
required: true
|
22
|
+
end
|
23
|
+
|
24
|
+
define_function :outer_voice, description: I18n.t('oxaiworkers.iterator.outer_voice.description') do
|
25
|
+
property :text, type: 'string', description: I18n.t('oxaiworkers.iterator.outer_voice.text'), required: true
|
26
|
+
end
|
27
|
+
|
28
|
+
define_function :action_request, description: I18n.t('oxaiworkers.iterator.action_request.description') do
|
29
|
+
property :action, type: 'string', description: I18n.t('oxaiworkers.iterator.action_request.action'),
|
30
|
+
required: true
|
31
|
+
end
|
32
|
+
|
33
|
+
define_function :summarize, description: I18n.t('oxaiworkers.iterator.summarize.description') do
|
34
|
+
property :text, type: 'string', description: I18n.t('oxaiworkers.iterator.summarize.text'), required: true
|
35
|
+
end
|
36
|
+
|
37
|
+
@monologue = steps || I18n.t('oxaiworkers.iterator.monologue')
|
38
|
+
end
|
26
39
|
|
27
|
-
def initialize(worker:, role: nil, tools: [], on_inner_monologue: nil, on_outer_voice: nil, on_action_request: nil,
|
28
|
-
on_pack_history: nil, steps: nil)
|
29
40
|
@worker = worker
|
30
|
-
@tools =
|
41
|
+
@tools = tools
|
31
42
|
@role = role
|
32
43
|
@context = []
|
33
|
-
@
|
44
|
+
@def_only = def_only || ITERATOR_FUNCTIONS
|
45
|
+
@def_except = def_except
|
34
46
|
|
35
47
|
@on_inner_monologue = on_inner_monologue
|
36
48
|
@on_outer_voice = on_outer_voice
|
37
49
|
@on_action_request = on_action_request
|
38
|
-
@
|
50
|
+
@on_summarize = on_summarize
|
39
51
|
|
40
52
|
cleanup
|
41
53
|
|
@@ -61,6 +73,7 @@ module OxAiWorkers
|
|
61
73
|
def outer_voice(text:)
|
62
74
|
# @queue.pop
|
63
75
|
@queue << { role: :assistant, content: text.to_s }
|
76
|
+
complete! unless available_defs.include?(:action_request)
|
64
77
|
@on_outer_voice&.call(text: text)
|
65
78
|
nil
|
66
79
|
end
|
@@ -77,11 +90,13 @@ module OxAiWorkers
|
|
77
90
|
def summarize(text:)
|
78
91
|
@milestones << text.to_s
|
79
92
|
@messages = []
|
80
|
-
|
93
|
+
with_locale do
|
94
|
+
@queue << { role: :assistant, content: I18n.t('oxaiworkers.iterator.summarize.result') }
|
95
|
+
end
|
81
96
|
@worker.finish
|
82
97
|
rebuild_worker
|
83
|
-
|
84
|
-
@
|
98
|
+
complete! if can_complete?
|
99
|
+
@on_summarize&.call(text: text)
|
85
100
|
nil
|
86
101
|
end
|
87
102
|
|
@@ -93,12 +108,29 @@ module OxAiWorkers
|
|
93
108
|
def rebuild_worker
|
94
109
|
@worker.messages = []
|
95
110
|
@worker.append(role: :system, content: @role) if @role.present?
|
96
|
-
@worker.append(role: :system, content:
|
111
|
+
@worker.append(role: :system, content: valid_monologue.join("\n"))
|
97
112
|
@worker.append(messages: @context) if @context.present?
|
98
113
|
@tasks.each { |task| @worker.append(role: :user, content: task) }
|
99
114
|
@milestones.each { |milestone| @worker.append(role: :assistant, content: milestone) }
|
100
115
|
@worker.append(messages: @messages)
|
101
|
-
@worker.tools =
|
116
|
+
@worker.tools = function_schemas.to_openai_format(only: available_defs)
|
117
|
+
return unless @tools.present?
|
118
|
+
|
119
|
+
@worker.tools += @tools.map do |tool|
|
120
|
+
if tool.respond_to?(:function_schemas)
|
121
|
+
tool.function_schemas.to_openai_format
|
122
|
+
else
|
123
|
+
tool.class.function_schemas.to_openai_format
|
124
|
+
end
|
125
|
+
end.flatten
|
126
|
+
end
|
127
|
+
|
128
|
+
def available_defs
|
129
|
+
@def_only - @def_except
|
130
|
+
end
|
131
|
+
|
132
|
+
def valid_monologue
|
133
|
+
@monologue.reject { |item| @def_except.any? { |fun| item.include?(self.class.full_function_name(fun)) } }
|
102
134
|
end
|
103
135
|
|
104
136
|
def next_iteration
|
@@ -123,12 +155,13 @@ module OxAiWorkers
|
|
123
155
|
if @worker.tool_calls.present?
|
124
156
|
@queue << { role: :assistant, content: @worker.tool_calls_raw.to_s }
|
125
157
|
@worker.tool_calls.each do |external_call|
|
126
|
-
tool = @tools.select do |t|
|
127
|
-
|
158
|
+
tool = ([self] + @tools).select do |t|
|
159
|
+
tool_name = t.respond_to?(:tool_name) ? t.tool_name : t.class.tool_name
|
160
|
+
tool_name == external_call[:class] && t.respond_to?(external_call[:name])
|
128
161
|
end.first
|
129
162
|
unless tool.nil?
|
130
163
|
out = tool.send(external_call[:name], **external_call[:args])
|
131
|
-
@queue << { role: :
|
164
|
+
@queue << { role: :system, content: out.to_s } if out.present?
|
132
165
|
end
|
133
166
|
end
|
134
167
|
@worker.finish
|
@@ -2,3 +2,18 @@
|
|
2
2
|
|
3
3
|
require 'i18n'
|
4
4
|
I18n.load_path += Dir["#{File.expand_path('../../locales', __dir__)}/*.yml"]
|
5
|
+
|
6
|
+
module OxAiWorkers
|
7
|
+
module LoadI18n
|
8
|
+
attr_accessor :locale
|
9
|
+
|
10
|
+
def with_locale(&action)
|
11
|
+
# locale = respond_to?(:locale) ? self.locale : OxAiWorkers.configuration.locale
|
12
|
+
I18n.with_locale(@locale, &action)
|
13
|
+
end
|
14
|
+
|
15
|
+
def store_locale
|
16
|
+
@locale = I18n.locale
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -10,28 +10,9 @@ module OxAiWorkers
|
|
10
10
|
# database = OxAiWorkers::Tool::Database.new(connection_string: "postgres://user:password@localhost:5432/db_name")
|
11
11
|
#
|
12
12
|
class Database
|
13
|
-
|
13
|
+
include OxAiWorkers::ToolDefinition
|
14
14
|
include OxAiWorkers::DependencyHelper
|
15
|
-
|
16
|
-
define_function :list_tables,
|
17
|
-
description: I18n.t('oxaiworkers.tool.database_tool.list_tables.description')
|
18
|
-
|
19
|
-
define_function :describe_tables,
|
20
|
-
description: I18n.t('oxaiworkers.tool.database_tool.describe_tables.description') do
|
21
|
-
property :tables, type: 'string',
|
22
|
-
description: I18n.t('oxaiworkers.tool.database_tool.describe_tables.tables'),
|
23
|
-
required: true
|
24
|
-
end
|
25
|
-
|
26
|
-
define_function :dump_schema,
|
27
|
-
description: I18n.t('oxaiworkers.tool.database_tool.dump_schema.description')
|
28
|
-
|
29
|
-
define_function :execute,
|
30
|
-
description: I18n.t('oxaiworkers.tool.database_tool.execute.description') do
|
31
|
-
property :input, type: 'string',
|
32
|
-
description: I18n.t('oxaiworkers.tool.database_tool.execute.input'),
|
33
|
-
required: true
|
34
|
-
end
|
15
|
+
include OxAiWorkers::LoadI18n
|
35
16
|
|
36
17
|
attr_reader :db, :requested_tables, :excluded_tables
|
37
18
|
|
@@ -41,11 +22,35 @@ module OxAiWorkers
|
|
41
22
|
# @param tables [Array<Symbol>] The tables to use. Will use all if empty.
|
42
23
|
# @param except_tables [Array<Symbol>] The tables to exclude. Will exclude none if empty.
|
43
24
|
# @return [Database] Database object
|
44
|
-
def initialize(connection_string:, tables: [], exclude_tables: [])
|
25
|
+
def initialize(connection_string:, tables: [], exclude_tables: [], only: nil)
|
45
26
|
depends_on 'sequel'
|
46
27
|
|
47
28
|
raise StandardError, 'connection_string parameter cannot be blank' if connection_string.empty?
|
48
29
|
|
30
|
+
store_locale
|
31
|
+
|
32
|
+
init_white_list_with only
|
33
|
+
|
34
|
+
define_function :list_tables,
|
35
|
+
description: I18n.t('oxaiworkers.tool.database_tool.list_tables.description')
|
36
|
+
|
37
|
+
define_function :describe_tables,
|
38
|
+
description: I18n.t('oxaiworkers.tool.database_tool.describe_tables.description') do
|
39
|
+
property :tables, type: 'string',
|
40
|
+
description: I18n.t('oxaiworkers.tool.database_tool.describe_tables.tables'),
|
41
|
+
required: true
|
42
|
+
end
|
43
|
+
|
44
|
+
define_function :dump_schema,
|
45
|
+
description: I18n.t('oxaiworkers.tool.database_tool.dump_schema.description')
|
46
|
+
|
47
|
+
define_function :execute,
|
48
|
+
description: I18n.t('oxaiworkers.tool.database_tool.execute.description') do
|
49
|
+
property :input, type: 'string',
|
50
|
+
description: I18n.t('oxaiworkers.tool.database_tool.execute.input'),
|
51
|
+
required: true
|
52
|
+
end
|
53
|
+
|
49
54
|
@db = Sequel.connect(connection_string)
|
50
55
|
@requested_tables = tables
|
51
56
|
@excluded_tables = exclude_tables
|
@@ -5,21 +5,28 @@ require 'open3'
|
|
5
5
|
module OxAiWorkers
|
6
6
|
module Tool
|
7
7
|
class Eval
|
8
|
-
|
8
|
+
include OxAiWorkers::ToolDefinition
|
9
9
|
include OxAiWorkers::DependencyHelper
|
10
|
+
include OxAiWorkers::LoadI18n
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
# end
|
12
|
+
def initialize(only: nil)
|
13
|
+
store_locale
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
init_white_list_with only
|
16
|
+
|
17
|
+
define_function :ruby, description: I18n.t('oxaiworkers.tool.eval.ruby.description') do
|
18
|
+
property :input, type: 'string', description: I18n.t('oxaiworkers.tool.eval.ruby.input'), required: true
|
19
|
+
end
|
20
|
+
|
21
|
+
define_function :sh, description: I18n.t('oxaiworkers.tool.eval.sh.description') do
|
22
|
+
property :input, type: 'string', description: I18n.t('oxaiworkers.tool.eval.sh.input'), required: true
|
23
|
+
end
|
17
24
|
end
|
18
25
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
26
|
+
def ruby(input:)
|
27
|
+
puts "Executing ruby: \"#{input}\"".colorize(:red)
|
28
|
+
eval(input)
|
29
|
+
end
|
23
30
|
|
24
31
|
def sh(input:)
|
25
32
|
OxAiWorkers.logger.info("Executing sh: \"#{input}\"", for: self.class)
|
@@ -11,30 +11,35 @@ module OxAiWorkers
|
|
11
11
|
# file_system = OxAiWorkers::Tool::FileSystem.new
|
12
12
|
#
|
13
13
|
class FileSystem
|
14
|
-
|
14
|
+
include OxAiWorkers::ToolDefinition
|
15
15
|
include OxAiWorkers::DependencyHelper
|
16
|
+
include OxAiWorkers::LoadI18n
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
property :directory_path, type: 'string',
|
20
|
-
description: I18n.t('oxaiworkers.tool.file_system.list_directory.directory_path'),
|
21
|
-
required: true
|
22
|
-
end
|
18
|
+
def initialize(only: nil)
|
19
|
+
depends_on 'ptools'
|
23
20
|
|
24
|
-
|
25
|
-
property :file_path, type: 'string', description: I18n.t('oxaiworkers.tool.file_system.read_file.file_path'),
|
26
|
-
required: true
|
27
|
-
end
|
21
|
+
store_locale
|
28
22
|
|
29
|
-
|
30
|
-
property :file_path, type: 'string',
|
31
|
-
description: I18n.t('oxaiworkers.tool.file_system.write_to_file.file_path'), required: true
|
32
|
-
property :content, type: 'string', description: I18n.t('oxaiworkers.tool.file_system.write_to_file.content'),
|
33
|
-
required: true
|
34
|
-
end
|
23
|
+
init_white_list_with only
|
35
24
|
|
36
|
-
|
37
|
-
|
25
|
+
define_function :list_directory,
|
26
|
+
description: I18n.t('oxaiworkers.tool.file_system.list_directory.description') do
|
27
|
+
property :directory_path, type: 'string',
|
28
|
+
description: I18n.t('oxaiworkers.tool.file_system.list_directory.directory_path'),
|
29
|
+
required: true
|
30
|
+
end
|
31
|
+
|
32
|
+
define_function :read_file, description: I18n.t('oxaiworkers.tool.file_system.read_file.description') do
|
33
|
+
property :file_path, type: 'string', description: I18n.t('oxaiworkers.tool.file_system.read_file.file_path'),
|
34
|
+
required: true
|
35
|
+
end
|
36
|
+
|
37
|
+
define_function :write_to_file, description: I18n.t('oxaiworkers.tool.file_system.write_to_file.description') do
|
38
|
+
property :file_path, type: 'string',
|
39
|
+
description: I18n.t('oxaiworkers.tool.file_system.write_to_file.file_path'), required: true
|
40
|
+
property :content, type: 'string', description: I18n.t('oxaiworkers.tool.file_system.write_to_file.content'),
|
41
|
+
required: true
|
42
|
+
end
|
38
43
|
end
|
39
44
|
|
40
45
|
def list_directory(directory_path:)
|
@@ -42,31 +47,31 @@ module OxAiWorkers
|
|
42
47
|
list = Dir.entries(directory_path)
|
43
48
|
list.delete_if { |f| f.start_with?('.') }
|
44
49
|
if list.present?
|
45
|
-
"Contents of directory \"#{directory_path}\":\n #{list.join("\n")}"
|
50
|
+
with_locale { "Contents of directory \"#{directory_path}\":\n #{list.join("\n")}" }
|
46
51
|
else
|
47
|
-
"Directory is empty: #{directory_path}"
|
52
|
+
with_locale { "Directory is empty: #{directory_path}" }
|
48
53
|
end
|
49
54
|
rescue Errno::ENOENT
|
50
|
-
"No such directory: #{directory_path}"
|
55
|
+
with_locale { "No such directory: #{directory_path}" }
|
51
56
|
end
|
52
57
|
|
53
58
|
def read_file(file_path:)
|
54
59
|
OxAiWorkers.logger.info("Reading file: #{file_path}", for: self.class)
|
55
60
|
if File.binary?(file_path)
|
56
|
-
"File is binary: #{file_path}"
|
61
|
+
with_locale { "File is binary: #{file_path}" }
|
57
62
|
else
|
58
63
|
File.read(file_path).to_s
|
59
64
|
end
|
60
65
|
rescue Errno::ENOENT
|
61
|
-
"No such file: #{file_path}"
|
66
|
+
with_locale { "No such file: #{file_path}" }
|
62
67
|
end
|
63
68
|
|
64
69
|
def write_to_file(file_path:, content:)
|
65
70
|
OxAiWorkers.logger.info("Writing to file: #{file_path}", for: self.class)
|
66
71
|
File.write(file_path, content)
|
67
|
-
"Content was successfully written to the file: #{file_path}"
|
72
|
+
with_locale { "Content was successfully written to the file: #{file_path}" }
|
68
73
|
rescue Errno::EACCES
|
69
|
-
"Permission denied: #{file_path}"
|
74
|
+
with_locale { "Permission denied: #{file_path}" }
|
70
75
|
end
|
71
76
|
end
|
72
77
|
end
|
@@ -37,13 +37,21 @@ require "json"
|
|
37
37
|
# end
|
38
38
|
#
|
39
39
|
module OxAiWorkers::ToolDefinition
|
40
|
+
attr_accessor :white_list
|
41
|
+
|
42
|
+
def init_white_list_with only
|
43
|
+
@white_list = only.is_a?(Array) ? only : [only]
|
44
|
+
end
|
45
|
+
|
40
46
|
# Defines a function for the tool
|
41
47
|
#
|
42
48
|
# @param method_name [Symbol] Name of the method to define
|
43
49
|
# @param description [String] Description of the function
|
44
50
|
# @yield Block that defines the parameters for the function
|
45
51
|
def define_function(method_name, description:, &)
|
46
|
-
|
52
|
+
if @white_list.nil? || @white_list == method_name || @white_list.include?(method_name)
|
53
|
+
function_schemas.add_function(method_name:, description:, &)
|
54
|
+
end
|
47
55
|
end
|
48
56
|
|
49
57
|
# Returns the FunctionSchemas instance for this tool
|
@@ -57,12 +65,16 @@ module OxAiWorkers::ToolDefinition
|
|
57
65
|
#
|
58
66
|
# @return [String] The snake_case version of the class name
|
59
67
|
def tool_name
|
60
|
-
@tool_name ||= name
|
68
|
+
@tool_name ||= (self.respond_to?(:name) ? name : self.class.name)
|
61
69
|
.gsub("::", "_")
|
62
70
|
.gsub(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_")
|
63
71
|
.downcase
|
64
72
|
end
|
65
73
|
|
74
|
+
def full_function_name(fun)
|
75
|
+
function_schemas.function_name(fun)
|
76
|
+
end
|
77
|
+
|
66
78
|
# Manages schemas for functions
|
67
79
|
class FunctionSchemas
|
68
80
|
def initialize(tool_name)
|
@@ -70,6 +82,10 @@ module OxAiWorkers::ToolDefinition
|
|
70
82
|
@tool_name = tool_name
|
71
83
|
end
|
72
84
|
|
85
|
+
def function_name method_name
|
86
|
+
"#{@tool_name}__#{method_name}"
|
87
|
+
end
|
88
|
+
|
73
89
|
# Adds a function to the schemas
|
74
90
|
#
|
75
91
|
# @param method_name [Symbol] Name of the method to add
|
@@ -77,7 +93,7 @@ module OxAiWorkers::ToolDefinition
|
|
77
93
|
# @yield Block that defines the parameters for the function
|
78
94
|
# @raise [ArgumentError] If a block is defined and no parameters are specified for the function
|
79
95
|
def add_function(method_name:, description:, &)
|
80
|
-
name =
|
96
|
+
name = function_name(method_name)
|
81
97
|
|
82
98
|
if block_given?
|
83
99
|
parameters = ParameterBuilder.new(parent_type: "object").build(&)
|
@@ -96,15 +112,23 @@ module OxAiWorkers::ToolDefinition
|
|
96
112
|
# Converts schemas to OpenAI-compatible format
|
97
113
|
#
|
98
114
|
# @return [String] JSON string of schemas in OpenAI format
|
99
|
-
def to_openai_format
|
100
|
-
|
115
|
+
def to_openai_format(only: nil)
|
116
|
+
valid_schemas(only: only).values
|
117
|
+
end
|
118
|
+
|
119
|
+
def valid_schemas(only: nil)
|
120
|
+
if only.nil?
|
121
|
+
@schemas
|
122
|
+
else
|
123
|
+
@schemas.select { |name, schema| only.include?(name) }
|
124
|
+
end
|
101
125
|
end
|
102
126
|
|
103
127
|
# Converts schemas to Anthropic-compatible format
|
104
128
|
#
|
105
129
|
# @return [String] JSON string of schemas in Anthropic format
|
106
|
-
def to_anthropic_format
|
107
|
-
|
130
|
+
def to_anthropic_format(only: nil)
|
131
|
+
valid_schemas(only: only).values.map do |schema|
|
108
132
|
schema[:function].transform_keys("parameters" => "input_schema")
|
109
133
|
end
|
110
134
|
end
|
@@ -112,8 +136,8 @@ module OxAiWorkers::ToolDefinition
|
|
112
136
|
# Converts schemas to Google Gemini-compatible format
|
113
137
|
#
|
114
138
|
# @return [String] JSON string of schemas in Google Gemini format
|
115
|
-
def to_google_gemini_format
|
116
|
-
|
139
|
+
def to_google_gemini_format(only: nil)
|
140
|
+
valid_schemas(only: only).values.map { |schema| schema[:function] }
|
117
141
|
end
|
118
142
|
end
|
119
143
|
|
data/lib/oxaiworkers/version.rb
CHANGED
data/locales/en.iterator.yml
CHANGED
@@ -10,7 +10,7 @@ en:
|
|
10
10
|
action_request:
|
11
11
|
description: A function for interactive interaction with the user. Allows you to ask a clarifying question, request actions, or complete the current step. The function waits for the user's response and returns it.
|
12
12
|
action: Text of the request or action
|
13
|
-
|
13
|
+
summarize:
|
14
14
|
description: The function saves key facts, nuances, and actions from previous messages, including the provided response. After calling this function, all previous messages will be deleted. Use it only after all intermediate steps are completed and when the exact content of previous messages is no longer relevant.
|
15
15
|
text: Enumeration of important facts and nuances
|
16
16
|
result: Messages deleted
|
@@ -18,5 +18,5 @@ en:
|
|
18
18
|
- Step 1. Develop your own solution to the problem, taking initiative and making assumptions.
|
19
19
|
- Step 2. Enclose all your developments from the previous step in the ox_ai_workers_iterator__inner_monologue function.
|
20
20
|
- Step 3. Call the necessary functions one after another until the desired result is achieved.
|
21
|
-
- Step 4. When all intermediate steps are completed and the exact content of previous messages is no longer relevant, use the
|
21
|
+
- Step 4. When all intermediate steps are completed and the exact content of previous messages is no longer relevant, use the ox_ai_workers_iterator__summarize function.
|
22
22
|
- Step 5. When the solution is ready, notify about it and wait for the user's response.
|
data/locales/ru.iterator.yml
CHANGED
@@ -10,7 +10,7 @@ ru:
|
|
10
10
|
action_request:
|
11
11
|
description: Функция для интерактивного взаимодействия с пользователем. Позволяет задать уточняющий вопрос, запросить выполнение действий или завершить текущий шаг. Функция ожидает ответ пользователя и возвращает его.
|
12
12
|
action: Текст запроса или действия
|
13
|
-
|
13
|
+
summarize:
|
14
14
|
description: Функция сохраняет ключевые факты, нюансы и действия из предыдущих сообщений, включая предоставленный ответ. После вызова этой функции все предыдущие сообщения будут удалены. Используйте её только после завершения всех промежуточных шагов и когда точное содержание предыдущих сообщений больше не имеет значения.
|
15
15
|
text: Перечисление важных фактов и нюансов
|
16
16
|
result: Сообщения удалены
|
@@ -18,5 +18,5 @@ ru:
|
|
18
18
|
- Шаг 1. Разработай собственное решение проблемы, проявляя инициативу и делая предположения.
|
19
19
|
- Шаг 2. Заключи все свои наработки из предыдущего шага в функцию ox_ai_workers_iterator__inner_monologue.
|
20
20
|
- Шаг 3. Вызывай необходимые функции друг за другом, пока желаемый результат не будет достигнут.
|
21
|
-
- Шаг 4. Когда все промежуточные шаги завершены и точное содержание предыдущих сообщений больше не имеет значения, используй функцию
|
21
|
+
- Шаг 4. Когда все промежуточные шаги завершены и точное содержание предыдущих сообщений больше не имеет значения, используй функцию ox_ai_workers_iterator__summarize.
|
22
22
|
- Шаг 5. Когда решение готово, сообщи об этом и ожидай ответ пользователя.
|
data/template/my_assistant.rb
CHANGED
@@ -10,20 +10,26 @@ class MyAssistant
|
|
10
10
|
# Optional instructions
|
11
11
|
# steps = []
|
12
12
|
# steps << 'Step 1. Develop your own solution to the problem, taking initiative and making assumptions.'
|
13
|
-
# steps <<
|
13
|
+
# steps << "Step 2. Enclose all your developments from the previous step in the #{OxAiWorkers::Iterator.full_function_name(:inner_monologue)} function."
|
14
14
|
# steps << 'Step 3. Call the necessary functions one after another until the desired result is achieved.'
|
15
|
-
# steps <<
|
15
|
+
# steps << "Step 4. When all intermediate steps are completed and the exact content of previous messages is no longer relevant, use the #{OxAiWorkers::Iterator.full_function_name(:summarize)} function."
|
16
16
|
# steps << "Step 5. When the solution is ready, notify about it and wait for the user's response."
|
17
17
|
|
18
|
+
# To retain the locale if you have assistants in different languages in your project.
|
19
|
+
# store_locale # Optional
|
20
|
+
|
18
21
|
@iterator = OxAiWorkers::Iterator.new(
|
19
22
|
worker: init_worker(delayed: delayed, model: model),
|
20
23
|
role: 'You are a software agent inside my computer',
|
21
24
|
tools: [MyTool.new],
|
25
|
+
# locale: @locale || I18n.locale,
|
22
26
|
# steps: steps,
|
27
|
+
# def_except: [:summarize], # It's except steps with that functions
|
28
|
+
# def_only: [:inner_monologue, :outer_voice], # Use it only with your steps
|
23
29
|
on_inner_monologue: ->(text:) { puts "monologue: #{text}".colorize(:yellow) },
|
24
30
|
on_outer_voice: ->(text:) { puts "voice: #{text}".colorize(:green) },
|
25
31
|
on_action_request: ->(text:) { puts "action: #{text}".colorize(:red) },
|
26
|
-
|
32
|
+
on_summarize: ->(text:) { puts "summary: #{text}".colorize(:blue) }
|
27
33
|
)
|
28
34
|
end
|
29
35
|
end
|
data/template/tools/my_tool.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class MyTool
|
4
|
-
extend OxAiWorkers::ToolDefinition
|
5
|
-
include OxAiWorkers::DependencyHelper
|
4
|
+
extend OxAiWorkers::ToolDefinition # (!) extend
|
6
5
|
|
7
6
|
define_function :sh, description: 'Execute a sh command and get the result (stdout + stderr)' do
|
8
7
|
property :input, type: 'string', description: 'Source command', required: true
|
9
8
|
end
|
10
9
|
|
10
|
+
# Alternative implementation if you need to filter functions during initialization.
|
11
|
+
# include OxAiWorkers::ToolDefinition # (!) include
|
12
|
+
# include OxAiWorkers::LoadI18n # to support multiple languages
|
13
|
+
#
|
14
|
+
# def initialize(only: nil)
|
15
|
+
# store_locale # To retain the locale if you have MyTool in different languages.
|
16
|
+
# init_white_list_with only
|
17
|
+
# define_function :sh, description: I18n.t('Execute a sh command and get the result (stdout + stderr)') do
|
18
|
+
# property :input, type: 'string', description: I18n.t('Source command'), required: true
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
|
11
22
|
def sh(input:)
|
12
23
|
OxAiWorkers.logger.info("Executing sh: \"#{input}\"", for: self.class)
|
13
24
|
stdout_and_stderr_s, = Open3.capture2e(input)
|
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.5.
|
4
|
+
version: 0.5.5
|
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-
|
11
|
+
date: 2024-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -108,17 +108,17 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '1'
|
111
|
-
description: |
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
description: |2
|
112
|
+
OxAiWorkers (ox-ai-workers) is a Ruby gem that provides a powerful and flexible state machine with
|
113
|
+
integration of generative intelligence using the ruby-openai gem. This gem allows you to create state
|
114
|
+
machines for solving complex tasks, enhancing the final result by leveraging internal logic (state machine)
|
115
|
+
and external tools (OpenAI generative intelligence).
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
117
|
+
Features:
|
118
|
+
- State Machine: Easily create and manage state machines to model various states and transitions in your application.
|
119
|
+
- OpenAI Integration: Utilize the capabilities of generative intelligence to make decisions and perform tasks, improving the quality and accuracy of results.
|
120
|
+
- Flexibility and Extensibility: Customize the behavior of the state machine and OpenAI integration according to your needs.
|
121
|
+
- Ease of Use: Intuitive syntax and documentation make it easy to get started with the gem.
|
122
122
|
email:
|
123
123
|
- smolev@me.com
|
124
124
|
executables:
|