gemini-ai 2.2.0 → 3.1.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 +3 -3
- data/README.md +219 -65
- data/components/errors.rb +1 -1
- data/controllers/client.rb +39 -19
- data/gemini-ai.gemspec +1 -1
- data/static/gem.rb +1 -1
- data/tasks/generate-readme.clj +1 -1
- data/template.md +203 -55
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36be77448ab7ba00008ac7548c25fdca45b92980762303631de5d6bdbcd2d01a
|
4
|
+
data.tar.gz: e0772790afa6019424282e8ac665fb0049da13a76ecc43909b147655f6f95a3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4d6c7391dff2ce75a46e3ceb5c7ef9c07c6cdcfc55477df68be1df2e06f77ff77d795ee6e70abc9ad73001e8dd583d39949a394140b129d08c2db94c2b8f7c4
|
7
|
+
data.tar.gz: 2acedebdfe562b7b68d19046a2f2b06bec6fbe75768cc7066b7c1e31cb72bc4caa551a8d656ffc6537c714f0d11965f3cc9f74542e7bc9315f0998f35390d5f4
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gemini-ai (
|
4
|
+
gemini-ai (3.1.0)
|
5
5
|
event_stream_parser (~> 1.0)
|
6
|
-
faraday (~> 2.
|
6
|
+
faraday (~> 2.8, >= 2.8.1)
|
7
7
|
googleauth (~> 1.9, >= 1.9.1)
|
8
8
|
|
9
9
|
GEM
|
@@ -16,7 +16,7 @@ GEM
|
|
16
16
|
byebug (11.1.3)
|
17
17
|
coderay (1.1.3)
|
18
18
|
event_stream_parser (1.0.0)
|
19
|
-
faraday (2.
|
19
|
+
faraday (2.8.1)
|
20
20
|
base64
|
21
21
|
faraday-net_http (>= 2.0, < 3.1)
|
22
22
|
ruby2_keywords (>= 0.0.4)
|
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.1.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,17 +81,23 @@ 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)
|
99
|
+
- [Request Options](#request-options)
|
100
|
+
- [Timeout](#timeout)
|
95
101
|
- [Error Handling](#error-handling)
|
96
102
|
- [Rescuing](#rescuing)
|
97
103
|
- [For Short](#for-short)
|
@@ -108,11 +114,11 @@ Result:
|
|
108
114
|
### Installing
|
109
115
|
|
110
116
|
```sh
|
111
|
-
gem install gemini-ai -v
|
117
|
+
gem install gemini-ai -v 3.1.0
|
112
118
|
```
|
113
119
|
|
114
120
|
```sh
|
115
|
-
gem 'gemini-ai', '~>
|
121
|
+
gem 'gemini-ai', '~> 3.1.0'
|
116
122
|
```
|
117
123
|
|
118
124
|
### Credentials
|
@@ -279,7 +285,7 @@ client = Gemini.new(
|
|
279
285
|
service: 'generative-language-api',
|
280
286
|
api_key: ENV['GOOGLE_API_KEY']
|
281
287
|
},
|
282
|
-
options: { model: 'gemini-pro',
|
288
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
283
289
|
)
|
284
290
|
|
285
291
|
# With a Service Account Credentials File
|
@@ -289,7 +295,7 @@ client = Gemini.new(
|
|
289
295
|
file_path: 'google-credentials.json',
|
290
296
|
region: 'us-east4'
|
291
297
|
},
|
292
|
-
options: { model: 'gemini-pro',
|
298
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
293
299
|
)
|
294
300
|
|
295
301
|
# With Application Default Credentials
|
@@ -298,15 +304,118 @@ client = Gemini.new(
|
|
298
304
|
service: 'vertex-ai-api',
|
299
305
|
region: 'us-east4'
|
300
306
|
},
|
301
|
-
options: { model: 'gemini-pro',
|
307
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
302
308
|
)
|
303
309
|
```
|
304
310
|
|
305
|
-
###
|
311
|
+
### Methods
|
306
312
|
|
307
|
-
####
|
313
|
+
#### stream_generate_content
|
308
314
|
|
309
|
-
#####
|
315
|
+
##### Receiving Stream Events
|
316
|
+
|
317
|
+
Ensure that you have enabled [Server-Sent Events](#streaming-vs-server-sent-events-sse) before using blocks for streaming:
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
client.stream_generate_content(
|
321
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
322
|
+
) do |event, parsed, raw|
|
323
|
+
puts event
|
324
|
+
end
|
325
|
+
```
|
326
|
+
|
327
|
+
Event:
|
328
|
+
```ruby
|
329
|
+
{ 'candidates' =>
|
330
|
+
[{ 'content' => {
|
331
|
+
'role' => 'model',
|
332
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
333
|
+
},
|
334
|
+
'finishReason' => 'STOP',
|
335
|
+
'safetyRatings' =>
|
336
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
337
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
338
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
339
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
340
|
+
'usageMetadata' => {
|
341
|
+
'promptTokenCount' => 2,
|
342
|
+
'candidatesTokenCount' => 8,
|
343
|
+
'totalTokenCount' => 10
|
344
|
+
} }
|
345
|
+
```
|
346
|
+
|
347
|
+
##### Without Events
|
348
|
+
|
349
|
+
You can use `stream_generate_content` without events:
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
result = client.stream_generate_content(
|
353
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
354
|
+
)
|
355
|
+
```
|
356
|
+
|
357
|
+
In this case, the result will be an array with all the received events:
|
358
|
+
|
359
|
+
```ruby
|
360
|
+
[{ 'candidates' =>
|
361
|
+
[{ 'content' => {
|
362
|
+
'role' => 'model',
|
363
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
364
|
+
},
|
365
|
+
'finishReason' => 'STOP',
|
366
|
+
'safetyRatings' =>
|
367
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
368
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
369
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
370
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
371
|
+
'usageMetadata' => {
|
372
|
+
'promptTokenCount' => 2,
|
373
|
+
'candidatesTokenCount' => 8,
|
374
|
+
'totalTokenCount' => 10
|
375
|
+
} }]
|
376
|
+
```
|
377
|
+
|
378
|
+
You can mix both as well:
|
379
|
+
```ruby
|
380
|
+
result = client.stream_generate_content(
|
381
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
382
|
+
) do |event, parsed, raw|
|
383
|
+
puts event
|
384
|
+
end
|
385
|
+
```
|
386
|
+
|
387
|
+
#### generate_content
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
result = client.generate_content(
|
391
|
+
{ contents: { role: 'user', parts: { text: 'hi!' } } }
|
392
|
+
)
|
393
|
+
```
|
394
|
+
|
395
|
+
Result:
|
396
|
+
```ruby
|
397
|
+
{ 'candidates' =>
|
398
|
+
[{ 'content' => { 'parts' => [{ 'text' => 'Hello! How can I assist you today?' }], 'role' => 'model' },
|
399
|
+
'finishReason' => 'STOP',
|
400
|
+
'index' => 0,
|
401
|
+
'safetyRatings' =>
|
402
|
+
[{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
403
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
404
|
+
{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
405
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
406
|
+
'promptFeedback' =>
|
407
|
+
{ 'safetyRatings' =>
|
408
|
+
[{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
409
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
410
|
+
{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
411
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] } }
|
412
|
+
```
|
413
|
+
|
414
|
+
As of the writing of this README, only the `generative-language-api` service supports the `generate_content` method; `vertex-ai-api` does not.
|
415
|
+
|
416
|
+
### Modes
|
417
|
+
|
418
|
+
#### Text
|
310
419
|
|
311
420
|
```ruby
|
312
421
|
result = client.stream_generate_content({
|
@@ -334,7 +443,7 @@ Result:
|
|
334
443
|
} }]
|
335
444
|
```
|
336
445
|
|
337
|
-
|
446
|
+
#### Image
|
338
447
|
|
339
448
|
![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
449
|
|
@@ -345,7 +454,7 @@ Switch to the `gemini-pro-vision` model:
|
|
345
454
|
```ruby
|
346
455
|
client = Gemini.new(
|
347
456
|
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
348
|
-
options: { model: 'gemini-pro-vision',
|
457
|
+
options: { model: 'gemini-pro-vision', server_sent_events: true }
|
349
458
|
)
|
350
459
|
```
|
351
460
|
|
@@ -391,7 +500,7 @@ The result:
|
|
391
500
|
'usageMetadata' => { 'promptTokenCount' => 263, 'candidatesTokenCount' => 50, 'totalTokenCount' => 313 } }]
|
392
501
|
```
|
393
502
|
|
394
|
-
|
503
|
+
#### Video
|
395
504
|
|
396
505
|
https://gist.github.com/assets/29520/f82bccbf-02d2-4899-9c48-eb8a0a5ef741
|
397
506
|
|
@@ -404,7 +513,7 @@ Switch to the `gemini-pro-vision` model:
|
|
404
513
|
```ruby
|
405
514
|
client = Gemini.new(
|
406
515
|
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
407
|
-
options: { model: 'gemini-pro-vision',
|
516
|
+
options: { model: 'gemini-pro-vision', server_sent_events: true }
|
408
517
|
)
|
409
518
|
```
|
410
519
|
|
@@ -451,41 +560,15 @@ The result:
|
|
451
560
|
"usageMetadata"=>{"promptTokenCount"=>1037, "candidatesTokenCount"=>32, "totalTokenCount"=>1069}}]
|
452
561
|
```
|
453
562
|
|
454
|
-
|
455
|
-
|
456
|
-
```ruby
|
457
|
-
result = client.stream_generate_content({
|
458
|
-
contents: { role: 'user', parts: { text: 'hi!' } }
|
459
|
-
})
|
460
|
-
```
|
461
|
-
|
462
|
-
Result:
|
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
|
-
```
|
563
|
+
### Streaming vs. Server-Sent Events (SSE)
|
481
564
|
|
482
|
-
|
565
|
+
[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.
|
483
566
|
|
484
|
-
You can set up the client to use
|
567
|
+
You can set up the client to use Server-Sent Events (SSE) for all supported endpoints:
|
485
568
|
```ruby
|
486
569
|
client = Gemini.new(
|
487
570
|
credentials: { ... },
|
488
|
-
options: { model: 'gemini-pro',
|
571
|
+
options: { model: 'gemini-pro', server_sent_events: true }
|
489
572
|
)
|
490
573
|
```
|
491
574
|
|
@@ -493,11 +576,11 @@ Or, you can decide on a request basis:
|
|
493
576
|
```ruby
|
494
577
|
client.stream_generate_content(
|
495
578
|
{ contents: { role: 'user', parts: { text: 'hi!' } } },
|
496
|
-
|
579
|
+
server_sent_events: true
|
497
580
|
)
|
498
581
|
```
|
499
582
|
|
500
|
-
With
|
583
|
+
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
584
|
|
502
585
|
```ruby
|
503
586
|
client.stream_generate_content(
|
@@ -527,14 +610,16 @@ Event:
|
|
527
610
|
} }
|
528
611
|
```
|
529
612
|
|
530
|
-
|
613
|
+
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.
|
614
|
+
|
615
|
+
#### Server-Sent Events (SSE) Hang
|
531
616
|
|
532
|
-
Method calls will _hang_ until the
|
617
|
+
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
618
|
|
534
619
|
```ruby
|
535
620
|
result = client.stream_generate_content(
|
536
621
|
{ contents: { role: 'user', parts: { text: 'hi!' } } },
|
537
|
-
|
622
|
+
server_sent_events: true
|
538
623
|
)
|
539
624
|
```
|
540
625
|
|
@@ -558,7 +643,40 @@ Result:
|
|
558
643
|
} }]
|
559
644
|
```
|
560
645
|
|
561
|
-
####
|
646
|
+
#### Non-Streaming
|
647
|
+
|
648
|
+
Depending on the service, you can use the [`generate_content`](#generate_content) method, which does not stream the answer.
|
649
|
+
|
650
|
+
You can also use methods designed for streaming without necessarily processing partial events; instead, you can wait for the result of all received events:
|
651
|
+
|
652
|
+
```ruby
|
653
|
+
result = client.stream_generate_content({
|
654
|
+
contents: { role: 'user', parts: { text: 'hi!' } },
|
655
|
+
server_sent_events: false
|
656
|
+
})
|
657
|
+
```
|
658
|
+
|
659
|
+
Result:
|
660
|
+
```ruby
|
661
|
+
[{ 'candidates' =>
|
662
|
+
[{ 'content' => {
|
663
|
+
'role' => 'model',
|
664
|
+
'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
|
665
|
+
},
|
666
|
+
'finishReason' => 'STOP',
|
667
|
+
'safetyRatings' =>
|
668
|
+
[{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
|
669
|
+
{ 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
|
670
|
+
{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
|
671
|
+
{ 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
|
672
|
+
'usageMetadata' => {
|
673
|
+
'promptTokenCount' => 2,
|
674
|
+
'candidatesTokenCount' => 8,
|
675
|
+
'totalTokenCount' => 10
|
676
|
+
} }]
|
677
|
+
```
|
678
|
+
|
679
|
+
### Back-and-Forth Conversations
|
562
680
|
|
563
681
|
To maintain a back-and-forth conversation, you need to append the received responses and build a history for your requests:
|
564
682
|
|
@@ -600,7 +718,7 @@ Result:
|
|
600
718
|
} }]
|
601
719
|
```
|
602
720
|
|
603
|
-
|
721
|
+
### Tools (Functions) Calling
|
604
722
|
|
605
723
|
> 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
724
|
|
@@ -756,6 +874,42 @@ result = client.request(
|
|
756
874
|
)
|
757
875
|
```
|
758
876
|
|
877
|
+
### Request Options
|
878
|
+
|
879
|
+
#### Timeout
|
880
|
+
|
881
|
+
You can set the maximum number of seconds to wait for the request to complete with the `timeout` option:
|
882
|
+
|
883
|
+
```ruby
|
884
|
+
client = Gemini.new(
|
885
|
+
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
886
|
+
options: {
|
887
|
+
model: 'gemini-pro',
|
888
|
+
connection: { request: { timeout: 5 } }
|
889
|
+
}
|
890
|
+
)
|
891
|
+
```
|
892
|
+
|
893
|
+
You can also have more fine-grained control over [Faraday's Request Options](https://lostisland.github.io/faraday/#/customization/request-options?id=request-options) if you prefer:
|
894
|
+
|
895
|
+
```ruby
|
896
|
+
client = Gemini.new(
|
897
|
+
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
898
|
+
options: {
|
899
|
+
model: 'gemini-pro',
|
900
|
+
connection: {
|
901
|
+
request: {
|
902
|
+
timeout: 5,
|
903
|
+
open_timeout: 5,
|
904
|
+
read_timeout: 5,
|
905
|
+
write_timeout: 5
|
906
|
+
}
|
907
|
+
}
|
908
|
+
}
|
909
|
+
)
|
910
|
+
```
|
911
|
+
|
912
|
+
|
759
913
|
### Error Handling
|
760
914
|
|
761
915
|
#### Rescuing
|
@@ -803,7 +957,7 @@ GeminiError
|
|
803
957
|
|
804
958
|
MissingProjectIdError
|
805
959
|
UnsupportedServiceError
|
806
|
-
|
960
|
+
BlockWithoutServerSentEventsError
|
807
961
|
|
808
962
|
RequestError
|
809
963
|
```
|
@@ -826,7 +980,7 @@ gem build gemini-ai.gemspec
|
|
826
980
|
|
827
981
|
gem signin
|
828
982
|
|
829
|
-
gem push gemini-ai-
|
983
|
+
gem push gemini-ai-3.1.0.gem
|
830
984
|
```
|
831
985
|
|
832
986
|
### 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,11 +5,13 @@ 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
|
12
12
|
class Client
|
13
|
+
ALLOWED_REQUEST_OPTIONS = %i[timeout open_timeout read_timeout write_timeout].freeze
|
14
|
+
|
13
15
|
def initialize(config)
|
14
16
|
if config[:credentials][:api_key]
|
15
17
|
@authentication = :api_key
|
@@ -26,49 +28,67 @@ module Gemini
|
|
26
28
|
end
|
27
29
|
|
28
30
|
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
|
31
|
+
@project_id = config[:credentials][:project_id] || @authorizer.project_id || @authorizer.quota_project_id
|
34
32
|
|
35
33
|
raise MissingProjectIdError, 'Could not determine project_id, which is required.' if @project_id.nil?
|
36
34
|
end
|
37
35
|
|
38
|
-
@
|
36
|
+
@service = config[:credentials][:service]
|
37
|
+
|
38
|
+
@address = case @service
|
39
39
|
when 'vertex-ai-api'
|
40
40
|
"https://#{config[:credentials][:region]}-aiplatform.googleapis.com/v1/projects/#{@project_id}/locations/#{config[:credentials][:region]}/publishers/google/models/#{config[:options][:model]}"
|
41
41
|
when 'generative-language-api'
|
42
42
|
"https://generativelanguage.googleapis.com/v1/models/#{config[:options][:model]}"
|
43
43
|
else
|
44
|
-
raise UnsupportedServiceError, "Unsupported service: #{
|
44
|
+
raise UnsupportedServiceError, "Unsupported service: #{@service}"
|
45
45
|
end
|
46
46
|
|
47
|
-
@
|
47
|
+
@server_sent_events = config[:options][:server_sent_events]
|
48
|
+
|
49
|
+
@request_options = config.dig(:options, :connection, :request)
|
50
|
+
|
51
|
+
@request_options = if @request_options.is_a?(Hash)
|
52
|
+
@request_options.select do |key, _|
|
53
|
+
ALLOWED_REQUEST_OPTIONS.include?(key)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
{}
|
57
|
+
end
|
48
58
|
end
|
49
59
|
|
50
|
-
def stream_generate_content(payload,
|
51
|
-
request('streamGenerateContent', payload,
|
60
|
+
def stream_generate_content(payload, server_sent_events: nil, &callback)
|
61
|
+
request('streamGenerateContent', payload, server_sent_events:, &callback)
|
62
|
+
end
|
63
|
+
|
64
|
+
def generate_content(payload, server_sent_events: nil, &callback)
|
65
|
+
result = request('generateContent', payload, server_sent_events:, &callback)
|
66
|
+
|
67
|
+
return result.first if result.is_a?(Array) && result.size == 1
|
68
|
+
|
69
|
+
result
|
52
70
|
end
|
53
71
|
|
54
|
-
def request(path, payload,
|
55
|
-
|
72
|
+
def request(path, payload, server_sent_events: nil, &callback)
|
73
|
+
server_sent_events_enabled = server_sent_events.nil? ? @server_sent_events : server_sent_events
|
56
74
|
url = "#{@address}:#{path}"
|
57
75
|
params = []
|
58
76
|
|
59
|
-
params << 'alt=sse' if
|
77
|
+
params << 'alt=sse' if server_sent_events_enabled
|
60
78
|
params << "key=#{@api_key}" if @authentication == :api_key
|
61
79
|
|
62
80
|
url += "?#{params.join('&')}" if params.size.positive?
|
63
81
|
|
64
|
-
if !callback.nil? && !
|
65
|
-
raise
|
82
|
+
if !callback.nil? && !server_sent_events_enabled
|
83
|
+
raise BlockWithoutServerSentEventsError,
|
84
|
+
'You are trying to use a block without Server Sent Events (SSE) enabled.'
|
66
85
|
end
|
67
86
|
|
68
87
|
results = []
|
69
88
|
|
70
|
-
response = Faraday.new do |faraday|
|
89
|
+
response = Faraday.new(request: @request_options) do |faraday|
|
71
90
|
faraday.response :raise_error
|
91
|
+
faraday.options.timeout = @timeout if @timeout
|
72
92
|
end.post do |request|
|
73
93
|
request.url url
|
74
94
|
request.headers['Content-Type'] = 'application/json'
|
@@ -78,7 +98,7 @@ module Gemini
|
|
78
98
|
|
79
99
|
request.body = payload.to_json
|
80
100
|
|
81
|
-
if
|
101
|
+
if server_sent_events_enabled
|
82
102
|
parser = EventStreamParser::Parser.new
|
83
103
|
|
84
104
|
request.options.on_data = proc do |chunk, bytes, env|
|
@@ -107,7 +127,7 @@ module Gemini
|
|
107
127
|
end
|
108
128
|
end
|
109
129
|
|
110
|
-
return safe_parse_json(response.body) unless
|
130
|
+
return safe_parse_json(response.body) unless server_sent_events_enabled
|
111
131
|
|
112
132
|
results.map { |result| result[:event] }
|
113
133
|
rescue Faraday::ServerError => e
|
data/gemini-ai.gemspec
CHANGED
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
|
|
30
30
|
spec.require_paths = ['ports/dsl']
|
31
31
|
|
32
32
|
spec.add_dependency 'event_stream_parser', '~> 1.0'
|
33
|
-
spec.add_dependency 'faraday', '~> 2.
|
33
|
+
spec.add_dependency 'faraday', '~> 2.8', '>= 2.8.1'
|
34
34
|
spec.add_dependency 'googleauth', '~> 1.9', '>= 1.9.1'
|
35
35
|
|
36
36
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
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.1.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.1.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.1.0
|
81
81
|
```
|
82
82
|
|
83
83
|
```sh
|
84
|
-
gem 'gemini-ai', '~>
|
84
|
+
gem 'gemini-ai', '~> 3.1.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
|
-
|
424
|
-
|
425
|
-
```ruby
|
426
|
-
result = client.stream_generate_content({
|
427
|
-
contents: { role: 'user', parts: { text: 'hi!' } }
|
428
|
-
})
|
429
|
-
```
|
430
|
-
|
431
|
-
Result:
|
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
|
-
```
|
526
|
+
### Streaming vs. Server-Sent Events (SSE)
|
450
527
|
|
451
|
-
|
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.
|
452
529
|
|
453
|
-
You can set up the client to use
|
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.
|
577
|
+
|
578
|
+
#### Server-Sent Events (SSE) Hang
|
500
579
|
|
501
|
-
Method calls will _hang_ until the
|
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
|
|
@@ -725,6 +837,42 @@ result = client.request(
|
|
725
837
|
)
|
726
838
|
```
|
727
839
|
|
840
|
+
### Request Options
|
841
|
+
|
842
|
+
#### Timeout
|
843
|
+
|
844
|
+
You can set the maximum number of seconds to wait for the request to complete with the `timeout` option:
|
845
|
+
|
846
|
+
```ruby
|
847
|
+
client = Gemini.new(
|
848
|
+
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
849
|
+
options: {
|
850
|
+
model: 'gemini-pro',
|
851
|
+
connection: { request: { timeout: 5 } }
|
852
|
+
}
|
853
|
+
)
|
854
|
+
```
|
855
|
+
|
856
|
+
You can also have more fine-grained control over [Faraday's Request Options](https://lostisland.github.io/faraday/#/customization/request-options?id=request-options) if you prefer:
|
857
|
+
|
858
|
+
```ruby
|
859
|
+
client = Gemini.new(
|
860
|
+
credentials: { service: 'vertex-ai-api', region: 'us-east4' },
|
861
|
+
options: {
|
862
|
+
model: 'gemini-pro',
|
863
|
+
connection: {
|
864
|
+
request: {
|
865
|
+
timeout: 5,
|
866
|
+
open_timeout: 5,
|
867
|
+
read_timeout: 5,
|
868
|
+
write_timeout: 5
|
869
|
+
}
|
870
|
+
}
|
871
|
+
}
|
872
|
+
)
|
873
|
+
```
|
874
|
+
|
875
|
+
|
728
876
|
### Error Handling
|
729
877
|
|
730
878
|
#### Rescuing
|
@@ -772,7 +920,7 @@ GeminiError
|
|
772
920
|
|
773
921
|
MissingProjectIdError
|
774
922
|
UnsupportedServiceError
|
775
|
-
|
923
|
+
BlockWithoutServerSentEventsError
|
776
924
|
|
777
925
|
RequestError
|
778
926
|
```
|
@@ -795,7 +943,7 @@ gem build gemini-ai.gemspec
|
|
795
943
|
|
796
944
|
gem signin
|
797
945
|
|
798
|
-
gem push gemini-ai-
|
946
|
+
gem push gemini-ai-3.1.0.gem
|
799
947
|
```
|
800
948
|
|
801
949
|
### 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.1.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-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: event_stream_parser
|
@@ -30,20 +30,20 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '2.
|
33
|
+
version: '2.8'
|
34
34
|
- - ">="
|
35
35
|
- !ruby/object:Gem::Version
|
36
|
-
version: 2.
|
36
|
+
version: 2.8.1
|
37
37
|
type: :runtime
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
41
|
- - "~>"
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: '2.
|
43
|
+
version: '2.8'
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 2.
|
46
|
+
version: 2.8.1
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: googleauth
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|