nano-bots 0.1.1 → 1.0.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/.ruby-version +1 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +37 -22
- data/README.md +6 -6
- data/components/adapter.rb +16 -30
- data/components/embedding.rb +89 -0
- data/components/provider.rb +1 -1
- data/components/providers/openai/tools.rb +101 -0
- data/components/providers/openai.rb +103 -20
- data/components/storage.rb +1 -1
- data/components/stream.rb +6 -1
- data/controllers/cartridges.rb +1 -1
- data/controllers/instance.rb +4 -4
- data/controllers/interfaces/cli.rb +1 -1
- data/controllers/interfaces/tools.rb +104 -0
- data/controllers/session.rb +80 -26
- data/docker-compose.example.yml +1 -1
- data/logic/cartridge/adapters.rb +1 -1
- data/logic/cartridge/affixes.rb +1 -1
- data/logic/cartridge/default.rb +2 -2
- data/logic/cartridge/fetch.rb +24 -0
- data/logic/cartridge/interaction.rb +7 -7
- data/logic/cartridge/safety.rb +39 -0
- data/logic/cartridge/tools.rb +53 -0
- data/logic/providers/openai/tools.rb +58 -0
- data/logic/providers/openai.rb +57 -0
- data/nano-bots.gemspec +3 -2
- data/static/cartridges/baseline.yml +1 -1
- data/static/cartridges/default.yml +16 -0
- data/static/fennel/fennel.lua +1538 -1734
- data/static/gem.rb +1 -1
- metadata +36 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0fc904784fe79cd5bd873dc3861dd82412f037012ca125011ab795e5cb01e2b1
|
4
|
+
data.tar.gz: 76f64af2d36b5b2765d98f90281577a88e0fcb166143d859829e1b8607fe9e57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3889cdb807954fb4b09add6e53d9ed314457b1d4c0f7e6b1969a0d421f10062b69d76505aa67eba0ee0f5de91e3fd44c7e4934606537ff688897ca0d13007156
|
7
|
+
data.tar.gz: f280c854038822d48ede7bdd1076256d1f8d535c5cc336948921cd1843e34f9a5fb4d207035ac1196efb7e72bc8fecd3d7be57b66b98fc7da94f5b3f7536430e
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.1.4
|
data/Gemfile
CHANGED
@@ -5,7 +5,8 @@ source 'https://rubygems.org'
|
|
5
5
|
gemspec
|
6
6
|
|
7
7
|
group :test, :development do
|
8
|
+
gem 'pry-byebug', '~> 3.10', '>= 3.10.1'
|
8
9
|
gem 'rspec', '~> 3.12'
|
9
|
-
gem 'rubocop', '~> 1.
|
10
|
-
gem 'rubocop-rspec', '~> 2.
|
10
|
+
gem 'rubocop', '~> 1.57', '>= 1.57.2'
|
11
|
+
gem 'rubocop-rspec', '~> 2.25'
|
11
12
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nano-bots (0.
|
4
|
+
nano-bots (1.0.0)
|
5
5
|
babosa (~> 2.0)
|
6
|
+
concurrent-ruby (~> 1.2, >= 1.2.2)
|
6
7
|
dotenv (~> 2.8, >= 2.8.1)
|
7
|
-
faraday (~> 2.7, >= 2.7.
|
8
|
+
faraday (~> 2.7, >= 2.7.12)
|
8
9
|
pry (~> 0.14.2)
|
9
10
|
rainbow (~> 3.1, >= 3.1.1)
|
10
11
|
rbnacl (~> 7.1, >= 7.1.1)
|
11
|
-
ruby-openai (~>
|
12
|
+
ruby-openai (~> 6.3)
|
12
13
|
sweet-moon (~> 0.0.7)
|
13
14
|
|
14
15
|
GEM
|
@@ -16,30 +17,41 @@ GEM
|
|
16
17
|
specs:
|
17
18
|
ast (2.4.2)
|
18
19
|
babosa (2.0.0)
|
20
|
+
base64 (0.2.0)
|
21
|
+
byebug (11.1.3)
|
19
22
|
coderay (1.1.3)
|
23
|
+
concurrent-ruby (1.2.2)
|
20
24
|
diff-lcs (1.5.0)
|
21
25
|
dotenv (2.8.1)
|
22
|
-
|
26
|
+
event_stream_parser (1.0.0)
|
27
|
+
faraday (2.7.12)
|
28
|
+
base64
|
23
29
|
faraday-net_http (>= 2.0, < 3.1)
|
24
30
|
ruby2_keywords (>= 0.0.4)
|
25
31
|
faraday-multipart (1.0.4)
|
26
32
|
multipart-post (~> 2)
|
27
33
|
faraday-net_http (3.0.2)
|
28
|
-
ffi (1.
|
34
|
+
ffi (1.16.3)
|
29
35
|
json (2.6.3)
|
36
|
+
language_server-protocol (3.17.0.3)
|
30
37
|
method_source (1.0.0)
|
31
38
|
multipart-post (2.3.0)
|
32
39
|
parallel (1.23.0)
|
33
|
-
parser (3.2.2.
|
40
|
+
parser (3.2.2.4)
|
34
41
|
ast (~> 2.4.1)
|
42
|
+
racc
|
35
43
|
pry (0.14.2)
|
36
44
|
coderay (~> 1.1)
|
37
45
|
method_source (~> 1.0)
|
46
|
+
pry-byebug (3.10.1)
|
47
|
+
byebug (~> 11.0)
|
48
|
+
pry (>= 0.13, < 0.15)
|
49
|
+
racc (1.7.3)
|
38
50
|
rainbow (3.1.1)
|
39
51
|
rbnacl (7.1.1)
|
40
52
|
ffi
|
41
|
-
regexp_parser (2.8.
|
42
|
-
rexml (3.2.
|
53
|
+
regexp_parser (2.8.2)
|
54
|
+
rexml (3.2.6)
|
43
55
|
rspec (3.12.0)
|
44
56
|
rspec-core (~> 3.12.0)
|
45
57
|
rspec-expectations (~> 3.12.0)
|
@@ -49,47 +61,50 @@ GEM
|
|
49
61
|
rspec-expectations (3.12.3)
|
50
62
|
diff-lcs (>= 1.2.0, < 2.0)
|
51
63
|
rspec-support (~> 3.12.0)
|
52
|
-
rspec-mocks (3.12.
|
64
|
+
rspec-mocks (3.12.6)
|
53
65
|
diff-lcs (>= 1.2.0, < 2.0)
|
54
66
|
rspec-support (~> 3.12.0)
|
55
|
-
rspec-support (3.12.
|
56
|
-
rubocop (1.
|
67
|
+
rspec-support (3.12.1)
|
68
|
+
rubocop (1.57.2)
|
57
69
|
json (~> 2.3)
|
70
|
+
language_server-protocol (>= 3.17.0)
|
58
71
|
parallel (~> 1.10)
|
59
|
-
parser (>= 3.2.
|
72
|
+
parser (>= 3.2.2.4)
|
60
73
|
rainbow (>= 2.2.2, < 4.0)
|
61
74
|
regexp_parser (>= 1.8, < 3.0)
|
62
75
|
rexml (>= 3.2.5, < 4.0)
|
63
|
-
rubocop-ast (>= 1.28.
|
76
|
+
rubocop-ast (>= 1.28.1, < 2.0)
|
64
77
|
ruby-progressbar (~> 1.7)
|
65
78
|
unicode-display_width (>= 2.4.0, < 3.0)
|
66
|
-
rubocop-ast (1.
|
79
|
+
rubocop-ast (1.30.0)
|
67
80
|
parser (>= 3.2.1.0)
|
68
|
-
rubocop-capybara (2.
|
81
|
+
rubocop-capybara (2.19.0)
|
69
82
|
rubocop (~> 1.41)
|
70
|
-
rubocop-factory_bot (2.
|
71
|
-
rubocop (~> 1.33)
|
72
|
-
rubocop-rspec (2.22.0)
|
83
|
+
rubocop-factory_bot (2.24.0)
|
73
84
|
rubocop (~> 1.33)
|
85
|
+
rubocop-rspec (2.25.0)
|
86
|
+
rubocop (~> 1.40)
|
74
87
|
rubocop-capybara (~> 2.17)
|
75
88
|
rubocop-factory_bot (~> 2.22)
|
76
|
-
ruby-openai (
|
89
|
+
ruby-openai (6.3.0)
|
90
|
+
event_stream_parser (>= 0.3.0, < 2.0.0)
|
77
91
|
faraday (>= 1)
|
78
92
|
faraday-multipart (>= 1)
|
79
93
|
ruby-progressbar (1.13.0)
|
80
94
|
ruby2_keywords (0.0.5)
|
81
95
|
sweet-moon (0.0.7)
|
82
96
|
ffi (~> 1.15, >= 1.15.5)
|
83
|
-
unicode-display_width (2.
|
97
|
+
unicode-display_width (2.5.0)
|
84
98
|
|
85
99
|
PLATFORMS
|
86
100
|
x86_64-linux
|
87
101
|
|
88
102
|
DEPENDENCIES
|
89
103
|
nano-bots!
|
104
|
+
pry-byebug (~> 3.10, >= 3.10.1)
|
90
105
|
rspec (~> 3.12)
|
91
|
-
rubocop (~> 1.
|
92
|
-
rubocop-rspec (~> 2.
|
106
|
+
rubocop (~> 1.57, >= 1.57.2)
|
107
|
+
rubocop-rspec (~> 2.25)
|
93
108
|
|
94
109
|
BUNDLED WITH
|
95
110
|
2.4.13
|
data/README.md
CHANGED
@@ -28,13 +28,13 @@ https://user-images.githubusercontent.com/113217272/238141567-c58a240c-7b67-4b3b
|
|
28
28
|
For a system usage:
|
29
29
|
|
30
30
|
```sh
|
31
|
-
gem install nano-bots -v 0.
|
31
|
+
gem install nano-bots -v 1.0.0
|
32
32
|
```
|
33
33
|
|
34
34
|
To use it in a project, add it to your `Gemfile`:
|
35
35
|
|
36
36
|
```ruby
|
37
|
-
gem 'nano-bots', '~> 0.
|
37
|
+
gem 'nano-bots', '~> 1.0.0'
|
38
38
|
```
|
39
39
|
|
40
40
|
```sh
|
@@ -85,7 +85,7 @@ version: '3.7'
|
|
85
85
|
services:
|
86
86
|
nano-bots:
|
87
87
|
image: ruby:3.2.2-slim-bullseye
|
88
|
-
command: sh -c "gem install nano-bots -v 0.
|
88
|
+
command: sh -c "gem install nano-bots -v 1.0.0 && bash"
|
89
89
|
environment:
|
90
90
|
OPENAI_API_ADDRESS: https://api.openai.com
|
91
91
|
OPENAI_API_KEY: your-access-token
|
@@ -309,7 +309,7 @@ NanoBot.security.check
|
|
309
309
|
# => { encryption: true, password: true }
|
310
310
|
```
|
311
311
|
|
312
|
-
|
312
|
+
### End-user IDs
|
313
313
|
|
314
314
|
A common strategy for deploying Nano Bots to multiple users through APIs or automations is to assign a unique [end-user ID](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids) for each user. This can be useful if any of your users violate the provider's policy due to abusive behavior. By providing the end-user ID, you can unravel that even though the activity originated from your API Key, the actions taken were not your own.
|
315
315
|
|
@@ -362,7 +362,7 @@ Actually, to enhance privacy, neither your user nor your users' identifiers will
|
|
362
362
|
|
363
363
|
In this manner, you possess identifiers if required, however, their actual content can only be decrypted by you via your secure password (`NANO_BOTS_ENCRYPTION_PASSWORD`).
|
364
364
|
|
365
|
-
|
365
|
+
### Decrypting
|
366
366
|
|
367
367
|
To decrypt your encrypted data, once you have properly configured your password, you can simply run:
|
368
368
|
|
@@ -408,5 +408,5 @@ gem build nano-bots.gemspec
|
|
408
408
|
|
409
409
|
gem signin
|
410
410
|
|
411
|
-
gem push nano-bots-0.
|
411
|
+
gem push nano-bots-1.0.0.gem
|
412
412
|
```
|
data/components/adapter.rb
CHANGED
@@ -1,48 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative 'embedding'
|
4
|
+
require_relative '../logic/cartridge/safety'
|
4
5
|
|
5
6
|
module NanoBot
|
6
7
|
module Components
|
7
8
|
class Adapter
|
8
|
-
def self.apply(
|
9
|
+
def self.apply(params, cartridge)
|
9
10
|
content = params[:content]
|
10
11
|
|
11
|
-
if
|
12
|
-
|
13
|
-
|
12
|
+
raise StandardError, 'conflicting adapters' if %i[fennel lua clojure].count { |key| !params[key].nil? } > 1
|
13
|
+
|
14
|
+
call = {
|
15
|
+
parameters: %w[content], values: [content],
|
16
|
+
safety: { sandboxed: Logic::Cartridge::Safety.sandboxed?(cartridge) }
|
17
|
+
}
|
14
18
|
|
15
19
|
if params[:fennel]
|
16
|
-
|
20
|
+
call[:source] = params[:fennel]
|
21
|
+
content = Components::Embedding.fennel(**call)
|
22
|
+
elsif params[:clojure]
|
23
|
+
call[:source] = params[:clojure]
|
24
|
+
content = Components::Embedding.clojure(**call)
|
17
25
|
elsif params[:lua]
|
18
|
-
|
26
|
+
call[:source] = params[:lua]
|
27
|
+
content = Components::Embedding.lua(**call)
|
19
28
|
end
|
20
29
|
|
21
30
|
"#{params[:prefix]}#{content}#{params[:suffix]}"
|
22
31
|
end
|
23
|
-
|
24
|
-
def self.fennel(content, expression)
|
25
|
-
path = "#{File.expand_path('../static/fennel', __dir__)}/?.lua"
|
26
|
-
state = SweetMoon::State.new(package_path: path).fennel
|
27
|
-
# TODO: global is deprecated...
|
28
|
-
state.fennel.eval(
|
29
|
-
"(global adapter (fn [content] #{expression}))", 1,
|
30
|
-
{ allowedGlobals: %w[math string table] }
|
31
|
-
)
|
32
|
-
adapter = state.get(:adapter)
|
33
|
-
adapter.call([content])
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.lua(content, expression)
|
37
|
-
state = SweetMoon::State.new
|
38
|
-
code = "_, adapter = pcall(load('return function(content) return #{
|
39
|
-
expression.gsub("'", "\\\\'")
|
40
|
-
}; end', nil, 't', {math=math,string=string,table=table}))"
|
41
|
-
|
42
|
-
state.eval(code)
|
43
|
-
adapter = state.get(:adapter)
|
44
|
-
adapter.call([content])
|
45
|
-
end
|
46
32
|
end
|
47
33
|
end
|
48
34
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sweet-moon'
|
4
|
+
|
5
|
+
require 'open3'
|
6
|
+
require 'json'
|
7
|
+
require 'tempfile'
|
8
|
+
|
9
|
+
module NanoBot
|
10
|
+
module Components
|
11
|
+
class Embedding
|
12
|
+
def self.ensure_safety!(safety)
|
13
|
+
raise 'missing safety definitions' unless safety.key?(:sandboxed)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.lua(source:, parameters:, values:, safety:)
|
17
|
+
ensure_safety!(safety)
|
18
|
+
|
19
|
+
allowed = ''
|
20
|
+
allowed = ', {math=math,string=string,table=table}' if safety[:sandboxed]
|
21
|
+
|
22
|
+
state = SweetMoon::State.new
|
23
|
+
code = "_, embedded = pcall(load([[\nreturn function(#{parameters.join(', ')})\n#{source}\nend\n]], nil, 't'#{allowed}))"
|
24
|
+
|
25
|
+
state.eval(code)
|
26
|
+
embedded = state.get(:embedded)
|
27
|
+
embedded.call(values)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.fennel(source:, parameters:, values:, safety:)
|
31
|
+
ensure_safety!(safety)
|
32
|
+
|
33
|
+
path = "#{File.expand_path('../static/fennel', __dir__)}/?.lua"
|
34
|
+
state = SweetMoon::State.new(package_path: path).fennel
|
35
|
+
|
36
|
+
# TODO: `global` is deprecated.
|
37
|
+
state.fennel.eval(
|
38
|
+
"(global embedded (fn [#{parameters.join(' ')}] #{source}))", 1,
|
39
|
+
safety[:sandboxed] ? { allowedGlobals: %w[math string table] } : nil
|
40
|
+
)
|
41
|
+
embedded = state.get(:embedded)
|
42
|
+
embedded.call(values)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.clojure(source:, parameters:, values:, safety:)
|
46
|
+
ensure_safety!(safety)
|
47
|
+
|
48
|
+
raise 'TODO: sandboxed Clojure through Babashka not implemented' if safety[:sandboxed]
|
49
|
+
|
50
|
+
raise 'invalid Clojure parameter name' if parameters.include?('injected-parameters')
|
51
|
+
|
52
|
+
key_value = {}
|
53
|
+
|
54
|
+
parameters.each_with_index { |key, index| key_value[key] = values[index] }
|
55
|
+
|
56
|
+
parameters_json = key_value.to_json
|
57
|
+
|
58
|
+
json_file = Tempfile.new(['nano-bot', '.json'])
|
59
|
+
clojure_file = Tempfile.new(['nano-bot', '.clj'])
|
60
|
+
|
61
|
+
begin
|
62
|
+
json_file.write(parameters_json)
|
63
|
+
json_file.close
|
64
|
+
|
65
|
+
clojure_source = <<~CLOJURE
|
66
|
+
(require '[cheshire.core :as json])
|
67
|
+
(def injected-parameters (json/parse-string (slurp (java.io.FileReader. "#{json_file.path}"))))
|
68
|
+
|
69
|
+
#{parameters.map { |p| "(def #{p} (get injected-parameters \"#{p}\"))" }.join("\n")}
|
70
|
+
|
71
|
+
#{source}
|
72
|
+
CLOJURE
|
73
|
+
|
74
|
+
clojure_file.write(clojure_source)
|
75
|
+
clojure_file.close
|
76
|
+
|
77
|
+
bb_command = "bb --prn #{clojure_file.path} | bb -e \"(->> *in* slurp read-string print)\""
|
78
|
+
|
79
|
+
stdout, stderr, status = Open3.capture3(bb_command)
|
80
|
+
|
81
|
+
status.success? ? stdout : stderr
|
82
|
+
ensure
|
83
|
+
json_file&.unlink
|
84
|
+
clojure_file&.unlink
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/components/provider.rb
CHANGED
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../embedding'
|
4
|
+
require_relative '../../../logic/cartridge/safety'
|
5
|
+
|
6
|
+
require 'concurrent'
|
7
|
+
|
8
|
+
module NanoBot
|
9
|
+
module Components
|
10
|
+
module Providers
|
11
|
+
class OpenAI < Base
|
12
|
+
module Tools
|
13
|
+
def self.confirming(tool, feedback)
|
14
|
+
feedback.call(
|
15
|
+
{ should_be_stored: false,
|
16
|
+
interaction: { who: 'AI', message: nil, meta: {
|
17
|
+
tool: { action: 'confirming', id: tool[:id], name: tool[:name], parameters: tool[:parameters] }
|
18
|
+
} } }
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.apply(cartridge, function_cartridge, tools, feedback)
|
23
|
+
prepared_tools = NanoBot::Logic::OpenAI::Tools.prepare(function_cartridge, tools)
|
24
|
+
|
25
|
+
if Logic::Cartridge::Safety.confirmable?(cartridge)
|
26
|
+
prepared_tools.each { |tool| tool[:allowed] = confirming(tool, feedback) }
|
27
|
+
else
|
28
|
+
prepared_tools.each { |tool| tool[:allowed] = true }
|
29
|
+
end
|
30
|
+
|
31
|
+
futures = prepared_tools.map do |tool|
|
32
|
+
Concurrent::Promises.future do
|
33
|
+
if tool[:allowed]
|
34
|
+
process!(tool, feedback, function_cartridge, cartridge)
|
35
|
+
else
|
36
|
+
tool[:output] =
|
37
|
+
"We asked the user you're chatting with for permission, but the user did not allow you to run this tool or function."
|
38
|
+
tool
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
results = Concurrent::Promises.zip(*futures).value!
|
44
|
+
|
45
|
+
results.map do |applied_tool|
|
46
|
+
{
|
47
|
+
who: 'tool',
|
48
|
+
message: applied_tool[:output],
|
49
|
+
meta: { id: applied_tool[:id], name: applied_tool[:name] }
|
50
|
+
}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.process!(tool, feedback, _function_cartridge, cartridge)
|
55
|
+
feedback.call(
|
56
|
+
{ should_be_stored: false,
|
57
|
+
interaction: { who: 'AI', message: nil, meta: {
|
58
|
+
tool: { action: 'executing', id: tool[:id], name: tool[:name], parameters: tool[:parameters] }
|
59
|
+
} } }
|
60
|
+
)
|
61
|
+
|
62
|
+
call = {
|
63
|
+
parameters: %w[parameters],
|
64
|
+
values: [tool[:parameters]],
|
65
|
+
safety: { sandboxed: Logic::Cartridge::Safety.sandboxed?(cartridge) }
|
66
|
+
}
|
67
|
+
|
68
|
+
if %i[fennel lua clojure].count { |key| !tool[:source][key].nil? } > 1
|
69
|
+
raise StandardError, 'conflicting tools'
|
70
|
+
end
|
71
|
+
|
72
|
+
if !tool[:source][:fennel].nil?
|
73
|
+
call[:source] = tool[:source][:fennel]
|
74
|
+
tool[:output] = Components::Embedding.fennel(**call)
|
75
|
+
elsif !tool[:source][:clojure].nil?
|
76
|
+
call[:source] = tool[:source][:clojure]
|
77
|
+
tool[:output] = Components::Embedding.clojure(**call)
|
78
|
+
elsif !tool[:source][:lua].nil?
|
79
|
+
call[:source] = tool[:source][:lua]
|
80
|
+
tool[:output] = Components::Embedding.lua(**call)
|
81
|
+
else
|
82
|
+
raise 'missing source code'
|
83
|
+
end
|
84
|
+
|
85
|
+
feedback.call(
|
86
|
+
{ should_be_stored: false,
|
87
|
+
interaction: { who: 'AI', message: nil, meta: {
|
88
|
+
tool: {
|
89
|
+
action: 'responding', id: tool[:id], name: tool[:name],
|
90
|
+
parameters: tool[:parameters], output: tool[:output]
|
91
|
+
}
|
92
|
+
} } }
|
93
|
+
)
|
94
|
+
|
95
|
+
tool
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -2,9 +2,14 @@
|
|
2
2
|
|
3
3
|
require 'openai'
|
4
4
|
|
5
|
-
require_relative '
|
5
|
+
require_relative 'base'
|
6
6
|
require_relative '../crypto'
|
7
7
|
|
8
|
+
require_relative '../../logic/providers/openai/tools'
|
9
|
+
require_relative '../../controllers/interfaces/tools'
|
10
|
+
|
11
|
+
require_relative 'openai/tools'
|
12
|
+
|
8
13
|
module NanoBot
|
9
14
|
module Components
|
10
15
|
module Providers
|
@@ -32,17 +37,16 @@ module NanoBot
|
|
32
37
|
@client = ::OpenAI::Client.new(uri_base:, access_token: @credentials[:'access-token'])
|
33
38
|
end
|
34
39
|
|
35
|
-
def
|
36
|
-
provider = @settings.key?(:stream) ? @settings[:stream] : true
|
37
|
-
interface = input[:interface].key?(:stream) ? input[:interface][:stream] : true
|
38
|
-
|
39
|
-
provider && interface
|
40
|
-
end
|
41
|
-
|
42
|
-
def evaluate(input, &block)
|
40
|
+
def evaluate(input, streaming, cartridge, &feedback)
|
43
41
|
messages = input[:history].map do |event|
|
44
|
-
|
45
|
-
content: event[:
|
42
|
+
if event[:message].nil? && event[:meta] && event[:meta][:tool_calls]
|
43
|
+
{ role: 'assistant', content: nil, tool_calls: event[:meta][:tool_calls] }
|
44
|
+
elsif event[:who] == 'tool'
|
45
|
+
{ role: event[:who], content: event[:message].to_s,
|
46
|
+
tool_call_id: event[:meta][:id], name: event[:meta][:name] }
|
47
|
+
else
|
48
|
+
{ role: event[:who] == 'user' ? 'user' : 'assistant', content: event[:message] }
|
49
|
+
end
|
46
50
|
end
|
47
51
|
|
48
52
|
%i[instruction backdrop directive].each do |key|
|
@@ -62,26 +66,105 @@ module NanoBot
|
|
62
66
|
|
63
67
|
payload.delete(:logit_bias) if payload.key?(:logit_bias) && payload[:logit_bias].nil?
|
64
68
|
|
65
|
-
if
|
69
|
+
payload[:tools] = input[:tools].map { |raw| NanoBot::Logic::OpenAI::Tools.adapt(raw) } if input[:tools]
|
70
|
+
|
71
|
+
if streaming
|
66
72
|
content = ''
|
73
|
+
tools = []
|
67
74
|
|
68
75
|
payload[:stream] = proc do |chunk, _bytesize|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
76
|
+
partial_content = chunk.dig('choices', 0, 'delta', 'content')
|
77
|
+
partial_tools = chunk.dig('choices', 0, 'delta', 'tool_calls')
|
78
|
+
|
79
|
+
if partial_tools
|
80
|
+
partial_tools.each do |partial_tool|
|
81
|
+
tools[partial_tool['index']] = {} if tools[partial_tool['index']].nil?
|
82
|
+
|
83
|
+
partial_tool.keys.reject { |key| ['index'].include?(key) }.each do |key|
|
84
|
+
target = tools[partial_tool['index']]
|
85
|
+
|
86
|
+
if partial_tool[key].is_a?(Hash)
|
87
|
+
target[key] = {} if target[key].nil?
|
88
|
+
partial_tool[key].each_key do |sub_key|
|
89
|
+
target[key][sub_key] = '' if target[key][sub_key].nil?
|
90
|
+
|
91
|
+
target[key][sub_key] += partial_tool[key][sub_key]
|
92
|
+
end
|
93
|
+
else
|
94
|
+
target[key] = '' if target[key].nil?
|
95
|
+
|
96
|
+
target[key] += partial_tool[key]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
73
100
|
end
|
74
101
|
|
75
|
-
|
102
|
+
if partial_content
|
103
|
+
content += partial_content
|
104
|
+
feedback.call(
|
105
|
+
{ should_be_stored: false,
|
106
|
+
interaction: { who: 'AI', message: partial_content } }
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
if chunk.dig('choices', 0, 'finish_reason')
|
111
|
+
if tools&.size&.positive?
|
112
|
+
feedback.call(
|
113
|
+
{ should_be_stored: true,
|
114
|
+
needs_another_round: true,
|
115
|
+
interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } }
|
116
|
+
)
|
117
|
+
Tools.apply(cartridge, input[:tools], tools, feedback).each do |interaction|
|
118
|
+
feedback.call({ should_be_stored: true, needs_another_round: true, interaction: })
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
feedback.call(
|
123
|
+
{ should_be_stored: !(content.nil? || content == ''),
|
124
|
+
interaction: content.nil? || content == '' ? nil : { who: 'AI', message: content },
|
125
|
+
finished: true }
|
126
|
+
)
|
127
|
+
end
|
76
128
|
end
|
77
129
|
|
78
|
-
|
130
|
+
begin
|
131
|
+
@client.chat(parameters: payload)
|
132
|
+
rescue StandardError => e
|
133
|
+
raise e.class, e.response[:body] if e.response && e.response[:body]
|
134
|
+
|
135
|
+
raise e
|
136
|
+
end
|
79
137
|
else
|
80
|
-
|
138
|
+
begin
|
139
|
+
result = @client.chat(parameters: payload)
|
140
|
+
rescue StandardError => e
|
141
|
+
raise e.class, e.response[:body] if e.response && e.response[:body]
|
142
|
+
|
143
|
+
raise e
|
144
|
+
end
|
81
145
|
|
82
146
|
raise StandardError, result['error'] if result['error']
|
83
147
|
|
84
|
-
|
148
|
+
tools = result.dig('choices', 0, 'message', 'tool_calls')
|
149
|
+
|
150
|
+
if tools&.size&.positive?
|
151
|
+
feedback.call(
|
152
|
+
{ should_be_stored: true,
|
153
|
+
needs_another_round: true,
|
154
|
+
interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } }
|
155
|
+
)
|
156
|
+
Tools.apply(cartridge, input[:tools], tools, feedback).each do |interaction|
|
157
|
+
feedback.call({ should_be_stored: true, needs_another_round: true, interaction: })
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
content = result.dig('choices', 0, 'message', 'content')
|
162
|
+
|
163
|
+
feedback.call(
|
164
|
+
{ should_be_stored: !(content.nil? || content == ''),
|
165
|
+
interaction: content.nil? || content == '' ? nil : { who: 'AI', message: content },
|
166
|
+
finished: true }
|
167
|
+
)
|
85
168
|
end
|
86
169
|
end
|
87
170
|
|
data/components/storage.rb
CHANGED
data/components/stream.rb
CHANGED
@@ -7,7 +7,12 @@ module NanoBot
|
|
7
7
|
class Stream < StringIO
|
8
8
|
def write(*args)
|
9
9
|
if @callback
|
10
|
-
|
10
|
+
begin
|
11
|
+
@accumulated += args.first
|
12
|
+
rescue StandardError => _e
|
13
|
+
@accumulated = "#{@accumulated.force_encoding('UTF-8')}#{args.first.force_encoding('UTF-8')}"
|
14
|
+
end
|
15
|
+
|
11
16
|
@callback.call(@accumulated, args.first, false)
|
12
17
|
end
|
13
18
|
super
|
data/controllers/cartridges.rb
CHANGED
@@ -23,7 +23,7 @@ module NanoBot
|
|
23
23
|
|
24
24
|
files.values.uniq.map do |file|
|
25
25
|
cartridge = Logic::Helpers::Hash.symbolize_keys(
|
26
|
-
YAML.
|
26
|
+
YAML.safe_load_file(file[:path], permitted_classes: [Symbol])
|
27
27
|
).merge({
|
28
28
|
system: {
|
29
29
|
id: file[:path].to_s.sub(/^#{Regexp.escape(file[:base])}/, '').sub(%r{^/}, '').sub(/\.[^.]+\z/,
|