diff options
author | icebaker <icebaker@proton.me> | 2023-11-18 20:08:34 -0300 |
---|---|---|
committer | icebaker <icebaker@proton.me> | 2023-11-18 20:08:34 -0300 |
commit | 89962f27a75183947fc44cd051a1061ce157221d (patch) | |
tree | 1d39a227a58ffc24a73401f697592af229be1d0c | |
parent | 989c276b6acf9d0e2b584d980b72a4eb9564a77c (diff) |
adding safety sandbox
-rw-r--r-- | components/adapter.rb | 8 | ||||
-rw-r--r-- | components/embedding.rb | 20 | ||||
-rw-r--r-- | components/providers/openai.rb | 6 | ||||
-rw-r--r-- | components/providers/openai/tools.rb | 16 | ||||
-rw-r--r-- | controllers/interfaces/tools.rb | 7 | ||||
-rw-r--r-- | controllers/session.rb | 6 | ||||
-rw-r--r-- | logic/cartridge/safety.rb | 18 | ||||
-rw-r--r-- | static/cartridges/default.yml | 6 |
8 files changed, 68 insertions, 19 deletions
diff --git a/components/adapter.rb b/components/adapter.rb index a79fee6..32aa169 100644 --- a/components/adapter.rb +++ b/components/adapter.rb @@ -1,16 +1,20 @@ # frozen_string_literal: true require_relative 'embedding' +require_relative '../logic/cartridge/safety' module NanoBot module Components class Adapter - def self.apply(_direction, params) + def self.apply(params, cartridge) content = params[:content] raise StandardError, 'conflicting adapters' if %i[fennel lua clojure].count { |key| !params[key].nil? } > 1 - call = { parameters: %w[content], values: [content], safety: false } + call = { + parameters: %w[content], values: [content], + safety: { sandboxed: Logic::Cartridge::Safety.sandboxed?(cartridge) } + } if params[:fennel] call[:source] = params[:fennel] diff --git a/components/embedding.rb b/components/embedding.rb index c464244..46d0fba 100644 --- a/components/embedding.rb +++ b/components/embedding.rb @@ -9,10 +9,18 @@ require 'tempfile' module NanoBot module Components class Embedding + def self.ensure_safety!(safety) + raise 'missing safety definitions' unless safety.key?(:sandboxed) + end + def self.lua(source:, parameters:, values:, safety:) + ensure_safety!(safety) + + allowed = '' + allowed = ', {math=math,string=string,table=table}' if safety[:sandboxed] + state = SweetMoon::State.new - # code = "_, embedded = pcall(load([[\nreturn function(#{parameters.join(', ')})\nreturn #{source}\nend\n]], nil, 't', {math=math,string=string,table=table}))" - code = "_, embedded = pcall(load([[\nreturn function(#{parameters.join(', ')})\n#{source}\nend\n]], nil, 't'))" + code = "_, embedded = pcall(load([[\nreturn function(#{parameters.join(', ')})\n#{source}\nend\n]], nil, 't'#{allowed}))" state.eval(code) embedded = state.get(:embedded) @@ -20,19 +28,25 @@ module NanoBot end def self.fennel(source:, parameters:, values:, safety:) + ensure_safety!(safety) + path = "#{File.expand_path('../static/fennel', __dir__)}/?.lua" state = SweetMoon::State.new(package_path: path).fennel # TODO: global is deprecated... state.fennel.eval( "(global embedded (fn [#{parameters.join(' ')}] #{source}))", 1, - safety ? { allowedGlobals: %w[math string table] } : nil + safety[:sandboxed] ? { allowedGlobals: %w[math string table] } : nil ) embedded = state.get(:embedded) embedded.call(values) end def self.clojure(source:, parameters:, values:, safety:) + ensure_safety!(safety) + + raise 'TODO: sandboxed Clojure through Babashka not implemented' if safety[:sandboxed] + raise 'invalid Clojure parameter name' if parameters.include?('injected-parameters') key_value = {} diff --git a/components/providers/openai.rb b/components/providers/openai.rb index bf5ae73..996f7f6 100644 --- a/components/providers/openai.rb +++ b/components/providers/openai.rb @@ -45,7 +45,7 @@ module NanoBot provider && interface end - def evaluate(input, &feedback) + def evaluate(input, cartridge, &feedback) messages = input[:history].map do |event| if event[:message].nil? && event[:meta] && event[:meta][:tool_calls] { role: 'assistant', content: nil, tool_calls: event[:meta][:tool_calls] } @@ -122,7 +122,7 @@ module NanoBot needs_another_round: true, interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } } ) - Tools.apply(input[:tools], tools, feedback).each do |interaction| + Tools.apply(cartridge, input[:tools], tools, feedback).each do |interaction| feedback.call({ should_be_stored: true, needs_another_round: true, interaction: }) end end @@ -149,7 +149,7 @@ module NanoBot needs_another_round: true, interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } } ) - Tools.apply(input[:tools], tools, feedback).each do |interaction| + Tools.apply(cartridge, input[:tools], tools, feedback).each do |interaction| feedback.call({ should_be_stored: true, needs_another_round: true, interaction: }) end end diff --git a/components/providers/openai/tools.rb b/components/providers/openai/tools.rb index ea34ae6..50eead9 100644 --- a/components/providers/openai/tools.rb +++ b/components/providers/openai/tools.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative '../../embedding' +require_relative '../../../logic/cartridge/safety' require 'concurrent' @@ -9,11 +10,12 @@ module NanoBot module Providers class OpenAI < Base module Tools - def self.apply(cartridge, tools, feedback) - prepared_tools = NanoBot::Logic::OpenAI::Tools.prepare(cartridge, tools) + def self.apply(cartridge, function_cartridge, tools, feedback) + prepared_tools = NanoBot::Logic::OpenAI::Tools.prepare(function_cartridge, tools) + # TODO: Confirm before starting futures. futures = prepared_tools.map do |tool| - Concurrent::Promises.future { process!(tool, feedback) } + Concurrent::Promises.future { process!(tool, feedback, function_cartridge, cartridge) } end results = Concurrent::Promises.zip(*futures).value! @@ -27,7 +29,7 @@ module NanoBot end end - def self.process!(tool, feedback) + def self.process!(tool, feedback, _function_cartridge, cartridge) feedback.call( { should_be_stored: false, interaction: { who: 'AI', message: nil, meta: { @@ -35,7 +37,11 @@ module NanoBot } } } ) - call = { parameters: %w[parameters], values: [tool[:parameters]], safety: false } + call = { + parameters: %w[parameters], + values: [tool[:parameters]], + safety: { sandboxed: Logic::Cartridge::Safety.sandboxed?(cartridge) } + } if %i[fennel lua clojure].count { |key| !tool[:source][key].nil? } > 1 raise StandardError, 'conflicting tools' diff --git a/controllers/interfaces/tools.rb b/controllers/interfaces/tools.rb index 5105da1..1136600 100644 --- a/controllers/interfaces/tools.rb +++ b/controllers/interfaces/tools.rb @@ -3,13 +3,14 @@ require 'rainbow' require_relative '../../logic/cartridge/tools' +require_relative '../../logic/cartridge/safety' require_relative '../../components/embedding' module NanoBot module Controllers module Interfaces module Tool - def self.adapt(feedback, adapter) + def self.adapt(feedback, adapter, cartridge) call = { parameters: %w[id name parameters parameters-as-json output], values: [ @@ -17,7 +18,7 @@ module NanoBot feedback[:parameters].to_json, feedback[:output] ], - safety: false + safety: { sandboxed: Logic::Cartridge::Safety.sandboxed?(cartridge) } } raise StandardError, 'conflicting adapters' if %i[fennel lua clojure].count { |key| !adapter[key].nil? } > 1 @@ -49,7 +50,7 @@ module NanoBot adapter = Tool.adapter(cartridge, mode, feedback) if %i[fennel lua clojure].any? { |key| !adapter[key].nil? } - message = adapt(feedback, adapter) + message = adapt(feedback, adapter, cartridge) else message = "(#{feedback[:name]} #{feedback[:parameters].to_json})" diff --git a/controllers/session.rb b/controllers/session.rb index 10d0194..546a891 100644 --- a/controllers/session.rb +++ b/controllers/session.rb @@ -72,7 +72,7 @@ module NanoBot mode: mode.to_s, input: message, message: Components::Adapter.apply( - :input, Logic::Cartridge::Interaction.input(@cartridge, mode.to_sym, message) + Logic::Cartridge::Interaction.input(@cartridge, mode.to_sym, message), @cartridge ) } @@ -117,7 +117,7 @@ module NanoBot needs_another_round = false - @provider.evaluate(input) do |feedback| + @provider.evaluate(input, @cartridge) do |feedback| updated_at = Time.now needs_another_round = true if feedback[:needs_another_round] @@ -137,7 +137,7 @@ module NanoBot output = Logic::Cartridge::Interaction.output( @cartridge, mode.to_sym, feedback[:interaction], streaming, feedback[:finished] ) - output[:message] = Components::Adapter.apply(:output, output[:message]) + output[:message] = Components::Adapter.apply(output[:message], @cartridge) event[:output] = (output[:message]).to_s end end diff --git a/logic/cartridge/safety.rb b/logic/cartridge/safety.rb new file mode 100644 index 0000000..84b39d7 --- /dev/null +++ b/logic/cartridge/safety.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative 'fetch' + +module NanoBot + module Logic + module Cartridge + module Safety + def self.sandboxed?(cartridge) + sandboxed = Fetch.cascate(cartridge, [%i[safety functions sandboxed]]) + return true if sandboxed.nil? + + sandboxed + end + end + end + end +end diff --git a/static/cartridges/default.yml b/static/cartridges/default.yml index 167fa83..57eeada 100644 --- a/static/cartridges/default.yml +++ b/static/cartridges/default.yml @@ -1,4 +1,10 @@ --- +safety: + functions: + sandboxed: true + tools: + confirmable: true + interfaces: repl: output: |