nano-bots 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e2cff31d309d896be4e019fa6f39a86d68c6463c83c552b44d1bdf270d160b5
4
- data.tar.gz: 8d506a1a13e2f85efb47a5149dc9ecd4519a49be8f26d139b5309f23e846aebe
3
+ metadata.gz: 32f9bfa761f2b8f34c638d86f207b804a1861873b6ff09dc09202e815fb60943
4
+ data.tar.gz: 274c62930860eef8ba3bedff9d169513627160539fd7c30077e883b882fb5330
5
5
  SHA512:
6
- metadata.gz: a95508200927eff8dc6bbb7d6a1ca84e6c4ec4a0ff0ff549d6650f862a17ec5f56fe5e68ddfa3acf699f2ef2462527e0f63ec753c494c62149ff604cc17f6983
7
- data.tar.gz: cf285799aba18d3dfe7680cbc47a2a09805953d1bf2ed65a29f8da78553f9d4e06a56b2ec9984f5819847790659df80636b02255c3b32ba1acb58670d4398458
6
+ metadata.gz: 7eb6dd2df0d4cbc9a7d7ad880b7ffa633a8ed3ebb52367f1e379efbc78f142a9675603bbcb26c05aa8a2533f3ca9aa5b120faea83843bcc4120a4f01895fa38f
7
+ data.tar.gz: a7451773e12f9c25362ffcb04f1c8ce78f7aff0988fec4178d0456d4f91bf70feffa631232a7e182b0cefd6d015ee5fd52ab19448eeeefb77ff821bf2f34d4ea
data/Gemfile CHANGED
@@ -7,6 +7,6 @@ gemspec
7
7
  group :test, :development do
8
8
  gem 'pry-byebug', '~> 3.10', '>= 3.10.1'
9
9
  gem 'rspec', '~> 3.12'
10
- gem 'rubocop', '~> 1.58'
10
+ gem 'rubocop', '~> 1.59'
11
11
  gem 'rubocop-rspec', '~> 2.25'
12
12
  end
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nano-bots (1.1.2)
4
+ nano-bots (1.2.0)
5
5
  babosa (~> 2.0)
6
6
  concurrent-ruby (~> 1.2, >= 1.2.2)
7
7
  dotenv (~> 2.8, >= 2.8.1)
8
- faraday (~> 2.7, >= 2.7.12)
8
+ gemini-ai (~> 1.0)
9
9
  pry (~> 0.14.2)
10
10
  rainbow (~> 3.1, >= 3.1.1)
11
11
  rbnacl (~> 7.1, >= 7.1.1)
@@ -15,6 +15,8 @@ PATH
15
15
  GEM
16
16
  remote: https://rubygems.org/
17
17
  specs:
18
+ addressable (2.8.6)
19
+ public_suffix (>= 2.0.2, < 6.0)
18
20
  ast (2.4.2)
19
21
  babosa (2.0.0)
20
22
  base64 (0.2.0)
@@ -32,10 +34,26 @@ GEM
32
34
  multipart-post (~> 2)
33
35
  faraday-net_http (3.0.2)
34
36
  ffi (1.16.3)
37
+ gemini-ai (1.0.0)
38
+ event_stream_parser (~> 1.0)
39
+ faraday (~> 2.7, >= 2.7.12)
40
+ googleauth (~> 1.9, >= 1.9.1)
41
+ google-cloud-env (2.1.0)
42
+ faraday (>= 1.0, < 3.a)
43
+ googleauth (1.9.1)
44
+ faraday (>= 1.0, < 3.a)
45
+ google-cloud-env (~> 2.1)
46
+ jwt (>= 1.4, < 3.0)
47
+ multi_json (~> 1.11)
48
+ os (>= 0.9, < 2.0)
49
+ signet (>= 0.16, < 2.a)
35
50
  json (2.7.1)
51
+ jwt (2.7.1)
36
52
  language_server-protocol (3.17.0.3)
37
53
  method_source (1.0.0)
54
+ multi_json (1.15.0)
38
55
  multipart-post (2.3.0)
56
+ os (1.1.4)
39
57
  parallel (1.23.0)
40
58
  parser (3.2.2.4)
41
59
  ast (~> 2.4.1)
@@ -46,6 +64,7 @@ GEM
46
64
  pry-byebug (3.10.1)
47
65
  byebug (~> 11.0)
48
66
  pry (>= 0.13, < 0.15)
67
+ public_suffix (5.0.4)
49
68
  racc (1.7.3)
50
69
  rainbow (3.1.1)
51
70
  rbnacl (7.1.1)
@@ -65,7 +84,7 @@ GEM
65
84
  diff-lcs (>= 1.2.0, < 2.0)
66
85
  rspec-support (~> 3.12.0)
67
86
  rspec-support (3.12.1)
68
- rubocop (1.58.0)
87
+ rubocop (1.59.0)
69
88
  json (~> 2.3)
70
89
  language_server-protocol (>= 3.17.0)
71
90
  parallel (~> 1.10)
@@ -92,6 +111,11 @@ GEM
92
111
  faraday-multipart (>= 1)
93
112
  ruby-progressbar (1.13.0)
94
113
  ruby2_keywords (0.0.5)
114
+ signet (0.18.0)
115
+ addressable (~> 2.8)
116
+ faraday (>= 0.17.5, < 3.a)
117
+ jwt (>= 1.5, < 3.0)
118
+ multi_json (~> 1.10)
95
119
  sweet-moon (0.0.7)
96
120
  ffi (~> 1.15, >= 1.15.5)
97
121
  unicode-display_width (2.5.0)
@@ -103,7 +127,7 @@ DEPENDENCIES
103
127
  nano-bots!
104
128
  pry-byebug (~> 3.10, >= 3.10.1)
105
129
  rspec (~> 3.12)
106
- rubocop (~> 1.58)
130
+ rubocop (~> 1.59)
107
131
  rubocop-rspec (~> 2.25)
108
132
 
109
133
  BUNDLED WITH
data/README.md CHANGED
@@ -8,35 +8,41 @@ _Image artificially created by Midjourney through a prompt generated by a Nano B
8
8
  https://user-images.githubusercontent.com/113217272/238141567-c58a240c-7b67-4b3b-864a-0f49bbf6e22f.mp4
9
9
 
10
10
  - [Setup](#setup)
11
- - [Docker](#docker)
11
+ - [OpenAI ChatGPT](#openai-chatgpt)
12
+ - [Google Gemini](#google-gemini)
13
+ - [Docker](#docker)
14
+ - [OpenAI ChatGPT](#openai-chatgpt-1)
15
+ - [Google Gemini](#google-gemini-1)
12
16
  - [Usage](#usage)
13
- - [Command Line](#command-line)
14
- - [Library](#library)
17
+ - [Command Line](#command-line)
18
+ - [Library](#library)
15
19
  - [Cartridges](#cartridges)
16
- - [Tools (Functions)](#tools-functions)
17
- - [Experimental Clojure Support](#experimental-clojure-support)
18
- - [Marketplace](#marketplace)
20
+ - [OpenAI ChatGPT](#openai-chatgpt-2)
21
+ - [Google Gemini](#google-gemini-2)
22
+ - [Tools (Functions)](#tools-functions)
23
+ - [Experimental Clojure Support](#experimental-clojure-support)
24
+ - [Marketplace](#marketplace)
19
25
  - [Security and Privacy](#security-and-privacy)
20
- - [Cryptography](#cryptography)
21
- - [End-user IDs](#end-user-ids)
22
- - [Decrypting](#decrypting)
26
+ - [Cryptography](#cryptography)
27
+ - [End-user IDs](#end-user-ids)
28
+ - [Decrypting](#decrypting)
23
29
  - [Providers](#providers)
24
30
  - [Debugging](#debugging)
25
31
  - [Development](#development)
26
- - [Publish to RubyGems](#publish-to-rubygems)
32
+ - [Publish to RubyGems](#publish-to-rubygems)
27
33
 
28
34
  ## Setup
29
35
 
30
36
  For a system usage:
31
37
 
32
38
  ```sh
33
- gem install nano-bots -v 1.1.2
39
+ gem install nano-bots -v 1.2.0
34
40
  ```
35
41
 
36
42
  To use it in a project, add it to your `Gemfile`:
37
43
 
38
44
  ```ruby
39
- gem 'nano-bots', '~> 1.1.2'
45
+ gem 'nano-bots', '~> 1.2.0'
40
46
  ```
41
47
 
42
48
  ```sh
@@ -45,6 +51,8 @@ bundle install
45
51
 
46
52
  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:
47
53
 
54
+ ### OpenAI ChatGPT
55
+
48
56
  ```sh
49
57
  export OPENAI_API_ADDRESS=https://api.openai.com
50
58
  export OPENAI_API_KEY=your-access-token
@@ -69,6 +77,36 @@ NANO_BOTS_END_USER=your-user
69
77
  # NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges
70
78
  ```
71
79
 
80
+ ### Google Gemini
81
+
82
+ Click [here](https://github.com/gbaptista/gemini-ai#credentials) to learn how to obtain your credentials.
83
+
84
+ ```sh
85
+ export GOOGLE_CREDENTIALS_FILE_PATH=google-credentials.json
86
+ export GOOGLE_PROJECT_ID=your-project-id
87
+ export GOOGLE_REGION=us-east4
88
+
89
+ export NANO_BOTS_ENCRYPTION_PASSWORD=UNSAFE
90
+ export NANO_BOTS_END_USER=your-user
91
+
92
+ # export NANO_BOTS_STATE_DIRECTORY=/home/user/.local/state/nano-bots
93
+ # export NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges
94
+ ```
95
+
96
+ Alternatively, if your current directory has a `.env` file with the environment variables, they will be automatically loaded:
97
+
98
+ ```sh
99
+ GOOGLE_CREDENTIALS_FILE_PATH=google-credentials.json
100
+ GOOGLE_PROJECT_ID=your-project-id
101
+ GOOGLE_REGION=us-east4
102
+
103
+ NANO_BOTS_ENCRYPTION_PASSWORD=UNSAFE
104
+ NANO_BOTS_END_USER=your-user
105
+
106
+ # NANO_BOTS_STATE_DIRECTORY=/home/user/.local/state/nano-bots
107
+ # NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges
108
+ ```
109
+
72
110
  ## Docker
73
111
 
74
112
  Clone the repository and copy the Docker Compose template:
@@ -81,12 +119,14 @@ cp docker-compose.example.yml docker-compose.yml
81
119
 
82
120
  Set your provider credentials and choose your desired directory for the cartridges files:
83
121
 
122
+ ### OpenAI ChatGPT
123
+
84
124
  ```yaml
85
125
  ---
86
126
  services:
87
127
  nano-bots:
88
128
  image: ruby:3.2.2-slim-bookworm
89
- command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev curl && curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash && gem install nano-bots -v 1.1.2 && bash"
129
+ command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev curl && curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash && gem install nano-bots -v 1.2.0 && bash"
90
130
  environment:
91
131
  OPENAI_API_ADDRESS: https://api.openai.com
92
132
  OPENAI_API_KEY: your-access-token
@@ -97,6 +137,28 @@ services:
97
137
  - ./your-state-path:/root/.local/state/nano-bots
98
138
  ```
99
139
 
140
+ ### Google Gemini
141
+
142
+ ```yaml
143
+ ---
144
+ services:
145
+ nano-bots:
146
+ image: ruby:3.2.2-slim-bookworm
147
+ command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev curl && curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash && gem install nano-bots -v 1.2.0 && bash"
148
+ environment:
149
+ GOOGLE_CREDENTIALS_FILE_PATH: /root/.config/google-credentials.json
150
+ GOOGLE_PROJECT_ID: your-project-id
151
+ GOOGLE_REGION: us-east4
152
+ NANO_BOTS_ENCRYPTION_PASSWORD: UNSAFE
153
+ NANO_BOTS_END_USER: your-user
154
+ volumes:
155
+ - ./google-credentials.json:/root/.config/google-credentials.json
156
+ - ./your-cartridges:/root/.local/share/nano-bots/cartridges
157
+ - ./your-state-path:/root/.local/state/nano-bots
158
+ ```
159
+
160
+ ### Container
161
+
100
162
  Enter the container:
101
163
  ```sh
102
164
  docker compose run nano-bots
@@ -246,8 +308,16 @@ end
246
308
 
247
309
  ## Cartridges
248
310
 
311
+ Check the Nano Bots specification to learn more about [how to build cartridges](https://spec.nbots.io/#/README?id=cartridges).
312
+
313
+ Try the [Nano Bots Clinic (Live Editor)](https://clinic.nbots.io) to learn about creating Cartridges.
314
+
249
315
  Here's what a Nano Bot Cartridge looks like:
250
316
 
317
+ ### OpenAI ChatGPT
318
+
319
+ Read the [full specification](https://spec.nbots.io/#/README?id=open-ai-chatgpt) for OpenAI ChatGPT.
320
+
251
321
  ```yaml
252
322
  ---
253
323
  meta:
@@ -269,12 +339,36 @@ provider:
269
339
  access-token: ENV/OPENAI_API_KEY
270
340
  settings:
271
341
  user: ENV/NANO_BOTS_END_USER
272
- model: gpt-3.5-turbo
342
+ model: gpt-4-1106-preview
273
343
  ```
274
344
 
275
- Check the Nano Bots specification to learn more about [how to build cartridges](https://spec.nbots.io/#/README?id=cartridges).
345
+ ### Google Gemini
276
346
 
277
- Try the [Nano Bots Clinic (Live Editor)](https://clinic.nbots.io) to learn about creating Cartridges.
347
+ Read the [full specification](https://spec.nbots.io/#/README?id=google-gemini) for Google Gemini.
348
+
349
+ ```yaml
350
+ ---
351
+ meta:
352
+ symbol: 🤖
353
+ name: Nano Bot Name
354
+ author: Your Name
355
+ version: 1.0.0
356
+ license: CC0-1.0
357
+ description: A helpful assistant.
358
+
359
+ behaviors:
360
+ interaction:
361
+ directive: You are a helpful assistant.
362
+
363
+ provider:
364
+ id: google
365
+ credentials:
366
+ project-id: ENV/GOOGLE_PROJECT_ID
367
+ file-path: ENV/GOOGLE_CREDENTIALS_FILE_PATH
368
+ region: ENV/GOOGLE_REGION
369
+ options:
370
+ model: gemini-pro
371
+ ```
278
372
 
279
373
  ### Tools (Functions)
280
374
 
@@ -301,22 +395,9 @@ The randomly generated number is 59.
301
395
 
302
396
  🤖> |
303
397
  ```
398
+ To successfully use Tools (Functions), you need to specify a provider and a model that supports them. As of the writing of this README, the provider that supports them is [OpenAI](https://platform.openai.com/docs/models), with models `gpt-3.5-turbo-1106` and `gpt-4-1106-preview`, and [Google](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/function-calling#supported_models), with the model `gemini-pro`.
304
399
 
305
- To successfully use Tools (Functions), you need to specify a provider and a model that support them. As of the writing of this README, the provider that supports them is [OpenAI](https://platform.openai.com/docs/models), with models `gpt-3.5-turbo-1106` and `gpt-4-1106-preview`:
306
-
307
- ```yaml
308
- ---
309
- provider:
310
- id: openai
311
- credentials:
312
- address: ENV/OPENAI_API_ADDRESS
313
- access-token: ENV/OPENAI_API_KEY
314
- settings:
315
- user: ENV/NANO_BOTS_END_USER
316
- model: gpt-4-1106-preview
317
- ```
318
-
319
- Check the [Nano Bots specification](https://spec.nbots.io/#/README?id=tools-functions-2) to learn more about them.
400
+ Check the [Nano Bots specification](https://spec.nbots.io/#/README?id=tools-functions-2) to learn more about Tools (Functions).
320
401
 
321
402
  #### Experimental Clojure Support
322
403
 
@@ -469,13 +550,16 @@ If you lose your password, you lose your data. It is not possible to recover it
469
550
 
470
551
  Currently supported providers:
471
552
 
472
- - [x] [FastChat (Vicuna)](https://github.com/lm-sys/FastChat)
473
- - [x] [Open AI](https://platform.openai.com/docs/api-reference)
474
- - [ ] [Google PaLM](https://developers.generativeai.google/)
475
- - [ ] [Alpaca](https://github.com/tatsu-lab/stanford_alpaca)
476
- - [ ] [LLaMA](https://github.com/facebookresearch/llama)
553
+ - [x] [Open AI ChatGPT](https://platform.openai.com/docs/api-reference)
554
+ - [x] [Google Gemini](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini)
555
+ - [ ] [Anthropic Claude](https://www.anthropic.com)
556
+ - [ ] [Cohere Command](https://cohere.com)
557
+ - [ ] [Meta Llama](https://ai.meta.com/llama/)
558
+ - [ ] [01.AI Yi](https://01.ai)
559
+ - [ ] [WizardLM](https://wizardlm.github.io)
560
+ - [ ] [LMSYS Org FastChat Vicuna](https://github.com/lm-sys/FastChat)
477
561
 
478
- Although only OpenAI has been officially tested, some of the open-source providers offer APIs that are compatible with OpenAI, such as [FastChat](https://github.com/lm-sys/FastChat#openai-compatible-restful-apis--sdk). Therefore, it is highly probable that they will work just fine.
562
+ Although only OpenAI ChatGPT and Google Gemini have been officially tested, some alternative providers offer APIs that are compatible with, for example, OpenAI, such as [FastChat](https://github.com/lm-sys/FastChat#openai-compatible-restful-apis--sdk). Therefore, it is highly probable that they will work just fine.
479
563
 
480
564
  ## Development
481
565
 
@@ -492,5 +576,5 @@ gem build nano-bots.gemspec
492
576
 
493
577
  gem signin
494
578
 
495
- gem push nano-bots-1.1.2.gem
579
+ gem push nano-bots-1.2.0.gem
496
580
  ```
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'openai'
4
-
5
3
  require_relative 'providers/openai'
4
+ require_relative 'providers/google'
6
5
 
7
6
  module NanoBot
8
7
  module Components
@@ -11,6 +10,8 @@ module NanoBot
11
10
  case provider[:id]
12
11
  when 'openai'
13
12
  Providers::OpenAI.new(provider[:settings], provider[:credentials], environment:)
13
+ when 'google'
14
+ Providers::Google.new(provider[:options], provider[:settings], provider[:credentials], environment:)
14
15
  else
15
16
  raise "Unsupported provider \"#{provider[:id]}\""
16
17
  end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gemini-ai'
4
+
5
+ require_relative 'base'
6
+
7
+ require_relative '../../logic/providers/google/tools'
8
+ require_relative '../../logic/providers/google/tokens'
9
+
10
+ require_relative 'tools'
11
+
12
+ module NanoBot
13
+ module Components
14
+ module Providers
15
+ class Google < Base
16
+ SETTINGS = {
17
+ generationConfig: %i[
18
+ temperature topP topK candidateCount maxOutputTokens stopSequences
19
+ ].freeze
20
+ }.freeze
21
+
22
+ SAFETY_SETTINGS = %i[category threshold].freeze
23
+
24
+ attr_reader :settings
25
+
26
+ def initialize(options, settings, credentials, _environment)
27
+ @settings = settings
28
+
29
+ @client = Gemini.new(
30
+ credentials: {
31
+ file_path: credentials[:'file-path'],
32
+ project_id: credentials[:'project-id'],
33
+ region: credentials[:region]
34
+ },
35
+ settings: { model: options[:model], stream: options[:stream] }
36
+ )
37
+ end
38
+
39
+ def evaluate(input, streaming, cartridge, &feedback)
40
+ messages = input[:history].map do |event|
41
+ if event[:message].nil? && event[:meta] && event[:meta][:tool_calls]
42
+ { role: 'model',
43
+ parts: event[:meta][:tool_calls],
44
+ _meta: { at: event[:at] } }
45
+ elsif event[:who] == 'tool'
46
+ { role: 'function',
47
+ parts: [
48
+ { functionResponse: {
49
+ name: event[:meta][:name],
50
+ response: { name: event[:meta][:name], content: event[:message].to_s }
51
+ } }
52
+ ],
53
+ _meta: { at: event[:at] } }
54
+ else
55
+ { role: event[:who] == 'user' ? 'user' : 'model',
56
+ parts: { text: event[:message] },
57
+ _meta: { at: event[:at] } }
58
+ end
59
+ end
60
+
61
+ %i[backdrop directive].each do |key|
62
+ next unless input[:behavior][key]
63
+
64
+ # TODO: Does Gemini have system messages?
65
+ messages.prepend(
66
+ { role: key == :directive ? 'user' : 'user',
67
+ parts: { text: input[:behavior][key] },
68
+ _meta: { at: Time.now } }
69
+ )
70
+ end
71
+
72
+ payload = { contents: messages, generationConfig: { candidateCount: 1 } }
73
+
74
+ if @settings
75
+ SETTINGS.each_key do |key|
76
+ SETTINGS[key].each do |sub_key|
77
+ if @settings.key?(key) && @settings[key].key?(sub_key)
78
+ payload[key] = {} unless payload.key?(key)
79
+ payload[key][sub_key] = @settings[key][sub_key]
80
+ end
81
+ end
82
+ end
83
+
84
+ if @settings[:safetySettings].is_a?(Array)
85
+ payload[:safetySettings] = [] unless payload.key?(:safetySettings)
86
+
87
+ @settings[:safetySettings].each do |safety_setting|
88
+ setting = {}
89
+ SAFETY_SETTINGS.each { |key| setting[key] = safety_setting[key] }
90
+ payload[:safetySettings] << setting
91
+ end
92
+ end
93
+ end
94
+
95
+ if input[:tools]
96
+ payload[:tools] = {
97
+ function_declarations: input[:tools].map { |raw| Logic::Google::Tools.adapt(raw) }
98
+ }
99
+ end
100
+
101
+ if streaming
102
+ content = ''
103
+ tools = []
104
+
105
+ stream_call_back = proc do |event, _parsed, _raw|
106
+ partial_content = event.dig('candidates', 0, 'content', 'parts').filter do |part|
107
+ part.key?('text')
108
+ end.map { |part| part['text'] }.join
109
+
110
+ partial_tools = event.dig('candidates', 0, 'content', 'parts').filter do |part|
111
+ part.key?('functionCall')
112
+ end
113
+
114
+ tools.concat(partial_tools) if partial_tools.size.positive?
115
+
116
+ if partial_content
117
+ content += partial_content
118
+ feedback.call(
119
+ { should_be_stored: false,
120
+ interaction: { who: 'AI', message: partial_content } }
121
+ )
122
+ end
123
+
124
+ if event.dig('candidates', 0, 'finishReason')
125
+ if tools&.size&.positive?
126
+ feedback.call(
127
+ { should_be_stored: true,
128
+ needs_another_round: true,
129
+ interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } }
130
+ )
131
+ Tools.apply(
132
+ cartridge, input[:tools], tools, feedback, Logic::Google::Tools
133
+ ).each do |interaction|
134
+ feedback.call({ should_be_stored: true, needs_another_round: true, interaction: })
135
+ end
136
+ end
137
+
138
+ feedback.call(
139
+ { should_be_stored: !(content.nil? || content == ''),
140
+ interaction: content.nil? || content == '' ? nil : { who: 'AI', message: content },
141
+ finished: true }
142
+ )
143
+ end
144
+ end
145
+
146
+ begin
147
+ @client.stream_generate_content(
148
+ Logic::Google::Tokens.apply_policies!(cartridge, payload),
149
+ stream: true, &stream_call_back
150
+ )
151
+ rescue StandardError => e
152
+ raise e.class, e.response[:body] if e.response && e.response[:body]
153
+
154
+ raise e
155
+ end
156
+ else
157
+ begin
158
+ result = @client.stream_generate_content(
159
+ Logic::Google::Tokens.apply_policies!(cartridge, payload),
160
+ stream: false
161
+ )
162
+ rescue StandardError => e
163
+ raise e.class, e.response[:body] if e.response && e.response[:body]
164
+
165
+ raise e
166
+ end
167
+
168
+ tools = result.dig(0, 'candidates', 0, 'content', 'parts').filter do |part|
169
+ part.key?('functionCall')
170
+ end
171
+
172
+ if tools&.size&.positive?
173
+ feedback.call(
174
+ { should_be_stored: true,
175
+ needs_another_round: true,
176
+ interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } }
177
+ )
178
+
179
+ Tools.apply(
180
+ cartridge, input[:tools], tools, feedback, Logic::Google::Tools
181
+ ).each do |interaction|
182
+ feedback.call({ should_be_stored: true, needs_another_round: true, interaction: })
183
+ end
184
+ end
185
+
186
+ content = result.map do |answer|
187
+ answer.dig('candidates', 0, 'content', 'parts').filter do |part|
188
+ part.key?('text')
189
+ end.map { |part| part['text'] }.join
190
+ end.join
191
+
192
+ feedback.call(
193
+ { should_be_stored: !(content.nil? || content.to_s.strip == ''),
194
+ interaction: content.nil? || content == '' ? nil : { who: 'AI', message: content },
195
+ finished: true }
196
+ )
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -6,9 +6,9 @@ require_relative 'base'
6
6
  require_relative '../crypto'
7
7
 
8
8
  require_relative '../../logic/providers/openai/tools'
9
- require_relative '../../controllers/interfaces/tools'
9
+ require_relative '../../logic/providers/openai/tokens'
10
10
 
11
- require_relative 'openai/tools'
11
+ require_relative 'tools'
12
12
 
13
13
  module NanoBot
14
14
  module Components
@@ -18,7 +18,7 @@ module NanoBot
18
18
 
19
19
  CHAT_SETTINGS = %i[
20
20
  model stream temperature top_p n stop max_tokens
21
- presence_penalty frequency_penalty logit_bias
21
+ presence_penalty frequency_penalty logit_bias seed response_format
22
22
  ].freeze
23
23
 
24
24
  attr_reader :settings
@@ -40,12 +40,18 @@ module NanoBot
40
40
  def evaluate(input, streaming, cartridge, &feedback)
41
41
  messages = input[:history].map do |event|
42
42
  if event[:message].nil? && event[:meta] && event[:meta][:tool_calls]
43
- { role: 'assistant', content: nil, tool_calls: event[:meta][:tool_calls] }
43
+ { role: 'assistant', content: nil,
44
+ tool_calls: event[:meta][:tool_calls],
45
+ _meta: { at: event[:at] } }
44
46
  elsif event[:who] == 'tool'
45
47
  { role: event[:who], content: event[:message].to_s,
46
- tool_call_id: event[:meta][:id], name: event[:meta][:name] }
48
+ tool_call_id: event[:meta][:id],
49
+ name: event[:meta][:name],
50
+ _meta: { at: event[:at] } }
47
51
  else
48
- { role: event[:who] == 'user' ? 'user' : 'assistant', content: event[:message] }
52
+ { role: event[:who] == 'user' ? 'user' : 'assistant',
53
+ content: event[:message],
54
+ _meta: { at: event[:at] } }
49
55
  end
50
56
  end
51
57
 
@@ -54,7 +60,8 @@ module NanoBot
54
60
 
55
61
  messages.prepend(
56
62
  { role: key == :directive ? 'system' : 'user',
57
- content: input[:behavior][key] }
63
+ content: input[:behavior][key],
64
+ _meta: { at: Time.now } }
58
65
  )
59
66
  end
60
67
 
@@ -66,7 +73,7 @@ module NanoBot
66
73
 
67
74
  payload.delete(:logit_bias) if payload.key?(:logit_bias) && payload[:logit_bias].nil?
68
75
 
69
- payload[:tools] = input[:tools].map { |raw| NanoBot::Logic::OpenAI::Tools.adapt(raw) } if input[:tools]
76
+ payload[:tools] = input[:tools].map { |raw| Logic::OpenAI::Tools.adapt(raw) } if input[:tools]
70
77
 
71
78
  if streaming
72
79
  content = ''
@@ -114,13 +121,15 @@ module NanoBot
114
121
  needs_another_round: true,
115
122
  interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } }
116
123
  )
117
- Tools.apply(cartridge, input[:tools], tools, feedback).each do |interaction|
124
+ Tools.apply(
125
+ cartridge, input[:tools], tools, feedback, Logic::OpenAI::Tools
126
+ ).each do |interaction|
118
127
  feedback.call({ should_be_stored: true, needs_another_round: true, interaction: })
119
128
  end
120
129
  end
121
130
 
122
131
  feedback.call(
123
- { should_be_stored: !(content.nil? || content == ''),
132
+ { should_be_stored: !(content.nil? || content.to_s.strip == ''),
124
133
  interaction: content.nil? || content == '' ? nil : { who: 'AI', message: content },
125
134
  finished: true }
126
135
  )
@@ -128,7 +137,7 @@ module NanoBot
128
137
  end
129
138
 
130
139
  begin
131
- @client.chat(parameters: payload)
140
+ @client.chat(parameters: Logic::OpenAI::Tokens.apply_policies!(cartridge, payload))
132
141
  rescue StandardError => e
133
142
  raise e.class, e.response[:body] if e.response && e.response[:body]
134
143
 
@@ -136,7 +145,7 @@ module NanoBot
136
145
  end
137
146
  else
138
147
  begin
139
- result = @client.chat(parameters: payload)
148
+ result = @client.chat(parameters: Logic::OpenAI::Tokens.apply_policies!(cartridge, payload))
140
149
  rescue StandardError => e
141
150
  raise e.class, e.response[:body] if e.response && e.response[:body]
142
151
 
@@ -153,7 +162,9 @@ module NanoBot
153
162
  needs_another_round: true,
154
163
  interaction: { who: 'AI', message: nil, meta: { tool_calls: tools } } }
155
164
  )
156
- Tools.apply(cartridge, input[:tools], tools, feedback).each do |interaction|
165
+ Tools.apply(
166
+ cartridge, input[:tools], tools, feedback, Logic::OpenAI::Tools
167
+ ).each do |interaction|
157
168
  feedback.call({ should_be_stored: true, needs_another_round: true, interaction: })
158
169
  end
159
170
  end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../embedding'
4
+ require_relative '../../logic/cartridge/safety'
5
+
6
+ require 'concurrent'
7
+
8
+ module NanoBot
9
+ module Components
10
+ module Providers
11
+ module Tools
12
+ def self.confirming(tool, feedback)
13
+ feedback.call(
14
+ { should_be_stored: false,
15
+ interaction: { who: 'AI', message: nil, meta: {
16
+ tool: { action: 'confirming', id: tool[:id], name: tool[:label], parameters: tool[:parameters] }
17
+ } } }
18
+ )
19
+ end
20
+
21
+ def self.apply(cartridge, function_cartridge, tools, feedback, tools_logic)
22
+ prepared_tools = tools_logic.prepare(function_cartridge, tools)
23
+
24
+ if Logic::Cartridge::Safety.confirmable?(cartridge)
25
+ prepared_tools.each { |tool| tool[:allowed] = confirming(tool, feedback) }
26
+ else
27
+ prepared_tools.each { |tool| tool[:allowed] = true }
28
+ end
29
+
30
+ futures = prepared_tools.map do |tool|
31
+ Concurrent::Promises.future do
32
+ if tool[:allowed]
33
+ process!(tool, feedback, function_cartridge, cartridge)
34
+ else
35
+ tool[:output] =
36
+ "We asked the user you're chatting with for permission, but the user did not allow you to run this tool or function."
37
+ tool
38
+ end
39
+ end
40
+ end
41
+
42
+ results = Concurrent::Promises.zip(*futures).value!
43
+
44
+ results.map do |applied_tool|
45
+ {
46
+ who: 'tool',
47
+ message: applied_tool[:output],
48
+ meta: { id: applied_tool[:id], name: applied_tool[:name] }
49
+ }
50
+ end
51
+ end
52
+
53
+ def self.process!(tool, feedback, _function_cartridge, cartridge)
54
+ feedback.call(
55
+ { should_be_stored: false,
56
+ interaction: { who: 'AI', message: nil, meta: {
57
+ tool: { action: 'executing', id: tool[:id], name: tool[:label], parameters: tool[:parameters] }
58
+ } } }
59
+ )
60
+
61
+ call = {
62
+ parameters: %w[parameters],
63
+ values: [tool[:parameters]],
64
+ safety: { sandboxed: Logic::Cartridge::Safety.sandboxed?(cartridge) }
65
+ }
66
+
67
+ if %i[fennel lua clojure].count { |key| !tool[:source][key].nil? } > 1
68
+ raise StandardError, 'conflicting tools'
69
+ end
70
+
71
+ if !tool[:source][:fennel].nil?
72
+ call[:source] = tool[:source][:fennel]
73
+ tool[:output] = Components::Embedding.fennel(**call)
74
+ elsif !tool[:source][:clojure].nil?
75
+ call[:source] = tool[:source][:clojure]
76
+ tool[:output] = Components::Embedding.clojure(**call)
77
+ elsif !tool[:source][:lua].nil?
78
+ call[:source] = tool[:source][:lua]
79
+ tool[:output] = Components::Embedding.lua(**call)
80
+ else
81
+ raise 'missing source code'
82
+ end
83
+
84
+ feedback.call(
85
+ { should_be_stored: false,
86
+ interaction: { who: 'AI', message: nil, meta: {
87
+ tool: {
88
+ action: 'responding', id: tool[:id], name: tool[:label],
89
+ parameters: tool[:parameters], output: tool[:output]
90
+ }
91
+ } } }
92
+ )
93
+
94
+ tool
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -63,6 +63,7 @@ module NanoBot
63
63
  behavior = Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors boot]) || {}
64
64
 
65
65
  @state[:history] << {
66
+ at: Time.now,
66
67
  who: 'user',
67
68
  mode: mode.to_s,
68
69
  input: instruction,
@@ -78,6 +79,7 @@ module NanoBot
78
79
  behavior = Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors interaction]) || {}
79
80
 
80
81
  @state[:history] << {
82
+ at: Time.now,
81
83
  who: 'user',
82
84
  mode: mode.to_s,
83
85
  input: message,
@@ -159,7 +161,10 @@ module NanoBot
159
161
  end
160
162
  end
161
163
 
162
- @state[:history] << event if feedback[:should_be_stored]
164
+ if feedback[:should_be_stored]
165
+ event[:at] = Time.now
166
+ @state[:history] << event
167
+ end
163
168
 
164
169
  if event[:output] && ((!feedback[:finished] && streaming) || (!streaming && feedback[:finished]))
165
170
  self.print(color ? Rainbow(event[:output]).send(color) : event[:output])
@@ -2,7 +2,7 @@
2
2
  services:
3
3
  nano-bots:
4
4
  image: ruby:3.2.2-slim-bookworm
5
- command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev curl && curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash && gem install nano-bots -v 1.1.2 && bash"
5
+ command: sh -c "apt-get update && apt-get install -y --no-install-recommends build-essential libffi-dev libsodium-dev lua5.4-dev curl && curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash && gem install nano-bots -v 1.2.0 && bash"
6
6
  environment:
7
7
  OPENAI_API_ADDRESS: https://api.openai.com
8
8
  OPENAI_API_KEY: your-access-token
@@ -7,7 +7,14 @@ module NanoBot
7
7
  module Cartridge
8
8
  module Streaming
9
9
  def self.enabled?(cartridge, interface)
10
- return false if Helpers::Hash.fetch(cartridge, %i[provider settings stream]) == false
10
+ provider_stream = case Helpers::Hash.fetch(cartridge, %i[provider id])
11
+ when 'openai'
12
+ Helpers::Hash.fetch(cartridge, %i[provider settings stream])
13
+ when 'google'
14
+ Helpers::Hash.fetch(cartridge, %i[provider options stream])
15
+ end
16
+
17
+ return false if provider_stream == false
11
18
 
12
19
  specific_interface = Helpers::Hash.fetch(cartridge, [:interfaces, interface, :output, :stream])
13
20
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ module NanoBot
6
+ module Logic
7
+ module Google
8
+ module Tokens
9
+ def self.apply_policies!(_cartridge, payload)
10
+ payload[:contents] = payload[:contents].map { |message| message.except(:_meta) }
11
+ payload
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'babosa'
5
+
6
+ require_relative '../../helpers/hash'
7
+
8
+ module NanoBot
9
+ module Logic
10
+ module Google
11
+ module Tools
12
+ def self.prepare(cartridge, tools)
13
+ applies = []
14
+
15
+ tools = Marshal.load(Marshal.dump(tools))
16
+
17
+ tools.each do |tool|
18
+ tool = Helpers::Hash.symbolize_keys(tool)
19
+
20
+ cartridge.each do |candidate|
21
+ candidate_key = candidate[:name].to_slug.normalize.gsub('-', '_')
22
+ tool_key = tool[:functionCall][:name].to_slug.normalize.gsub('-', '_')
23
+
24
+ next unless candidate_key == tool_key
25
+
26
+ source = {}
27
+
28
+ source[:clojure] = candidate[:clojure] if candidate[:clojure]
29
+ source[:fennel] = candidate[:fennel] if candidate[:fennel]
30
+ source[:lua] = candidate[:lua] if candidate[:lua]
31
+
32
+ applies << {
33
+ label: candidate[:name],
34
+ name: tool[:functionCall][:name],
35
+ type: 'function',
36
+ parameters: tool[:functionCall][:args],
37
+ source:
38
+ }
39
+ end
40
+ end
41
+
42
+ raise 'missing tool' if applies.size != tools.size
43
+
44
+ applies
45
+ end
46
+
47
+ def self.adapt(cartridge)
48
+ output = {
49
+ name: cartridge[:name],
50
+ description: cartridge[:description]
51
+ }
52
+
53
+ output[:parameters] = (cartridge[:parameters] || { type: 'object', properties: {} })
54
+
55
+ output
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openai'
4
+
5
+ module NanoBot
6
+ module Logic
7
+ module OpenAI
8
+ module Tokens
9
+ def self.apply_policies!(_cartridge, payload)
10
+ payload[:messages] = payload[:messages].map { |message| message.except(:_meta) }
11
+ payload
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require 'babosa'
4
5
 
5
6
  require_relative '../../helpers/hash'
6
7
 
@@ -17,7 +18,10 @@ module NanoBot
17
18
  tool = Helpers::Hash.symbolize_keys(tool)
18
19
 
19
20
  cartridge.each do |candidate|
20
- next unless tool[:function][:name] == candidate[:name]
21
+ candidate_key = candidate[:name].to_slug.normalize.gsub('-', '_')
22
+ tool_key = tool[:function][:name].to_slug.normalize.gsub('-', '_')
23
+
24
+ next unless candidate_key == tool_key
21
25
 
22
26
  source = {}
23
27
 
@@ -27,6 +31,7 @@ module NanoBot
27
31
 
28
32
  applies << {
29
33
  id: tool[:id],
34
+ label: candidate[:name],
30
35
  name: tool[:function][:name],
31
36
  type: 'function',
32
37
  parameters: JSON.parse(tool[:function][:arguments]),
data/nano-bots.gemspec CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency 'babosa', '~> 2.0'
35
35
  spec.add_dependency 'concurrent-ruby', '~> 1.2', '>= 1.2.2'
36
36
  spec.add_dependency 'dotenv', '~> 2.8', '>= 2.8.1'
37
- spec.add_dependency 'faraday', '~> 2.7', '>= 2.7.12'
37
+ spec.add_dependency 'gemini-ai', '~> 1.0'
38
38
  spec.add_dependency 'pry', '~> 0.14.2'
39
39
  spec.add_dependency 'rainbow', '~> 3.1', '>= 3.1.1'
40
40
  spec.add_dependency 'rbnacl', '~> 7.1', '>= 7.1.1'
@@ -30,5 +30,7 @@ interfaces:
30
30
  feedback: true
31
31
 
32
32
  provider:
33
+ options:
34
+ stream: true
33
35
  settings:
34
36
  stream: true
data/static/gem.rb CHANGED
@@ -3,10 +3,10 @@
3
3
  module NanoBot
4
4
  GEM = {
5
5
  name: 'nano-bots',
6
- version: '1.1.2',
6
+ version: '1.2.0',
7
7
  author: 'icebaker',
8
- summary: 'Ruby Implementation of Nano Bots: small, AI-powered bots',
9
- description: 'Ruby Implementation of Nano Bots: small, AI-powered bots easily shared as a single file, designed to support multiple providers such as Vicuna, OpenAI ChatGPT, Google PaLM, Alpaca, and LLaMA.',
8
+ summary: 'Ruby Implementation of Nano Bots: small, AI-powered bots for OpenAI ChatGPT and Google Gemini.',
9
+ description: 'Ruby Implementation of Nano Bots: small, AI-powered bots that can be easily shared as a single file, designed to support multiple providers such as OpenAI ChatGPT and Google Gemini, with support for calling Tools (Functions).',
10
10
  github: 'https://github.com/icebaker/ruby-nano-bots',
11
11
  gem_server: 'https://rubygems.org',
12
12
  license: 'MIT',
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nano-bots
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - icebaker
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-10 00:00:00.000000000 Z
11
+ date: 2023-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: babosa
@@ -65,25 +65,19 @@ dependencies:
65
65
  - !ruby/object:Gem::Version
66
66
  version: 2.8.1
67
67
  - !ruby/object:Gem::Dependency
68
- name: faraday
68
+ name: gemini-ai
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
- version: '2.7'
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: 2.7.12
73
+ version: '1.0'
77
74
  type: :runtime
78
75
  prerelease: false
79
76
  version_requirements: !ruby/object:Gem::Requirement
80
77
  requirements:
81
78
  - - "~>"
82
79
  - !ruby/object:Gem::Version
83
- version: '2.7'
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- version: 2.7.12
80
+ version: '1.0'
87
81
  - !ruby/object:Gem::Dependency
88
82
  name: pry
89
83
  requirement: !ruby/object:Gem::Requirement
@@ -172,9 +166,9 @@ dependencies:
172
166
  - - "~>"
173
167
  - !ruby/object:Gem::Version
174
168
  version: 0.0.7
175
- description: 'Ruby Implementation of Nano Bots: small, AI-powered bots easily shared
176
- as a single file, designed to support multiple providers such as Vicuna, OpenAI
177
- ChatGPT, Google PaLM, Alpaca, and LLaMA.'
169
+ description: 'Ruby Implementation of Nano Bots: small, AI-powered bots that can be
170
+ easily shared as a single file, designed to support multiple providers such as OpenAI
171
+ ChatGPT and Google Gemini, with support for calling Tools (Functions).'
178
172
  email:
179
173
  executables:
180
174
  - nb
@@ -195,8 +189,9 @@ files:
195
189
  - components/embedding.rb
196
190
  - components/provider.rb
197
191
  - components/providers/base.rb
192
+ - components/providers/google.rb
198
193
  - components/providers/openai.rb
199
- - components/providers/openai/tools.rb
194
+ - components/providers/tools.rb
200
195
  - components/storage.rb
201
196
  - components/stream.rb
202
197
  - controllers/cartridges.rb
@@ -217,7 +212,10 @@ files:
217
212
  - logic/cartridge/streaming.rb
218
213
  - logic/cartridge/tools.rb
219
214
  - logic/helpers/hash.rb
215
+ - logic/providers/google/tokens.rb
216
+ - logic/providers/google/tools.rb
220
217
  - logic/providers/openai.rb
218
+ - logic/providers/openai/tokens.rb
221
219
  - logic/providers/openai/tools.rb
222
220
  - nano-bots.gemspec
223
221
  - ports/dsl/nano-bots.rb
@@ -250,8 +248,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
250
248
  - !ruby/object:Gem::Version
251
249
  version: '0'
252
250
  requirements: []
253
- rubygems_version: 3.3.3
251
+ rubygems_version: 3.4.22
254
252
  signing_key:
255
253
  specification_version: 4
256
- summary: 'Ruby Implementation of Nano Bots: small, AI-powered bots'
254
+ summary: 'Ruby Implementation of Nano Bots: small, AI-powered bots for OpenAI ChatGPT
255
+ and Google Gemini.'
257
256
  test_files: []
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../embedding'
4
- require_relative '../../../logic/cartridge/safety'
5
-
6
- require 'concurrent'
7
-
8
- module NanoBot
9
- module Components
10
- module Providers
11
- class OpenAI < Base
12
- module Tools
13
- def self.confirming(tool, feedback)
14
- feedback.call(
15
- { should_be_stored: false,
16
- interaction: { who: 'AI', message: nil, meta: {
17
- tool: { action: 'confirming', id: tool[:id], name: tool[:name], parameters: tool[:parameters] }
18
- } } }
19
- )
20
- end
21
-
22
- def self.apply(cartridge, function_cartridge, tools, feedback)
23
- prepared_tools = NanoBot::Logic::OpenAI::Tools.prepare(function_cartridge, tools)
24
-
25
- if Logic::Cartridge::Safety.confirmable?(cartridge)
26
- prepared_tools.each { |tool| tool[:allowed] = confirming(tool, feedback) }
27
- else
28
- prepared_tools.each { |tool| tool[:allowed] = true }
29
- end
30
-
31
- futures = prepared_tools.map do |tool|
32
- Concurrent::Promises.future do
33
- if tool[:allowed]
34
- process!(tool, feedback, function_cartridge, cartridge)
35
- else
36
- tool[:output] =
37
- "We asked the user you're chatting with for permission, but the user did not allow you to run this tool or function."
38
- tool
39
- end
40
- end
41
- end
42
-
43
- results = Concurrent::Promises.zip(*futures).value!
44
-
45
- results.map do |applied_tool|
46
- {
47
- who: 'tool',
48
- message: applied_tool[:output],
49
- meta: { id: applied_tool[:id], name: applied_tool[:name] }
50
- }
51
- end
52
- end
53
-
54
- def self.process!(tool, feedback, _function_cartridge, cartridge)
55
- feedback.call(
56
- { should_be_stored: false,
57
- interaction: { who: 'AI', message: nil, meta: {
58
- tool: { action: 'executing', id: tool[:id], name: tool[:name], parameters: tool[:parameters] }
59
- } } }
60
- )
61
-
62
- call = {
63
- parameters: %w[parameters],
64
- values: [tool[:parameters]],
65
- safety: { sandboxed: Logic::Cartridge::Safety.sandboxed?(cartridge) }
66
- }
67
-
68
- if %i[fennel lua clojure].count { |key| !tool[:source][key].nil? } > 1
69
- raise StandardError, 'conflicting tools'
70
- end
71
-
72
- if !tool[:source][:fennel].nil?
73
- call[:source] = tool[:source][:fennel]
74
- tool[:output] = Components::Embedding.fennel(**call)
75
- elsif !tool[:source][:clojure].nil?
76
- call[:source] = tool[:source][:clojure]
77
- tool[:output] = Components::Embedding.clojure(**call)
78
- elsif !tool[:source][:lua].nil?
79
- call[:source] = tool[:source][:lua]
80
- tool[:output] = Components::Embedding.lua(**call)
81
- else
82
- raise 'missing source code'
83
- end
84
-
85
- feedback.call(
86
- { should_be_stored: false,
87
- interaction: { who: 'AI', message: nil, meta: {
88
- tool: {
89
- action: 'responding', id: tool[:id], name: tool[:name],
90
- parameters: tool[:parameters], output: tool[:output]
91
- }
92
- } } }
93
- )
94
-
95
- tool
96
- end
97
- end
98
- end
99
- end
100
- end
101
- end