ox-ai-workers 0.8.7 → 0.9.1

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.
@@ -2,23 +2,19 @@
2
2
 
3
3
  module OxAiWorkers
4
4
  class ModuleRequest
5
- attr_accessor :result, :client, :messages, :model, :max_tokens, :custom_id, :temperature, :tools, :errors,
6
- :tool_calls_raw, :tool_calls, :is_truncated, :finish_reason, :uri_base, :on_stream_proc
5
+ attr_accessor :result, :client, :messages, :model, :custom_id, :tools, :errors,
6
+ :tool_calls_raw, :tool_calls, :is_truncated, :finish_reason, :on_stream_proc
7
7
 
8
- def initialize_requests(model: nil, max_tokens: nil, temperature: nil, uri_base: nil, on_stream: nil)
9
- @max_tokens = max_tokens || OxAiWorkers.configuration.max_tokens
8
+ def initialize_requests(model:, on_stream: nil)
10
9
  @custom_id = SecureRandom.uuid
11
- @model = model || OxAiWorkers.configuration.model
12
- @temperature = temperature || OxAiWorkers.configuration.temperature
13
- @uri_base = uri_base
10
+ @model = model
14
11
  @client = nil
15
12
  @is_truncated = false
16
13
  @finish_reason = nil
17
14
  @on_stream_proc = on_stream
18
15
 
19
- OxAiWorkers.configuration.access_token ||= ENV['OPENAI']
20
- if OxAiWorkers.configuration.access_token.nil?
21
- error_text = 'OpenAi access token missing!'
16
+ if @model.api_key.nil?
17
+ error_text = "#{@model.model} access token missing!"
22
18
  raise OxAiWorkers::ConfigurationError, error_text
23
19
  end
24
20
 
@@ -27,8 +23,8 @@ module OxAiWorkers
27
23
 
28
24
  def cleanup
29
25
  @client ||= OpenAI::Client.new(
30
- access_token: OxAiWorkers.configuration.access_token,
31
- uri_base: @uri_base || OxAiWorkers.configuration.uri_base,
26
+ access_token: @model.api_key,
27
+ uri_base: @model.uri_base,
32
28
  log_errors: true # Highly recommended in development, so you can see what errors OpenAI is returning. Not recommended in production because it could leak private data to your logs.
33
29
  )
34
30
  @result = nil
@@ -47,10 +43,10 @@ module OxAiWorkers
47
43
 
48
44
  def params
49
45
  parameters = {
50
- model: @model,
46
+ model: @model.model,
51
47
  messages: @messages,
52
- temperature: @temperature,
53
- max_tokens: @max_tokens
48
+ temperature: @model.temperature,
49
+ max_tokens: @model.max_tokens
54
50
  }
55
51
  if @tools.present?
56
52
  parameters[:tools] = @tools
@@ -9,10 +9,13 @@ module OxAiWorkers
9
9
  include OxAiWorkers::DependencyHelper
10
10
  include OxAiWorkers::LoadI18n
11
11
 
12
- def initialize(only: nil)
12
+ attr_accessor :current_dir
13
+
14
+ def initialize(only: nil, current_dir: nil)
13
15
  store_locale
14
16
 
15
17
  init_white_list_with only
18
+ @current_dir = current_dir
16
19
 
17
20
  define_function :ruby, description: I18n.t('oxaiworkers.tool.eval.ruby.description') do
18
21
  property :input, type: 'string', description: I18n.t('oxaiworkers.tool.eval.ruby.input'), required: true
@@ -25,20 +28,28 @@ module OxAiWorkers
25
28
 
26
29
  def ruby(input:)
27
30
  OxAiWorkers.logger.info("Executing ruby: \"#{input}\"", for: self.class)
28
- eval(input)
31
+ execute_in_directory { eval(input) }
29
32
  end
30
33
 
31
34
  def sh(input:)
32
35
  OxAiWorkers.logger.info("Executing sh: \"#{input}\"", for: self.class)
33
- begin
36
+ execute_in_directory do
34
37
  stdout_and_stderr_s, status = Open3.capture2e(input)
35
- return stdout_and_stderr_s if stdout_and_stderr_s.present?
38
+ stdout_and_stderr_s.present? ? stdout_and_stderr_s : status.to_s
39
+ end
40
+ end
41
+
42
+ private
36
43
 
37
- status.to_s
38
- rescue StandardError => e
39
- OxAiWorkers.logger.debug(e.message, for: self.class)
40
- e.message
44
+ def execute_in_directory(&block)
45
+ if @current_dir.present?
46
+ Dir.chdir(@current_dir, &block)
47
+ else
48
+ yield
41
49
  end
50
+ rescue StandardError => e
51
+ OxAiWorkers.logger.debug(e.message, for: self.class)
52
+ e.message
42
53
  end
43
54
  end
44
55
  end
@@ -15,7 +15,9 @@ module OxAiWorkers
15
15
  include OxAiWorkers::DependencyHelper
16
16
  include OxAiWorkers::LoadI18n
17
17
 
18
- def initialize(only: nil)
18
+ attr_accessor :current_dir
19
+
20
+ def initialize(current_dir: nil, only: nil)
19
21
  depends_on 'ptools'
20
22
 
21
23
  store_locale
@@ -40,38 +42,51 @@ module OxAiWorkers
40
42
  property :content, type: 'string', description: I18n.t('oxaiworkers.tool.file_system.write_to_file.content'),
41
43
  required: true
42
44
  end
45
+
46
+ @current_dir = current_dir
43
47
  end
44
48
 
45
49
  def list_directory(directory_path:)
46
- OxAiWorkers.logger.info("Listing directory: #{directory_path}", for: self.class)
47
- list = Dir.entries(directory_path)
50
+ path = full_path(directory_path)
51
+ OxAiWorkers.logger.info("Listing directory: #{path}", for: self.class)
52
+ list = Dir.entries(path)
48
53
  list.delete_if { |f| f.start_with?('.') }
49
54
  if list.present?
50
- with_locale { "Contents of directory \"#{directory_path}\":\n #{list.join("\n")}" }
55
+ "Contents of directory \"#{path}\":\n #{list.join("\n")}"
51
56
  else
52
- with_locale { "Directory is empty: #{directory_path}" }
57
+ "Directory is empty: #{path}"
53
58
  end
54
59
  rescue Errno::ENOENT
55
- with_locale { "No such directory: #{directory_path}" }
60
+ "No such directory: #{path}"
56
61
  end
57
62
 
58
63
  def read_file(file_path:)
59
- OxAiWorkers.logger.info("Reading file: #{file_path}", for: self.class)
60
- if File.binary?(file_path)
61
- with_locale { "File is binary: #{file_path}" }
64
+ path = full_path(file_path)
65
+ OxAiWorkers.logger.info("Reading file: #{path}", for: self.class)
66
+ if File.binary?(path)
67
+ "File is binary: #{path}"
62
68
  else
63
- File.read(file_path).to_s
69
+ File.read(path).to_s
64
70
  end
65
71
  rescue Errno::ENOENT
66
- with_locale { "No such file: #{file_path}" }
72
+ "No such file: #{path}"
67
73
  end
68
74
 
69
75
  def write_to_file(file_path:, content:)
70
- OxAiWorkers.logger.info("Writing to file: #{file_path}", for: self.class)
71
- File.write(file_path, content)
72
- with_locale { "Content was successfully written to the file: #{file_path}" }
76
+ path = full_path(file_path)
77
+ OxAiWorkers.logger.info("Writing to file: #{path}", for: self.class)
78
+ # Ensure directory exists if using current_dir
79
+ FileUtils.mkdir_p(File.dirname(path)) if @current_dir.present? && !Dir.exist?(File.dirname(path))
80
+ File.write(path, content)
81
+ "Content was successfully written to the file: #{path}"
73
82
  rescue Errno::EACCES
74
- with_locale { "Permission denied: #{file_path}" }
83
+ "Permission denied: #{path}"
84
+ end
85
+
86
+ private
87
+
88
+ def full_path(path)
89
+ @current_dir.present? ? File.join(@current_dir, path) : path
75
90
  end
76
91
  end
77
92
  end
@@ -0,0 +1,106 @@
1
+ module OxAiWorkers
2
+ module Tool
3
+ class Pipeline
4
+ include OxAiWorkers::ToolDefinition
5
+ include OxAiWorkers::DependencyHelper
6
+ include OxAiWorkers::LoadI18n
7
+
8
+ attr_accessor :assistants, :messages
9
+
10
+ def initialize(only: nil, on_message: nil)
11
+ store_locale
12
+
13
+ init_white_list_with only
14
+
15
+ define_function :send_message, description: I18n.t('oxaiworkers.tool.pipeline.send_message.description') do
16
+ property :message, type: 'string', description: I18n.t('oxaiworkers.tool.pipeline.send_message.message'),
17
+ required: true
18
+ property :result, type: 'string', description: I18n.t('oxaiworkers.tool.pipeline.send_message.result'),
19
+ required: true
20
+ property :example, type: 'string', description: I18n.t('oxaiworkers.tool.pipeline.send_message.example')
21
+ property :to_id, type: 'string', description: I18n.t('oxaiworkers.tool.pipeline.send_message.to_id'),
22
+ required: true
23
+ end
24
+
25
+ @messages = []
26
+ @on_message = on_message
27
+ end
28
+
29
+ def send_message(message:, result:, example:, to_id:)
30
+ puts "send_message to #{to_id}: #{message}".colorize(:red)
31
+ puts " Result: #{result}"
32
+ puts " Example: #{example}"
33
+ context = context_for(to_id)
34
+ @assistants[to_id].replace_context(context)
35
+ @assistants[to_id].add_task message
36
+ @assistants[to_id].add_task "#{I18n.t('oxaiworkers.tool.pipeline.send_message.result')}: #{result}"
37
+ @assistants[to_id].add_task "#{I18n.t('oxaiworkers.tool.pipeline.send_message.example')}: #{example}"
38
+ @assistants[to_id].execute
39
+ nil
40
+ end
41
+
42
+ def add_assistant(assistant)
43
+ unless assistant.id.present? &&
44
+ assistant.title.present? &&
45
+ assistant.role.present? &&
46
+ assistant.description.present? &&
47
+ assistant.capabilities.present?
48
+ raise 'Assistant is not valid: id, title, role, description and capabilities are required'
49
+ end
50
+
51
+ assistant.iterator.on_inner_monologue = ->(text:) { recive_monologue(from_id: assistant.id, message: text) }
52
+ assistant.iterator.on_outer_voice = ->(text:) { recive_voice(from_id: assistant.id, message: text) }
53
+
54
+ @assistants ||= {}
55
+ @assistants[assistant.id] = assistant
56
+ end
57
+
58
+ def recive_monologue(from_id:, message:)
59
+ recive_message(id: from_id, type: 'monologue', message:, color: :yellow)
60
+ end
61
+
62
+ def recive_voice(from_id:, message:)
63
+ recive_message(id: from_id, type: 'voice', message:, color: :green)
64
+ end
65
+
66
+ def recive_message(id:, type:, message:, color: :grey)
67
+ puts "#{id} [#{type}]: #{message}".colorize(color)
68
+ @messages ||= []
69
+ m = { id:, type:, message: }
70
+ @messages << m
71
+ @on_message.call(text: format_message(m)) if !@on_message.nil? && @assistants.key?(id)
72
+ # @assistants.each_value do |assistant|
73
+ # assistant.iterator.add_queue format_message(m), role: :system if assistant.id != id
74
+ # end
75
+ end
76
+
77
+ def context_for(id)
78
+ array = @messages.select { |message| message[:id] == id || message[:type] == 'voice' }
79
+ array.map { |m| format_message(m) }.join("\n\n")
80
+ end
81
+
82
+ def context
83
+ "#{assistants_info}\n\n#{@messages.map { |m| format_message(m) }.join("\n\n")}"
84
+ end
85
+
86
+ def assistants_info
87
+ @assistants.values.map { |assistant| format_assistant(assistant) }.join("\n\n")
88
+ end
89
+
90
+ def format_message(message)
91
+ "**#{message[:id]}**\n: #{message[:message]}"
92
+ end
93
+
94
+ def format_assistant(assistant)
95
+ with_locale do
96
+ I18n.t('oxaiworkers.tool.pipeline.assistant_info',
97
+ id: assistant.id,
98
+ title: assistant.title,
99
+ role: assistant.role,
100
+ description: assistant.description,
101
+ capabilities: assistant.capabilities.join(', '))
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,76 @@
1
+ require 'fileutils'
2
+ require 'open-uri'
3
+
4
+ module OxAiWorkers
5
+ module Tool
6
+ class Pixels
7
+ include OxAiWorkers::ToolDefinition
8
+ include OxAiWorkers::DependencyHelper
9
+ include OxAiWorkers::LoadI18n
10
+
11
+ attr_accessor :worker, :url, :current_dir
12
+
13
+ def initialize(worker:, current_dir: nil, only: nil)
14
+ store_locale
15
+
16
+ init_white_list_with only
17
+
18
+ define_function :generate_image, description: I18n.t('oxaiworkers.tool.pixels.generate_image.description') do
19
+ property :prompt, type: 'string', description: I18n.t('oxaiworkers.tool.pixels.generate_image.prompt'),
20
+ required: true
21
+ property :size, type: 'string', description: I18n.t('oxaiworkers.tool.pixels.generate_image.size'),
22
+ enum: %w[1024x1792 1792x1024 1024x1024]
23
+ if current_dir.present?
24
+ property :file_name, type: 'string',
25
+ description: I18n.t('oxaiworkers.tool.pixels.generate_image.file_name'),
26
+ required: true
27
+ end
28
+ property :quality, type: 'string', description: I18n.t('oxaiworkers.tool.pixels.generate_image.quality'),
29
+ enum: %w[standard hd]
30
+ end
31
+
32
+ @worker = worker
33
+ @current_dir = current_dir
34
+ end
35
+
36
+ def generate_image(prompt:, file_name: nil, size: '1024x1792', quality: 'standard')
37
+ puts "generate_image: #{prompt}"
38
+
39
+ response = @worker.client.images.generate(
40
+ parameters: {
41
+ prompt:,
42
+ model: 'dall-e-3',
43
+ size:,
44
+ quality:
45
+ }
46
+ )
47
+
48
+ @url = response.dig('data', 0, 'url')
49
+ revised_prompt = response.dig('data', 0, 'revised_prompt')
50
+ if file_name.present?
51
+ path = save_generated_image(file_name:)
52
+ "url: #{@url}\nfile_name: #{path}\n\nrevised_prompt: #{revised_prompt}"
53
+ else
54
+ "url: #{@url}\n\nrevised_prompt: #{revised_prompt}"
55
+ end
56
+ end
57
+
58
+ def save_generated_image(file_name:)
59
+ unless @current_dir.present?
60
+ return 'Current directory not set for OxAiWorkers::Tool::Pixels. Please set current directory first.'
61
+ end
62
+
63
+ # Ensure current_dir exists
64
+ FileUtils.mkdir_p(@current_dir) unless Dir.exist?(@current_dir)
65
+
66
+ path = File.join(@current_dir, file_name)
67
+
68
+ File.open(path, 'wb') do |file|
69
+ file.write(URI.open(@url).read)
70
+ end
71
+
72
+ file_name
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OxAiWorkers
4
- VERSION = '0.8.7'
4
+ VERSION = '0.9.1'
5
5
  end
data/template/start CHANGED
@@ -18,18 +18,16 @@ puts "OxAiWorkers #{OxAiWorkers::VERSION}"
18
18
 
19
19
  # Configure
20
20
  OxAiWorkers.configure do |config|
21
- config.access_token = ENV.fetch('OPENAI')
22
- config.model = 'gpt-4o-mini'
23
- # config.auto_execute = false
21
+ config.access_token_openai = ENV.fetch('OPENAI')
24
22
  end
25
23
 
24
+ OxAiWorkers.default_model = OxAiWorkers::Models::OpenaiMini.new
25
+
26
26
  # OxAiWorkers.logger.level = :debug
27
27
 
28
28
  # Main algorithm
29
29
  @assistant = MyAssistant.new
30
30
  @assistant.task = '2 + 2 is ?'
31
-
32
- # Uncomment this if auto_execute is false
33
- # @assistant.execute
31
+ @assistant.execute
34
32
 
35
33
  IRB.start(__FILE__)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ox-ai-workers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.7
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Smolev
@@ -143,14 +143,22 @@ files:
143
143
  - lib/oxaiworkers/assistant/coder.rb
144
144
  - lib/oxaiworkers/assistant/localizer.rb
145
145
  - lib/oxaiworkers/assistant/module_base.rb
146
+ - lib/oxaiworkers/assistant/orchestrator.rb
147
+ - lib/oxaiworkers/assistant/painter.rb
146
148
  - lib/oxaiworkers/assistant/sysop.rb
147
149
  - lib/oxaiworkers/compatibility.rb
148
150
  - lib/oxaiworkers/contextual_logger.rb
149
151
  - lib/oxaiworkers/delayed_request.rb
150
152
  - lib/oxaiworkers/dependency_helper.rb
151
153
  - lib/oxaiworkers/engine.rb
154
+ - lib/oxaiworkers/image_iterator.rb
152
155
  - lib/oxaiworkers/iterator.rb
153
156
  - lib/oxaiworkers/load_i18n.rb
157
+ - lib/oxaiworkers/models/deepseek_max.rb
158
+ - lib/oxaiworkers/models/module_base.rb
159
+ - lib/oxaiworkers/models/openai_max.rb
160
+ - lib/oxaiworkers/models/openai_mini.rb
161
+ - lib/oxaiworkers/models/openai_nano.rb
154
162
  - lib/oxaiworkers/module_request.rb
155
163
  - lib/oxaiworkers/present_compat.rb
156
164
  - lib/oxaiworkers/request.rb
@@ -161,6 +169,8 @@ files:
161
169
  - lib/oxaiworkers/tool/database.rb
162
170
  - lib/oxaiworkers/tool/eval.rb
163
171
  - lib/oxaiworkers/tool/file_system.rb
172
+ - lib/oxaiworkers/tool/pipeline.rb
173
+ - lib/oxaiworkers/tool/pixels.rb
164
174
  - lib/oxaiworkers/tool_definition.rb
165
175
  - lib/oxaiworkers/version.rb
166
176
  - lib/ruby/ox-ai-workers.rb