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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34c154dd79d968e939b91f26e410e0f7a4e51c5099347b3577dc05708ab8b341
4
- data.tar.gz: 294f14527bb5ef82e885ee5c93e6577d93c1820f844baad1139fbd385cb895c2
3
+ metadata.gz: ca54c1ff9c1f4360ddfe986c4b5bc6fb6b7c0cde0484d1b2f88bfe4d8c5707b7
4
+ data.tar.gz: 2c54e55fedd99af36f7a510ab923c54581faecfc8b36caa4d228f14349e293a8
5
5
  SHA512:
6
- metadata.gz: 1f2c528093acd8ef4a6214ef65898807f72172f10e211096cbd490589d04e55220ff679c57cc04dd677ab04a211445fe4eb8cfefbc0beb1b453fcb7282dfabd0
7
- data.tar.gz: 14f317606790d54f1c735f437145da15243a86af05c28bfcaf63464929c841df1b4de02cc6326b94571ddb0334a3bd13298ffe78078e39e1c24e7314d386fea7
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
- [![Gem Version](https://badge.fury.io/rb/ox-ai-workers.svg)](https://rubygems.org/gems/ox-ai-workers)
2
-
3
1
  # OxAiWorkers (ox-ai-workers)
4
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/ox-ai-workers.svg)](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 = "your 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
- on_pack_history: ->(text:) { puts "summary: #{text}".colorize(:blue) }
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 << 'Step 2. Enclose all your developments from the previous step in the ox_ai_workers_iterator__inner_monologue function.'
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 << '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__pack_history function.'
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
- on_pack_history: ->(text:) { puts "summary: #{text}".colorize(:blue) }
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. 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).
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
@@ -17,4 +17,8 @@ end
17
17
 
18
18
  @assistant = OxAiWorkers::Assistant::Sysop.new
19
19
 
20
+ # Anything you want
21
+
22
+ puts 'Type and Run: @assistant.task = "show files in current directory"'
23
+
20
24
  IRB.start(__FILE__)
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
- on_pack_history: ->(text:) { puts "summary: #{text}".colorize(:blue) }
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
- on_pack_history: ->(text:) { puts "summary: #{text}".colorize(:blue) }
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
 
@@ -3,6 +3,8 @@
3
3
  module OxAiWorkers
4
4
  module Assistant
5
5
  module ModuleBase
6
+ include OxAiWorkers::LoadI18n
7
+
6
8
  attr_accessor :iterator
7
9
 
8
10
  def task=(task)
@@ -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
- on_pack_history: ->(text:) { puts "summary: #{text}".colorize(:blue) }
18
+ on_summarize: ->(text:) { puts "summary: #{text}".colorize(:blue) }
17
19
  )
18
20
  end
19
21
  end
@@ -2,40 +2,52 @@
2
2
 
3
3
  module OxAiWorkers
4
4
  class Iterator < OxAiWorkers::StateTools
5
- extend OxAiWorkers::ToolDefinition
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
- define_function :inner_monologue, description: I18n.t('oxaiworkers.iterator.inner_monologue.description') do
10
- property :speach, type: 'string', description: I18n.t('oxaiworkers.iterator.inner_monologue.speach'),
11
- required: true
12
- end
7
+ include OxAiWorkers::ToolDefinition
8
+ include OxAiWorkers::LoadI18n
13
9
 
14
- define_function :outer_voice, description: I18n.t('oxaiworkers.iterator.outer_voice.description') do
15
- property :text, type: 'string', description: I18n.t('oxaiworkers.iterator.outer_voice.text'), required: true
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
- define_function :action_request, description: I18n.t('oxaiworkers.iterator.action_request.description') do
19
- property :action, type: 'string', description: I18n.t('oxaiworkers.iterator.action_request.action'),
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
- define_function :summarize, description: I18n.t('oxaiworkers.iterator.pack_history.description') do
24
- property :text, type: 'string', description: I18n.t('oxaiworkers.iterator.pack_history.text'), required: true
25
- end
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 = [self] + tools
41
+ @tools = tools
31
42
  @role = role
32
43
  @context = []
33
- @monologue = steps || I18n.t('oxaiworkers.iterator.monologue')
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
- @on_pack_history = on_pack_history
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
- @queue << { role: :assistant, content: I18n.t('oxaiworkers.iterator.pack_history.result') }
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
- # complete! if can_complete?
84
- @on_pack_history&.call(text: text)
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: @monologue.join("\n"))
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 = @tools.map { |tool| tool.class.function_schemas.to_openai_format }.flatten if @tools.present?
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
- t.class.tool_name == external_call[:class] && t.respond_to?(external_call[:name])
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: :user, content: out.to_s } if out.present?
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
- extend OxAiWorkers::ToolDefinition
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
- extend OxAiWorkers::ToolDefinition
8
+ include OxAiWorkers::ToolDefinition
9
9
  include OxAiWorkers::DependencyHelper
10
+ include OxAiWorkers::LoadI18n
10
11
 
11
- # define_function :ruby, description: I18n.t('oxaiworkers.tool.eval.ruby.description') do
12
- # property :input, type: 'string', description: I18n.t('oxaiworkers.tool.eval.ruby.input'), required: true
13
- # end
12
+ def initialize(only: nil)
13
+ store_locale
14
14
 
15
- define_function :sh, description: I18n.t('oxaiworkers.tool.eval.sh.description') do
16
- property :input, type: 'string', description: I18n.t('oxaiworkers.tool.eval.sh.input'), required: true
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
- # def ruby(input:)
20
- # puts "Executing ruby: \"#{input}\"".colorize(:red)
21
- # eval(input)
22
- # end
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
- extend OxAiWorkers::ToolDefinition
14
+ include OxAiWorkers::ToolDefinition
15
15
  include OxAiWorkers::DependencyHelper
16
+ include OxAiWorkers::LoadI18n
16
17
 
17
- define_function :list_directory,
18
- description: I18n.t('oxaiworkers.tool.file_system.list_directory.description') do
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
- define_function :read_file, description: I18n.t('oxaiworkers.tool.file_system.read_file.description') do
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
- define_function :write_to_file, description: I18n.t('oxaiworkers.tool.file_system.write_to_file.description') do
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
- def initialize
37
- depends_on 'ptools'
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
- function_schemas.add_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
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 = "#{@tool_name}__#{method_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
- @schemas.values#.map { |schema| schema[:function] }
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
- @schemas.values.map do |schema|
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
- @schemas.values.map { |schema| schema[:function] }
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OxAiWorkers
4
- VERSION = '0.5.3.1'
4
+ VERSION = '0.5.5'
5
5
  end
@@ -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
- pack_history:
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 ox_ai_workers_iterator__pack_history function.
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.
@@ -10,7 +10,7 @@ ru:
10
10
  action_request:
11
11
  description: Функция для интерактивного взаимодействия с пользователем. Позволяет задать уточняющий вопрос, запросить выполнение действий или завершить текущий шаг. Функция ожидает ответ пользователя и возвращает его.
12
12
  action: Текст запроса или действия
13
- pack_history:
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. Когда все промежуточные шаги завершены и точное содержание предыдущих сообщений больше не имеет значения, используй функцию ox_ai_workers_iterator__pack_history.
21
+ - Шаг 4. Когда все промежуточные шаги завершены и точное содержание предыдущих сообщений больше не имеет значения, используй функцию ox_ai_workers_iterator__summarize.
22
22
  - Шаг 5. Когда решение готово, сообщи об этом и ожидай ответ пользователя.
@@ -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 << 'Step 2. Enclose all your developments from the previous step in the ox_ai_workers_iterator__inner_monologue function.'
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 << '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__pack_history function.'
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
- on_pack_history: ->(text:) { puts "summary: #{text}".colorize(:blue) }
32
+ on_summarize: ->(text:) { puts "summary: #{text}".colorize(:blue) }
27
33
  )
28
34
  end
29
35
  end
@@ -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.3.1
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-07-31 00:00:00.000000000 Z
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
- 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).
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
- 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.
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: