gemini-ai 2.2.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +181 -65
- data/components/errors.rb +1 -1
- data/controllers/client.rb +25 -18
- data/static/gem.rb +1 -1
- data/tasks/generate-readme.clj +1 -1
- data/template.md +167 -55
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80621f6cd2de526141e994a339d645b53492f6ece960955bc56f2e7430be1d0c
|
4
|
+
data.tar.gz: b627386a0dacc899112e0b08806f63b571dd980f2fb749df7681d7c70656d707
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5425c3239dac6eca23cccdb2483f90bea23500b0830b865e8acc0fe235cf2ad3cb001d110e7a6ec0cbb59ed5c2f65e74f883edef961cb675b76e7d6117d88fc
|
7
|
+
data.tar.gz: 041122dc5a6d1d596411ea697503aa8d7846b4d2bc6c493b8b7d9102033c9ef3226957340d8caeb443751f9f4f465624a80ef1749e0f5893833e4c72d43ba907
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@ A Ruby Gem for interacting with [Gemini](https://deepmind.google/technologies/ge
|
|
9
9
|
## TL;DR and Quick Start
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
gem 'gemini-ai', '~>
|
12
|
+
gem 'gemini-ai', '~> 3.0.0'
|
13
13
|
```
|
14
14
|
|
15
15
|
```ruby
|
@@ -21,7 +21,7 @@ client = Gemini.new(
|
|
21
21
|
service: 'generative-language-api',
|
22
22
|
api_key: ENV['GOOGLE_API_KEY']
|
23
23
|
},
|
24
|
-
options: { model: 'gemini-pro',
|
24
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
25
25
|
)
|
26
26
|
|
27
27
|
# With a Service Account Credentials File
|
@@ -31,7 +31,7 @@ client = Gemini.new(
|
|
31
31
|
file_path: 'google-credentials.json',
|
32
32
|
region: 'us-east4'
|
33
33
|
},
|
34
|
-
options: { model: 'gemini-pro',
|
34
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
35
35
|
)
|
36
36
|
|
37
37
|
# With Application Default Credentials
|
@@ -40,7 +40,7 @@ client = Gemini.new(
|
|
40
40
|
service: 'vertex-ai-api',
|
41
41
|
region: 'us-east4'
|
42
42
|
},
|
43
|
-
options: { model: 'gemini-pro',
|
43
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
44
44
|
)
|
45
45
|
|
46
46
|
result = client.stream_generate_content({
|
@@ -81,16 +81,20 @@ Result:
|
|
81
81
|
- [Required Data](#required-data)
|
82
82
|
- [Usage](#usage)
|
83
83
|
- [Client](#client)
|
84
|
-
- [
|
85
|
-
- [
|
86
|
-
- [
|
87
|
-
- [
|
88
|
-
|
89
|
-
|
90
|
-
- [
|
91
|
-
- [
|
92
|
-
- [
|
93
|
-
|
84
|
+
- [Methods](#methods)
|
85
|
+
- [stream_generate_content](#stream_generate_content)
|
86
|
+
- [Receiving Stream Events](#receiving-stream-events)
|
87
|
+
- [Without Events](#without-events)
|
88
|
+
- [generate_content](#generate_content)
|
89
|
+
- [Modes](#modes)
|
90
|
+
- [Text](#text)
|
91
|
+
- [Image](#image)
|
92
|
+
- [Video](#video)
|
93
|
+
- [Streaming vs. Server-Sent Events (SSE)](#streaming-vs-server-sent-events-sse)
|
94
|
+
- [Server-Sent Events (SSE) Hang](#server-sent-events-sse-hang)
|
95
|
+
- [Non-Streaming](#non-streaming)
|
96
|
+
- [Back-and-Forth Conversations](#back-and-forth-conversations)
|
97
|
+
- [Tools (Functions) Calling](#tools-functions-calling)
|
94
98
|
- [New Functionalities and APIs](#new-functionalities-and-apis)
|
95
99
|
- [Error Handling](#error-handling)
|
96
100
|
- [Rescuing](#rescuing)
|
@@ -108,11 +112,11 @@ Result:
|
|
108
112
|
### Installing
|
109
113
|
|
110
114
|
```sh
|
111
|
-
gem install gemini-ai -v
|
115
|
+
gem install gemini-ai -v 3.0.0
|
112
116
|
```
|
113
117
|
|
114
118
|
```sh
|
115
|
-
gem 'gemini-ai', '~>
|
119
|
+
gem 'gemini-ai', '~> 3.0.0'
|
116
120
|
```
|
117
121
|
|
118
122
|
### Credentials
|
@@ -279,7 +283,7 @@ client = Gemini.new(
|
|
279
283
|
service: 'generative-language-api',
|
280
284
|
api_key: ENV['GOOGLE_API_KEY']
|
281
285
|
},
|
282
|
-
options: { model: 'gemini-pro',
|
286
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
283
287
|
)
|
284
288
|
|
285
289
|
# With a Service Account Credentials File
|
@@ -289,7 +293,7 @@ client = Gemini.new(
|
|
289
293
|
file_path: 'google-credentials.json',
|
290
294
|
region: 'us-east4'
|
291
295
|
},
|
292
|
-
options: { model: 'gemini-pro',
|
296
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
293
297
|
)
|
294
298
|
|
295
299
|
# With Application Default Credentials
|
@@ -298,15 +302,118 @@ client = Gemini.new(
|
|
298
302
|
service: 'vertex-ai-api',
|
299
303
|
region: 'us-east4'
|
300
304
|
},
|
301
|
-
options: { model: 'gemini-pro',
|
305
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
302
306
|
)
|
303
307
|
```
|
304
308
|
|
305
|
-
###
|
309
|
+
### Methods
|
306
310
|
|
307
|
-
####
|
311
|
+
#### stream_generate_content
|
308
312
|
|
309
|
-
#####
|
313
|
+
##### Receiving Stream Events
|
314
|
+
|
315
|
+
Ensure that you have enabled [Server-Sent Events](#streaming-vs-server-sent-events-sse) before using blocks for streaming:
|
316
|
+
|
317
|
+
```ruby
|
318
|
+
client.stream_generate_content(
|
319
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
320
|
+
) do |event, parsed, raw|
|
321
|
+
puts event
|
322
|
+
end
|
323
|
+
```
|
324
|
+
|
325
|
+
Event:
|
326
|
+
```ruby
|
327
|
+
{ 'candidates' =>
|
328
|
+
[{ 'content' => {
|
329
|
+
'role' => 'model',
|
330
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
331
|
+
},
|
332
|
+
'finishReason' => 'STOP',
|
333
|
+
'safetyRatings' =>
|
334
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
335
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
336
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
337
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
338
|
+
'usageMetadata' => {
|
339
|
+
'promptTokenCount' => 2,
|
340
|
+
'candidatesTokenCount' => 8,
|
341
|
+
'totalTokenCount' => 10
|
342
|
+
} }
|
343
|
+
```
|
344
|
+
|
345
|
+
##### Without Events
|
346
|
+
|
347
|
+
You can use `stream_generate_content` without events:
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
result = client.stream_generate_content(
|
351
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
352
|
+
)
|
353
|
+
```
|
354
|
+
|
355
|
+
In this case, the result will be an array with all the received events:
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
[{ 'candidates' =>
|
359
|
+
[{ 'content' => {
|
360
|
+
'role' => 'model',
|
361
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
362
|
+
},
|
363
|
+
'finishReason' => 'STOP',
|
364
|
+
'safetyRatings' =>
|
365
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
366
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
367
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
368
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
369
|
+
'usageMetadata' => {
|
370
|
+
'promptTokenCount' => 2,
|
371
|
+
'candidatesTokenCount' => 8,
|
372
|
+
'totalTokenCount' => 10
|
373
|
+
} }]
|
374
|
+
```
|
375
|
+
|
376
|
+
You can mix both as well:
|
377
|
+
```ruby
|
378
|
+
result = client.stream_generate_content(
|
379
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
380
|
+
) do |event, parsed, raw|
|
381
|
+
puts event
|
382
|
+
end
|
383
|
+
```
|
384
|
+
|
385
|
+
#### generate_content
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
result = client.generate_content(
|
389
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
390
|
+
)
|
391
|
+
```
|
392
|
+
|
393
|
+
Result:
|
394
|
+
```ruby
|
395
|
+
{ 'candidates' =>
|
396
|
+
[{ 'content' => { 'parts' => [{ 'text' => 'Hello! How can I assist you today?' }], 'role' => 'model' },
|
397
|
+
'finishReason' => 'STOP',
|
398
|
+
'index' => 0,
|
399
|
+
'safetyRatings' =>
|
400
|
+
[{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
401
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
402
|
+
{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
403
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
404
|
+
'promptFeedback' =>
|
405
|
+
{ 'safetyRatings' =>
|
406
|
+
[{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
407
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
408
|
+
{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
409
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] } }
|
410
|
+
```
|
411
|
+
|
412
|
+
As of the writing of this README, only the `generative-language-api` service supports the `generate_content` method; `vertex-ai-api` does not.
|
413
|
+
|
414
|
+
### Modes
|
415
|
+
|
416
|
+
#### Text
|
310
417
|
|
311
418
|
```ruby
|
312
419
|
result = client.stream_generate_content({
|
@@ -334,7 +441,7 @@ Result:
|
|
334
441
|
} }]
|
335
442
|
```
|
336
443
|
|
337
|
-
|
444
|
+
#### Image
|
338
445
|
|
339
446
|
![A black and white image of an old piano. The piano is an upright model, with the keys on the right side of the image. The piano is sitting on a tiled floor. There is a small round object on the top of the piano.](https://raw.githubusercontent.com/gbaptista/assets/main/gemini-ai/piano.jpg)
|
340
447
|
|
@@ -345,7 +452,7 @@ Switch to the `gemini-pro-vision` model:
|
|
345
452
|
```ruby
|
346
453
|
client = Gemini.new(
|
347
454
|
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
348
|
-
options: { model: 'gemini-pro-vision',
|
455
|
+
options: { model: 'gemini-pro-vision', server_sent_events: true }
|
349
456
|
)
|
350
457
|
```
|
351
458
|
|
@@ -391,7 +498,7 @@ The result:
|
|
391
498
|
'usageMetadata' => { 'promptTokenCount' => 263, 'candidatesTokenCount' => 50, 'totalTokenCount' => 313 } }]
|
392
499
|
```
|
393
500
|
|
394
|
-
|
501
|
+
#### Video
|
395
502
|
|
396
503
|
https://gist.github.com/assets/29520/f82bccbf-02d2-4899-9c48-eb8a0a5ef741
|
397
504
|
|
@@ -404,7 +511,7 @@ Switch to the `gemini-pro-vision` model:
|
|
404
511
|
```ruby
|
405
512
|
client = Gemini.new(
|
406
513
|
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
407
|
-
options: { model: 'gemini-pro-vision',
|
514
|
+
options: { model: 'gemini-pro-vision', server_sent_events: true }
|
408
515
|
)
|
409
516
|
```
|
410
517
|
|
@@ -451,41 +558,15 @@ The result:
|
|
451
558
|
"usageMetadata"=>{"promptTokenCount"=>1037, "candidatesTokenCount"=>32, "totalTokenCount"=>1069}}]
|
452
559
|
```
|
453
560
|
|
454
|
-
|
561
|
+
### Streaming vs. Server-Sent Events (SSE)
|
455
562
|
|
456
|
-
|
457
|
-
result = client.stream_generate_content({
|
458
|
-
contents: { role: 'user', parts: { text: 'hi!' } }
|
459
|
-
})
|
460
|
-
```
|
563
|
+
[Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events) is a technology that allows certain endpoints to offer streaming capabilities, such as creating the impression that "the model is typing along with you," rather than delivering the entire answer all at once.
|
461
564
|
|
462
|
-
|
463
|
-
```ruby
|
464
|
-
[{ 'candidates' =>
|
465
|
-
[{ 'content' => {
|
466
|
-
'role' => 'model',
|
467
|
-
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
468
|
-
},
|
469
|
-
'finishReason' => 'STOP',
|
470
|
-
'safetyRatings' =>
|
471
|
-
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
472
|
-
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
473
|
-
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
474
|
-
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
475
|
-
'usageMetadata' => {
|
476
|
-
'promptTokenCount' => 2,
|
477
|
-
'candidatesTokenCount' => 8,
|
478
|
-
'totalTokenCount' => 10
|
479
|
-
} }]
|
480
|
-
```
|
481
|
-
|
482
|
-
#### Streaming
|
483
|
-
|
484
|
-
You can set up the client to use streaming for all supported endpoints:
|
565
|
+
You can set up the client to use Server-Sent Events (SSE) for all supported endpoints:
|
485
566
|
```ruby
|
486
567
|
client = Gemini.new(
|
487
568
|
credentials: { ... },
|
488
|
-
options: { model: 'gemini-pro',
|
569
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
489
570
|
)
|
490
571
|
```
|
491
572
|
|
@@ -493,11 +574,11 @@ Or, you can decide on a request basis:
|
|
493
574
|
```ruby
|
494
575
|
client.stream_generate_content(
|
495
576
|
{ contents: { role: 'user', parts: { text: 'hi!' } } },
|
496
|
-
|
577
|
+
server_sent_events: true
|
497
578
|
)
|
498
579
|
```
|
499
580
|
|
500
|
-
With
|
581
|
+
With Server-Sent Events (SSE) enabled, you can use a block to receive partial results via events. This feature is particularly useful for methods that offer streaming capabilities, such as `stream_generate_content`:
|
501
582
|
|
502
583
|
```ruby
|
503
584
|
client.stream_generate_content(
|
@@ -527,14 +608,16 @@ Event:
|
|
527
608
|
} }
|
528
609
|
```
|
529
610
|
|
530
|
-
|
611
|
+
Even though streaming methods utilize Server-Sent Events (SSE), using this feature doesn't necessarily mean streaming data. For example, when `generate_content` is called with SSE enabled, you will receive all the data at once in a single event, rather than through multiple partial events. This occurs because `generate_content` isn't designed for streaming, even though it is capable of utilizing Server-Sent Events.
|
531
612
|
|
532
|
-
|
613
|
+
#### Server-Sent Events (SSE) Hang
|
614
|
+
|
615
|
+
Method calls will _hang_ until the server-sent events finish, so even without providing a block, you can obtain the final results of the received events:
|
533
616
|
|
534
617
|
```ruby
|
535
618
|
result = client.stream_generate_content(
|
536
619
|
{ contents: { role: 'user', parts: { text: 'hi!' } } },
|
537
|
-
|
620
|
+
server_sent_events: true
|
538
621
|
)
|
539
622
|
```
|
540
623
|
|
@@ -558,7 +641,40 @@ Result:
|
|
558
641
|
} }]
|
559
642
|
```
|
560
643
|
|
561
|
-
####
|
644
|
+
#### Non-Streaming
|
645
|
+
|
646
|
+
Depending on the service, you can use the [`generate_content`](#generate_content) method, which does not stream the answer.
|
647
|
+
|
648
|
+
You can also use methods designed for streaming without necessarily processing partial events; instead, you can wait for the result of all received events:
|
649
|
+
|
650
|
+
```ruby
|
651
|
+
result = client.stream_generate_content({
|
652
|
+
contents: { role: 'user', parts: { text: 'hi!' } },
|
653
|
+
server_sent_events: false
|
654
|
+
})
|
655
|
+
```
|
656
|
+
|
657
|
+
Result:
|
658
|
+
```ruby
|
659
|
+
[{ 'candidates' =>
|
660
|
+
[{ 'content' => {
|
661
|
+
'role' => 'model',
|
662
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
663
|
+
},
|
664
|
+
'finishReason' => 'STOP',
|
665
|
+
'safetyRatings' =>
|
666
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
667
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
668
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
669
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
670
|
+
'usageMetadata' => {
|
671
|
+
'promptTokenCount' => 2,
|
672
|
+
'candidatesTokenCount' => 8,
|
673
|
+
'totalTokenCount' => 10
|
674
|
+
} }]
|
675
|
+
```
|
676
|
+
|
677
|
+
### Back-and-Forth Conversations
|
562
678
|
|
563
679
|
To maintain a back-and-forth conversation, you need to append the received responses and build a history for your requests:
|
564
680
|
|
@@ -600,7 +716,7 @@ Result:
|
|
600
716
|
} }]
|
601
717
|
```
|
602
718
|
|
603
|
-
|
719
|
+
### Tools (Functions) Calling
|
604
720
|
|
605
721
|
> As of the writing of this README, only the `vertex-ai-api` service and the `gemini-pro` model [supports](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/function-calling#supported_models) tools (functions) calls.
|
606
722
|
|
@@ -803,7 +919,7 @@ GeminiError
|
|
803
919
|
|
804
920
|
MissingProjectIdError
|
805
921
|
UnsupportedServiceError
|
806
|
-
|
922
|
+
BlockWithoutServerSentEventsError
|
807
923
|
|
808
924
|
RequestError
|
809
925
|
```
|
@@ -826,7 +942,7 @@ gem build gemini-ai.gemspec
|
|
826
942
|
|
827
943
|
gem signin
|
828
944
|
|
829
|
-
gem push gemini-ai-
|
945
|
+
gem push gemini-ai-3.0.0.gem
|
830
946
|
```
|
831
947
|
|
832
948
|
### Updating the README
|
data/components/errors.rb
CHANGED
@@ -10,7 +10,7 @@ module Gemini
|
|
10
10
|
|
11
11
|
class MissingProjectIdError < GeminiError; end
|
12
12
|
class UnsupportedServiceError < GeminiError; end
|
13
|
-
class
|
13
|
+
class BlockWithoutServerSentEventsError < GeminiError; end
|
14
14
|
|
15
15
|
class RequestError < GeminiError
|
16
16
|
attr_reader :request, :payload
|
data/controllers/client.rb
CHANGED
@@ -5,7 +5,7 @@ require 'faraday'
|
|
5
5
|
require 'json'
|
6
6
|
require 'googleauth'
|
7
7
|
|
8
|
-
require_relative '../
|
8
|
+
require_relative '../ports/dsl/gemini-ai/errors'
|
9
9
|
|
10
10
|
module Gemini
|
11
11
|
module Controllers
|
@@ -26,43 +26,50 @@ module Gemini
|
|
26
26
|
end
|
27
27
|
|
28
28
|
if @authentication == :service_account || @authentication == :default_credentials
|
29
|
-
@project_id =
|
30
|
-
@authorizer.project_id || @authorizer.quota_project_id
|
31
|
-
else
|
32
|
-
config[:credentials][:project_id]
|
33
|
-
end
|
29
|
+
@project_id = config[:credentials][:project_id] || @authorizer.project_id || @authorizer.quota_project_id
|
34
30
|
|
35
31
|
raise MissingProjectIdError, 'Could not determine project_id, which is required.' if @project_id.nil?
|
36
32
|
end
|
37
33
|
|
38
|
-
@
|
34
|
+
@service = config[:credentials][:service]
|
35
|
+
|
36
|
+
@address = case @service
|
39
37
|
when 'vertex-ai-api'
|
40
38
|
"https://#{config[:credentials][:region]}-aiplatform.googleapis.com/v1/projects/#{@project_id}/locations/#{config[:credentials][:region]}/publishers/google/models/#{config[:options][:model]}"
|
41
39
|
when 'generative-language-api'
|
42
40
|
"https://generativelanguage.googleapis.com/v1/models/#{config[:options][:model]}"
|
43
41
|
else
|
44
|
-
raise UnsupportedServiceError, "Unsupported service: #{
|
42
|
+
raise UnsupportedServiceError, "Unsupported service: #{@service}"
|
45
43
|
end
|
46
44
|
|
47
|
-
@
|
45
|
+
@server_sent_events = config[:options][:server_sent_events]
|
46
|
+
end
|
47
|
+
|
48
|
+
def stream_generate_content(payload, server_sent_events: nil, &callback)
|
49
|
+
request('streamGenerateContent', payload, server_sent_events:, &callback)
|
48
50
|
end
|
49
51
|
|
50
|
-
def
|
51
|
-
request('
|
52
|
+
def generate_content(payload, server_sent_events: nil, &callback)
|
53
|
+
result = request('generateContent', payload, server_sent_events:, &callback)
|
54
|
+
|
55
|
+
return result.first if result.is_a?(Array) && result.size == 1
|
56
|
+
|
57
|
+
result
|
52
58
|
end
|
53
59
|
|
54
|
-
def request(path, payload,
|
55
|
-
|
60
|
+
def request(path, payload, server_sent_events: nil, &callback)
|
61
|
+
server_sent_events_enabled = server_sent_events.nil? ? @server_sent_events : server_sent_events
|
56
62
|
url = "#{@address}:#{path}"
|
57
63
|
params = []
|
58
64
|
|
59
|
-
params << 'alt=sse' if
|
65
|
+
params << 'alt=sse' if server_sent_events_enabled
|
60
66
|
params << "key=#{@api_key}" if @authentication == :api_key
|
61
67
|
|
62
68
|
url += "?#{params.join('&')}" if params.size.positive?
|
63
69
|
|
64
|
-
if !callback.nil? && !
|
65
|
-
raise
|
70
|
+
if !callback.nil? && !server_sent_events_enabled
|
71
|
+
raise BlockWithoutServerSentEventsError,
|
72
|
+
'You are trying to use a block without Server Sent Events (SSE) enabled.'
|
66
73
|
end
|
67
74
|
|
68
75
|
results = []
|
@@ -78,7 +85,7 @@ module Gemini
|
|
78
85
|
|
79
86
|
request.body = payload.to_json
|
80
87
|
|
81
|
-
if
|
88
|
+
if server_sent_events_enabled
|
82
89
|
parser = EventStreamParser::Parser.new
|
83
90
|
|
84
91
|
request.options.on_data = proc do |chunk, bytes, env|
|
@@ -107,7 +114,7 @@ module Gemini
|
|
107
114
|
end
|
108
115
|
end
|
109
116
|
|
110
|
-
return safe_parse_json(response.body) unless
|
117
|
+
return safe_parse_json(response.body) unless server_sent_events_enabled
|
111
118
|
|
112
119
|
results.map { |result| result[:event] }
|
113
120
|
rescue Faraday::ServerError => e
|
data/static/gem.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Gemini
|
4
4
|
GEM = {
|
5
5
|
name: 'gemini-ai',
|
6
|
-
version: '
|
6
|
+
version: '3.0.0',
|
7
7
|
author: 'gbaptista',
|
8
8
|
summary: "Interact with Google's Gemini AI.",
|
9
9
|
description: "A Ruby Gem for interacting with Gemini through Vertex AI, Generative Language API, or AI Studio, Google's generative AI services.",
|
data/tasks/generate-readme.clj
CHANGED
data/template.md
CHANGED
@@ -9,7 +9,7 @@ A Ruby Gem for interacting with [Gemini](https://deepmind.google/technologies/ge
|
|
9
9
|
## TL;DR and Quick Start
|
10
10
|
|
11
11
|
```ruby
|
12
|
-
gem 'gemini-ai', '~>
|
12
|
+
gem 'gemini-ai', '~> 3.0.0'
|
13
13
|
```
|
14
14
|
|
15
15
|
```ruby
|
@@ -21,7 +21,7 @@ client = Gemini.new(
|
|
21
21
|
service: 'generative-language-api',
|
22
22
|
api_key: ENV['GOOGLE_API_KEY']
|
23
23
|
},
|
24
|
-
options: { model: 'gemini-pro',
|
24
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
25
25
|
)
|
26
26
|
|
27
27
|
# With a Service Account Credentials File
|
@@ -31,7 +31,7 @@ client = Gemini.new(
|
|
31
31
|
file_path: 'google-credentials.json',
|
32
32
|
region: 'us-east4'
|
33
33
|
},
|
34
|
-
options: { model: 'gemini-pro',
|
34
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
35
35
|
)
|
36
36
|
|
37
37
|
# With Application Default Credentials
|
@@ -40,7 +40,7 @@ client = Gemini.new(
|
|
40
40
|
service: 'vertex-ai-api',
|
41
41
|
region: 'us-east4'
|
42
42
|
},
|
43
|
-
options: { model: 'gemini-pro',
|
43
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
44
44
|
)
|
45
45
|
|
46
46
|
result = client.stream_generate_content({
|
@@ -77,11 +77,11 @@ Result:
|
|
77
77
|
### Installing
|
78
78
|
|
79
79
|
```sh
|
80
|
-
gem install gemini-ai -v
|
80
|
+
gem install gemini-ai -v 3.0.0
|
81
81
|
```
|
82
82
|
|
83
83
|
```sh
|
84
|
-
gem 'gemini-ai', '~>
|
84
|
+
gem 'gemini-ai', '~> 3.0.0'
|
85
85
|
```
|
86
86
|
|
87
87
|
### Credentials
|
@@ -248,7 +248,7 @@ client = Gemini.new(
|
|
248
248
|
service: 'generative-language-api',
|
249
249
|
api_key: ENV['GOOGLE_API_KEY']
|
250
250
|
},
|
251
|
-
options: { model: 'gemini-pro',
|
251
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
252
252
|
)
|
253
253
|
|
254
254
|
# With a Service Account Credentials File
|
@@ -258,7 +258,7 @@ client = Gemini.new(
|
|
258
258
|
file_path: 'google-credentials.json',
|
259
259
|
region: 'us-east4'
|
260
260
|
},
|
261
|
-
options: { model: 'gemini-pro',
|
261
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
262
262
|
)
|
263
263
|
|
264
264
|
# With Application Default Credentials
|
@@ -267,15 +267,118 @@ client = Gemini.new(
|
|
267
267
|
service: 'vertex-ai-api',
|
268
268
|
region: 'us-east4'
|
269
269
|
},
|
270
|
-
options: { model: 'gemini-pro',
|
270
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
271
271
|
)
|
272
272
|
```
|
273
273
|
|
274
|
-
###
|
274
|
+
### Methods
|
275
275
|
|
276
|
-
####
|
276
|
+
#### stream_generate_content
|
277
277
|
|
278
|
-
#####
|
278
|
+
##### Receiving Stream Events
|
279
|
+
|
280
|
+
Ensure that you have enabled [Server-Sent Events](#streaming-vs-server-sent-events-sse) before using blocks for streaming:
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
client.stream_generate_content(
|
284
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
285
|
+
) do |event, parsed, raw|
|
286
|
+
puts event
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
Event:
|
291
|
+
```ruby
|
292
|
+
{ 'candidates' =>
|
293
|
+
[{ 'content' => {
|
294
|
+
'role' => 'model',
|
295
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
296
|
+
},
|
297
|
+
'finishReason' => 'STOP',
|
298
|
+
'safetyRatings' =>
|
299
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
300
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
301
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
302
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
303
|
+
'usageMetadata' => {
|
304
|
+
'promptTokenCount' => 2,
|
305
|
+
'candidatesTokenCount' => 8,
|
306
|
+
'totalTokenCount' => 10
|
307
|
+
} }
|
308
|
+
```
|
309
|
+
|
310
|
+
##### Without Events
|
311
|
+
|
312
|
+
You can use `stream_generate_content` without events:
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
result = client.stream_generate_content(
|
316
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
317
|
+
)
|
318
|
+
```
|
319
|
+
|
320
|
+
In this case, the result will be an array with all the received events:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
[{ 'candidates' =>
|
324
|
+
[{ 'content' => {
|
325
|
+
'role' => 'model',
|
326
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
327
|
+
},
|
328
|
+
'finishReason' => 'STOP',
|
329
|
+
'safetyRatings' =>
|
330
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
331
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
332
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
333
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
334
|
+
'usageMetadata' => {
|
335
|
+
'promptTokenCount' => 2,
|
336
|
+
'candidatesTokenCount' => 8,
|
337
|
+
'totalTokenCount' => 10
|
338
|
+
} }]
|
339
|
+
```
|
340
|
+
|
341
|
+
You can mix both as well:
|
342
|
+
```ruby
|
343
|
+
result = client.stream_generate_content(
|
344
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
345
|
+
) do |event, parsed, raw|
|
346
|
+
puts event
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
#### generate_content
|
351
|
+
|
352
|
+
```ruby
|
353
|
+
result = client.generate_content(
|
354
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
355
|
+
)
|
356
|
+
```
|
357
|
+
|
358
|
+
Result:
|
359
|
+
```ruby
|
360
|
+
{ 'candidates' =>
|
361
|
+
[{ 'content' => { 'parts' => [{ 'text' => 'Hello! How can I assist you today?' }], 'role' => 'model' },
|
362
|
+
'finishReason' => 'STOP',
|
363
|
+
'index' => 0,
|
364
|
+
'safetyRatings' =>
|
365
|
+
[{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
366
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
367
|
+
{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
368
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
369
|
+
'promptFeedback' =>
|
370
|
+
{ 'safetyRatings' =>
|
371
|
+
[{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
372
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
373
|
+
{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
374
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] } }
|
375
|
+
```
|
376
|
+
|
377
|
+
As of the writing of this README, only the `generative-language-api` service supports the `generate_content` method; `vertex-ai-api` does not.
|
378
|
+
|
379
|
+
### Modes
|
380
|
+
|
381
|
+
#### Text
|
279
382
|
|
280
383
|
```ruby
|
281
384
|
result = client.stream_generate_content({
|
@@ -303,7 +406,7 @@ Result:
|
|
303
406
|
} }]
|
304
407
|
```
|
305
408
|
|
306
|
-
|
409
|
+
#### Image
|
307
410
|
|
308
411
|
![A black and white image of an old piano. The piano is an upright model, with the keys on the right side of the image. The piano is sitting on a tiled floor. There is a small round object on the top of the piano.](https://raw.githubusercontent.com/gbaptista/assets/main/gemini-ai/piano.jpg)
|
309
412
|
|
@@ -314,7 +417,7 @@ Switch to the `gemini-pro-vision` model:
|
|
314
417
|
```ruby
|
315
418
|
client = Gemini.new(
|
316
419
|
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
317
|
-
options: { model: 'gemini-pro-vision',
|
420
|
+
options: { model: 'gemini-pro-vision', server_sent_events: true }
|
318
421
|
)
|
319
422
|
```
|
320
423
|
|
@@ -360,7 +463,7 @@ The result:
|
|
360
463
|
'usageMetadata' => { 'promptTokenCount' => 263, 'candidatesTokenCount' => 50, 'totalTokenCount' => 313 } }]
|
361
464
|
```
|
362
465
|
|
363
|
-
|
466
|
+
#### Video
|
364
467
|
|
365
468
|
https://gist.github.com/assets/29520/f82bccbf-02d2-4899-9c48-eb8a0a5ef741
|
366
469
|
|
@@ -373,7 +476,7 @@ Switch to the `gemini-pro-vision` model:
|
|
373
476
|
```ruby
|
374
477
|
client = Gemini.new(
|
375
478
|
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
376
|
-
options: { model: 'gemini-pro-vision',
|
479
|
+
options: { model: 'gemini-pro-vision', server_sent_events: true }
|
377
480
|
)
|
378
481
|
```
|
379
482
|
|
@@ -420,41 +523,15 @@ The result:
|
|
420
523
|
"usageMetadata"=>{"promptTokenCount"=>1037, "candidatesTokenCount"=>32, "totalTokenCount"=>1069}}]
|
421
524
|
```
|
422
525
|
|
423
|
-
|
526
|
+
### Streaming vs. Server-Sent Events (SSE)
|
424
527
|
|
425
|
-
|
426
|
-
result = client.stream_generate_content({
|
427
|
-
contents: { role: 'user', parts: { text: 'hi!' } }
|
428
|
-
})
|
429
|
-
```
|
528
|
+
[Server-Sent Events (SSE)](https://en.wikipedia.org/wiki/Server-sent_events) is a technology that allows certain endpoints to offer streaming capabilities, such as creating the impression that "the model is typing along with you," rather than delivering the entire answer all at once.
|
430
529
|
|
431
|
-
|
432
|
-
```ruby
|
433
|
-
[{ 'candidates' =>
|
434
|
-
[{ 'content' => {
|
435
|
-
'role' => 'model',
|
436
|
-
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
437
|
-
},
|
438
|
-
'finishReason' => 'STOP',
|
439
|
-
'safetyRatings' =>
|
440
|
-
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
441
|
-
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
442
|
-
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
443
|
-
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
444
|
-
'usageMetadata' => {
|
445
|
-
'promptTokenCount' => 2,
|
446
|
-
'candidatesTokenCount' => 8,
|
447
|
-
'totalTokenCount' => 10
|
448
|
-
} }]
|
449
|
-
```
|
450
|
-
|
451
|
-
#### Streaming
|
452
|
-
|
453
|
-
You can set up the client to use streaming for all supported endpoints:
|
530
|
+
You can set up the client to use Server-Sent Events (SSE) for all supported endpoints:
|
454
531
|
```ruby
|
455
532
|
client = Gemini.new(
|
456
533
|
credentials: { ... },
|
457
|
-
options: { model: 'gemini-pro',
|
534
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
458
535
|
)
|
459
536
|
```
|
460
537
|
|
@@ -462,11 +539,11 @@ Or, you can decide on a request basis:
|
|
462
539
|
```ruby
|
463
540
|
client.stream_generate_content(
|
464
541
|
{ contents: { role: 'user', parts: { text: 'hi!' } } },
|
465
|
-
|
542
|
+
server_sent_events: true
|
466
543
|
)
|
467
544
|
```
|
468
545
|
|
469
|
-
With
|
546
|
+
With Server-Sent Events (SSE) enabled, you can use a block to receive partial results via events. This feature is particularly useful for methods that offer streaming capabilities, such as `stream_generate_content`:
|
470
547
|
|
471
548
|
```ruby
|
472
549
|
client.stream_generate_content(
|
@@ -496,14 +573,16 @@ Event:
|
|
496
573
|
} }
|
497
574
|
```
|
498
575
|
|
499
|
-
|
576
|
+
Even though streaming methods utilize Server-Sent Events (SSE), using this feature doesn't necessarily mean streaming data. For example, when `generate_content` is called with SSE enabled, you will receive all the data at once in a single event, rather than through multiple partial events. This occurs because `generate_content` isn't designed for streaming, even though it is capable of utilizing Server-Sent Events.
|
500
577
|
|
501
|
-
|
578
|
+
#### Server-Sent Events (SSE) Hang
|
579
|
+
|
580
|
+
Method calls will _hang_ until the server-sent events finish, so even without providing a block, you can obtain the final results of the received events:
|
502
581
|
|
503
582
|
```ruby
|
504
583
|
result = client.stream_generate_content(
|
505
584
|
{ contents: { role: 'user', parts: { text: 'hi!' } } },
|
506
|
-
|
585
|
+
server_sent_events: true
|
507
586
|
)
|
508
587
|
```
|
509
588
|
|
@@ -527,7 +606,40 @@ Result:
|
|
527
606
|
} }]
|
528
607
|
```
|
529
608
|
|
530
|
-
####
|
609
|
+
#### Non-Streaming
|
610
|
+
|
611
|
+
Depending on the service, you can use the [`generate_content`](#generate_content) method, which does not stream the answer.
|
612
|
+
|
613
|
+
You can also use methods designed for streaming without necessarily processing partial events; instead, you can wait for the result of all received events:
|
614
|
+
|
615
|
+
```ruby
|
616
|
+
result = client.stream_generate_content({
|
617
|
+
contents: { role: 'user', parts: { text: 'hi!' } },
|
618
|
+
server_sent_events: false
|
619
|
+
})
|
620
|
+
```
|
621
|
+
|
622
|
+
Result:
|
623
|
+
```ruby
|
624
|
+
[{ 'candidates' =>
|
625
|
+
[{ 'content' => {
|
626
|
+
'role' => 'model',
|
627
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
628
|
+
},
|
629
|
+
'finishReason' => 'STOP',
|
630
|
+
'safetyRatings' =>
|
631
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
632
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
633
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
634
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
635
|
+
'usageMetadata' => {
|
636
|
+
'promptTokenCount' => 2,
|
637
|
+
'candidatesTokenCount' => 8,
|
638
|
+
'totalTokenCount' => 10
|
639
|
+
} }]
|
640
|
+
```
|
641
|
+
|
642
|
+
### Back-and-Forth Conversations
|
531
643
|
|
532
644
|
To maintain a back-and-forth conversation, you need to append the received responses and build a history for your requests:
|
533
645
|
|
@@ -569,7 +681,7 @@ Result:
|
|
569
681
|
} }]
|
570
682
|
```
|
571
683
|
|
572
|
-
|
684
|
+
### Tools (Functions) Calling
|
573
685
|
|
574
686
|
> As of the writing of this README, only the `vertex-ai-api` service and the `gemini-pro` model [supports](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/function-calling#supported_models) tools (functions) calls.
|
575
687
|
|
@@ -772,7 +884,7 @@ GeminiError
|
|
772
884
|
|
773
885
|
MissingProjectIdError
|
774
886
|
UnsupportedServiceError
|
775
|
-
|
887
|
+
BlockWithoutServerSentEventsError
|
776
888
|
|
777
889
|
RequestError
|
778
890
|
```
|
@@ -795,7 +907,7 @@ gem build gemini-ai.gemspec
|
|
795
907
|
|
796
908
|
gem signin
|
797
909
|
|
798
|
-
gem push gemini-ai-
|
910
|
+
gem push gemini-ai-3.0.0.gem
|
799
911
|
```
|
800
912
|
|
801
913
|
### Updating the README
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gemini-ai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- gbaptista
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: event_stream_parser
|
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
111
|
requirements: []
|
112
|
-
rubygems_version: 3.
|
112
|
+
rubygems_version: 3.4.22
|
113
113
|
signing_key:
|
114
114
|
specification_version: 4
|
115
115
|
summary: Interact with Google's Gemini AI.
|