diff options
-rw-r--r-- | Gemfile.lock | 2 | ||||
-rw-r--r-- | components/storage.rb | 24 | ||||
-rw-r--r-- | controllers/cartridges.rb | 28 | ||||
-rw-r--r-- | controllers/instance.rb | 7 | ||||
-rw-r--r-- | logic/cartridge/parser.rb | 56 | ||||
-rw-r--r-- | nano-bots.gemspec | 1 | ||||
-rw-r--r-- | ports/dsl/nano-bots.rb | 3 | ||||
-rw-r--r-- | ports/dsl/nano-bots/cartridges.rb | 15 | ||||
-rw-r--r-- | spec/data/cartridges/markdown.md | 37 | ||||
-rw-r--r-- | spec/logic/cartridge/parser_spec.rb | 31 |
10 files changed, 178 insertions, 26 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 6b3cc60..fc0da2e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,6 +13,7 @@ PATH pry (~> 0.14.2) rainbow (~> 3.1, >= 3.1.1) rbnacl (~> 7.1, >= 7.1.1) + redcarpet (~> 3.6) ruby-openai (~> 6.3, >= 6.3.1) sweet-moon (~> 0.0.7) @@ -82,6 +83,7 @@ GEM rainbow (3.1.1) rbnacl (7.1.1) ffi + redcarpet (3.6.0) regexp_parser (2.8.3) rexml (3.2.6) rspec (3.12.0) diff --git a/components/storage.rb b/components/storage.rb index 6a3fe13..839cd02 100644 --- a/components/storage.rb +++ b/components/storage.rb @@ -8,6 +8,8 @@ require_relative 'crypto' module NanoBot module Components class Storage + EXTENSIONS = %w[yml yaml markdown mdown mkdn md].freeze + def self.end_user(cartridge, environment) user = ENV.fetch('NANO_BOTS_END_USER', nil) @@ -74,11 +76,11 @@ module NanoBot def self.cartridge_path(path) partial = File.join(File.dirname(path), File.basename(path, File.extname(path))) - candidates = [ - path, - "#{partial}.yml", - "#{partial}.yaml" - ] + candidates = [path] + + EXTENSIONS.each do |extension| + candidates << "#{partial}.#{extension}" + end unless ENV.fetch('NANO_BOTS_CARTRIDGES_DIRECTORY', nil).nil? directory = ENV.fetch('NANO_BOTS_CARTRIDGES_DIRECTORY').sub(%r{/$}, '') @@ -88,8 +90,10 @@ module NanoBot partial = partial.sub(%r{^\.?/}, '') candidates << "#{directory}/#{partial}" - candidates << "#{directory}/#{partial}.yml" - candidates << "#{directory}/#{partial}.yaml" + + EXTENSIONS.each do |extension| + candidates << "#{directory}/#{partial}.#{extension}" + end end directory = "#{user_home!.sub(%r{/$}, '')}/.local/share/nano-bots/cartridges" @@ -99,8 +103,10 @@ module NanoBot partial = partial.sub(%r{^\.?/}, '') candidates << "#{directory}/#{partial}" - candidates << "#{directory}/#{partial}.yml" - candidates << "#{directory}/#{partial}.yaml" + + EXTENSIONS.each do |extension| + candidates << "#{directory}/#{partial}.#{extension}" + end candidates = candidates.uniq diff --git a/controllers/cartridges.rb b/controllers/cartridges.rb index df474a9..7215a99 100644 --- a/controllers/cartridges.rb +++ b/controllers/cartridges.rb @@ -3,16 +3,21 @@ require_relative '../components/storage' require_relative '../logic/helpers/hash' require_relative '../logic/cartridge/default' +require_relative '../logic/cartridge/parser' module NanoBot module Controllers class Cartridges + def self.load(path) + Logic::Cartridge::Parser.parse(File.read(path), format: File.extname(path)) + end + def self.all files = {} path = Components::Storage.cartridges_path - Dir.glob("#{path}/**/*.{yml,yaml}").each do |file| + Dir.glob("#{path}/**/*.{yml,yaml,markdown,mdown,mkdn,md}").each do |file| files[Pathname.new(file).realpath] = { base: path, path: Pathname.new(file).realpath @@ -22,16 +27,17 @@ module NanoBot cartridges = [] files.values.uniq.map do |file| - cartridge = Logic::Helpers::Hash.symbolize_keys( - YAML.safe_load_file(file[:path], permitted_classes: [Symbol]) - ).merge({ - system: { - id: file[:path].to_s.sub(/^#{Regexp.escape(file[:base])}/, '').sub(%r{^/}, '').sub(/\.[^.]+\z/, - ''), - path: file[:path], - base: file[:base] - } - }) + cartridge = load_cartridge(file[:path]).merge( + { + system: { + id: file[:path].to_s.sub( + /^#{Regexp.escape(file[:base])}/, '' + ).sub(%r{^/}, '').sub(/\.[^.]+\z/, ''), + path: file[:path], + base: file[:base] + } + } + ) next if cartridge[:meta][:name].nil? diff --git a/controllers/instance.rb b/controllers/instance.rb index 259a548..85b97ec 100644 --- a/controllers/instance.rb +++ b/controllers/instance.rb @@ -1,11 +1,10 @@ # frozen_string_literal: true -require 'yaml' - require_relative '../logic/helpers/hash' require_relative '../components/provider' require_relative '../components/storage' require_relative '../components/stream' +require_relative 'cartridges' require_relative 'interfaces/repl' require_relative 'interfaces/eval' require_relative 'session' @@ -83,13 +82,11 @@ module NanoBot raise StandardError, "Cartridge file not found: \"#{path}\"" end - @cartridge = YAML.safe_load_file(elected_path, permitted_classes: [Symbol]) + @cartridge = Cartridges.load(elected_path) end @safe_cartridge = Marshal.load(Marshal.dump(@cartridge)) - @cartridge = Logic::Helpers::Hash.symbolize_keys(@cartridge) - inject_environment_variables!(@cartridge) end diff --git a/logic/cartridge/parser.rb b/logic/cartridge/parser.rb new file mode 100644 index 0000000..440c929 --- /dev/null +++ b/logic/cartridge/parser.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'singleton' + +require 'redcarpet' +require 'redcarpet/render_strip' + +module NanoBot + module Logic + module Cartridge + module Parser + def self.parse(raw, format:) + normalized = format.to_s.downcase.gsub('.', '') + + if %w[yml yaml].include?(normalized) + yaml(raw) + elsif %w[markdown mdown mkdn md].include?(normalized) + markdown(raw) + else + raise "Unknown cartridge format: '#{format}'" + end + end + + def self.markdown(raw) + yaml(Markdown.instance.render(raw)) + end + + def self.yaml(raw) + Logic::Helpers::Hash.symbolize_keys( + YAML.safe_load(raw, permitted_classes: [Symbol]) + ) + end + + class Renderer < Redcarpet::Render::Base + def block_code(code, _language) + "\n#{code}\n" + end + end + + class Markdown + include Singleton + + attr_reader :markdown + + def initialize + @markdown = Redcarpet::Markdown.new(Renderer, fenced_code_blocks: true) + end + + def render(raw) + @markdown.render(raw) + end + end + end + end + end +end diff --git a/nano-bots.gemspec b/nano-bots.gemspec index f843f37..87c4f48 100644 --- a/nano-bots.gemspec +++ b/nano-bots.gemspec @@ -37,6 +37,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'pry', '~> 0.14.2' spec.add_dependency 'rainbow', '~> 3.1', '>= 3.1.1' spec.add_dependency 'rbnacl', '~> 7.1', '>= 7.1.1' + spec.add_dependency 'redcarpet', '~> 3.6' spec.add_dependency 'sweet-moon', '~> 0.0.7' spec.add_dependency 'cohere-ai', '~> 1.0', '>= 1.0.1' diff --git a/ports/dsl/nano-bots.rb b/ports/dsl/nano-bots.rb index e01b2c4..20d8f14 100644 --- a/ports/dsl/nano-bots.rb +++ b/ports/dsl/nano-bots.rb @@ -8,6 +8,7 @@ require_relative '../../controllers/instance' require_relative '../../controllers/security' require_relative '../../controllers/interfaces/cli' require_relative '../../components/stream' +require_relative 'nano-bots/cartridges' module NanoBot def self.new(cartridge: '-', state: '-', environment: {}) @@ -24,7 +25,7 @@ module NanoBot end def self.cartridges - Controllers::Cartridges.all + Cartridges end def self.cli diff --git a/ports/dsl/nano-bots/cartridges.rb b/ports/dsl/nano-bots/cartridges.rb new file mode 100644 index 0000000..7c0f05b --- /dev/null +++ b/ports/dsl/nano-bots/cartridges.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative '../../controllers/cartridges' + +module NanoBot + module Cartridges + def self.all + Controllers::Cartridges.all + end + + def self.load(path) + Controllers::Cartridges.load(path) + end + end +end diff --git a/spec/data/cartridges/markdown.md b/spec/data/cartridges/markdown.md new file mode 100644 index 0000000..cd50b7b --- /dev/null +++ b/spec/data/cartridges/markdown.md @@ -0,0 +1,37 @@ +A cartridge is a YAML file with human-readable data that outlines the bot's goals, expected behaviors, and settings for authentication and provider utilization. + +We begin with the meta section, which provides information about what this cartridge is designed for: + +```yaml +meta: + symbol: 🤖 + name: ChatGPT 4 Turbo + author: icebaker + version: 0.0.1 + license: CC0-1.0 + description: A helpful assistant. +``` + +It includes details like versioning and license. + +Next, we add a behavior section that will provide the bot with a directive on how it should behave: + +```yaml +behaviors: + interaction: + directive: You are a helpful assistant. +``` + +Now, we need to provide instructions on how this Nano Bot should connect with a provider, which credentials to use, and what specific configurations for the LLM are required: + +```yaml +provider: + id: openai + credentials: + access-token: ENV/OPENAI_API_KEY + settings: + user: ENV/NANO_BOTS_END_USER + model: gpt-4-1106-preview +``` + +In my API, I have set the environment variables `OPENAI_API_KEY` and `NANO_BOTS_END_USER`, which is where the values for these will come from. diff --git a/spec/logic/cartridge/parser_spec.rb b/spec/logic/cartridge/parser_spec.rb new file mode 100644 index 0000000..f8d1302 --- /dev/null +++ b/spec/logic/cartridge/parser_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative '../../../logic/cartridge/parser' + +RSpec.describe NanoBot::Logic::Cartridge::Parser do + context 'markdown' do + let(:raw) { File.read('spec/data/cartridges/markdown.md') } + + it 'parses markdown cartridge' do + expect(described_class.parse(raw, format: 'md')).to eq( + { meta: { + symbol: '🤖', + name: 'ChatGPT 4 Turbo', + author: 'icebaker', + version: '0.0.1', + license: 'CC0-1.0', + description: 'A helpful assistant.' + }, + behaviors: { interaction: { directive: 'You are a helpful assistant.' } }, + provider: { + id: 'openai', + credentials: { 'access-token': 'ENV/OPENAI_API_KEY' }, + settings: { + user: 'ENV/NANO_BOTS_END_USER', + model: 'gpt-4-1106-preview' + } + } } + ) + end + end +end |