From c470d63b169058d81f44569a5f1c4c1fb222279f Mon Sep 17 00:00:00 2001 From: icebaker Date: Sat, 18 Nov 2023 21:45:16 -0300 Subject: adding support for confirm --- components/embedding.rb | 2 +- components/providers/openai/tools.rb | 26 ++++++++++++- controllers/interfaces/tools.rb | 13 +++++++ controllers/session.rb | 75 ++++++++++++++++++++---------------- logic/cartridge/safety.rb | 21 ++++++++++ 5 files changed, 101 insertions(+), 36 deletions(-) diff --git a/components/embedding.rb b/components/embedding.rb index 46d0fba..f08ff3d 100644 --- a/components/embedding.rb +++ b/components/embedding.rb @@ -33,7 +33,7 @@ module NanoBot path = "#{File.expand_path('../static/fennel', __dir__)}/?.lua" state = SweetMoon::State.new(package_path: path).fennel - # TODO: global is deprecated... + # TODO: `global` is deprecated. state.fennel.eval( "(global embedded (fn [#{parameters.join(' ')}] #{source}))", 1, safety[:sandboxed] ? { allowedGlobals: %w[math string table] } : nil diff --git a/components/providers/openai/tools.rb b/components/providers/openai/tools.rb index 50eead9..10c2709 100644 --- a/components/providers/openai/tools.rb +++ b/components/providers/openai/tools.rb @@ -10,12 +10,34 @@ module NanoBot module Providers class OpenAI < Base module Tools + def self.confirm(tool, feedback) + feedback.call( + { should_be_stored: false, + interaction: { who: 'AI', message: nil, meta: { + tool: { action: 'confirm', id: tool[:id], name: tool[:name], parameters: tool[:parameters] } + } } } + ) + end + def self.apply(cartridge, function_cartridge, tools, feedback) prepared_tools = NanoBot::Logic::OpenAI::Tools.prepare(function_cartridge, tools) - # TODO: Confirm before starting futures. + if Logic::Cartridge::Safety.confirmable?(cartridge) + prepared_tools.each { |tool| tool[:allowed] = confirm(tool, feedback) } + else + prepared_tools.each { |tool| tool[:allowed] = true } + end + futures = prepared_tools.map do |tool| - Concurrent::Promises.future { process!(tool, feedback, function_cartridge, cartridge) } + Concurrent::Promises.future do + if tool[:allowed] + process!(tool, feedback, function_cartridge, cartridge) + else + tool[:output] = + "We asked the user you're chatting with for permission, but the user did not allow you to run this tool or function." + tool + end + end end results = Concurrent::Promises.zip(*futures).value! diff --git a/controllers/interfaces/tools.rb b/controllers/interfaces/tools.rb index 1136600..d32afed 100644 --- a/controllers/interfaces/tools.rb +++ b/controllers/interfaces/tools.rb @@ -10,6 +10,17 @@ module NanoBot module Controllers module Interfaces module Tool + def self.confirm(session, cartridge, mode, feedback) + yeses = Logic::Cartridge::Safety.yeses(cartridge) + default_answer = Logic::Cartridge::Safety.default_answer(cartridge) + dispatch_feedback(session, cartridge, mode, feedback) + session.flush + answer = $stdin.gets.chomp.to_s.downcase.strip + answer = default_answer if answer == '' + session.print("\n") + yeses.include?(answer) + end + def self.adapt(feedback, adapter, cartridge) call = { parameters: %w[id name parameters parameters-as-json output], @@ -41,6 +52,8 @@ module NanoBot def self.dispatch_feedback(session, cartridge, mode, feedback) enabled = Logic::Cartridge::Tools.feedback?(cartridge, mode.to_sym, feedback[:action].to_sym) + enabled = true if feedback[:action].to_sym == :confirm + return unless enabled color = Logic::Cartridge::Tools.fetch_from_interface( diff --git a/controllers/session.rb b/controllers/session.rb index 546a891..b3cc7ef 100644 --- a/controllers/session.rb +++ b/controllers/session.rb @@ -6,6 +6,7 @@ require 'fileutils' require 'rainbow' require_relative '../logic/helpers/hash' +require_relative '../logic/cartridge/safety' require_relative '../logic/cartridge/streaming' require_relative '../logic/cartridge/interaction' require_relative '../logic/cartridge/fetch' @@ -102,10 +103,9 @@ module NanoBot prefix = Logic::Cartridge::Affixes.get(@cartridge, mode.to_sym, :output, :prefix) suffix = Logic::Cartridge::Affixes.get(@cartridge, mode.to_sym, :output, :suffix) - color = Logic::Cartridge::Fetch.cascate(@cartridge, [ - [:interfaces, mode.to_sym, :output, :color], - %i[interfaces output color] - ]) + color = Logic::Cartridge::Fetch.cascate( + @cartridge, [[:interfaces, mode.to_sym, :output, :color], %i[interfaces output color]] + ) color = color.to_sym if color @@ -118,44 +118,53 @@ module NanoBot needs_another_round = false @provider.evaluate(input, @cartridge) do |feedback| + needs_another_round = true if feedback[:needs_another_round] + updated_at = Time.now - needs_another_round = true if feedback[:needs_another_round] + if feedback[:interaction] && + feedback.dig(:interaction, :meta, :tool, :action) && + feedback[:interaction][:meta][:tool][:action] == 'confirm' + Interfaces::Tool.confirm(self, @cartridge, mode, feedback[:interaction][:meta][:tool]) + else - if feedback[:interaction] && feedback.dig(:interaction, :meta, :tool, :action) - Interfaces::Tool.dispatch_feedback(self, @cartridge, mode, feedback[:interaction][:meta][:tool]) - end + if feedback[:interaction] && feedback.dig(:interaction, :meta, :tool, :action) + Interfaces::Tool.dispatch_feedback( + self, @cartridge, mode, feedback[:interaction][:meta][:tool] + ) + end - if feedback[:interaction] - event = Marshal.load(Marshal.dump(feedback[:interaction])) - event[:mode] = mode.to_s - event[:output] = nil - - if feedback[:interaction][:who] == 'AI' && feedback[:interaction][:message] - event[:output] = feedback[:interaction][:message] - unless streaming - output = Logic::Cartridge::Interaction.output( - @cartridge, mode.to_sym, feedback[:interaction], streaming, feedback[:finished] - ) - output[:message] = Components::Adapter.apply(output[:message], @cartridge) - event[:output] = (output[:message]).to_s + if feedback[:interaction] + event = Marshal.load(Marshal.dump(feedback[:interaction])) + event[:mode] = mode.to_s + event[:output] = nil + + if feedback[:interaction][:who] == 'AI' && feedback[:interaction][:message] + event[:output] = feedback[:interaction][:message] + unless streaming + output = Logic::Cartridge::Interaction.output( + @cartridge, mode.to_sym, feedback[:interaction], streaming, feedback[:finished] + ) + output[:message] = Components::Adapter.apply(output[:message], @cartridge) + event[:output] = (output[:message]).to_s + end end - end - @state[:history] << event if feedback[:should_be_stored] + @state[:history] << event if feedback[:should_be_stored] - if event[:output] && ((!feedback[:finished] && streaming) || (!streaming && feedback[:finished])) - self.print(color ? Rainbow(event[:output]).send(color) : event[:output]) - end + if event[:output] && ((!feedback[:finished] && streaming) || (!streaming && feedback[:finished])) + self.print(color ? Rainbow(event[:output]).send(color) : event[:output]) + end - # The `print` function already outputs a prefix and a suffix, so - # we should add them afterwards to avoid printing them twice. - event[:output] = "#{prefix}#{event[:output]}#{suffix}" - end + # The `print` function already outputs a prefix and a suffix, so + # we should add them afterwards to avoid printing them twice. + event[:output] = "#{prefix}#{event[:output]}#{suffix}" + end - if feedback[:finished] - flush - ready = true + if feedback[:finished] + flush + ready = true + end end end diff --git a/logic/cartridge/safety.rb b/logic/cartridge/safety.rb index 84b39d7..6414e51 100644 --- a/logic/cartridge/safety.rb +++ b/logic/cartridge/safety.rb @@ -6,6 +6,27 @@ module NanoBot module Logic module Cartridge module Safety + def self.default_answer(cartridge) + default = Fetch.cascate(cartridge, [%i[interfaces tools confirm default]]) + return [] if default.nil? + + default + end + + def self.yeses(cartridge) + yeses_values = Fetch.cascate(cartridge, [%i[interfaces tools confirm yeses]]) + return [] if yeses_values.nil? + + yeses_values + end + + def self.confirmable?(cartridge) + confirmable = Fetch.cascate(cartridge, [%i[safety tools confirmable]]) + return true if confirmable.nil? + + confirmable + end + def self.sandboxed?(cartridge) sandboxed = Fetch.cascate(cartridge, [%i[safety functions sandboxed]]) return true if sandboxed.nil? -- cgit v1.2.3