summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/providers/openai.rb28
-rw-r--r--components/providers/openai/tools.rb10
-rw-r--r--controllers/interfaces/repl.rb2
-rw-r--r--controllers/interfaces/tools.rb6
-rw-r--r--controllers/session.rb6
-rw-r--r--logic/cartridge/safety.rb4
-rw-r--r--logic/providers/openai/tools.rb31
-rw-r--r--spec/data/cartridges/tools.yml39
-rw-r--r--spec/logic/cartridge/tools_spec.rb58
-rw-r--r--spec/logic/providers/openai/tools_spec.rb24
-rw-r--r--static/cartridges/baseline.yml2
-rw-r--r--static/cartridges/default.yml4
12 files changed, 110 insertions, 104 deletions
diff --git a/components/providers/openai.rb b/components/providers/openai.rb
index 996f7f6..a7e7abe 100644
--- a/components/providers/openai.rb
+++ b/components/providers/openai.rb
@@ -37,15 +37,7 @@ module NanoBot
@client = ::OpenAI::Client.new(uri_base:, access_token: @credentials[:'access-token'])
end
- def stream(input)
- provider = @settings.key?(:stream) ? @settings[:stream] : true
-
- interface = input[:interface].key?(:stream) ? input[:interface][:stream] : true
-
- provider && interface
- end
-
- def evaluate(input, cartridge, &feedback)
+ def evaluate(input, streaming, 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] }
@@ -76,7 +68,7 @@ module NanoBot
payload[:tools] = input[:tools].map { |raw| NanoBot::Logic::OpenAI::Tools.adapt(raw) } if input[:tools]
- if stream(input)
+ if streaming
content = ''
tools = []
@@ -135,9 +127,21 @@ module NanoBot
end
end
- @client.chat(parameters: payload)
+ begin
+ @client.chat(parameters: payload)
+ rescue StandardError => e
+ raise e.class, e.response[:body] if e.response && e.response[:body]
+
+ raise e
+ end
else
- result = @client.chat(parameters: payload)
+ begin
+ result = @client.chat(parameters: payload)
+ rescue StandardError => e
+ raise e.class, e.response[:body] if e.response && e.response[:body]
+
+ raise e
+ end
raise StandardError, result['error'] if result['error']
diff --git a/components/providers/openai/tools.rb b/components/providers/openai/tools.rb
index 10c2709..cd35e80 100644
--- a/components/providers/openai/tools.rb
+++ b/components/providers/openai/tools.rb
@@ -10,11 +10,11 @@ module NanoBot
module Providers
class OpenAI < Base
module Tools
- def self.confirm(tool, feedback)
+ def self.confirming(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] }
+ tool: { action: 'confirming', id: tool[:id], name: tool[:name], parameters: tool[:parameters] }
} } }
)
end
@@ -23,7 +23,7 @@ module NanoBot
prepared_tools = NanoBot::Logic::OpenAI::Tools.prepare(function_cartridge, tools)
if Logic::Cartridge::Safety.confirmable?(cartridge)
- prepared_tools.each { |tool| tool[:allowed] = confirm(tool, feedback) }
+ prepared_tools.each { |tool| tool[:allowed] = confirming(tool, feedback) }
else
prepared_tools.each { |tool| tool[:allowed] = true }
end
@@ -55,7 +55,7 @@ module NanoBot
feedback.call(
{ should_be_stored: false,
interaction: { who: 'AI', message: nil, meta: {
- tool: { action: 'call', id: tool[:id], name: tool[:name], parameters: tool[:parameters] }
+ tool: { action: 'executing', id: tool[:id], name: tool[:name], parameters: tool[:parameters] }
} } }
)
@@ -86,7 +86,7 @@ module NanoBot
{ should_be_stored: false,
interaction: { who: 'AI', message: nil, meta: {
tool: {
- action: 'response', id: tool[:id], name: tool[:name],
+ action: 'responding', id: tool[:id], name: tool[:name],
parameters: tool[:parameters], output: tool[:output]
}
} } }
diff --git a/controllers/interfaces/repl.rb b/controllers/interfaces/repl.rb
index d4191b1..fd16ea6 100644
--- a/controllers/interfaces/repl.rb
+++ b/controllers/interfaces/repl.rb
@@ -37,8 +37,6 @@ module NanoBot
[proc { prompt }, proc { 'MISSING INPUT' }]
)
- Logic::Cartridge::Streaming.enabled?(cartridge, :repl)
-
Pry.commands.block_command(/(.*)/, 'handler') do |line|
session.print(prefix) unless prefix.nil?
session.evaluate_and_print(line, mode: 'repl')
diff --git a/controllers/interfaces/tools.rb b/controllers/interfaces/tools.rb
index d32afed..aa4fb61 100644
--- a/controllers/interfaces/tools.rb
+++ b/controllers/interfaces/tools.rb
@@ -10,7 +10,7 @@ module NanoBot
module Controllers
module Interfaces
module Tool
- def self.confirm(session, cartridge, mode, feedback)
+ def self.confirming(session, cartridge, mode, feedback)
yeses = Logic::Cartridge::Safety.yeses(cartridge)
default_answer = Logic::Cartridge::Safety.default_answer(cartridge)
dispatch_feedback(session, cartridge, mode, feedback)
@@ -52,7 +52,7 @@ 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
+ enabled = true if feedback[:action].to_sym == :confirming
return unless enabled
@@ -67,7 +67,7 @@ module NanoBot
else
message = "(#{feedback[:name]} #{feedback[:parameters].to_json})"
- message += " =>\n#{feedback[:output]}" if feedback[:action].to_sym == :response
+ message += " =>\n#{feedback[:output]}" if feedback[:action].to_sym == :responding
end
message = "#{adapter[:prefix]}#{message}#{adapter[:suffix]}"
diff --git a/controllers/session.rb b/controllers/session.rb
index b3cc7ef..378a11d 100644
--- a/controllers/session.rb
+++ b/controllers/session.rb
@@ -117,15 +117,15 @@ module NanoBot
needs_another_round = false
- @provider.evaluate(input, @cartridge) do |feedback|
+ @provider.evaluate(input, streaming, @cartridge) do |feedback|
needs_another_round = true if feedback[:needs_another_round]
updated_at = Time.now
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])
+ feedback[:interaction][:meta][:tool][:action] == 'confirming'
+ Interfaces::Tool.confirming(self, @cartridge, mode, feedback[:interaction][:meta][:tool])
else
if feedback[:interaction] && feedback.dig(:interaction, :meta, :tool, :action)
diff --git a/logic/cartridge/safety.rb b/logic/cartridge/safety.rb
index 6414e51..39826a9 100644
--- a/logic/cartridge/safety.rb
+++ b/logic/cartridge/safety.rb
@@ -7,14 +7,14 @@ module NanoBot
module Cartridge
module Safety
def self.default_answer(cartridge)
- default = Fetch.cascate(cartridge, [%i[interfaces tools confirm default]])
+ default = Fetch.cascate(cartridge, [%i[interfaces tools confirming default]])
return [] if default.nil?
default
end
def self.yeses(cartridge)
- yeses_values = Fetch.cascate(cartridge, [%i[interfaces tools confirm yeses]])
+ yeses_values = Fetch.cascate(cartridge, [%i[interfaces tools confirming yeses]])
return [] if yeses_values.nil?
yeses_values
diff --git a/logic/providers/openai/tools.rb b/logic/providers/openai/tools.rb
index 1aa9029..68f2209 100644
--- a/logic/providers/openai/tools.rb
+++ b/logic/providers/openai/tools.rb
@@ -17,10 +17,7 @@ module NanoBot
tool = Helpers::Hash.symbolize_keys(tool)
cartridge.each do |candidate|
- next unless (
- candidate[:type].nil? ||
- (candidate[:type] == 'function' && tool[:type] == candidate[:type])
- ) && tool[:function][:name] == candidate[:name]
+ next unless tool[:function][:name] == candidate[:name]
source = {}
@@ -31,40 +28,26 @@ module NanoBot
applies << {
id: tool[:id],
name: tool[:function][:name],
- type: candidate[:type] || 'function',
+ type: 'function',
parameters: JSON.parse(tool[:function][:arguments]),
source:
}
end
end
+ raise 'missing tool' if applies.size != tools.size
+
applies
end
def self.adapt(cartridge)
- raise 'unsupported tool' if cartridge[:type] != 'function' && !cartridge[:type].nil?
-
- adapted = {
- type: cartridge[:type] || 'function',
+ {
+ type: 'function',
function: {
name: cartridge[:name], description: cartridge[:description],
- parameters: { type: 'object', properties: {} }
+ parameters: cartridge[:parameters]
}
}
-
- properties = adapted[:function][:parameters][:properties]
-
- adapted[:function][:parameters][:required] = cartridge[:required] if cartridge[:required]
-
- cartridge[:parameters]&.each do |parameter|
- key = parameter[:name].to_sym
- properties[key] = {}
- properties[key][:type] = parameter[:type] || 'string'
- properties[key][:description] = parameter[:description] if parameter[:description]
- properties[key][:items] = parameter[:items].slice(:type) if parameter[:items]
- end
-
- adapted
end
end
end
diff --git a/spec/data/cartridges/tools.yml b/spec/data/cartridges/tools.yml
index 0c2a30b..08164a4 100644
--- a/spec/data/cartridges/tools.yml
+++ b/spec/data/cartridges/tools.yml
@@ -1,28 +1,41 @@
---
tools:
+ - name: what-time-is-it
+ description: Returns the current date and time for a given timezone.
+ parameters:
+ type: object
+ properties:
+ timezone:
+ type: string
+ description: A string representing the timezone that should be used to provide a datetime, following the IANA (Internet Assigned Numbers Authority) Time Zone Database. Examples are "Asia/Tokyo" and "Europe/Paris".
+ required:
+ - timezone
+ fennel: |
+ (os.date)
+
- name: get-current-weather
- type: function
description: Get the current weather in a given location.
parameters:
- - name: location
- - name: unit
+ type: object
+ properties:
+ location:
+ type: string
+ unit:
+ type: string
fennel: |
(let [{:location location :unit unit} parameters]
(.. "Here is the weather in " location ", in " unit ": 35.8°C."))
- - name: what-time-is-it
- description: Returns the current date and time.
- fennel: |
- (os.date)
-
- name: sh
description: It has access to computer users' data and can be used to run shell commands, similar to those in a Linux terminal, to extract information. Please be mindful and careful to avoid running dangerous commands on users' computers.
parameters:
- - name: command
- type: array
- items:
- type: string
- description: An array of strings that represents a shell command along with its arguments or options. For instance, `["df", "-h"]` executes the `df -h` command, where each array element specifies either the command itself or an associated argument/option.
+ type: object
+ properties:
+ command:
+ type: array
+ description: An array of strings that represents a shell command along with its arguments or options. For instance, `["df", "-h"]` executes the `df -h` command, where each array element specifies either the command itself or an associated argument/option.
+ items:
+ type: string
clojure: |
(require '[clojure.java.shell :refer [sh]])
(println (apply sh (get parameters "command")))
diff --git a/spec/logic/cartridge/tools_spec.rb b/spec/logic/cartridge/tools_spec.rb
index 42b8c57..913fa62 100644
--- a/spec/logic/cartridge/tools_spec.rb
+++ b/spec/logic/cartridge/tools_spec.rb
@@ -10,11 +10,11 @@ RSpec.describe NanoBot::Logic::Cartridge::Tools do
let(:cartridge) { {} }
it 'uses default values when appropriate' do
- expect(described_class.feedback?(cartridge, :repl, :call)).to be(true)
- expect(described_class.feedback?(cartridge, :eval, :call)).to be(true)
+ expect(described_class.feedback?(cartridge, :repl, :executing)).to be(true)
+ expect(described_class.feedback?(cartridge, :eval, :executing)).to be(true)
- expect(described_class.feedback?(cartridge, :repl, :response)).to be(false)
- expect(described_class.feedback?(cartridge, :eval, :response)).to be(false)
+ expect(described_class.feedback?(cartridge, :repl, :responding)).to be(false)
+ expect(described_class.feedback?(cartridge, :eval, :responding)).to be(false)
end
end
@@ -24,11 +24,11 @@ RSpec.describe NanoBot::Logic::Cartridge::Tools do
end
it 'overrides default values when appropriate' do
- expect(described_class.feedback?(cartridge, :repl, :call)).to be(false)
- expect(described_class.feedback?(cartridge, :eval, :call)).to be(false)
+ expect(described_class.feedback?(cartridge, :repl, :executing)).to be(false)
+ expect(described_class.feedback?(cartridge, :eval, :executing)).to be(false)
- expect(described_class.feedback?(cartridge, :repl, :response)).to be(false)
- expect(described_class.feedback?(cartridge, :eval, :response)).to be(false)
+ expect(described_class.feedback?(cartridge, :repl, :responding)).to be(false)
+ expect(described_class.feedback?(cartridge, :eval, :responding)).to be(false)
end
end
@@ -38,59 +38,59 @@ RSpec.describe NanoBot::Logic::Cartridge::Tools do
end
it 'overrides default values when appropriate' do
- expect(described_class.feedback?(cartridge, :repl, :call)).to be(true)
- expect(described_class.feedback?(cartridge, :eval, :call)).to be(true)
+ expect(described_class.feedback?(cartridge, :repl, :executing)).to be(true)
+ expect(described_class.feedback?(cartridge, :eval, :executing)).to be(true)
- expect(described_class.feedback?(cartridge, :repl, :response)).to be(true)
- expect(described_class.feedback?(cartridge, :eval, :response)).to be(true)
+ expect(described_class.feedback?(cartridge, :repl, :responding)).to be(true)
+ expect(described_class.feedback?(cartridge, :eval, :responding)).to be(true)
end
end
context 'top-level-specific overrides' do
let(:cartridge) do
- { interfaces: { tools: { call: { feedback: false }, response: { feedback: true } } } }
+ { interfaces: { tools: { executing: { feedback: false }, responding: { feedback: true } } } }
end
it 'overrides default values when appropriate' do
- expect(described_class.feedback?(cartridge, :repl, :call)).to be(false)
- expect(described_class.feedback?(cartridge, :eval, :call)).to be(false)
+ expect(described_class.feedback?(cartridge, :repl, :executing)).to be(false)
+ expect(described_class.feedback?(cartridge, :eval, :executing)).to be(false)
- expect(described_class.feedback?(cartridge, :repl, :response)).to be(true)
- expect(described_class.feedback?(cartridge, :eval, :response)).to be(true)
+ expect(described_class.feedback?(cartridge, :repl, :responding)).to be(true)
+ expect(described_class.feedback?(cartridge, :eval, :responding)).to be(true)
end
end
context 'repl interface overrides' do
let(:cartridge) do
{ interfaces: {
- tools: { call: { feedback: false }, response: { feedback: true } },
- repl: { tools: { call: { feedback: true }, response: { feedback: false } } }
+ tools: { executing: { feedback: false }, responding: { feedback: true } },
+ repl: { tools: { executing: { feedback: true }, responding: { feedback: false } } }
} }
end
it 'overrides default values when appropriate' do
- expect(described_class.feedback?(cartridge, :repl, :call)).to be(true)
- expect(described_class.feedback?(cartridge, :eval, :call)).to be(false)
+ expect(described_class.feedback?(cartridge, :repl, :executing)).to be(true)
+ expect(described_class.feedback?(cartridge, :eval, :executing)).to be(false)
- expect(described_class.feedback?(cartridge, :repl, :response)).to be(false)
- expect(described_class.feedback?(cartridge, :eval, :response)).to be(true)
+ expect(described_class.feedback?(cartridge, :repl, :responding)).to be(false)
+ expect(described_class.feedback?(cartridge, :eval, :responding)).to be(true)
end
end
context 'eval interface overrides' do
let(:cartridge) do
{ interfaces: {
- tools: { call: { feedback: false }, response: { feedback: true } },
- eval: { tools: { call: { feedback: true }, response: { feedback: false } } }
+ tools: { executing: { feedback: false }, responding: { feedback: true } },
+ eval: { tools: { executing: { feedback: true }, responding: { feedback: false } } }
} }
end
it 'overrides default values when appropriate' do
- expect(described_class.feedback?(cartridge, :repl, :call)).to be(false)
- expect(described_class.feedback?(cartridge, :eval, :call)).to be(true)
+ expect(described_class.feedback?(cartridge, :repl, :executing)).to be(false)
+ expect(described_class.feedback?(cartridge, :eval, :executing)).to be(true)
- expect(described_class.feedback?(cartridge, :repl, :response)).to be(true)
- expect(described_class.feedback?(cartridge, :eval, :response)).to be(false)
+ expect(described_class.feedback?(cartridge, :repl, :responding)).to be(true)
+ expect(described_class.feedback?(cartridge, :eval, :responding)).to be(false)
end
end
end
diff --git a/spec/logic/providers/openai/tools_spec.rb b/spec/logic/providers/openai/tools_spec.rb
index c92c374..1758e5f 100644
--- a/spec/logic/providers/openai/tools_spec.rb
+++ b/spec/logic/providers/openai/tools_spec.rb
@@ -13,14 +13,16 @@ RSpec.describe NanoBot::Logic::OpenAI::Tools do
expect(described_class.adapt(cartridge[:tools][0])).to eq(
{ type: 'function',
function: {
- name: 'get-current-weather',
- description: 'Get the current weather in a given location.',
+ name: 'what-time-is-it',
+ description: 'Returns the current date and time for a given timezone.',
parameters: {
type: 'object',
properties: {
- location: { type: 'string' },
- unit: { type: 'string' }
- }
+ timezone: {
+ type: 'string',
+ description: 'A string representing the timezone that should be used to provide a datetime, following the IANA (Internet Assigned Numbers Authority) Time Zone Database. Examples are "Asia/Tokyo" and "Europe/Paris".'
+ }
+ }, required: ['timezone']
}
} }
)
@@ -28,9 +30,15 @@ RSpec.describe NanoBot::Logic::OpenAI::Tools do
expect(described_class.adapt(cartridge[:tools][1])).to eq(
{ type: 'function',
function: {
- name: 'what-time-is-it',
- description: 'Returns the current date and time.',
- parameters: { properties: {}, type: 'object' }
+ name: 'get-current-weather',
+ description: 'Get the current weather in a given location.',
+ parameters: {
+ type: 'object',
+ properties: {
+ location: { type: 'string' },
+ unit: { type: 'string' }
+ }
+ }
} }
)
diff --git a/static/cartridges/baseline.yml b/static/cartridges/baseline.yml
index 50c4756..f3aaab1 100644
--- a/static/cartridges/baseline.yml
+++ b/static/cartridges/baseline.yml
@@ -14,4 +14,4 @@ provider:
access-token: ENV/OPENAI_API_KEY
settings:
user: ENV/NANO_BOTS_END_USER
- model: gpt-3.5-turbo
+ model: gpt-3.5-turbo-1106
diff --git a/static/cartridges/default.yml b/static/cartridges/default.yml
index 57eeada..ce2614d 100644
--- a/static/cartridges/default.yml
+++ b/static/cartridges/default.yml
@@ -19,9 +19,9 @@ interfaces:
stream: true
suffix: "\n"
tools:
- call:
+ executing:
feedback: true
- response:
+ responding:
feedback: false
provider: