nano-bots 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: eb32dfa429c178b7af6c3f9a4c8046846ab9ae894c94bbf2038644ceef9c2368
4
+ data.tar.gz: 21e37c25c085c27f0f0357f8bb878cba2a366641f9d4649dc25536bcd51afc28
5
+ SHA512:
6
+ metadata.gz: 0a4a1f1a7f33eb6da088b8418cd0d4da61d8e6890ee8b6a0276330217164966b7266c032eabf3e3454e7879bd5e525b47cc99eff40259b7a1a9f9cf86a72db4f
7
+ data.tar.gz: 2098e8e5da98da2499ea4267e70be61ba9a06feb5c3fb6375d8ff05dbd01ae72134d34e5205884af80f3c4029c32dc09fec3c91b744ee6f61b05db8a5309b8ec
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .env
2
+ *.gem
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,9 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1.4
3
+ NewCops: enable
4
+
5
+ Style/Documentation:
6
+ Enabled: false
7
+
8
+ require:
9
+ - rubocop-rspec
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :test, :development do
8
+ gem 'rspec', '~> 3.12'
9
+ gem 'rubocop', '~> 1.47'
10
+ gem 'rubocop-rspec', '~> 2.22'
11
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,88 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nano-bots (0.0.1)
5
+ babosa (~> 2.0)
6
+ dotenv (~> 2.8, >= 2.8.1)
7
+ faraday (~> 2.7, >= 2.7.4)
8
+ pry (~> 0.14.2)
9
+ rainbow (~> 3.1, >= 3.1.1)
10
+ ruby-openai (~> 4.0)
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ ast (2.4.2)
16
+ babosa (2.0.0)
17
+ coderay (1.1.3)
18
+ diff-lcs (1.5.0)
19
+ dotenv (2.8.1)
20
+ faraday (2.7.4)
21
+ faraday-net_http (>= 2.0, < 3.1)
22
+ ruby2_keywords (>= 0.0.4)
23
+ faraday-multipart (1.0.4)
24
+ multipart-post (~> 2)
25
+ faraday-net_http (3.0.2)
26
+ json (2.6.3)
27
+ method_source (1.0.0)
28
+ multipart-post (2.3.0)
29
+ parallel (1.23.0)
30
+ parser (3.2.2.1)
31
+ ast (~> 2.4.1)
32
+ pry (0.14.2)
33
+ coderay (~> 1.1)
34
+ method_source (~> 1.0)
35
+ rainbow (3.1.1)
36
+ regexp_parser (2.8.0)
37
+ rexml (3.2.5)
38
+ rspec (3.12.0)
39
+ rspec-core (~> 3.12.0)
40
+ rspec-expectations (~> 3.12.0)
41
+ rspec-mocks (~> 3.12.0)
42
+ rspec-core (3.12.2)
43
+ rspec-support (~> 3.12.0)
44
+ rspec-expectations (3.12.3)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.12.0)
47
+ rspec-mocks (3.12.5)
48
+ diff-lcs (>= 1.2.0, < 2.0)
49
+ rspec-support (~> 3.12.0)
50
+ rspec-support (3.12.0)
51
+ rubocop (1.50.2)
52
+ json (~> 2.3)
53
+ parallel (~> 1.10)
54
+ parser (>= 3.2.0.0)
55
+ rainbow (>= 2.2.2, < 4.0)
56
+ regexp_parser (>= 1.8, < 3.0)
57
+ rexml (>= 3.2.5, < 4.0)
58
+ rubocop-ast (>= 1.28.0, < 2.0)
59
+ ruby-progressbar (~> 1.7)
60
+ unicode-display_width (>= 2.4.0, < 3.0)
61
+ rubocop-ast (1.28.1)
62
+ parser (>= 3.2.1.0)
63
+ rubocop-capybara (2.18.0)
64
+ rubocop (~> 1.41)
65
+ rubocop-factory_bot (2.22.0)
66
+ rubocop (~> 1.33)
67
+ rubocop-rspec (2.22.0)
68
+ rubocop (~> 1.33)
69
+ rubocop-capybara (~> 2.17)
70
+ rubocop-factory_bot (~> 2.22)
71
+ ruby-openai (4.0.0)
72
+ faraday (>= 1)
73
+ faraday-multipart (>= 1)
74
+ ruby-progressbar (1.13.0)
75
+ ruby2_keywords (0.0.5)
76
+ unicode-display_width (2.4.2)
77
+
78
+ PLATFORMS
79
+ x86_64-linux
80
+
81
+ DEPENDENCIES
82
+ nano-bots!
83
+ rspec (~> 3.12)
84
+ rubocop (~> 1.47)
85
+ rubocop-rspec (~> 2.22)
86
+
87
+ BUNDLED WITH
88
+ 2.4.13
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 icebaker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # Nano Bots 💎 🤖
2
+
3
+ A Ruby implementation of the [Nano Bots](https://github.com/icebaker/nano-bots) specification.
4
+
5
+ ![Ruby Nano Bots](https://user-images.githubusercontent.com/113217272/237839690-7880915a-b287-4484-a75e-0b96284b8a32.png)
6
+ _Image artificially created by Midjourney through a prompt generated by a Nano Bot specialized in Midjourney._
7
+
8
+ https://user-images.githubusercontent.com/113217272/237840989-1e29a5cc-6644-48d0-87b4-62798dc6ebd3.mp4
9
+
10
+ - [Setup](#setup)
11
+ - [Usage](#usage)
12
+ - [Command Line](#command-line)
13
+ - [Library](#library)
14
+ - [Cartridges](#cartridges)
15
+ - [Development](#development)
16
+ - [Publish to RubyGems](#publish-to-rubygems)
17
+
18
+ ## Setup
19
+
20
+ For a system usage:
21
+
22
+ ```sh
23
+ gem install nano-bots -v 0.0.1
24
+ ```
25
+
26
+ To use it in a project, add it to your `Gemfile`:
27
+
28
+ ```ruby
29
+ gem 'nano-bots', '~> 0.0.1'
30
+ ```
31
+
32
+ ```sh
33
+ bundle install
34
+ ```
35
+
36
+ For credentials and configurations, relevant environment variables can be set in your `.bashrc`, `.zshrc`, or equivalent files, as well as in your Docker Container or System Environment. Example:
37
+
38
+ ```sh
39
+ export OPENAI_API_ADDRESS=https://api.openai.com
40
+ export OPENAI_API_ACCESS_TOKEN=your-token
41
+ export OPENAI_API_USER_IDENTIFIER=your-user
42
+
43
+ export NANO_BOTS_STATE_DIRECTORY=/home/user/.local/state/nano-bots
44
+ export NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges
45
+ ```
46
+
47
+ Alternatively, if your current directory has a `.env` file with the environment variables, they will be automatically loaded.
48
+
49
+ ## Usage
50
+
51
+ ### Command Line
52
+
53
+ After installing the gem, the `rnb` binary command will be available for your project or system.
54
+
55
+ Examples of usage:
56
+
57
+ ```bash
58
+ rnb to-en-us-translator.yml - eval "Salut, comment ça va?"
59
+ # => Hello, how are you doing?
60
+
61
+ rnb midjourney.yml - eval "happy and friendly cyberpunk robot"
62
+ # => The robot exploring a bustling city, surrounded by neon lights
63
+ # and high-rise buildings. The prompt should include colorful
64
+ # lighting and a sense of excitement in the facial expression.
65
+
66
+ rnb lisp.yml - eval "(+ 1 2)"
67
+ # => 3
68
+
69
+ cat article.txt |
70
+ rnb to-en-us-translator.yml - eval |
71
+ rnb summarizer.yml - eval
72
+ # -> LLM stands for Large Language Model, which refers to an
73
+ # artificial intelligence algorithm capable of processing
74
+ # and understanding vast amounts of natural language data,
75
+ # allowing it to generate human-like responses and perform
76
+ # a range of language-related tasks.
77
+ ```
78
+
79
+ ```bash
80
+ rnb assistant.yml - repl
81
+ ```
82
+
83
+ All of the commands above are stateless. If you want to preserve the history of your interactions, replace the `-` with a state key. You can use a simple key, such as your username, or a randomly generated one:
84
+
85
+ ```ruby
86
+ require 'securerandom'
87
+
88
+ SecureRandom.hex # => 6ea6c43c42a1c076b1e3c36fa349ac2c
89
+ ```
90
+
91
+ ```bash
92
+ rnb assistant.yml your-user eval "Salut, comment ça va?"
93
+ rnb assistant.yml your-user repl
94
+
95
+ rnb assistant.yml 6ea6c43c42a1c076b1e3c36fa349ac2c eval "Salut, comment ça va?"
96
+ rnb assistant.yml 6ea6c43c42a1c076b1e3c36fa349ac2c repl
97
+ ```
98
+
99
+ ### Library
100
+
101
+ To use it as a library:
102
+
103
+ ```ruby
104
+ require 'nano-bots/cli' # Equivalent to the `rnb` command.
105
+ ```
106
+
107
+ ```ruby
108
+ require 'nano-bots'
109
+
110
+ NanoBot.cli # Equivalent to the `rnb` command.
111
+
112
+ NanoBot.repl(cartridge: 'cartridge.yml') # Starts a new REPL.
113
+
114
+ bot = NanoBot.new(cartridge: 'cartridge.yml')
115
+
116
+ bot.eval('Hello')
117
+
118
+ bot.repl # Starts a new REPL.
119
+
120
+ NanoBot.repl(cartridge: 'cartridge.yml', state: '6ea6c43c42a1c076b1e3c36fa349ac2c')
121
+
122
+ bot = NanoBot.new(cartridge: 'cartridge.yml', state: '6ea6c43c42a1c076b1e3c36fa349ac2c')
123
+ ```
124
+
125
+ ## Cartridges
126
+
127
+ Here's what a Nano Bot Cartridge looks like:
128
+
129
+ ```yaml
130
+ ---
131
+ name: Assistant
132
+ version: 0.0.1
133
+
134
+ behaviors:
135
+ interaction:
136
+ directive: You are a helpful assistant.
137
+
138
+ interfaces:
139
+ repl:
140
+ prompt:
141
+ - text: '🤖'
142
+ - text: '> '
143
+ color: blue
144
+
145
+ provider:
146
+ name: openai
147
+ settings:
148
+ model: gpt-3.5-turbo
149
+ credentials:
150
+ address: ENV/OPENAI_API_ADDRESS
151
+ access-token: ENV/OPENAI_API_ACCESS_TOKEN
152
+ user-identifier: ENV/OPENAI_API_USER_IDENTIFIER
153
+ ```
154
+
155
+ Check the Nano Bots specification to learn more about [how to build cartridges](https://icebaker.github.io/nano-bots/#/README?id=cartridges).
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ bundle
161
+ rubocop -A
162
+ rspec
163
+ ```
164
+
165
+ ### Publish to RubyGems
166
+
167
+ ```bash
168
+ gem build nano-bots.gemspec
169
+
170
+ gem signin
171
+
172
+ gem push nano-bots-0.0.1.gem
173
+ ```
data/bin/rnb ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'nano-bots/cli'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ require_relative './providers/openai'
6
+
7
+ module NanoBot
8
+ module Components
9
+ class Provider
10
+ def self.new(provider)
11
+ case provider[:name]
12
+ when 'openai'
13
+ Providers::OpenAI.new(provider[:settings])
14
+ else
15
+ raise "Unsupported provider #{provider[:name]}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ module NanoBot
6
+ module Components
7
+ module Providers
8
+ class Base
9
+ def evaluate(_payload)
10
+ raise NoMethodError, "The 'evaluate' method is not implemented for the current provider."
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ require_relative './base'
6
+
7
+ module NanoBot
8
+ module Components
9
+ module Providers
10
+ class OpenAI < Base
11
+ CHAT_SETTINGS = %i[
12
+ model stream temperature top_p n stop max_tokens
13
+ presence_penalty frequency_penalty logit_bias
14
+ ].freeze
15
+
16
+ attr_reader :settings
17
+
18
+ def initialize(settings)
19
+ @settings = settings
20
+
21
+ @client = ::OpenAI::Client.new(
22
+ uri_base: "#{@settings[:credentials][:address].sub(%r{/$}, '')}/",
23
+ access_token: @settings[:credentials][:'access-token']
24
+ )
25
+ end
26
+
27
+ def evaluate(input, &block)
28
+ messages = input[:history].map do |event|
29
+ { role: event[:who] == 'user' ? 'user' : 'assistant',
30
+ content: event[:message] }
31
+ end
32
+
33
+ %i[instruction backdrop directive].each do |key|
34
+ next unless input[:behavior][key]
35
+
36
+ messages.prepend(
37
+ { role: key == :directive ? 'system' : 'user',
38
+ content: input[:behavior][key] }
39
+ )
40
+ end
41
+
42
+ payload = {
43
+ model: @settings[:model],
44
+ user: @settings[:credentials][:'user-identifier'],
45
+ messages:
46
+ }
47
+
48
+ CHAT_SETTINGS.each do |key|
49
+ payload[key] = @settings[key] if @settings.key?(key)
50
+ end
51
+
52
+ payload.delete(:logit_bias) if payload.key?(:logit_bias) && payload[:logit_bias].nil?
53
+
54
+ if @settings[:stream] && input[:interface][:stream]
55
+ content = ''
56
+
57
+ payload[:stream] = proc do |chunk, _bytesize|
58
+ partial = chunk.dig('choices', 0, 'delta', 'content')
59
+ if partial
60
+ content += partial
61
+ block.call({ who: 'AI', message: partial }, false)
62
+ end
63
+
64
+ block.call({ who: 'AI', message: content }, true) if chunk.dig('choices', 0, 'finish_reason')
65
+ end
66
+
67
+ @client.chat(parameters: payload)
68
+ else
69
+ result = @client.chat(parameters: payload)
70
+
71
+ raise StandardError, result['error'] if result['error']
72
+
73
+ block.call({ who: 'AI', message: result.dig('choices', 0, 'message', 'content') }, true)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'babosa'
4
+
5
+ require_relative '../logic/helpers/hash'
6
+
7
+ module NanoBot
8
+ module Components
9
+ class Storage
10
+ def self.build_path_and_ensure_state_file!(key, cartridge)
11
+ path = [
12
+ Logic::Helpers::Hash.fetch(cartridge, %i[state directory]),
13
+ ENV.fetch('NANO_BOTS_STATE_DIRECTORY', nil)
14
+ ].find do |candidate|
15
+ !candidate.nil? && !candidate.empty?
16
+ end
17
+
18
+ path = "#{user_home!.sub(%r{/$}, '')}/.local/state/nano-bots" if path.nil?
19
+
20
+ path = "#{path.sub(%r{/$}, '')}/nano-bots-rb/#{cartridge[:name].to_slug.normalize}"
21
+ path = "#{path}/#{cartridge[:version].to_slug.normalize}/#{key.to_slug.normalize}"
22
+ path = "#{path}/state.json"
23
+
24
+ FileUtils.mkdir_p(File.dirname(path))
25
+
26
+ File.write(path, JSON.generate({ key:, history: [] })) unless File.exist?(path)
27
+
28
+ path
29
+ end
30
+
31
+ def self.cartridge_path(path)
32
+ partial = File.join(File.dirname(path), File.basename(path, File.extname(path)))
33
+
34
+ candidates = [
35
+ path,
36
+ "#{partial}.yml",
37
+ "#{partial}.yaml"
38
+ ]
39
+
40
+ unless ENV.fetch('NANO_BOTS_CARTRIDGES_DIRECTORY', nil).nil?
41
+ directory = ENV.fetch('NANO_BOTS_CARTRIDGES_DIRECTORY').sub(%r{/$}, '')
42
+
43
+ partial = File.join(File.dirname(partial), File.basename(partial, File.extname(partial)))
44
+
45
+ partial = path.sub(%r{^\.?/}, '')
46
+
47
+ candidates << "#{directory}/#{partial}"
48
+ candidates << "#{directory}/#{partial}.yml"
49
+ candidates << "#{directory}/#{partial}.yaml"
50
+ end
51
+
52
+ directory = "#{user_home!.sub(%r{/$}, '')}/.local/share/nano-bots/cartridges"
53
+
54
+ partial = File.join(File.dirname(partial), File.basename(partial, File.extname(partial)))
55
+
56
+ partial = path.sub(%r{^\.?/}, '')
57
+
58
+ candidates << "#{directory}/#{partial}"
59
+ candidates << "#{directory}/#{partial}.yml"
60
+ candidates << "#{directory}/#{partial}.yaml"
61
+
62
+ candidates = candidates.uniq
63
+
64
+ candidates.find do |candidate|
65
+ File.exist?(candidate) && File.file?(candidate)
66
+ end
67
+ end
68
+
69
+ def self.user_home!
70
+ [Dir.home, `echo ~`.strip, '~'].find do |candidate|
71
+ !candidate.nil? && !candidate.empty?
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ require_relative '../logic/helpers/hash'
6
+ require_relative '../components/provider'
7
+ require_relative '../components/storage'
8
+ require_relative './interfaces/repl'
9
+ require_relative './session'
10
+
11
+ module NanoBot
12
+ module Controllers
13
+ class Instance
14
+ def initialize(cartridge_path:, state: nil)
15
+ load_cartridge!(cartridge_path)
16
+
17
+ provider = Components::Provider.new(@cartridge[:provider])
18
+
19
+ @session = Session.new(provider:, cartridge: @cartridge, state:)
20
+ end
21
+
22
+ def debug
23
+ @session.debug
24
+ end
25
+
26
+ def eval(input)
27
+ @session.evaluate_and_print(input, mode: 'eval')
28
+ end
29
+
30
+ def repl
31
+ Interfaces::REPL.start(@cartridge, @session)
32
+ end
33
+
34
+ private
35
+
36
+ def load_cartridge!(path)
37
+ @cartridge = Logic::Helpers::Hash.symbolize_keys(
38
+ YAML.safe_load(
39
+ File.read(Components::Storage.cartridge_path(path)),
40
+ permitted_classes: [Symbol]
41
+ )
42
+ )
43
+
44
+ inject_environment_variables!(@cartridge)
45
+ end
46
+
47
+ def inject_environment_variables!(node)
48
+ case node
49
+ when Hash
50
+ node.each do |key, value|
51
+ node[key] = inject_environment_variables!(value)
52
+ end
53
+ when Array
54
+ node.each_with_index do |value, index|
55
+ node[index] = inject_environment_variables!(value)
56
+ end
57
+ when String
58
+ node.start_with?('ENV') ? ENV.fetch(node.sub(/^ENV./, ''), nil) : node
59
+ else
60
+ node
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../instance'
4
+ require_relative '../../static/gem'
5
+
6
+ module NanoBot
7
+ module Controllers
8
+ module Interfaces
9
+ module CLI
10
+ def self.handle!
11
+ case ARGV[0]
12
+ when 'version'
13
+ puts NanoBot::GEM[:version]
14
+ exit
15
+ when 'help', '', nil
16
+ puts "Ruby Nano Bots #{NanoBot::GEM[:version]}"
17
+ puts ' rnb cartridge.yml - eval "Hello"'
18
+ puts ' rnb cartridge.yml - repl'
19
+ puts ' rnb cartridge.yml - debug'
20
+ puts ' rnb cartridge.yml STATE-KEY eval "Hello"'
21
+ puts ' rnb cartridge.yml STATE-KEY repl'
22
+ puts ' rnb cartridge.yml STATE-KEY debug'
23
+ puts ' rnb version'
24
+ exit
25
+ end
26
+
27
+ params = { cartridge_path: ARGV[0], state: ARGV[1], command: ARGV[2] }
28
+
29
+ bot = Instance.new(cartridge_path: params[:cartridge_path], state: params[:state])
30
+
31
+ case params[:command]
32
+ when 'eval'
33
+ params[:input] = ARGV[3..]&.join(' ')
34
+ params[:input] = $stdin.read.chomp if params[:input].nil? || params[:input].empty?
35
+ bot.eval(params[:input])
36
+ when 'repl'
37
+ bot.repl
38
+ when 'debug'
39
+ bot.debug
40
+ else
41
+ raise "TODO: [#{params[:command]}]"
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry'
4
+ require 'rainbow'
5
+
6
+ require_relative '../../logic/helpers/hash'
7
+
8
+ module NanoBot
9
+ module Controllers
10
+ module Interfaces
11
+ module REPL
12
+ def self.start(cartridge, session)
13
+ if Logic::Helpers::Hash.fetch(
14
+ cartridge, %i[interfaces repl prefix]
15
+ )
16
+ session.print(Logic::Helpers::Hash.fetch(cartridge,
17
+ %i[interfaces repl prefix]))
18
+ end
19
+
20
+ session.boot(mode: 'repl')
21
+
22
+ session.print(Logic::Helpers::Hash.fetch(cartridge, %i[interfaces repl postfix]) || "\n")
23
+
24
+ session.flush
25
+
26
+ prompt = build_prompt(cartridge[:interfaces][:repl][:prompt])
27
+
28
+ Pry.config.prompt = Pry::Prompt.new(
29
+ 'REPL',
30
+ 'REPL Prompt',
31
+ [proc { prompt }, proc { 'MISSING INPUT' }]
32
+ )
33
+
34
+ Pry.commands.block_command(/(.*)/, 'handler') do |line|
35
+ if Logic::Helpers::Hash.fetch(
36
+ cartridge, %i[interfaces repl prefix]
37
+ )
38
+ session.print(Logic::Helpers::Hash.fetch(
39
+ cartridge, %i[interfaces repl prefix]
40
+ ))
41
+ end
42
+
43
+ session.evaluate_and_print(line, mode: 'repl')
44
+ session.print(Logic::Helpers::Hash.fetch(cartridge, %i[interfaces repl postfix]) || "\n")
45
+ session.flush
46
+ end
47
+
48
+ Pry.start
49
+ end
50
+
51
+ def self.build_prompt(prompt)
52
+ result = ''
53
+
54
+ prompt.each do |partial|
55
+ result += if partial[:color]
56
+ Rainbow(partial[:text]).send(partial[:color])
57
+ else
58
+ partial[:text]
59
+ end
60
+ end
61
+
62
+ result
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'babosa'
4
+
5
+ require 'fileutils'
6
+
7
+ require_relative '../logic/helpers/hash'
8
+ require_relative '../components/storage'
9
+
10
+ module NanoBot
11
+ module Controllers
12
+ STREAM_TIMEOUT_IN_SECONDS = 5
13
+
14
+ class Session
15
+ def initialize(provider:, cartridge:, state: nil)
16
+ @provider = provider
17
+ @cartridge = cartridge
18
+
19
+ @output = $stdout
20
+
21
+ @stateless = state.nil? || state.strip == '-' || state.strip.empty?
22
+
23
+ if @stateless
24
+ @state = { history: [] }
25
+ else
26
+ @state_path = Components::Storage.build_path_and_ensure_state_file!(
27
+ state.strip, @cartridge
28
+ )
29
+ @state = load_state
30
+ end
31
+ end
32
+
33
+ def debug
34
+ pp({
35
+ state: {
36
+ path: @state_path,
37
+ content: @state
38
+ }
39
+ })
40
+ end
41
+
42
+ def load_state
43
+ @state = Logic::Helpers::Hash.symbolize_keys(JSON.parse(File.read(@state_path)))
44
+ end
45
+
46
+ def store_state!
47
+ File.write(@state_path, JSON.generate(@state))
48
+ end
49
+
50
+ def boot(mode:)
51
+ return unless Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors boot instruction])
52
+
53
+ behavior = Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors boot]) || {}
54
+
55
+ input = { behavior:, history: [] }
56
+
57
+ process(input, mode:)
58
+ end
59
+
60
+ def evaluate_and_print(message, mode:)
61
+ behavior = Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors interaction]) || {}
62
+
63
+ @state[:history] << ({ who: 'user', message: })
64
+
65
+ input = { behavior:, history: @state[:history] }
66
+
67
+ process(input, mode:)
68
+ end
69
+
70
+ def process(input, mode:)
71
+ streaming = @provider.settings[:stream] && Logic::Helpers::Hash.fetch(
72
+ @cartridge, [:interfaces, mode.to_sym, :stream]
73
+ )
74
+
75
+ interface = Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym]) || {}
76
+
77
+ input[:interface] = interface
78
+
79
+ updated_at = Time.now
80
+
81
+ ready = false
82
+ @provider.evaluate(input) do |output, finished|
83
+ updated_at = Time.now
84
+ if finished
85
+ @state[:history] << output
86
+ self.print(output[:message]) unless streaming
87
+ unless Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym, :postfix]).nil?
88
+ self.print(Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym, :postfix]))
89
+ end
90
+ ready = true
91
+ flush
92
+ elsif streaming
93
+ self.print(output[:message])
94
+ end
95
+ end
96
+
97
+ until ready
98
+ seconds = (Time.now - updated_at).to_i
99
+ raise StandardError, 'The stream has become unresponsive.' if seconds >= STREAM_TIMEOUT_IN_SECONDS
100
+ end
101
+
102
+ store_state! unless @stateless
103
+ end
104
+
105
+ def flush
106
+ @output.flush
107
+ end
108
+
109
+ def print(content)
110
+ @output.write(content)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NanoBot
4
+ module Logic
5
+ module Helpers
6
+ module Hash
7
+ def self.symbolize_keys(object)
8
+ case object
9
+ when ::Hash
10
+ object.each_with_object({}) do |(key, value), result|
11
+ result[key.to_sym] = symbolize_keys(value)
12
+ end
13
+ when Array
14
+ object.map { |e| symbolize_keys(e) }
15
+ else
16
+ object
17
+ end
18
+ end
19
+
20
+ def self.fetch(object, path)
21
+ node = object
22
+
23
+ return nil unless node
24
+
25
+ path.each do |key|
26
+ node = node[key]
27
+ break if node.nil?
28
+ end
29
+
30
+ node
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
data/nano-bots.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'static/gem'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = NanoBot::GEM[:name]
7
+ spec.version = NanoBot::GEM[:version]
8
+ spec.authors = [NanoBot::GEM[:author]]
9
+
10
+ spec.summary = NanoBot::GEM[:summary]
11
+ spec.description = NanoBot::GEM[:description]
12
+
13
+ spec.homepage = NanoBot::GEM[:github]
14
+
15
+ spec.license = NanoBot::GEM[:license]
16
+
17
+ spec.required_ruby_version = Gem::Requirement.new(">= #{NanoBot::GEM[:ruby]}")
18
+
19
+ spec.metadata['allowed_push_host'] = NanoBot::GEM[:gem_server]
20
+
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = NanoBot::GEM[:github]
23
+
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{\A(?:test|spec|features)/})
27
+ end
28
+ end
29
+
30
+ spec.require_paths = ['ports/dsl']
31
+
32
+ spec.executables = ['rnb']
33
+
34
+ spec.add_dependency 'babosa', '~> 2.0'
35
+ spec.add_dependency 'dotenv', '~> 2.8', '>= 2.8.1'
36
+ spec.add_dependency 'faraday', '~> 2.7', '>= 2.7.4'
37
+ spec.add_dependency 'pry', '~> 0.14.2'
38
+ spec.add_dependency 'rainbow', '~> 3.1', '>= 3.1.1'
39
+ spec.add_dependency 'ruby-openai', '~> 4.0'
40
+
41
+ spec.metadata['rubygems_mfa_required'] = 'true'
42
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nano-bots'
4
+
5
+ NanoBot.cli
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dotenv/load'
4
+
5
+ require_relative '../../static/gem'
6
+ require_relative '../../controllers/instance'
7
+ require_relative '../../controllers/interfaces/cli'
8
+
9
+ module NanoBot
10
+ def self.new(cartridge:, state: '-')
11
+ Controllers::Instance.new(cartridge_path: cartridge, state:)
12
+ end
13
+
14
+ def self.cli
15
+ Controllers::Interfaces::CLI.handle!
16
+ end
17
+
18
+ def self.repl(cartridge:, state: '-')
19
+ Controllers::Instance.new(cartridge_path: cartridge, state:).repl
20
+ end
21
+
22
+ def self.version
23
+ NanoBot::GEM[:version]
24
+ end
25
+ end
data/static/gem.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NanoBot
4
+ GEM = {
5
+ name: 'nano-bots',
6
+ version: '0.0.1',
7
+ author: 'icebaker',
8
+ summary: 'Ruby Implementation of Nano Bots: small, AI-powered bots',
9
+ description: 'Ruby Implementation of Nano Bots: small, AI-powered bots easily shared as a single file, designed to support multiple providers such as Vicuna, OpenAI ChatGPT, Google PaLM, Alpaca, and LLaMA.',
10
+ github: 'https://github.com/icebaker/ruby-nano-bots',
11
+ gem_server: 'https://rubygems.org',
12
+ license: 'MIT',
13
+ ruby: '3.1.4'
14
+ }.freeze
15
+ end
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nano-bots
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - icebaker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-05-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: babosa
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dotenv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.8'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 2.8.1
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '2.8'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 2.8.1
47
+ - !ruby/object:Gem::Dependency
48
+ name: faraday
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.7'
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 2.7.4
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '2.7'
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 2.7.4
67
+ - !ruby/object:Gem::Dependency
68
+ name: pry
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: 0.14.2
74
+ type: :runtime
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: 0.14.2
81
+ - !ruby/object:Gem::Dependency
82
+ name: rainbow
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '3.1'
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 3.1.1
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '3.1'
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 3.1.1
101
+ - !ruby/object:Gem::Dependency
102
+ name: ruby-openai
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '4.0'
108
+ type: :runtime
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '4.0'
115
+ description: 'Ruby Implementation of Nano Bots: small, AI-powered bots easily shared
116
+ as a single file, designed to support multiple providers such as Vicuna, OpenAI
117
+ ChatGPT, Google PaLM, Alpaca, and LLaMA.'
118
+ email:
119
+ executables:
120
+ - rnb
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - ".gitignore"
125
+ - ".rspec"
126
+ - ".rubocop.yml"
127
+ - Gemfile
128
+ - Gemfile.lock
129
+ - LICENSE
130
+ - README.md
131
+ - bin/rnb
132
+ - components/provider.rb
133
+ - components/providers/base.rb
134
+ - components/providers/openai.rb
135
+ - components/storage.rb
136
+ - controllers/instance.rb
137
+ - controllers/interfaces/cli.rb
138
+ - controllers/interfaces/repl.rb
139
+ - controllers/session.rb
140
+ - logic/helpers/hash.rb
141
+ - nano-bots.gemspec
142
+ - ports/dsl/nano-bots.rb
143
+ - ports/dsl/nano-bots/cli.rb
144
+ - static/gem.rb
145
+ homepage: https://github.com/icebaker/ruby-nano-bots
146
+ licenses:
147
+ - MIT
148
+ metadata:
149
+ allowed_push_host: https://rubygems.org
150
+ homepage_uri: https://github.com/icebaker/ruby-nano-bots
151
+ source_code_uri: https://github.com/icebaker/ruby-nano-bots
152
+ rubygems_mfa_required: 'true'
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - ports/dsl
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: 3.1.4
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubygems_version: 3.4.10
169
+ signing_key:
170
+ specification_version: 4
171
+ summary: 'Ruby Implementation of Nano Bots: small, AI-powered bots'
172
+ test_files: []