nano-bots 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Gemfile.lock +28 -4
- data/README.md +122 -38
- data/components/provider.rb +3 -2
- data/components/providers/google.rb +202 -0
- data/components/providers/openai.rb +24 -13
- data/components/providers/tools.rb +99 -0
- data/controllers/session.rb +6 -1
- data/docker-compose.example.yml +1 -1
- data/logic/cartridge/streaming.rb +8 -1
- data/logic/providers/google/tokens.rb +16 -0
- data/logic/providers/google/tools.rb +60 -0
- data/logic/providers/openai/tokens.rb +16 -0
- data/logic/providers/openai/tools.rb +6 -1
- data/nano-bots.gemspec +1 -1
- data/static/cartridges/default.yml +2 -0
- data/static/gem.rb +3 -3
- metadata +16 -17
- data/components/providers/openai/tools.rb +0 -101
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32f9bfa761f2b8f34c638d86f207b804a1861873b6ff09dc09202e815fb60943
|
4
|
+
data.tar.gz: 274c62930860eef8ba3bedff9d169513627160539fd7c30077e883b882fb5330
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7eb6dd2df0d4cbc9a7d7ad880b7ffa633a8ed3ebb52367f1e379efbc78f142a9675603bbcb26c05aa8a2533f3ca9aa5b120faea83843bcc4120a4f01895fa38f
|
7
|
+
data.tar.gz: a7451773e12f9c25362ffcb04f1c8ce78f7aff0988fec4178d0456d4f91bf70feffa631232a7e182b0cefd6d015ee5fd52ab19448eeeefb77ff821bf2f34d4ea
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
nano-bots (1.
|
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
|
-
|
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.
|
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.
|
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
|
-
|
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
|
-
|
14
|
-
|
17
|
+
- [Command Line](#command-line)
|
18
|
+
- [Library](#library)
|
15
19
|
- [Cartridges](#cartridges)
|
16
|
-
|
17
|
-
- [
|
18
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
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.
|
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.
|
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.
|
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-
|
342
|
+
model: gpt-4-1106-preview
|
273
343
|
```
|
274
344
|
|
275
|
-
|
345
|
+
### Google Gemini
|
276
346
|
|
277
|
-
|
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
|
-
|
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] [
|
473
|
-
- [x] [
|
474
|
-
- [ ] [
|
475
|
-
- [ ] [
|
476
|
-
- [ ] [
|
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
|
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.
|
579
|
+
gem push nano-bots-1.2.0.gem
|
496
580
|
```
|
data/components/provider.rb
CHANGED
@@ -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 '../../
|
9
|
+
require_relative '../../logic/providers/openai/tokens'
|
10
10
|
|
11
|
-
require_relative '
|
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,
|
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],
|
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',
|
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|
|
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(
|
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(
|
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
|
data/controllers/session.rb
CHANGED
@@ -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
|
-
|
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])
|
data/docker-compose.example.yml
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
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 '
|
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'
|
data/static/gem.rb
CHANGED
@@ -3,10 +3,10 @@
|
|
3
3
|
module NanoBot
|
4
4
|
GEM = {
|
5
5
|
name: 'nano-bots',
|
6
|
-
version: '1.
|
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
|
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.
|
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-
|
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:
|
68
|
+
name: gemini-ai
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
70
70
|
requirements:
|
71
71
|
- - "~>"
|
72
72
|
- !ruby/object:Gem::Version
|
73
|
-
version: '
|
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: '
|
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
|
176
|
-
as a single file, designed to support multiple providers such as
|
177
|
-
ChatGPT
|
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/
|
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.
|
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
|