summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoricebaker <icebaker@proton.me>2023-11-18 20:08:34 -0300
committericebaker <icebaker@proton.me>2023-11-18 20:08:34 -0300
commit89962f27a75183947fc44cd051a1061ce157221d (patch)
tree1d39a227a58ffc24a73401f697592af229be1d0c
parent989c276b6acf9d0e2b584d980b72a4eb9564a77c (diff)
adding safety sandbox
-rw-r--r--components/adapter.rb8
-rw-r--r--components/embedding.rb20
-rw-r--r--components/providers/openai.rb6
-rw-r--r--components/providers/openai/tools.rb16
-rw-r--r--controllers/interfaces/tools.rb7
-rw-r--r--controllers/session.rb6
-rw-r--r--logic/cartridge/safety.rb18
-rw-r--r--static/cartridges/default.yml6
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: