summaryrefslogtreecommitdiff
path: root/README.md
blob: 6e125e80279614199febfde4dbfc38b6069ec6e6 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# Nano Bots ๐Ÿ’Ž ๐Ÿค–

A Ruby implementation of the [Nano Bots](https://github.com/icebaker/nano-bots) specification.

![Ruby Nano Bots](https://user-images.githubusercontent.com/113217272/237839690-7880915a-b287-4484-a75e-0b96284b8a32.png)
_Image artificially created by Midjourney through a prompt generated by a Nano Bot specialized in Midjourney._

https://user-images.githubusercontent.com/113217272/238141567-c58a240c-7b67-4b3b-864a-0f49bbf6e22f.mp4

- [Setup](#setup)
  - [Docker](#docker)
- [Usage](#usage)
  - [Command Line](#command-line)
  - [Library](#library)
- [Cartridges](#cartridges)
  - [Tools (Functions)](#tools-functions)
    - [Experimental Clojure Support](#experimental-clojure-support)
  - [Marketplace](#marketplace)
- [Security and Privacy](#security-and-privacy)
  - [Cryptography](#cryptography)
  - [End-user IDs](#end-user-ids)
  - [Decrypting](#decrypting)
- [Providers](#providers)
- [Debugging](#debugging)
- [Development](#development)
  - [Publish to RubyGems](#publish-to-rubygems)

## Setup

For a system usage:

```sh
gem install nano-bots -v 1.1.2
```

To use it in a project, add it to your `Gemfile`:

```ruby
gem 'nano-bots', '~> 1.1.2'
```

```sh
bundle install
```

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:

```sh
export OPENAI_API_ADDRESS=https://api.openai.com
export OPENAI_API_KEY=your-access-token

export NANO_BOTS_ENCRYPTION_PASSWORD=UNSAFE
export NANO_BOTS_END_USER=your-user

# export NANO_BOTS_STATE_DIRECTORY=/home/user/.local/state/nano-bots
# export NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges
```

Alternatively, if your current directory has a `.env` file with the environment variables, they will be automatically loaded:

```sh
OPENAI_API_ADDRESS=https://api.openai.com
OPENAI_API_KEY=your-access-token

NANO_BOTS_ENCRYPTION_PASSWORD=UNSAFE
NANO_BOTS_END_USER=your-user

# NANO_BOTS_STATE_DIRECTORY=/home/user/.local/state/nano-bots
# NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges
```

## Docker

Clone the repository and copy the Docker Compose template:

```
git clone git@github.com:icebaker/ruby-nano-bots.git
cd ruby-nano-bots
cp docker-compose.example.yml docker-compose.yml
```

Set your provider credentials and choose your desired directory for the cartridges files:

```yaml
---
services:
  nano-bots:
    image: ruby:3.2.2-slim-bookworm
    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"
    environment:
      OPENAI_API_ADDRESS: https://api.openai.com
      OPENAI_API_KEY: your-access-token
      NANO_BOTS_ENCRYPTION_PASSWORD: UNSAFE
      NANO_BOTS_END_USER: your-user
    volumes:
      - ./your-cartridges:/root/.local/share/nano-bots/cartridges
      - ./your-state-path:/root/.local/state/nano-bots
```

Enter the container:
```sh
docker compose run nano-bots
```

Start playing:
```sh
nb - - eval "hello"
nb - - repl

nb assistant.yml - eval "hello"
nb assistant.yml - repl
```

You can exit the REPL by typing `exit`.

## Usage

### Command Line

After installing the gem, the `nb` binary command will be available for your project or system.

Examples of usage:

```bash
nb - - eval "hello"
# => Hello! How may I assist you today?

nb to-en-us-translator.yml - eval "Salut, comment รงa va?"
# => Hello, how are you doing?

nb midjourney.yml - eval "happy cyberpunk robot"
# => A cheerful and fun-loving robot is dancing wildly amidst a
#    futuristic and lively cityscape. Holographic advertisements
#    and vibrant neon colors can be seen in the background.

nb lisp.yml - eval "(+ 1 2)"
# => 3

cat article.txt |
  nb to-en-us-translator.yml - eval |
  nb summarizer.yml - eval
# -> LLM stands for Large Language Model, which refers to an
#    artificial intelligence algorithm capable of processing
#    and understanding vast amounts of natural language data,
#    allowing it to generate human-like responses and perform
#    a range of language-related tasks.
```

```bash
nb - - repl

nb assistant.yml - repl
```

```text
๐Ÿค–> Hi, how are you doing?

As an AI language model, I do not experience emotions but I am functioning
well. How can I assist you?

๐Ÿค–> |
```

You can exit the REPL by typing `exit`.

All of the commands above are stateless. If you want to preserve the history of your interactions, replace the `-` with a state key:

```bash
nb assistant.yml your-user eval "Salut, comment รงa va?"
nb assistant.yml your-user repl

nb assistant.yml 6ea6c43c42a1c076b1e3c36fa349ac2c eval "Salut, comment รงa va?"
nb assistant.yml 6ea6c43c42a1c076b1e3c36fa349ac2c repl
```

You can use a simple key, such as your username, or a randomly generated one:

```ruby
require 'securerandom'

SecureRandom.hex # => 6ea6c43c42a1c076b1e3c36fa349ac2c
```

### Debugging

```sh
nb - - cartridge
nb cartridge.yml - cartridge

nb - STATE-KEY state
nb cartridge.yml STATE-KEY state
```

### Library

To use it as a library:

```ruby
require 'nano-bots/cli' # Equivalent to the `nb` command.
```

```ruby
require 'nano-bots'

NanoBot.cli # Equivalent to the `nb` command.

NanoBot.repl(cartridge: 'cartridge.yml') # Starts a new REPL.

bot = NanoBot.new(cartridge: 'cartridge.yml')

bot = NanoBot.new(
  cartridge: YAML.safe_load(File.read('cartridge.yml'), permitted_classes: [Symbol])
)

bot = NanoBot.new(
  cartridge: { ... } # Parsed Cartridge Hash
)

bot.eval('Hello')

bot.eval('Hello', as: 'eval')
bot.eval('Hello', as: 'repl')

# When stream is enabled and available:
bot.eval('Hi!') do |content, fragment, finished, meta|
  print fragment unless fragment.nil?
end

bot.repl # Starts a new REPL.

NanoBot.repl(cartridge: 'cartridge.yml', state: '6ea6c43c42a1c076b1e3c36fa349ac2c')

bot = NanoBot.new(cartridge: 'cartridge.yml', state: '6ea6c43c42a1c076b1e3c36fa349ac2c')

bot.prompt # => "๐Ÿค–\u001b[34m> \u001b[0m"

bot.boot

bot.boot(as: 'eval')
bot.boot(as: 'repl')

bot.boot do |content, fragment, finished, meta|
  print fragment unless fragment.nil?
end
```

## Cartridges

Here's what a Nano Bot Cartridge looks like:

```yaml
---
meta:
  symbol: ๐Ÿค–
  name: Nano Bot Name
  author: Your Name
  version: 1.0.0
  license: CC0-1.0
  description: A helpful assistant.

behaviors:
  interaction:
    directive: You are a helpful assistant.

provider:
  id: openai
  credentials:
    address: ENV/OPENAI_API_ADDRESS
    access-token: ENV/OPENAI_API_KEY
  settings:
    user: ENV/NANO_BOTS_END_USER
    model: gpt-3.5-turbo
```

Check the Nano Bots specification to learn more about [how to build cartridges](https://spec.nbots.io/#/README?id=cartridges).

Try the [Nano Bots Clinic (Live Editor)](https://clinic.nbots.io) to learn about creating Cartridges.

### Tools (Functions)

Nano Bots can also be powered by _Tools_ (Functions):

```yaml
---
tools:
  - name: random-number
    description: Generates a random number between 1 and 100.
    fennel: |
      (math.random 1 100)
```

```
๐Ÿค–> please generate a random number

random-number {} [yN] y

random-number {}
59

The randomly generated number is 59.

๐Ÿค–> |
```

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`:

```yaml
---
provider:
  id: openai
  credentials:
    address: ENV/OPENAI_API_ADDRESS
    access-token: ENV/OPENAI_API_KEY
  settings:
    user: ENV/NANO_BOTS_END_USER
    model: gpt-4-1106-preview
```

Check the [Nano Bots specification](https://spec.nbots.io/#/README?id=tools-functions-2) to learn more about them.

#### Experimental Clojure Support

We are exploring the use of [Clojure](https://clojure.org) through [Babashka](https://babashka.org), powered by [GraalVM](https://www.graalvm.org).

The experimental support for Clojure would be similar to Lua and Fennel, using the `clojure:` key:

```yaml
---
clojure: |
  (-> (java.time.ZonedDateTime/now)
      (.format (java.time.format.DateTimeFormatter/ofPattern "yyyy-MM-dd HH:mm"))
      (clojure.string/trimr))
```

Unlike Lua and Fennel, Clojure support is not _embedded_ in this implementation. It relies on having the Babashka binary (`bb`) available in your environment where the Nano Bot is running.

Here's [how to install Babashka](https://github.com/babashka/babashka#quickstart):

```sh
curl -s https://raw.githubusercontent.com/babashka/babashka/master/install | bash
```

This is a quick check to ensure that it is available and working:
```sh
bb -e '{:hello "world"}'
# => {:hello "world"}
```

We don't have sandbox support for Clojure; this means that you need to disable it to be able to run Clojure code, which you do at your own risk:

```yaml
---
safety:
  functions:
    sandboxed: false
```

### Marketplace

You can explore the Nano Bots [Marketplace](https://nbots.io) to discover new Cartridges that can help you.

## Security and Privacy

Each provider will have its own security and privacy policies (e.g. [OpenAI Policy](https://openai.com/policies/api-data-usage-policies)), so you must consult them to understand their implications.

### Cryptography

By default, all states stored in your local disk are encrypted.

To ensure that the encryption is secure, you need to define a password through the `NANO_BOTS_ENCRYPTION_PASSWORD` environment variable. Otherwise, although the content will be encrypted, anyone would be able to decrypt it without a password.

It's important to note that the content shared with providers, despite being transmitted over secure connections (e.g., [HTTPS](https://en.wikipedia.org/wiki/HTTPS)), will be readable by the provider. This is because providers need to operate on the data, which would not be possible if the content was encrypted beyond HTTPS. So, the data stored locally on your system is encrypted, which does not mean that what you share with providers will not be readable by them.

To ensure that your encryption and password are configured properly, you can run the following command:
```sh
nb security
```

Which should return:
```text
โœ… Encryption is enabled and properly working.
     This means that your data is stored in an encrypted format on your disk.

โœ… A password is being used for the encrypted content.
     This means that only those who possess the password can decrypt your data.
```

Alternatively, you can check it at runtime with:
```ruby
require 'nano-bots'

NanoBot.security.check
# => { encryption: true, password: true }
```

### End-user IDs

A common strategy for deploying Nano Bots to multiple users through APIs or automations is to assign a unique [end-user ID](https://platform.openai.com/docs/guides/safety-best-practices/end-user-ids) for each user. This can be useful if any of your users violate the provider's policy due to abusive behavior. By providing the end-user ID, you can unravel that even though the activity originated from your API Key, the actions taken were not your own.

You can define custom end-user identifiers in the following way:

```ruby
NanoBot.new(environment: { NANO_BOTS_END_USER: 'custom-user-a' })
NanoBot.new(environment: { NANO_BOTS_END_USER: 'custom-user-b' })
```

Consider that you have the following end-user identifier in your environment:
```sh
NANO_BOTS_END_USER=your-name
```

Or a configuration in your Cartridge:
```yml
---
provider:
  id: openai
  settings:
    user: your-name
```

The requests will be performed as follows:

```ruby
NanoBot.new(cartridge: '-')
# { user: 'your-name' }

NanoBot.new(cartridge: '-', environment: { NANO_BOTS_END_USER: 'custom-user-a' })
# { user: 'custom-user-a' }

NanoBot.new(cartridge: '-', environment: { NANO_BOTS_END_USER: 'custom-user-b' })
# { user: 'custom-user-b' }
```

Actually, to enhance privacy, neither your user nor your users' identifiers will be shared in this way. Instead, they will be encrypted before being shared with the provider:

```ruby
'your-name'
# _O7OjYUESagb46YSeUeSfSMzoO1Yg0BZqpsAkPg4j62SeNYlgwq3kn51Ob2wmIehoA==

'custom-user-a'
# _O7OjYUESagb46YSeUeSfSMzoO1Yg0BZJgIXHCBHyADW-rn4IQr-s2RvP7vym8u5tnzYMIs=

'custom-user-b'
# _O7OjYUESagb46YSeUeSfSMzoO1Yg0BZkjUwCcsh9sVppKvYMhd2qGRvP7vym8u5tnzYMIg=
```

In this manner, you possess identifiers if required, however, their actual content can only be decrypted by you via your secure password (`NANO_BOTS_ENCRYPTION_PASSWORD`).

### Decrypting

To decrypt your encrypted data, once you have properly configured your password, you can simply run:

```ruby
require 'nano-bots'

NanoBot.security.decrypt('_O7OjYUESagb46YSeUeSfSMzoO1Yg0BZqpsAkPg4j62SeNYlgwq3kn51Ob2wmIehoA==')
# your-name

NanoBot.security.decrypt('_O7OjYUESagb46YSeUeSfSMzoO1Yg0BZJgIXHCBHyADW-rn4IQr-s2RvP7vym8u5tnzYMIs=')
# custom-user-a

NanoBot.security.decrypt('_O7OjYUESagb46YSeUeSfSMzoO1Yg0BZkjUwCcsh9sVppKvYMhd2qGRvP7vym8u5tnzYMIg=')
# custom-user-b
```

If you lose your password, you lose your data. It is not possible to recover it at all. For real.

## Providers

Currently supported providers:

- [x] [FastChat (Vicuna)](https://github.com/lm-sys/FastChat)
- [x] [Open AI](https://platform.openai.com/docs/api-reference)
- [ ] [Google PaLM](https://developers.generativeai.google/)
- [ ] [Alpaca](https://github.com/tatsu-lab/stanford_alpaca)
- [ ] [LLaMA](https://github.com/facebookresearch/llama)

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.

## Development

```bash
bundle
rubocop -A
rspec
```

### Publish to RubyGems

```bash
gem build nano-bots.gemspec

gem signin

gem push nano-bots-1.1.2.gem
```