nano-bots 0.1.1 → 1.0.1

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: c992bede26be258d71c430d42cde2bf369f0ca90b3b835998829008612c7e1aa
4
+ data.tar.gz: 122564e6d76bab01b3f18e825c75209ca4a6c531957a5f7ed94e8921e40a3d3a
5
5
  SHA512:
6
- metadata.gz: 617b3fda80392476c1943f3df9e5201d1cd5f377ede9b76b413748ca4197834b7058f4b48abc599af4eddff0879e111f7860a403759eda34cdcc3894f8d865e8
7
- data.tar.gz: 7907c44f88d19ea62e57b1288410f80045438c4ba7a9bc962fe07cb4535467b88aea43e618e5165971403e2ac30d16c52a0cf56077916bbb5082d0cee4945892
6
+ metadata.gz: 55e3ee927659469db0c0533c55241e752ebee17869fe290f9f242de28e3503f74ba7e5c69530d886ce8614b7c68e079c1bf912264c717cb61061e3bfc445e63e
7
+ data.tar.gz: 8e990faf709f6c720f9ba8f9aab59196e729416f762968f6a02badfe7879b9f24de5ff833b102e99d6aed53a694710f6712b1995e336403c40203dbbff621e1b
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.58'
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.1)
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)
29
- json (2.6.3)
34
+ ffi (1.16.3)
35
+ json (2.7.0)
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.58.0)
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.30.0, < 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.58)
107
+ rubocop-rspec (~> 2.25)
93
108
 
94
109
  BUNDLED WITH
95
110
  2.4.13
data/README.md CHANGED
@@ -13,6 +13,8 @@ https://user-images.githubusercontent.com/113217272/238141567-c58a240c-7b67-4b3b
13
13
  - [Command Line](#command-line)
14
14
  - [Library](#library)
15
15
  - [Cartridges](#cartridges)
16
+ - [Tools (Functions)](#tools-functions)
17
+ - [Experimental Clojure Support](#experimental-clojure-support)
16
18
  - [Marketplace](#marketplace)
17
19
  - [Security and Privacy](#security-and-privacy)
18
20
  - [Cryptography](#cryptography)
@@ -28,13 +30,13 @@ https://user-images.githubusercontent.com/113217272/238141567-c58a240c-7b67-4b3b
28
30
  For a system usage:
29
31
 
30
32
  ```sh
31
- gem install nano-bots -v 0.1.1
33
+ gem install nano-bots -v 1.0.1
32
34
  ```
33
35
 
34
36
  To use it in a project, add it to your `Gemfile`:
35
37
 
36
38
  ```ruby
37
- gem 'nano-bots', '~> 0.1.1'
39
+ gem 'nano-bots', '~> 1.0.1'
38
40
  ```
39
41
 
40
42
  ```sh
@@ -80,12 +82,11 @@ cp docker-compose.example.yml docker-compose.yml
80
82
  Set your provider credentials and choose your desired directory for the cartridges files:
81
83
 
82
84
  ```yaml
83
- version: '3.7'
84
-
85
+ ---
85
86
  services:
86
87
  nano-bots:
87
- image: ruby:3.2.2-slim-bullseye
88
- command: sh -c "gem install nano-bots -v 0.1.1 && bash"
88
+ image: ruby:3.2.2-slim-bookworm
89
+ command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev curl && curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash && gem install nano-bots -v 1.0.1 && bash"
89
90
  environment:
90
91
  OPENAI_API_ADDRESS: https://api.openai.com
91
92
  OPENAI_API_KEY: your-access-token
@@ -249,7 +250,7 @@ meta:
249
250
  symbol: 🤖
250
251
  name: Nano Bot Name
251
252
  author: Your Name
252
- version: 1.0.0
253
+ version: 1.0.1
253
254
  license: CC0-1.0
254
255
  description: A helpful assistant.
255
256
 
@@ -271,6 +272,85 @@ Check the Nano Bots specification to learn more about [how to build cartridges](
271
272
 
272
273
  Try the [Nano Bots Clinic (Live Editor)](https://clinic.nbots.io) to learn about creating Cartridges.
273
274
 
275
+ ### Tools (Functions)
276
+
277
+ Nano Bots can also be powered by _Tools_ (Functions):
278
+
279
+ ```yaml
280
+ ---
281
+ tools:
282
+ - name: random-number
283
+ description: Generates a random number between 1 and 100.
284
+ fennel: |
285
+ (math.random 1 100)
286
+ ```
287
+
288
+ ```
289
+ 🤖> please generate a random number
290
+
291
+ random-number {} [yN] y
292
+
293
+ random-number {}
294
+ 59
295
+
296
+ The randomly generated number is 59.
297
+
298
+ 🤖> |
299
+ ```
300
+
301
+ To successfully use Tools (Functions), you need to specify a provider and a model that support them. As of the writing of this README, the provider that supports them is [OpenAI](https://platform.openai.com/docs/models), with models `gpt-3.5-turbo-1106` and `gpt-4-1106-preview`:
302
+
303
+ ```yaml
304
+ ---
305
+ provider:
306
+ id: openai
307
+ credentials:
308
+ address: ENV/OPENAI_API_ADDRESS
309
+ access-token: ENV/OPENAI_API_KEY
310
+ settings:
311
+ user: ENV/NANO_BOTS_END_USER
312
+ model: gpt-4-1106-preview
313
+ ```
314
+
315
+ Check the [Nano Bots specification](https://spec.nbots.io/#/README?id=tools-functions-2) to learn more about them.
316
+
317
+ #### Experimental Clojure Support
318
+
319
+ We are exploring the use of [Clojure](https://clojure.org) through [Babashka](https://babashka.org), powered by [GraalVM](https://www.graalvm.org).
320
+
321
+ The experimental support for Clojure would be similar to Lua and Fennel, using the `clojure:` key:
322
+
323
+ ```yaml
324
+ ---
325
+ clojure: |
326
+ (-> (java.time.ZonedDateTime/now)
327
+ (.format (java.time.format.DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm"))
328
+ (clojure.string/trimr))
329
+ ```
330
+
331
+ Unlike Lua and Fennel, Clojure support is not _embedded_ in this implementation. It relies on having the Babashka binary (`bb`) available in your environment where the Nano Bot is running.
332
+
333
+ Here's [how to install Babashka](https://github.com/babashka/babashka#quickstart):
334
+
335
+ ```sh
336
+ curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash
337
+ ```
338
+
339
+ This is a quick check to ensure that it is available and working:
340
+ ```sh
341
+ bb -e '{:hello "world"}'
342
+ # => {:hello "world"}
343
+ ```
344
+
345
+ We don't have sandbox support for Clojure; this means that you need to disable it to be able to run Clojure code, which you do at your own risk:
346
+
347
+ ```yaml
348
+ ---
349
+ safety:
350
+ functions:
351
+ sandboxed: false
352
+ ```
353
+
274
354
  ### Marketplace
275
355
 
276
356
  You can explore the Nano Bots [Marketplace](https://nbots.io) to discover new Cartridges that can help you.
@@ -309,7 +389,7 @@ NanoBot.security.check
309
389
  # => { encryption: true, password: true }
310
390
  ```
311
391
 
312
- #### End-user IDs
392
+ ### End-user IDs
313
393
 
314
394
  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
395
 
@@ -362,7 +442,7 @@ Actually, to enhance privacy, neither your user nor your users' identifiers will
362
442
 
363
443
  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
444
 
365
- ## Decrypting
445
+ ### Decrypting
366
446
 
367
447
  To decrypt your encrypted data, once you have properly configured your password, you can simply run:
368
448
 
@@ -408,5 +488,5 @@ gem build nano-bots.gemspec
408
488
 
409
489
  gem signin
410
490
 
411
- gem push nano-bots-0.1.1.gem
491
+ gem push nano-bots-1.0.1.gem
412
492
  ```
@@ -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