From 1c497945d4bbfef5dd44a074800bec83057bcc0a Mon Sep 17 00:00:00 2001 From: icebaker Date: Sun, 4 Jun 2023 11:49:00 -0300 Subject: new spec and end-user flow --- README.md | 88 ++++++++++++++++++++++++++---------------- components/provider.rb | 6 +-- components/providers/base.rb | 4 ++ components/providers/openai.rb | 48 ++++++++++++++++------- components/storage.rb | 44 +++++++++++++-------- controllers/security.rb | 4 ++ docker-compose.example.yml | 10 ++--- static/cartridges/baseline.yml | 15 ++++--- static/cartridges/default.yml | 1 - 9 files changed, 139 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index d43d25e..ff600bc 100644 --- a/README.md +++ b/README.md @@ -44,10 +44,11 @@ bundle install 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: ```sh -export NANO_BOTS_ENCRYPTION_PASSWORD='UNSAFE' export OPENAI_API_ADDRESS=https://api.openai.com -export OPENAI_API_ACCESS_TOKEN=your-token -export OPENAI_API_USER_IDENTIFIER=your-user +export OPENAI_API_KEY=your-access-token + +export NANO_BOTS_ENCRYPTION_PASSWORD=UNSAFE +export NANO_BOTS_END_USER=your-user # export NANO_BOTS_STATE_DIRECTORY=/home/user/.local/state/nano-bots # export NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges @@ -56,10 +57,11 @@ export OPENAI_API_USER_IDENTIFIER=your-user Alternatively, if your current directory has a `.env` file with the environment variables, they will be automatically loaded: ```sh -NANO_BOTS_ENCRYPTION_PASSWORD='UNSAFE' OPENAI_API_ADDRESS=https://api.openai.com -OPENAI_API_ACCESS_TOKEN=your-token -OPENAI_API_USER_IDENTIFIER=your-user +OPENAI_API_KEY=your-access-token + +NANO_BOTS_ENCRYPTION_PASSWORD=UNSAFE +NANO_BOTS_END_USER=your-user # NANO_BOTS_STATE_DIRECTORY=/home/user/.local/state/nano-bots # NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges @@ -85,13 +87,13 @@ services: image: ruby:3.2.2-slim-bullseye command: sh -c "gem install nano-bots -v 0.0.10 && bash" environment: - NANO_BOTS_ENCRYPTION_PASSWORD: UNSAFE OPENAI_API_ADDRESS: https://api.openai.com - OPENAI_API_ACCESS_TOKEN: your-token - OPENAI_API_USER_IDENTIFIER: your-user + OPENAI_API_KEY: your-access-token + NANO_BOTS_ENCRYPTION_PASSWORD: UNSAFE + NANO_BOTS_END_USER: your-user volumes: - - ./your-cartridges:/cartridges - # - ./your-data:/data + - ./your-cartridges:/.local/share/nano-bots/cartridges + - ./your-state:/.local/state/nano-bots ``` Enter the container: @@ -104,8 +106,8 @@ Start playing: nb - - eval "hello" nb - - repl -nb cartridges/assistant.yml - eval "hello" -nb cartridges/assistant.yml - repl +nb assistant.yml - eval "hello" +nb assistant.yml - repl ``` ## Usage @@ -256,13 +258,13 @@ behaviors: directive: You are a helpful assistant. provider: - name: openai + id: openai + credentials: + address: ENV/OPENAI_API_ADDRESS + access-token: ENV/OPENAI_API_KEY settings: + user: ENV/NANO_BOTS_END_USER model: gpt-3.5-turbo - credentials: - address: ENV/OPENAI_API_ADDRESS - access-token: ENV/OPENAI_API_ACCESS_TOKEN - user-identifier: ENV/OPENAI_API_USER_IDENTIFIER ``` Check the Nano Bots specification to learn more about [how to build cartridges](https://spec.nbots.io/#/README?id=cartridges). @@ -314,33 +316,48 @@ A common strategy for deploying Nano Bots to multiple users through APIs or auto You can define custom end-user identifiers in the following way: ```ruby -NanoBot.new(environment: { NANO_BOTS_USER_IDENTIFIER: 'user-a' }) -NanoBot.new(environment: { NANO_BOTS_USER_IDENTIFIER: 'user-b' }) +NanoBot.new(environment: { NANO_BOTS_END_USER: 'custom-user-a' }) +NanoBot.new(environment: { NANO_BOTS_END_USER: 'custom-user-b' }) ``` -Consider that you have have the following OpenAI user identifier: +Consider that you have the following end-user identifier in your environment: ```sh -OPENAI_API_USER_IDENTIFIER=your-name +NANO_BOTS_END_USER=your-name +``` + +Or a configuration in your Cartridge: +```yml +--- +provider: + id: openai + settings: + user: your-name ``` The requests will be performed as follows: ```ruby -NanoBot.new(environment: {NANO_BOTS_USER_IDENTIFIER: 'user-a'}) -# { user: 'your-name/user-a' } +NanoBot.new(cartridge: '-') +# { user: 'your-name' } -NanoBot.new(environment: {NANO_BOTS_USER_IDENTIFIER: 'user-b'}) -# { user: 'your-name/user-b' } +NanoBot.new(cartridge: '-', environment: { NANO_BOTS_END_USER: 'custom-user-a' }) +# { user: 'custom-user-a' } + +NanoBot.new(cartridge: '-', environment: { NANO_BOTS_END_USER: 'custom-user-b' }) +# { user: 'custom-user-b' } ``` Actually, to enhance privacy, neither your user nor your users' identifiers will be shared in this way. Instead, they will be encrypted before being shared with the provider: ```ruby -'your-name/user-a' -# -onBK9GWafYz-JM8-cydhn4jd4Bkfkec5FtJ1ReCrtHCDPjkhCqUjRobG1zLnAz3BLo1kFhRW3w= +'your-name' +# _O7OjYUESagb46YSeUeSfSMzoO1Yg0BZqpsAkPg4j62SeNYlgwq3kn51Ob2wmIehoA== + +'custom-user-a' +# _O7OjYUESagb46YSeUeSfSMzoO1Yg0BZJgIXHCBHyADW-rn4IQr-s2RvP7vym8u5tnzYMIs= -'your-name/user-a' -# RldW4_xxktCksEAR9G8aORuq3skPAc9ivWj3eye2ICCHQy8gG_R5qLMS3Fg-0lY6LwxKGQur5Ww= +'custom-user-b' +# _O7OjYUESagb46YSeUeSfSMzoO1Yg0BZkjUwCcsh9sVppKvYMhd2qGRvP7vym8u5tnzYMIg= ``` 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`). @@ -352,11 +369,14 @@ To decrypt your encrypted data, once you have properly configured your password, ```ruby require 'nano-bots' -NanoBot.security.decrypt('-onBK9GWafYz-JM8-cydhn4jd4Bkfkec5FtJ1ReCrtHCDPjkhCqUjRobG1zLnAz3BLo1kFhRW3w=') -# your-name/user-b +NanoBot.security.decrypt('_O7OjYUESagb46YSeUeSfSMzoO1Yg0BZqpsAkPg4j62SeNYlgwq3kn51Ob2wmIehoA==') +# your-name + +NanoBot.security.decrypt('_O7OjYUESagb46YSeUeSfSMzoO1Yg0BZJgIXHCBHyADW-rn4IQr-s2RvP7vym8u5tnzYMIs=') +# custom-user-a -NanoBot.security.decrypt('RldW4_xxktCksEAR9G8aORuq3skPAc9ivWj3eye2ICCHQy8gG_R5qLMS3Fg-0lY6LwxKGQur5Ww=') -# your-name/user-b +NanoBot.security.decrypt('_O7OjYUESagb46YSeUeSfSMzoO1Yg0BZkjUwCcsh9sVppKvYMhd2qGRvP7vym8u5tnzYMIg=') +# custom-user-b ``` If you lose your password, you lose your data. It is not possible to recover it at all. For real. diff --git a/components/provider.rb b/components/provider.rb index 3138cc4..163b099 100644 --- a/components/provider.rb +++ b/components/provider.rb @@ -8,11 +8,11 @@ module NanoBot module Components class Provider def self.new(provider, environment: {}) - case provider[:name] + case provider[:id] when 'openai' - Providers::OpenAI.new(provider[:settings], environment:) + Providers::OpenAI.new(provider[:settings], provider[:credentials], environment:) else - raise "Unsupported provider #{provider[:name]}" + raise "Unsupported provider \"#{provider[:id]}\"" end end end diff --git a/components/providers/base.rb b/components/providers/base.rb index 011c5dd..7a99833 100644 --- a/components/providers/base.rb +++ b/components/providers/base.rb @@ -6,6 +6,10 @@ module NanoBot module Components module Providers class Base + def initialize(_settings, _credentials, _environment: {}) + raise NoMethodError, "The 'initialize' method is not implemented for the current provider." + end + def evaluate(_payload) raise NoMethodError, "The 'evaluate' method is not implemented for the current provider." end diff --git a/components/providers/openai.rb b/components/providers/openai.rb index c64a588..ce6fb33 100644 --- a/components/providers/openai.rb +++ b/components/providers/openai.rb @@ -9,6 +9,8 @@ module NanoBot module Components module Providers class OpenAI < Base + DEFAULT_ADDRESS = 'https://api.openai.com' + CHAT_SETTINGS = %i[ model stream temperature top_p n stop max_tokens presence_penalty frequency_penalty logit_bias @@ -16,14 +18,18 @@ module NanoBot attr_reader :settings - def initialize(settings, environment: {}) + def initialize(settings, credentials, environment: {}) @settings = settings + @credentials = credentials @environment = environment - @client = ::OpenAI::Client.new( - uri_base: "#{@settings[:credentials][:address].sub(%r{/$}, '')}/", - access_token: @settings[:credentials][:'access-token'] - ) + uri_base = if @credentials[:address].nil? || @credentials[:address].to_s.strip.empty? + "#{DEFAULT_ADDRESS}/" + else + "#{@credentials[:address].to_s.sub(%r{/$}, '')}/" + end + + @client = ::OpenAI::Client.new(uri_base:, access_token: @credentials[:'access-token']) end def stream(input) @@ -48,16 +54,7 @@ module NanoBot ) end - user = @settings[:credentials][:'user-identifier'] - - user_suffix = @environment && ( - @environment['NANO_BOTS_USER_IDENTIFIER'] || - @environment[:NANO_BOTS_USER_IDENTIFIER] - ) - - user = "#{user}/#{user_suffix}" if user_suffix && user_suffix != '' - - payload = { model: @settings[:model], user: Crypto.encrypt(user, soft: true), messages: } + payload = { user: OpenAI.end_user(@settings, @environment), messages: } CHAT_SETTINGS.each do |key| payload[key] = @settings[key] if @settings.key?(key) @@ -87,6 +84,27 @@ module NanoBot block.call({ who: 'AI', message: result.dig('choices', 0, 'message', 'content') }, true) end end + + def self.end_user(settings, environment) + user = ENV.fetch('NANO_BOTS_END_USER', nil) + + user = settings[:user] if !settings[:user].nil? && !settings[:user].to_s.strip.empty? + + candidate = environment && ( + environment['NANO_BOTS_END_USER'] || + environment[:NANO_BOTS_END_USER] + ) + + user = candidate if !candidate.nil? && !candidate.to_s.strip.empty? + + user = if user.nil? || user.to_s.strip.empty? + 'unknown' + else + user.to_s.strip + end + + Crypto.encrypt(user, soft: true) + end end end end diff --git a/components/storage.rb b/components/storage.rb index b6d8910..577ce67 100644 --- a/components/storage.rb +++ b/components/storage.rb @@ -8,6 +8,31 @@ require_relative './crypto' module NanoBot module Components class Storage + def self.end_user(cartridge, environment) + user = ENV.fetch('NANO_BOTS_END_USER', nil) + + if cartridge[:provider][:id] == 'openai' && + !cartridge[:provider][:settings][:user].nil? && + !cartridge[:provider][:settings][:user].to_s.strip.empty? + user = cartridge[:provider][:settings][:user] + end + + candidate = environment && ( + environment['NANO_BOTS_END_USER'] || + environment[:NANO_BOTS_END_USER] + ) + + user = candidate if !candidate.nil? && !candidate.to_s.strip.empty? + + user = if user.nil? || user.to_s.strip.empty? + 'unknown' + else + user.to_s.strip + end + + Crypto.encrypt(user, soft: true) + end + def self.build_path_and_ensure_state_file!(key, cartridge, environment: {}) path = [ Logic::Helpers::Hash.fetch(cartridge, %i[state directory]), @@ -18,27 +43,12 @@ module NanoBot path = "#{user_home!.sub(%r{/$}, '')}/.local/state/nano-bots" if path.nil? - prefix = environment && ( - environment['NANO_BOTS_USER_IDENTIFIER'] || - environment[:NANO_BOTS_USER_IDENTIFIER] - ) - - path = "#{path.sub(%r{/$}, '')}/ruby-nano-bots/vault" - - if prefix - normalized = prefix.split('/').map do |part| - Crypto.encrypt( - part.to_s.gsub('.', '-').force_encoding('UTF-8').to_slug.normalize, - soft: true - ) - end.join('/') - - path = "#{path}/#{normalized}" - end + path = "#{path.sub(%r{/$}, '')}/ruby-nano-bots" path = "#{path}/#{cartridge[:meta][:author].to_slug.normalize}" path = "#{path}/#{cartridge[:meta][:name].to_slug.normalize}" path = "#{path}/#{cartridge[:meta][:version].to_s.gsub('.', '-').to_slug.normalize}" + path = "#{path}/#{end_user(cartridge, environment)}" path = "#{path}/#{Crypto.encrypt(key, soft: true)}" path = "#{path}/state.json" diff --git a/controllers/security.rb b/controllers/security.rb index 8f066a5..c1180f2 100644 --- a/controllers/security.rb +++ b/controllers/security.rb @@ -9,6 +9,10 @@ module NanoBot Components::Crypto.decrypt(content) end + def self.encrypt(content, soft: false) + Components::Crypto.encrypt(content, soft:) + end + def self.check password = ENV.fetch('NANO_BOTS_ENCRYPTION_PASSWORD', nil) password = 'UNSAFE' unless password && password != '' diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 4b7b5ec..51b96aa 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -5,10 +5,10 @@ services: image: ruby:3.2.2-slim-bullseye command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev && gem install nano-bots -v 0.0.10 && bash" environment: - NANO_BOTS_ENCRYPTION_PASSWORD: UNSAFE OPENAI_API_ADDRESS: https://api.openai.com - OPENAI_API_ACCESS_TOKEN: your-token - OPENAI_API_USER_IDENTIFIER: your-user + OPENAI_API_KEY: your-access-token + NANO_BOTS_ENCRYPTION_PASSWORD: UNSAFE + NANO_BOTS_END_USER: your-user volumes: - - ./your-cartridges:/cartridges - # - ./your-data:/data + - ./your-cartridges:/.local/share/nano-bots/cartridges + - ./your-state:/.local/state/nano-bots diff --git a/static/cartridges/baseline.yml b/static/cartridges/baseline.yml index b5eed62..50c4756 100644 --- a/static/cartridges/baseline.yml +++ b/static/cartridges/baseline.yml @@ -1,14 +1,17 @@ --- meta: + symbol: 🤖 name: Unknown - author: Nobody + author: None version: 0.0.0 + license: CC0-1.0 + description: Unknown provider: - name: openai + id: openai + credentials: + address: ENV/OPENAI_API_ADDRESS + access-token: ENV/OPENAI_API_KEY settings: + user: ENV/NANO_BOTS_END_USER model: gpt-3.5-turbo - credentials: - address: ENV/OPENAI_API_ADDRESS - access-token: ENV/OPENAI_API_ACCESS_TOKEN - user-identifier: ENV/OPENAI_API_USER_IDENTIFIER diff --git a/static/cartridges/default.yml b/static/cartridges/default.yml index 9a89a4b..2cadc8c 100644 --- a/static/cartridges/default.yml +++ b/static/cartridges/default.yml @@ -8,7 +8,6 @@ interfaces: prompt: - text: '🤖' - text: '> ' - color: blue eval: output: stream: true -- cgit v1.2.3