ox-ai-workers 0.2.4 → 0.3.0

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: 68e329b2bdf72c3b0d75acc50542f1af17bfde774f5e1ad26b4d28ae8b2eb3d4
4
- data.tar.gz: 7e233262b802841e7a3c6241864376d5d25668391a9b482f441b47617e556703
3
+ metadata.gz: 2e3f7b60a9ac9e53b673390eb9a81a1607fc47890325e80dc659ce2050756f73
4
+ data.tar.gz: 818318dd86e9bb1b2e0b39492196cf1b1bc3e86095b94ab2bca7546cca8a8998
5
5
  SHA512:
6
- metadata.gz: ed6cb48bbf7ed1069fc29a4e3ca8e288f8c4e7af4fd08cd4ef4cf1a599de3720edb220afe52cb56db3b01f694cff197924b7d66908a7513979a64d818672b7c0
7
- data.tar.gz: bceb85175f975cd69100186012a4429f37e78e01f91962921979d7c27bf4e09dd0150dc8fa83d20711a62073ffb6ad58bf99bac756fdb6cf8c34de785bdf2c37
6
+ metadata.gz: ceeb35b06b858251cb880e4041e355df5fae35ad444658d2285e3f281870884e7773d2249fc88545b5db28604a14139f2baf74fc0d2c4480d6308c05e24a49b4
7
+ data.tar.gz: 17ffe3062ce9108fc83cad218aa461b9a68bca0b7e97c77aa3ed4e2f4dec388bf25a7bf06ec217f0e1dd19fcdab77921b7109ff0103ec22696536c1a26ae003b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2024-07-30
4
+
5
+ - on_inner_monologue: ->(text:) { puts Rainbow("monologue: #{text}").yellow }
6
+ - on_outer_voice: ->(text:) { puts Rainbow("voice: #{text}").green }
7
+ - on_action_request: ->(text:) { puts Rainbow("action: #{text}").red }
8
+ - on_pack_history: ->(text:) { puts Rainbow("summary: #{text}").blue }
9
+
10
+ ## [0.2.5] - 2024-07-30
11
+
12
+ - Improved start template
13
+
3
14
  ## [0.2.4] - 2024-07-30
4
15
 
5
16
  - Complete template for initialization: `oxaiworkers init full`
data/README.md CHANGED
@@ -101,10 +101,18 @@ assistant.addResponse("blah-blah-blah")
101
101
  Or you can create a lower-level iterator for more control:
102
102
 
103
103
  ```ruby
104
+ my_worker = OxAiWorkers::Request.new
105
+ my_tool = OxAiWorkers::Tool::Eval.new
106
+
104
107
  iterator = OxAiWorkers::Iterator.new(
105
- worker: OxAiWorkers::Request.new,
106
- tools: [OxAiWorkers::Tool::Eval.new],
107
- role: "You are a software agent inside my computer" )
108
+ worker: my_worker,
109
+ tools: [my_tool],
110
+ role: "You are a software agent inside my computer",
111
+ on_inner_monologue: ->(text:) { puts Rainbow("monologue: #{text}").yellow },
112
+ on_outer_voice: ->(text:) { puts Rainbow("voice: #{text}").green },
113
+ on_action_request: ->(text:) { puts Rainbow("action: #{text}").red },
114
+ on_pack_history: ->(text:) { puts Rainbow("summary: #{text}").blue }
115
+ )
108
116
 
109
117
  iterator.addTask("Show files in current directory.")
110
118
  # ...
@@ -131,15 +139,17 @@ As a worker, you can use different classes depending on your needs:
131
139
  oxaiworkers init
132
140
  ```
133
141
 
134
- or
142
+ This will create a `.oxaiworkers-local` directory with the necessary initial source code.
143
+
144
+ Additionally, you can initialize a more comprehensive example using the command:
135
145
 
136
146
  ```sh
137
147
  oxaiworkers init full
138
148
  ```
139
149
 
140
- This will create a `.oxaiworkers-local` directory with the necessary initial settings.
150
+ After this, in the `my_assistant.rb` file, you can find an example of an assistant that uses a tool from the `tools/my_tool.rb` file. In the `start` file, you will find the algorithm for applying this assistant.
141
151
 
142
- 3. Modify the settings as needed and run:
152
+ 3. Modify the code as needed and run:
143
153
 
144
154
  ```sh
145
155
  .oxaiworkers-local/start
@@ -9,7 +9,11 @@ module OxAiWorkers
9
9
  @iterator = OxAiWorkers::Iterator.new(
10
10
  worker: initWorker(delayed: delayed, model: model),
11
11
  role: I18n.t('oxaiworkers.assistant.sysop.role'),
12
- tools: [OxAiWorkers::Tool::Eval.new]
12
+ tools: [OxAiWorkers::Tool::Eval.new],
13
+ on_inner_monologue: ->(text:) { puts Rainbow("monologue: #{text}").yellow },
14
+ on_outer_voice: ->(text:) { puts Rainbow("voice: #{text}").green },
15
+ on_action_request: ->(text:) { puts Rainbow("action: #{text}").red },
16
+ on_pack_history: ->(text:) { puts Rainbow("summary: #{text}").blue }
13
17
  )
14
18
  end
15
19
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Ruby
2
4
  module OxAiWorkers
3
5
  VERSION = ::OxAiWorkers::VERSION
@@ -1,107 +1,109 @@
1
- class OxAiWorkers::DelayedRequest < OxAiWorkers::StateBatch
2
- def initialize(batch_id: nil, model: nil, max_tokens: nil, temperature: nil)
3
- initializeRequests(model: model, max_tokens: max_tokens, temperature: temperature)
4
- @custom_id = nil if batch_id.present?
5
- @batch_id = batch_id
6
- @file_id = nil
7
- super()
8
- end
9
-
10
- def postBatch
11
- response = @client.batches.create(
12
- parameters: {
13
- input_file_id: @file_id,
14
- endpoint: "/v1/chat/completions",
15
- completion_window: "24h"
16
- }
17
- )
18
- @batch_id = response["id"]
19
- end
1
+ # frozen_string_literal: true
20
2
 
21
- def cancelBatch
22
- not_found_is_ok{ @client.batches.cancel(id: @batch_id) }
23
- end
3
+ module OxAiWorkers
4
+ class DelayedRequest < OxAiWorkers::StateBatch
5
+ def initialize(batch_id: nil, model: nil, max_tokens: nil, temperature: nil)
6
+ initializeRequests(model: model, max_tokens: max_tokens, temperature: temperature)
7
+ @custom_id = nil if batch_id.present?
8
+ @batch_id = batch_id
9
+ @file_id = nil
10
+ super()
11
+ end
24
12
 
25
- def cleanStorage
26
- if @batch_id.present?
27
- batch = @client.batches.retrieve(id: @batch_id)
28
- if !batch["output_file_id"].nil?
29
- not_found_is_ok{ @client.files.delete(id: batch["output_file_id"]) }
30
- elsif !batch["error_file_id"].nil?
31
- not_found_is_ok{ @client.files.delete(id: batch["error_file_id"]) }
32
- end
33
- not_found_is_ok{ @client.files.delete(id: batch["input_file_id"]) }
34
- not_found_is_ok{ @client.files.delete(id: @file_id) } if @file_id.present? && @file_id != batch["input_file_id"]
35
- elsif @file_id.present?
36
- not_found_is_ok{ @client.files.delete(id: @file_id) }
13
+ def postBatch
14
+ response = @client.batches.create(
15
+ parameters: {
16
+ input_file_id: @file_id,
17
+ endpoint: '/v1/chat/completions',
18
+ completion_window: '24h'
19
+ }
20
+ )
21
+ @batch_id = response['id']
37
22
  end
38
- end
39
23
 
40
- def finish
41
- @custom_id = SecureRandom.uuid
42
- end_batch! unless batch_idle?
43
- end
24
+ def cancelBatch
25
+ not_found_is_ok { @client.batches.cancel(id: @batch_id) }
26
+ end
44
27
 
45
- def uploadToStorage
46
- item = {
47
- "custom_id": @custom_id,
48
- "method": "POST",
49
- "url": "/v1/chat/completions",
50
- "body": params
51
- }
28
+ def cleanStorage
29
+ if @batch_id.present?
30
+ batch = @client.batches.retrieve(id: @batch_id)
31
+ if !batch['output_file_id'].nil?
32
+ not_found_is_ok { @client.files.delete(id: batch['output_file_id']) }
33
+ elsif !batch['error_file_id'].nil?
34
+ not_found_is_ok { @client.files.delete(id: batch['error_file_id']) }
35
+ end
36
+ not_found_is_ok { @client.files.delete(id: batch['input_file_id']) }
37
+ if @file_id.present? && @file_id != batch['input_file_id']
38
+ not_found_is_ok do
39
+ @client.files.delete(id: @file_id)
40
+ end
41
+ end
42
+ elsif @file_id.present?
43
+ not_found_is_ok { @client.files.delete(id: @file_id) }
44
+ end
45
+ end
52
46
 
53
- file = Tempfile.new(["batch_#{@custom_id}", ".jsonl"])
54
- file.write item.to_json
55
- file.close
56
- begin
57
- response = @client.files.upload(parameters: { file: file.path, purpose: "batch"} )
58
- @file_id = response["id"]
59
- process_batch!
60
- rescue OpenAI::Error => e
61
- puts e.inspect
62
- fail_batch!
47
+ def finish
48
+ @custom_id = SecureRandom.uuid
49
+ end_batch! unless batch_idle?
63
50
  end
64
- file.unlink
65
- end
66
51
 
67
- def request!
68
- prepare_batch! if @messages.any?
69
- end
52
+ def uploadToStorage
53
+ item = {
54
+ "custom_id": @custom_id,
55
+ "method": 'POST',
56
+ "url": '/v1/chat/completions',
57
+ "body": params
58
+ }
70
59
 
71
- def cancel
72
- cancel_batch!
73
- end
60
+ file = Tempfile.new(["batch_#{@custom_id}", '.jsonl'])
61
+ file.write item.to_json
62
+ file.close
63
+ begin
64
+ response = @client.files.upload(parameters: { file: file.path, purpose: 'batch' })
65
+ @file_id = response['id']
66
+ process_batch!
67
+ rescue OpenAI::Error => e
68
+ puts e.inspect
69
+ fail_batch!
70
+ end
71
+ file.unlink
72
+ end
74
73
 
75
- def completed?
76
- return false if @batch_id.nil?
77
- batch = @client.batches.retrieve(id: @batch_id)
78
- if batch["status"] == "failed"
79
- @errors = batch["errors"]["data"].map{|e| e["message"]}
80
- fail_batch!
81
- return true
82
- elsif batch["status"] != "completed"
83
- return false
74
+ def request!
75
+ prepare_batch! if @messages.any?
84
76
  end
85
77
 
86
- if !batch["output_file_id"].nil?
87
- output = @client.files.content(id: batch["output_file_id"])
88
- output.each do |line|
89
- @custom_id = line["custom_id"]
90
- # @result = line.dig("response", "body", "choices", 0, "message", "content")
91
- parseChoices(line.dig("response", "body"))
92
- complete_batch!
93
- end
94
- elsif !batch["error_file_id"].nil?
95
- @errors = @client.files.content(id: batch["error_file_id"])
96
- fail_batch!
97
- end
98
- return true
78
+ def cancel
79
+ cancel_batch!
80
+ end
81
+
82
+ def completed?
83
+ return false if @batch_id.nil?
84
+
85
+ batch = @client.batches.retrieve(id: @batch_id)
86
+ if batch['status'] == 'failed'
87
+ @errors = batch['errors']['data'].map { |e| e['message'] }
88
+ fail_batch!
89
+ return true
90
+ elsif batch['status'] != 'completed'
91
+ return false
92
+ end
93
+
94
+ if !batch['output_file_id'].nil?
95
+ output = @client.files.content(id: batch['output_file_id'])
96
+ output.each do |line|
97
+ @custom_id = line['custom_id']
98
+ # @result = line.dig("response", "body", "choices", 0, "message", "content")
99
+ parseChoices(line.dig('response', 'body'))
100
+ complete_batch!
101
+ end
102
+ elsif !batch['error_file_id'].nil?
103
+ @errors = @client.files.content(id: batch['error_file_id'])
104
+ fail_batch!
105
+ end
106
+ true
107
+ end
99
108
  end
100
109
  end
101
-
102
- # r = OxAiWorkers::DelayedRequest.new
103
- # r.append(role: "user", content: "сколько будет 2+2?")
104
- # r.request!
105
- # r.completed?
106
- # r.result
107
- # r.finish
@@ -1,187 +1,182 @@
1
- class OxAiWorkers::Iterator < OxAiWorkers::StateTools
2
- extend OxAiWorkers::ToolDefinition
3
- attr_accessor :worker, :role, :messages, :context, :result, :tools, :queue, :monologue, :tasks, :milestones
1
+ # frozen_string_literal: true
4
2
 
5
- define_function :innerMonologue, description: I18n.t("oxaiworkers.iterator.inner_monologue.description") do
6
- property :speach, type: "string", description: I18n.t("oxaiworkers.iterator.inner_monologue.speach"), required: true
7
- end
3
+ module OxAiWorkers
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
8
8
 
9
- define_function :outerVoice, description: I18n.t("oxaiworkers.iterator.outer_voice.description") do
10
- property :text, type: "string", description: I18n.t("oxaiworkers.iterator.outer_voice.text"), required: true
11
- end
9
+ define_function :innerMonologue, 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
12
13
 
13
- define_function :actionRequest, description: I18n.t("oxaiworkers.iterator.action_request.description") do
14
- property :action, type: "string", description: I18n.t("oxaiworkers.iterator.action_request.action"), required: true
15
- end
14
+ define_function :outerVoice, 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
16
17
 
17
- define_function :packHistory, description: I18n.t("oxaiworkers.iterator.pack_history.description") do
18
- property :text, type: "string", description: I18n.t("oxaiworkers.iterator.pack_history.text"), required: true
19
- end
18
+ define_function :actionRequest, 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
20
22
 
21
- def initialize(worker:, role: nil, tools: [])
22
- puts "call: #{__method__}"
23
- @worker = worker
24
- @tools = [self] + tools
25
- @role = role
26
- @context = nil
27
- @monologue = I18n.t("oxaiworkers.iterator.monologue")
28
- cleanup()
23
+ define_function :packHistory, 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
29
26
 
30
- super()
31
- end
27
+ def initialize(worker:, role: nil, tools: [], on_inner_monologue: nil, on_outer_voice: nil, on_action_request: nil,
28
+ on_pack_history: nil)
29
+ puts "call: #{__method__}"
30
+ @worker = worker
31
+ @tools = [self] + tools
32
+ @role = role
33
+ @context = nil
34
+ @monologue = I18n.t('oxaiworkers.iterator.monologue')
32
35
 
33
- def cleanup
34
- @result = nil
35
- @queue = []
36
- @tasks = []
37
- @milestones = []
38
- @messages = []
39
- completeIteration()
40
- end
36
+ @on_inner_monologue = on_inner_monologue
37
+ @on_outer_voice = on_outer_voice
38
+ @on_action_request = on_action_request
39
+ @on_pack_history = on_pack_history
41
40
 
42
- def innerMonologue speach:
43
- puts Rainbow("monologue: #{speach}").yellow
44
- @queue.pop
45
- @queue << {role: :system, content: "#{__method__}: #{speach}"}
46
- return nil
47
- end
41
+ cleanup
48
42
 
49
- def outerVoice text:
50
- puts Rainbow("voice: #{text}").green
51
- @queue.pop
52
- @queue << {role: :system, content: "#{__method__}: #{text}"}
53
- return nil
54
- end
43
+ super()
44
+ end
55
45
 
56
- def actionRequest action:
57
- puts Rainbow("action: #{action}").red
58
- @result = action
59
- @queue.pop
60
- @messages << {role: :system, content: "#{__method__}: #{action}"}
61
- complete! if can_complete?
62
- return nil
63
- end
46
+ def cleanup
47
+ @result = nil
48
+ @queue = []
49
+ @tasks = []
50
+ @milestones = []
51
+ @messages = []
52
+ completeIteration
53
+ end
64
54
 
65
- def packHistory text:
66
- puts Rainbow("summarize: #{text}").blue
67
- @milestones << "#{__method__}: #{text}"
68
- @messages = []
69
- @worker.finish()
70
- rebuildWorker()
71
- complete! if can_complete?
72
- return nil
73
- end
55
+ def innerMonologue(speach:)
56
+ @queue.pop
57
+ @queue << { role: :system, content: "#{__method__}: #{speach}" }
58
+ @on_inner_monologue&.call(text: speach)
59
+ nil
60
+ end
74
61
 
75
- def init
76
- puts "call: #{__method__} state: #{state_name}"
77
- rebuildWorker()
78
- request!
79
- end
62
+ def outerVoice(text:)
63
+ @queue.pop
64
+ @queue << { role: :system, content: "#{__method__}: #{text}" }
65
+ @on_outer_voice&.call(text: text)
66
+ nil
67
+ end
80
68
 
81
- def rebuildWorker
82
- @worker.messages = []
83
- @worker.append(role: :system, content: @role) if !@role.nil? && @role.present?
84
- @worker.append(role: :system, content: @monologue.join("\n"))
85
- @worker.append(messages: @context) if !@context.nil? and @context.any?
86
- @tasks.each { |task| @worker.append(role: :user, content: task) }
87
- @milestones.each { |milestone| @worker.append(role: :system, content: milestone) }
88
- @worker.append(messages: @messages)
89
- @worker.tools = @tools.map { |tool| tool.class.function_schemas.to_openai_format }.flatten if @tools.any?
90
- end
69
+ def actionRequest(action:)
70
+ @result = action
71
+ @queue.pop
72
+ @messages << { role: :system, content: "#{__method__}: #{action}" }
73
+ complete! if can_complete?
74
+ @on_action_request&.call(text: action)
75
+ nil
76
+ end
91
77
 
92
- def nextIteration
93
- puts "call: #{__method__} state: #{state_name}"
94
- @worker.append(messages: @queue)
95
- @messages += @queue
96
- @queue = []
97
- request!
98
- end
78
+ def packHistory(text:)
79
+ @milestones << "#{__method__}: #{text}"
80
+ @messages = []
81
+ @worker.finish
82
+ rebuildWorker
83
+ complete! if can_complete?
84
+ @on_pack_history&.call(text: text)
85
+ nil
86
+ end
99
87
 
100
- def externalRequest
101
- puts "call: #{__method__} state: #{state_name}"
102
- @worker.request!()
103
- ticker()
104
- end
88
+ def init
89
+ puts "call: #{__method__} state: #{state_name}"
90
+ rebuildWorker
91
+ request!
92
+ end
105
93
 
106
- def ticker
107
- puts "call: #{__method__} state: #{state_name}"
108
- while !@worker.completed? do
109
- sleep(60)
94
+ def rebuildWorker
95
+ @worker.messages = []
96
+ @worker.append(role: :system, content: @role) if !@role.nil? && @role.present?
97
+ @worker.append(role: :system, content: @monologue.join("\n"))
98
+ @worker.append(messages: @context) if !@context.nil? and @context.any?
99
+ @tasks.each { |task| @worker.append(role: :user, content: task) }
100
+ @milestones.each { |milestone| @worker.append(role: :system, content: milestone) }
101
+ @worker.append(messages: @messages)
102
+ @worker.tools = @tools.map { |tool| tool.class.function_schemas.to_openai_format }.flatten if @tools.any?
103
+ end
104
+
105
+ def nextIteration
106
+ puts "call: #{__method__} state: #{state_name}"
107
+ @worker.append(messages: @queue)
108
+ @messages += @queue
109
+ @queue = []
110
+ request!
111
+ end
112
+
113
+ def externalRequest
114
+ puts "call: #{__method__} state: #{state_name}"
115
+ @worker.request!
116
+ ticker
117
+ end
118
+
119
+ def ticker
120
+ puts "call: #{__method__} state: #{state_name}"
121
+ sleep(60) until @worker.completed?
122
+ analyze!
110
123
  end
111
- analyze!
112
- end
113
124
 
114
- def processResult(transition)
115
- puts "call: #{__method__} state: #{state_name}"
116
- @result = @worker.result || @worker.errors
117
- if @worker.tool_calls.present?
118
- @queue << {role: :system, content: @worker.tool_calls_raw.to_s}
119
- @worker.tool_calls.each do |external_call|
120
- tool = @tools.select{|t| t.class.tool_name == external_call[:class] && t.respond_to?(external_call[:name]) }.first
121
- unless tool.nil?
122
- out = tool.send(external_call[:name], **external_call[:args])
123
- @queue << {role: :system, content: out.to_s} if out.present?
125
+ def processResult(_transition)
126
+ puts "call: #{__method__} state: #{state_name}"
127
+ @result = @worker.result || @worker.errors
128
+ if @worker.tool_calls.present?
129
+ @queue << { role: :system, content: @worker.tool_calls_raw.to_s }
130
+ @worker.tool_calls.each do |external_call|
131
+ tool = @tools.select do |t|
132
+ t.class.tool_name == external_call[:class] && t.respond_to?(external_call[:name])
133
+ end.first
134
+ unless tool.nil?
135
+ out = tool.send(external_call[:name], **external_call[:args])
136
+ @queue << { role: :system, content: out.to_s } if out.present?
137
+ end
124
138
  end
125
- end
126
- @worker.finish()
127
- iterate! if can_iterate?
128
-
129
- # tool = @tools.select{|t| t.class.tool_name == @worker.external_call[:class] && t.respond_to?(@worker.external_call[:name]) }.first
130
- # out = tool.send(@worker.external_call[:name], **@worker.external_call[:args])
131
- # if can_iterate?
132
- # @queue << {role: :system, content: out.to_s} if out.present?
133
- # iterate!
134
- # end
135
- elsif @worker.result.present?
139
+ @worker.finish
140
+ iterate! if can_iterate?
141
+
142
+ # tool = @tools.select{|t| t.class.tool_name == @worker.external_call[:class] && t.respond_to?(@worker.external_call[:name]) }.first
143
+ # out = tool.send(@worker.external_call[:name], **@worker.external_call[:args])
144
+ # if can_iterate?
145
+ # @queue << {role: :system, content: out.to_s} if out.present?
146
+ # iterate!
147
+ # end
148
+ elsif @worker.result.present?
136
149
  actionRequest action: @worker.result
150
+ end
137
151
  end
138
- end
139
152
 
140
- def completeIteration
141
- @queue = []
142
- @worker.finish()
143
- end
144
-
145
- def addTask task, auto_execute: true
146
- @tasks << task
147
- @messages << {role: :user, content: task}
148
- execute() if auto_execute
149
- end
153
+ def completeIteration
154
+ @queue = []
155
+ @worker.finish
156
+ end
150
157
 
151
- def appendContext text, role: :system
152
- @context << {role: role, content: text}
153
- end
158
+ def addTask(task, auto_execute: true)
159
+ @tasks << task
160
+ @messages << { role: :user, content: task }
161
+ execute if auto_execute
162
+ end
154
163
 
155
- def execute
156
- puts "call: #{__method__} state: #{state_name}"
157
- prepare! if valid?
158
- end
164
+ def appendContext(text, role: :system)
165
+ @context << { role: role, content: text }
166
+ end
159
167
 
160
- def cancel
161
- puts "call: #{__method__} state: #{state_name}"
162
- @worker.cancel if @worker.respond_to?(:cancel)
163
- end
168
+ def execute
169
+ puts "call: #{__method__} state: #{state_name}"
170
+ prepare! if valid?
171
+ end
164
172
 
165
- def valid?
166
- @messages.any?
167
- end
173
+ def cancel
174
+ puts "call: #{__method__} state: #{state_name}"
175
+ @worker.cancel if @worker.respond_to?(:cancel)
176
+ end
168
177
 
178
+ def valid?
179
+ @messages.any?
180
+ end
181
+ end
169
182
  end
170
-
171
- # r = OxAiWorkers::Iterator.new(worker: OxAiWorkers::Request.new)
172
- # r.addTask("сколько будет 2+2?")
173
- # r.execute
174
- # r.result
175
-
176
-
177
- # @worker.append(role: "user", content: "сколько будет 2+2?")
178
- # @worker.request!
179
- # @worker.completed?
180
- # @worker.result
181
- # @worker.finish
182
- #
183
- # r = OxAiWorkers::Iterator.new(worker: OxAiWorkers::Request.new)
184
- # r.role = "ты программный агент внутри моего компьютера"
185
- # r.tools = [OxAiWorkers::Tool::Eval.new]
186
- # r.addTask("покажи мне файлы на диске, используй код на ruby")
187
- # r.execute
@@ -1,82 +1,82 @@
1
- class OxAiWorkers::ModuleRequest
2
- attr_accessor :result, :client, :messages, :model, :max_tokens, :custom_id, :temperature, :tools, :errors
3
- attr_accessor :tool_calls_raw, :tool_calls
1
+ # frozen_string_literal: true
4
2
 
5
- def initializeRequests(model: nil, max_tokens: nil, temperature: nil)
6
- puts "call: ModuleRequest::#{__method__}"
7
- @max_tokens = max_tokens || OxAiWorkers.configuration.max_tokens
8
- @custom_id = SecureRandom.uuid
9
- @model = model || OxAiWorkers.configuration.model
10
- @temperature = temperature || OxAiWorkers.configuration.temperature
11
- @client = nil
12
-
13
- OxAiWorkers.configuration.access_token ||= ENV["OPENAI"]
14
- if OxAiWorkers.configuration.access_token.nil?
15
- error_text = "OpenAi access token missing!"
16
- raise OxAiWorkers::ConfigurationError, error_text
17
- end
3
+ module OxAiWorkers
4
+ class ModuleRequest
5
+ attr_accessor :result, :client, :messages, :model, :max_tokens, :custom_id, :temperature, :tools, :errors,
6
+ :tool_calls_raw, :tool_calls
18
7
 
19
- cleanup()
20
- end
8
+ def initializeRequests(model: nil, max_tokens: nil, temperature: nil)
9
+ puts "call: ModuleRequest::#{__method__}"
10
+ @max_tokens = max_tokens || OxAiWorkers.configuration.max_tokens
11
+ @custom_id = SecureRandom.uuid
12
+ @model = model || OxAiWorkers.configuration.model
13
+ @temperature = temperature || OxAiWorkers.configuration.temperature
14
+ @client = nil
21
15
 
22
- def cleanup
23
- puts "call: ModuleRequest::#{__method__}"
24
- @client ||= OpenAI::Client.new(
25
- access_token: OxAiWorkers.configuration.access_token,
26
- 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.
27
- )
28
- @result = nil
29
- @errors = nil
30
- @messages = []
31
- @tool_calls = nil
32
- @tool_calls_raw = nil
33
- end
16
+ OxAiWorkers.configuration.access_token ||= ENV['OPENAI']
17
+ if OxAiWorkers.configuration.access_token.nil?
18
+ error_text = 'OpenAi access token missing!'
19
+ raise OxAiWorkers::ConfigurationError, error_text
20
+ end
34
21
 
35
- def append role: nil, content: nil, messages: nil
36
- @messages << {role: role, content: content} if role.present? and content.present?
37
- @messages += messages if messages.present?
38
- end
22
+ cleanup
23
+ end
24
+
25
+ def cleanup
26
+ puts "call: ModuleRequest::#{__method__}"
27
+ @client ||= OpenAI::Client.new(
28
+ access_token: OxAiWorkers.configuration.access_token,
29
+ 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.
30
+ )
31
+ @result = nil
32
+ @errors = nil
33
+ @messages = []
34
+ @tool_calls = nil
35
+ @tool_calls_raw = nil
36
+ end
39
37
 
40
- def params
41
- parameters = {
38
+ def append(role: nil, content: nil, messages: nil)
39
+ @messages << { role: role, content: content } if role.present? and content.present?
40
+ @messages += messages if messages.present?
41
+ end
42
+
43
+ def params
44
+ parameters = {
42
45
  model: @model,
43
46
  messages: @messages,
44
47
  temperature: @temperature,
45
48
  max_tokens: @max_tokens
46
49
  }
47
- if @tools.present?
48
- parameters[:tools] = @tools
49
- parameters[:tool_choice] = "required"
50
+ if @tools.present?
51
+ parameters[:tools] = @tools
52
+ parameters[:tool_choice] = 'required'
53
+ end
54
+ parameters
50
55
  end
51
- parameters
52
- end
53
56
 
54
- def not_found_is_ok &block
55
- begin
57
+ def not_found_is_ok
56
58
  yield
57
59
  rescue Faraday::ResourceNotFound => e
58
60
  nil
59
61
  end
60
- end
61
62
 
62
- def parseChoices(response)
63
- # puts response.inspect
64
- @tool_calls = []
65
- @result = response.dig("choices", 0, "message", "content")
66
- @tool_calls_raw = response.dig("choices", 0, "message", "tool_calls")
63
+ def parseChoices(response)
64
+ # puts response.inspect
65
+ @tool_calls = []
66
+ @result = response.dig('choices', 0, 'message', 'content')
67
+ @tool_calls_raw = response.dig('choices', 0, 'message', 'tool_calls')
67
68
 
68
- @tool_calls_raw.each do |tool|
69
- function = tool["function"]
70
- args = JSON.parse(YAML.load(function["arguments"]).to_json, { symbolize_names: true } )
71
- @tool_calls << {
72
- class: function["name"].split("__").first,
73
- name: function["name"].split("__").last,
74
- args: args
75
- }
76
- end
77
-
78
- @tool_calls
69
+ @tool_calls_raw.each do |tool|
70
+ function = tool['function']
71
+ args = JSON.parse(YAML.load(function['arguments']).to_json, { symbolize_names: true })
72
+ @tool_calls << {
73
+ class: function['name'].split('__').first,
74
+ name: function['name'].split('__').last,
75
+ args: args
76
+ }
77
+ end
79
78
 
80
- # Assistant.send(function_name, **args)
79
+ @tool_calls
80
+ end
81
81
  end
82
- end
82
+ end
@@ -1,29 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OxAiWorkers
2
- module PresentCompat
3
- def present?
4
- !self.nil? and !self.empty?
5
- end
6
- end
4
+ module PresentCompat
5
+ def present?
6
+ !nil? and !empty?
7
+ end
8
+ end
9
+
10
+ module CamelizeCompat
11
+ def camelize(first_letter = :upper)
12
+ string = dup
13
+ string = if first_letter == :upper
14
+ string.sub(/^[a-z\d]*/) { |match| match.capitalize }
15
+ else
16
+ string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
17
+ end
18
+ string.gsub(%r{(?:_|(/))([a-z\d]*)}) do
19
+ "#{::Regexp.last_match(1)}#{::Regexp.last_match(2).capitalize}"
20
+ end.gsub('/', '::')
21
+ end
7
22
 
8
- module CamelizeCompat
9
- def camelize(first_letter = :upper)
10
- string = self.dup
11
- if first_letter == :upper
12
- string = string.sub(/^[a-z\d]*/) { |match| match.capitalize }
13
- else
14
- string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
15
- end
16
- string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub("/", "::")
17
- end
18
-
19
- def underscore
20
- word = self.dup
21
- word.gsub!(/::/, '/')
22
- word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
23
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
24
- word.tr!("-", "_")
25
- word.downcase!
26
- word
27
- end
28
- end
29
- end
23
+ def underscore
24
+ word = dup
25
+ word.gsub!(/::/, '/')
26
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
27
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
28
+ word.tr!('-', '_')
29
+ word.downcase!
30
+ word
31
+ end
32
+ end
33
+ end
@@ -1,23 +1,25 @@
1
- class OxAiWorkers::Request < OxAiWorkers::ModuleRequest
2
- alias_method :initialize, :initializeRequests
1
+ # frozen_string_literal: true
3
2
 
4
- def finish
5
- @custom_id = SecureRandom.uuid
6
- cleanup()
7
- end
3
+ module OxAiWorkers
4
+ class Request < OxAiWorkers::ModuleRequest
5
+ alias initialize initializeRequests
6
+
7
+ def finish
8
+ @custom_id = SecureRandom.uuid
9
+ cleanup
10
+ end
8
11
 
9
- def request!
10
- begin
12
+ def request!
11
13
  response = @client.chat(parameters: params)
12
14
  parseChoices(response)
13
- #@result = response.dig("choices", 0, "message", "content")
14
- #puts response.inspect
15
+ # @result = response.dig("choices", 0, "message", "content")
16
+ # puts response.inspect
15
17
  rescue OpenAI::Error => e
16
18
  puts e.inspect
17
19
  end
18
- end
19
20
 
20
- def completed?
21
- @result.present? or @errors.present? or @tool_calls.present?
21
+ def completed?
22
+ @result.present? or @errors.present? or @tool_calls.present?
23
+ end
22
24
  end
23
25
  end
@@ -1,61 +1,65 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'state_machine/core'
2
4
 
3
- class OxAiWorkers::StateBatch < OxAiWorkers::ModuleRequest
4
- include OxAiWorkers::StateHelper
5
- extend StateMachine::MacroMethods
5
+ module OxAiWorkers
6
+ class StateBatch < OxAiWorkers::ModuleRequest
7
+ include OxAiWorkers::StateHelper
8
+ extend StateMachine::MacroMethods
6
9
 
7
- alias_method :state_initialize, :initialize
8
- attr_accessor :file_id, :batch_id
10
+ alias state_initialize initialize
11
+ attr_accessor :file_id, :batch_id
9
12
 
10
- state_machine :batch_state, initial: ->(t){t.batch_id.present? ? :requested : :idle}, namespace: :batch do
11
- before_transition from: any, do: :log_me
13
+ state_machine :batch_state, initial: ->(t) { t.batch_id.present? ? :requested : :idle }, namespace: :batch do
14
+ before_transition from: any, do: :log_me
12
15
 
13
- after_transition on: :end, do: :cleanup
14
- before_transition on: :process, do: :postBatch
15
- after_transition on: :cancel, do: [:cancelBatch, :complete_batch!]
16
- after_transition on: :complete, do: [:cleanStorage]
17
- after_transition on: :prepare, do: :uploadToStorage
16
+ after_transition on: :end, do: :cleanup
17
+ before_transition on: :process, do: :postBatch
18
+ after_transition on: :cancel, do: %i[cancelBatch complete_batch!]
19
+ after_transition on: :complete, do: [:cleanStorage]
20
+ after_transition on: :prepare, do: :uploadToStorage
18
21
 
19
- event :end do
20
- transition [:finished, :canceled] => :idle
21
- end
22
+ event :end do
23
+ transition %i[finished canceled] => :idle
24
+ end
22
25
 
23
- event :prepare do
24
- transition :idle => :prepared
25
- end
26
+ event :prepare do
27
+ transition idle: :prepared
28
+ end
26
29
 
27
- event :process do
28
- transition :prepared => :requested
29
- end
30
+ event :process do
31
+ transition prepared: :requested
32
+ end
30
33
 
31
- event :complete do
32
- transition [:requested, :failed] => :finished
33
- end
34
+ event :complete do
35
+ transition %i[requested failed] => :finished
36
+ end
34
37
 
35
- event :cancel do
36
- transition [:requested, :failed, :prepared] => :canceled
37
- end
38
+ event :cancel do
39
+ transition %i[requested failed prepared] => :canceled
40
+ end
38
41
 
39
- event :fail do
40
- transition [:requested, :prepared] => :failed
41
- end
42
+ event :fail do
43
+ transition %i[requested prepared] => :failed
44
+ end
42
45
 
43
- state :requested
44
- state :idle
45
- state :prepared
46
- state :canceled
47
- state :finished
48
- state :failed
49
- end
46
+ state :requested
47
+ state :idle
48
+ state :prepared
49
+ state :canceled
50
+ state :finished
51
+ state :failed
52
+ end
50
53
 
51
- def cleanup
52
- @file_id = nil
53
- @batch_id = nil
54
- super()
55
- end
54
+ def cleanup
55
+ @file_id = nil
56
+ @batch_id = nil
57
+ super()
58
+ end
56
59
 
57
- def initialize
58
- puts "call: StateBatch::#{__method__}"
59
- super()
60
+ def initialize
61
+ puts "call: StateBatch::#{__method__}"
62
+ super()
63
+ end
60
64
  end
61
- end
65
+ end
@@ -1,5 +1,9 @@
1
- module OxAiWorkers::StateHelper
2
- def log_me(transition)
3
- # puts "`#{transition.event}` was called to transition from :#{transition.from} to :#{transition.to}"
1
+ # frozen_string_literal: true
2
+
3
+ module OxAiWorkers
4
+ module StateHelper
5
+ def log_me(transition)
6
+ # puts "`#{transition.event}` was called to transition from :#{transition.from} to :#{transition.to}"
7
+ end
4
8
  end
5
- end
9
+ end
@@ -1,47 +1,50 @@
1
- require 'state_machine/core'
2
-
3
- class OxAiWorkers::StateTools
4
- include OxAiWorkers::StateHelper
5
- extend StateMachine::MacroMethods
6
-
7
- state_machine :state, initial: :idle do
8
- before_transition from: any, do: :log_me
9
-
10
- after_transition on: :iterate, do: :nextIteration
11
- after_transition on: :request, do: :externalRequest
12
- after_transition on: :prepare, do: :init
13
- after_transition on: :analyze, do: :processResult
14
- after_transition on: :complete, do: :completeIteration
15
-
16
- event :prepare do
17
- transition [:idle, :finished] => :prepared
18
- end
19
-
20
- event :request do
21
- transition :prepared => :requested
22
- end
23
-
24
- event :analyze do
25
- transition [:requested] => :analyzed
26
- end
27
-
28
- event :complete do
29
- transition [:analyzed] => :finished
30
- end
1
+ # frozen_string_literal: true
31
2
 
32
- event :iterate do
33
- transition :analyzed => :prepared
34
- end
3
+ require 'state_machine/core'
35
4
 
36
- event :end do
37
- transition :finished => :idle
5
+ module OxAiWorkers
6
+ class StateTools
7
+ include OxAiWorkers::StateHelper
8
+ extend StateMachine::MacroMethods
9
+
10
+ state_machine :state, initial: :idle do
11
+ before_transition from: any, do: :log_me
12
+
13
+ after_transition on: :iterate, do: :nextIteration
14
+ after_transition on: :request, do: :externalRequest
15
+ after_transition on: :prepare, do: :init
16
+ after_transition on: :analyze, do: :processResult
17
+ after_transition on: :complete, do: :completeIteration
18
+
19
+ event :prepare do
20
+ transition %i[idle finished] => :prepared
21
+ end
22
+
23
+ event :request do
24
+ transition prepared: :requested
25
+ end
26
+
27
+ event :analyze do
28
+ transition [:requested] => :analyzed
29
+ end
30
+
31
+ event :complete do
32
+ transition [:analyzed] => :finished
33
+ end
34
+
35
+ event :iterate do
36
+ transition analyzed: :prepared
37
+ end
38
+
39
+ event :end do
40
+ transition finished: :idle
41
+ end
42
+
43
+ state :idle
44
+ state :prepared
45
+ state :requested
46
+ state :analyzed
47
+ state :finished
38
48
  end
39
-
40
- state :idle
41
- state :prepared
42
- state :requested
43
- state :analyzed
44
- state :finished
45
49
  end
46
-
47
- end
50
+ end
@@ -2,28 +2,30 @@
2
2
 
3
3
  require 'open3'
4
4
 
5
- module OxAiWorkers::Tool
6
- class Eval
7
- extend OxAiWorkers::ToolDefinition
8
- include OxAiWorkers::DependencyHelper
5
+ module OxAiWorkers
6
+ module Tool
7
+ class Eval
8
+ extend OxAiWorkers::ToolDefinition
9
+ include OxAiWorkers::DependencyHelper
9
10
 
10
- define_function :ruby, description: I18n.t('oxaiworkers.tool.eval.ruby.description') do
11
- property :input, type: 'string', description: I18n.t('oxaiworkers.tool.eval.ruby.input'), required: true
12
- end
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
13
14
 
14
- define_function :sh, description: I18n.t('oxaiworkers.tool.eval.sh.description') do
15
- property :input, type: 'string', description: I18n.t('oxaiworkers.tool.eval.sh.input'), required: true
16
- end
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
17
+ end
17
18
 
18
- def ruby(input:)
19
- puts Rainbow("Executing ruby: \"#{input}\"").red
20
- eval(input)
21
- end
19
+ def ruby(input:)
20
+ puts Rainbow("Executing ruby: \"#{input}\"").red
21
+ eval(input)
22
+ end
22
23
 
23
- def sh(input:)
24
- puts Rainbow("Executing sh: \"#{input}\"").red
25
- stdout_and_stderr_s, = Open3.capture2e(input)
26
- stdout_and_stderr_s
24
+ def sh(input:)
25
+ puts Rainbow("Executing sh: \"#{input}\"").red
26
+ stdout_and_stderr_s, = Open3.capture2e(input)
27
+ stdout_and_stderr_s
28
+ end
27
29
  end
28
30
  end
29
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OxAiWorkers
4
- VERSION = '0.2.4'
4
+ VERSION = '0.3.0'
5
5
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'rainbow'
3
4
  require_relative 'tools/my_tool'
4
5
 
5
6
  class MyAssistant
@@ -8,8 +9,12 @@ class MyAssistant
8
9
  def initialize(delayed: false, model: nil)
9
10
  @iterator = OxAiWorkers::Iterator.new(
10
11
  worker: initWorker(delayed: delayed, model: model),
11
- role: 'You are a helpful assistant',
12
- tools: [MyTool.new]
12
+ role: 'You are a software agent inside my computer',
13
+ tools: [MyTool.new],
14
+ on_inner_monologue: ->(text:) { puts Rainbow("monologue: #{text}").yellow },
15
+ on_outer_voice: ->(text:) { puts Rainbow("voice: #{text}").green },
16
+ on_action_request: ->(text:) { puts Rainbow("action: #{text}").red },
17
+ on_pack_history: ->(text:) { puts Rainbow("summary: #{text}").blue }
13
18
  )
14
19
  end
15
20
  end
data/template/start CHANGED
@@ -5,18 +5,24 @@ require 'i18n'
5
5
  I18n.load_path += Dir["#{File.expand_path('.oxaiworkers-local/locales')}/*.yml"]
6
6
  I18n.default_locale = :en
7
7
 
8
- # Start your code here
8
+ # ### Start your code here
9
+
10
+ # Required external gems
9
11
  require 'ox-ai-workers'
10
12
  require 'irb'
13
+
14
+ # Required my libs
11
15
  require_relative 'my_assistant'
12
16
 
13
17
  puts "OxAiWorkers #{OxAiWorkers::VERSION}"
14
18
 
19
+ # Configure
15
20
  OxAiWorkers.configure do |config|
16
21
  config.access_token = ENV.fetch('OPENAI')
17
22
  config.model = 'gpt-4o-mini'
18
23
  end
19
24
 
25
+ # Main algorithm
20
26
  @assistant = MyAssistant.new
21
27
 
22
28
  IRB.start(__FILE__)
@@ -4,11 +4,13 @@ class MyTool
4
4
  extend OxAiWorkers::ToolDefinition
5
5
  include OxAiWorkers::DependencyHelper
6
6
 
7
- define_function :printHello, description: 'Print hello' do
8
- property :name, type: 'string', description: 'Your name', required: true
7
+ define_function :sh, description: 'Execute a sh command and get the result (stdout + stderr)' do
8
+ property :input, type: 'string', description: 'Source command', required: true
9
9
  end
10
10
 
11
- def printHello(name:)
12
- puts "Hello #{name}"
11
+ def sh(input:)
12
+ puts "Executing sh: \"#{input}\""
13
+ stdout_and_stderr_s, = Open3.capture2e(input)
14
+ stdout_and_stderr_s
13
15
  end
14
16
  end
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.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Smolev