ox-ai-workers 0.2.5 → 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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +12 -4
- data/lib/oxaiworkers/assistant/sysop.rb +5 -1
- data/lib/oxaiworkers/compatibility.rb +2 -0
- data/lib/oxaiworkers/delayed_request.rb +95 -93
- data/lib/oxaiworkers/iterator.rb +154 -159
- data/lib/oxaiworkers/module_request.rb +62 -62
- data/lib/oxaiworkers/present_compat.rb +31 -27
- data/lib/oxaiworkers/request.rb +15 -13
- data/lib/oxaiworkers/state_batch.rb +50 -46
- data/lib/oxaiworkers/state_helper.rb +8 -4
- data/lib/oxaiworkers/state_tools.rb +46 -43
- data/lib/oxaiworkers/tool/eval.rb +20 -18
- data/lib/oxaiworkers/version.rb +1 -1
- data/template/my_assistant.rb +6 -1
- data/template/start +7 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2e3f7b60a9ac9e53b673390eb9a81a1607fc47890325e80dc659ce2050756f73
|
4
|
+
data.tar.gz: 818318dd86e9bb1b2e0b39492196cf1b1bc3e86095b94ab2bca7546cca8a8998
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ceeb35b06b858251cb880e4041e355df5fae35ad444658d2285e3f281870884e7773d2249fc88545b5db28604a14139f2baf74fc0d2c4480d6308c05e24a49b4
|
7
|
+
data.tar.gz: 17ffe3062ce9108fc83cad218aa461b9a68bca0b7e97c77aa3ed4e2f4dec388bf25a7bf06ec217f0e1dd19fcdab77921b7109ff0103ec22696536c1a26ae003b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
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
|
+
|
3
10
|
## [0.2.5] - 2024-07-30
|
4
11
|
|
5
12
|
- Improved start template
|
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
|
-
|
106
|
-
|
107
|
-
|
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,7 +139,7 @@ As a worker, you can use different classes depending on your needs:
|
|
131
139
|
oxaiworkers init
|
132
140
|
```
|
133
141
|
|
134
|
-
This will create a `.oxaiworkers-local` directory with the necessary initial
|
142
|
+
This will create a `.oxaiworkers-local` directory with the necessary initial source code.
|
135
143
|
|
136
144
|
Additionally, you can initialize a more comprehensive example using the command:
|
137
145
|
|
@@ -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,107 +1,109 @@
|
|
1
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
24
|
+
def cancelBatch
|
25
|
+
not_found_is_ok { @client.batches.cancel(id: @batch_id) }
|
26
|
+
end
|
44
27
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
data/lib/oxaiworkers/iterator.rb
CHANGED
@@ -1,187 +1,182 @@
|
|
1
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
31
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
50
|
-
|
51
|
-
@queue.pop
|
52
|
-
@queue << {role: :system, content: "#{__method__}: #{text}"}
|
53
|
-
return nil
|
54
|
-
end
|
43
|
+
super()
|
44
|
+
end
|
55
45
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
46
|
+
def cleanup
|
47
|
+
@result = nil
|
48
|
+
@queue = []
|
49
|
+
@tasks = []
|
50
|
+
@milestones = []
|
51
|
+
@messages = []
|
52
|
+
completeIteration
|
53
|
+
end
|
64
54
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
88
|
+
def init
|
89
|
+
puts "call: #{__method__} state: #{state_name}"
|
90
|
+
rebuildWorker
|
91
|
+
request!
|
92
|
+
end
|
105
93
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
152
|
-
|
153
|
-
|
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
|
-
|
156
|
-
|
157
|
-
|
158
|
-
end
|
164
|
+
def appendContext(text, role: :system)
|
165
|
+
@context << { role: role, content: text }
|
166
|
+
end
|
159
167
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
168
|
+
def execute
|
169
|
+
puts "call: #{__method__} state: #{state_name}"
|
170
|
+
prepare! if valid?
|
171
|
+
end
|
164
172
|
|
165
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
20
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
41
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
data/lib/oxaiworkers/request.rb
CHANGED
@@ -1,23 +1,25 @@
|
|
1
|
-
|
2
|
-
alias_method :initialize, :initializeRequests
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
10
|
-
begin
|
12
|
+
def request!
|
11
13
|
response = @client.chat(parameters: params)
|
12
14
|
parseChoices(response)
|
13
|
-
|
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
|
-
|
21
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
5
|
+
module OxAiWorkers
|
6
|
+
class StateBatch < OxAiWorkers::ModuleRequest
|
7
|
+
include OxAiWorkers::StateHelper
|
8
|
+
extend StateMachine::MacroMethods
|
6
9
|
|
7
|
-
|
8
|
-
|
10
|
+
alias state_initialize initialize
|
11
|
+
attr_accessor :file_id, :batch_id
|
9
12
|
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
+
event :end do
|
23
|
+
transition %i[finished canceled] => :idle
|
24
|
+
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
event :prepare do
|
27
|
+
transition idle: :prepared
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
event :process do
|
31
|
+
transition prepared: :requested
|
32
|
+
end
|
30
33
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
event :complete do
|
35
|
+
transition %i[requested failed] => :finished
|
36
|
+
end
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
+
event :cancel do
|
39
|
+
transition %i[requested failed prepared] => :canceled
|
40
|
+
end
|
38
41
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
+
event :fail do
|
43
|
+
transition %i[requested prepared] => :failed
|
44
|
+
end
|
42
45
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
state :requested
|
47
|
+
state :idle
|
48
|
+
state :prepared
|
49
|
+
state :canceled
|
50
|
+
state :finished
|
51
|
+
state :failed
|
52
|
+
end
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
def cleanup
|
55
|
+
@file_id = nil
|
56
|
+
@batch_id = nil
|
57
|
+
super()
|
58
|
+
end
|
56
59
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
+
def initialize
|
61
|
+
puts "call: StateBatch::#{__method__}"
|
62
|
+
super()
|
63
|
+
end
|
60
64
|
end
|
61
|
-
end
|
65
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
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
|
-
|
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
|
-
|
33
|
-
transition :analyzed => :prepared
|
34
|
-
end
|
3
|
+
require 'state_machine/core'
|
35
4
|
|
36
|
-
|
37
|
-
|
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
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
module OxAiWorkers
|
6
|
+
module Tool
|
7
|
+
class Eval
|
8
|
+
extend OxAiWorkers::ToolDefinition
|
9
|
+
include OxAiWorkers::DependencyHelper
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def ruby(input:)
|
20
|
+
puts Rainbow("Executing ruby: \"#{input}\"").red
|
21
|
+
eval(input)
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
data/lib/oxaiworkers/version.rb
CHANGED
data/template/my_assistant.rb
CHANGED
@@ -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
|
@@ -9,7 +10,11 @@ class MyAssistant
|
|
9
10
|
@iterator = OxAiWorkers::Iterator.new(
|
10
11
|
worker: initWorker(delayed: delayed, model: model),
|
11
12
|
role: 'You are a software agent inside my computer',
|
12
|
-
tools: [MyTool.new]
|
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__)
|