nano-bots 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acc012feeff1090abe2df65283bdd02c8eb1c44bdb5cad13a91ce5dc481727a8
4
- data.tar.gz: b34a3d078c723494e12bfcdf6793294d400adbe5150302d675eef569d471ff37
3
+ metadata.gz: 0fc904784fe79cd5bd873dc3861dd82412f037012ca125011ab795e5cb01e2b1
4
+ data.tar.gz: 76f64af2d36b5b2765d98f90281577a88e0fcb166143d859829e1b8607fe9e57
5
5
  SHA512:
6
- metadata.gz: 617b3fda80392476c1943f3df9e5201d1cd5f377ede9b76b413748ca4197834b7058f4b48abc599af4eddff0879e111f7860a403759eda34cdcc3894f8d865e8
7
- data.tar.gz: 7907c44f88d19ea62e57b1288410f80045438c4ba7a9bc962fe07cb4535467b88aea43e618e5165971403e2ac30d16c52a0cf56077916bbb5082d0cee4945892
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.47'
10
- gem 'rubocop-rspec', '~> 2.22'
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.1.1)
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.5)
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 (~> 4.0)
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
- faraday (2.7.5)
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.15.5)
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.1)
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.0)
42
- rexml (3.2.5)
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.5)
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.0)
56
- rubocop (1.52.0)
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.0.0)
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.0, < 2.0)
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.29.0)
79
+ rubocop-ast (1.30.0)
67
80
  parser (>= 3.2.1.0)
68
- rubocop-capybara (2.18.0)
81
+ rubocop-capybara (2.19.0)
69
82
  rubocop (~> 1.41)
70
- rubocop-factory_bot (2.23.1)
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 (4.1.0)
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.4.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.47)
92
- rubocop-rspec (~> 2.22)
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.1.1
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.1.1'
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.1.1 && bash"
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
- #### End-user IDs
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
- ## Decrypting
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.1.1.gem
411
+ gem push nano-bots-1.0.0.gem
412
412
  ```
@@ -1,48 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sweet-moon'
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(_direction, params)
9
+ def self.apply(params, cartridge)
9
10
  content = params[:content]
10
11
 
11
- if params[:fennel] && params[:lua]
12
- raise StandardError, 'Adapter conflict: You can only use either Lua or Fennel, not both.'
13
- end
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
- content = fennel(content, params[:fennel])
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
- content = lua(content, params[:lua])
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'openai'
4
4
 
5
- require_relative './providers/openai'
5
+ require_relative 'providers/openai'
6
6
 
7
7
  module NanoBot
8
8
  module Components
@@ -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 './base'
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 stream(input)
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
- { role: event[:who] == 'user' ? 'user' : 'assistant',
45
- content: event[:message] }
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 stream(input)
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
- partial = chunk.dig('choices', 0, 'delta', 'content')
70
- if partial
71
- content += partial
72
- block.call({ who: 'AI', message: partial }, false)
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
- block.call({ who: 'AI', message: content }, true) if chunk.dig('choices', 0, 'finish_reason')
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
- @client.chat(parameters: payload)
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
- result = @client.chat(parameters: payload)
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
- block.call({ who: 'AI', message: result.dig('choices', 0, 'message', 'content') }, true)
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
 
@@ -3,7 +3,7 @@
3
3
  require 'babosa'
4
4
 
5
5
  require_relative '../logic/helpers/hash'
6
- require_relative './crypto'
6
+ require_relative 'crypto'
7
7
 
8
8
  module NanoBot
9
9
  module Components
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
- @accumulated += args.first
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
@@ -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.safe_load(File.read(file[:path]), permitted_classes: [Symbol])
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/,