gemini-ai 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0504f65df629365e53197cbf3710261a01a5a6bd0bdc72d7ed86ae8435d97c92
4
+ data.tar.gz: f5bb325cc4eb668a8d2b0d6e8c4b76b0a3ae88dbc97ba76d329cbdc45a73a93a
5
+ SHA512:
6
+ metadata.gz: 1d2f70d87440b5e6a4cad3f29a961ab53a708c2e5519c600b7dfe6a7335291571bd69b320057bcd4d2dd32e2f60f48fd5a85e55e6e6ece0334b05517fb57415c
7
+ data.tar.gz: 004bddaf41f4a3e7a04f496ca31847f44193dd0b12d2446d92a1176241d29ee4d5a20ee7f0b246945f8f1f5277e8032136fc4f4d607042e5c748c0bcec339a0d
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/.rubocop.yml ADDED
@@ -0,0 +1,6 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1.0
3
+ NewCops: enable
4
+
5
+ Style/Documentation:
6
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.1.0
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :test, :development do
8
+ gem 'pry-byebug', '~> 3.10', '>= 3.10.1'
9
+ gem 'rubocop', '~> 1.58'
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,85 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gemini-ai (1.0.0)
5
+ event_stream_parser (~> 1.0)
6
+ faraday (~> 2.7, >= 2.7.12)
7
+ googleauth (~> 1.9, >= 1.9.1)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.8.6)
13
+ public_suffix (>= 2.0.2, < 6.0)
14
+ ast (2.4.2)
15
+ base64 (0.2.0)
16
+ byebug (11.1.3)
17
+ coderay (1.1.3)
18
+ event_stream_parser (1.0.0)
19
+ faraday (2.7.12)
20
+ base64
21
+ faraday-net_http (>= 2.0, < 3.1)
22
+ ruby2_keywords (>= 0.0.4)
23
+ faraday-net_http (3.0.2)
24
+ google-cloud-env (2.1.0)
25
+ faraday (>= 1.0, < 3.a)
26
+ googleauth (1.9.1)
27
+ faraday (>= 1.0, < 3.a)
28
+ google-cloud-env (~> 2.1)
29
+ jwt (>= 1.4, < 3.0)
30
+ multi_json (~> 1.11)
31
+ os (>= 0.9, < 2.0)
32
+ signet (>= 0.16, < 2.a)
33
+ json (2.7.1)
34
+ jwt (2.7.1)
35
+ language_server-protocol (3.17.0.3)
36
+ method_source (1.0.0)
37
+ multi_json (1.15.0)
38
+ os (1.1.4)
39
+ parallel (1.23.0)
40
+ parser (3.2.2.4)
41
+ ast (~> 2.4.1)
42
+ racc
43
+ pry (0.14.2)
44
+ coderay (~> 1.1)
45
+ method_source (~> 1.0)
46
+ pry-byebug (3.10.1)
47
+ byebug (~> 11.0)
48
+ pry (>= 0.13, < 0.15)
49
+ public_suffix (5.0.4)
50
+ racc (1.7.3)
51
+ rainbow (3.1.1)
52
+ regexp_parser (2.8.3)
53
+ rexml (3.2.6)
54
+ rubocop (1.59.0)
55
+ json (~> 2.3)
56
+ language_server-protocol (>= 3.17.0)
57
+ parallel (~> 1.10)
58
+ parser (>= 3.2.2.4)
59
+ rainbow (>= 2.2.2, < 4.0)
60
+ regexp_parser (>= 1.8, < 3.0)
61
+ rexml (>= 3.2.5, < 4.0)
62
+ rubocop-ast (>= 1.30.0, < 2.0)
63
+ ruby-progressbar (~> 1.7)
64
+ unicode-display_width (>= 2.4.0, < 3.0)
65
+ rubocop-ast (1.30.0)
66
+ parser (>= 3.2.1.0)
67
+ ruby-progressbar (1.13.0)
68
+ ruby2_keywords (0.0.5)
69
+ signet (0.18.0)
70
+ addressable (~> 2.8)
71
+ faraday (>= 0.17.5, < 3.a)
72
+ jwt (>= 1.5, < 3.0)
73
+ multi_json (~> 1.10)
74
+ unicode-display_width (2.5.0)
75
+
76
+ PLATFORMS
77
+ x86_64-linux
78
+
79
+ DEPENDENCIES
80
+ gemini-ai!
81
+ pry-byebug (~> 3.10, >= 3.10.1)
82
+ rubocop (~> 1.58)
83
+
84
+ BUNDLED WITH
85
+ 2.4.22
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright 2023 Guilherme Baptista
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,547 @@
1
+ # Gemini AI
2
+
3
+ A Ruby Gem for interacting with [Gemini](https://deepmind.google/technologies/gemini/) through [Vertex AI](https://cloud.google.com/vertex-ai), Google's generative AI service.
4
+
5
+ > _This Gem is designed to provide low-level access to Gemini, enabling people to build abstractions on top of it. If you are interested in more high-level abstractions or more user-friendly tools, you may want to consider [Nano Bots](https://github.com/icebaker/ruby-nano-bots) 💎 🤖._
6
+
7
+ ## TL;DR and Quick Start
8
+
9
+ ```ruby
10
+ gem 'gemini-ai', '~> 1.0'
11
+ ```
12
+
13
+ ```ruby
14
+ require 'gemini-ai'
15
+
16
+ client = Gemini.new(
17
+ credentials: { file_path: 'google-credentials.json', project_id: 'PROJECT_ID', region: 'us-east4' },
18
+ settings: { model: 'gemini-pro', stream: false }
19
+ )
20
+
21
+ result = client.stream_generate_content({
22
+ contents: { role: 'user', parts: { text: 'hi!' } }
23
+ })
24
+ ```
25
+
26
+ Result:
27
+ ```ruby
28
+ [{ 'candidates' =>
29
+ [{ 'content' => {
30
+ 'role' => 'model',
31
+ 'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
32
+ },
33
+ 'finishReason' => 'STOP',
34
+ 'safetyRatings' =>
35
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
36
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
37
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
38
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
39
+ 'usageMetadata' => {
40
+ 'promptTokenCount' => 2,
41
+ 'candidatesTokenCount' => 8,
42
+ 'totalTokenCount' => 10
43
+ } }]
44
+ ```
45
+
46
+ ## Index
47
+
48
+ - [TL;DR and Quick Start](#tldr-and-quick-start)
49
+ - [Index](#index)
50
+ - [Setup](#setup)
51
+ - [Credentials](#credentials)
52
+ - [Required Data](#required-data)
53
+ - [Usage](#usage)
54
+ - [Client](#client)
55
+ - [Generate Content](#generate-content)
56
+ - [Synchronous](#synchronous)
57
+ - [Streaming](#streaming)
58
+ - [Streaming Hang](#streaming-hang)
59
+ - [Back-and-Forth Conversations](#back-and-forth-conversations)
60
+ - [Tools (Functions) Calling](#tools-functions-calling)
61
+ - [New Functionalities and APIs](#new-functionalities-and-apis)
62
+ - [Development](#development)
63
+ - [Purpose](#purpose)
64
+ - [Publish to RubyGems](#publish-to-rubygems)
65
+ - [Updating the README](#updating-the-readme)
66
+ - [Resources and References](#resources-and-references)
67
+ - [Disclaimer](#disclaimer)
68
+
69
+ ## Setup
70
+
71
+ ```sh
72
+ gem install gemini-ai -v 1.0.0
73
+ ```
74
+
75
+ ```sh
76
+ gem 'gemini-ai', '~> 1.0'
77
+ ```
78
+
79
+ ### Credentials
80
+
81
+ > ⚠️ DISCLAIMER: Be careful with what you are doing, and never trust others' code related to this. These commands and instructions alter the level of access to your Google Cloud Account, and running them naively can lead to security risks as well as financial risks. People with access to your account can use it to steal data or incur charges. Run these commands at your own responsibility and due diligence; expect no warranties from the contributors of this project.
82
+
83
+ You need a [Google Cloud](https://console.cloud.google.com) [_Project_](https://cloud.google.com/resource-manager/docs/creating-managing-projects) and a [_Service Account_](https://cloud.google.com/iam/docs/service-account-overview) to use [Vertex AI](https://cloud.google.com/vertex-ai) API.
84
+
85
+ After creating them, you need to enable the Vertex AI API for your project by clicking `Enable` here: [Vertex AI API](https://console.cloud.google.com/apis/library/aiplatform.googleapis.com).
86
+
87
+ You can create credentials for your _Service Account_ [here](https://console.cloud.google.com/apis/credentials), where you will be able to download a JSON file named `google-credentials.json` that should have content similar to this:
88
+
89
+ ```json
90
+ {
91
+ "type": "service_account",
92
+ "project_id": "YOUR_PROJECT_ID",
93
+ "private_key_id": "a00...",
94
+ "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
95
+ "client_email": "PROJECT_ID@PROJECT_ID.iam.gserviceaccount.com",
96
+ "client_id": "000...",
97
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
98
+ "token_uri": "https://oauth2.googleapis.com/token",
99
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
100
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/..."
101
+ }
102
+ ```
103
+
104
+ You need to have the necessary [policies](https://cloud.google.com/iam/docs/policies) (`roles/aiplatform.user` and possibly `roles/ml.admin`) in place to use the Vertex AI API.
105
+
106
+ You can add them by navigating to the [IAM Console](https://console.cloud.google.com/iam-admin/iam) and clicking on the _"Edit principal"_ (✏️ pencil icon) next to your _Service Account_.
107
+
108
+ Alternatively, you can add them through the [gcloud CLI](https://cloud.google.com/sdk/gcloud) as follows:
109
+
110
+ ```sh
111
+ gcloud projects add-iam-policy-binding PROJECT_ID \
112
+ --member='serviceAccount:PROJECT_ID@PROJECT_ID.iam.gserviceaccount.com' \
113
+ --role='roles/aiplatform.user'
114
+ ```
115
+
116
+ Some people reported having trouble accessing the API, and adding the role `roles/ml.admin` fixed it:
117
+
118
+ ```sh
119
+ gcloud projects add-iam-policy-binding PROJECT_ID \
120
+ --member='serviceAccount:PROJECT_ID@PROJECT_ID.iam.gserviceaccount.com' \
121
+ --role='roles/ml.admin'
122
+ ```
123
+
124
+ If you are not using a _Service Account_:
125
+ ```sh
126
+ gcloud projects add-iam-policy-binding PROJECT_ID \
127
+ --member='user:YOUR@MAIL.COM' \
128
+ --role='roles/aiplatform.user'
129
+
130
+ gcloud projects add-iam-policy-binding PROJECT_ID \
131
+ --member='user:YOUR@MAIL.COM' \
132
+ --role='roles/ml.admin'
133
+ ```
134
+
135
+ > ⚠️ DISCLAIMER: Be careful with what you are doing, and never trust others' code related to this. These commands and instructions alter the level of access to your Google Cloud Account, and running them naively can lead to security risks as well as financial risks. People with access to your account can use it to steal data or incur charges. Run these commands at your own responsibility and due diligence; expect no warranties from the contributors of this project.
136
+
137
+ #### Required Data
138
+
139
+ After this, you should have all the necessary data and access to use Gemini: a `google-credentials.json` file, a `PROJECT_ID`, and a `REGION`:
140
+
141
+ ```ruby
142
+ {
143
+ file_path: 'google-credentials.json',
144
+ project_id: 'PROJECT_ID',
145
+ region: 'us-east4'
146
+ }
147
+ ```
148
+
149
+ As of the writing of this README, the following regions support Gemini:
150
+ ```text
151
+ Iowa (us-central1)
152
+ Las Vegas, Nevada (us-west4)
153
+ Montréal, Canada (northamerica-northeast1)
154
+ Northern Virginia (us-east4)
155
+ Oregon (us-west1)
156
+ Seoul, Korea (asia-northeast3)
157
+ Singapore (asia-southeast1)
158
+ Tokyo, Japan (asia-northeast1)
159
+ ```
160
+
161
+ You can follow here if new regions are available: [Gemini API](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini)
162
+
163
+ ## Usage
164
+
165
+ ### Client
166
+ Ensure that you have all the [required data](#required-data) for authentication.
167
+
168
+ Create a new client:
169
+ ```ruby
170
+ require 'gemini-ai'
171
+
172
+ client = Gemini.new(
173
+ credentials: { file_path: 'google-credentials.json', project_id: 'PROJECT_ID', region: 'us-east4' },
174
+ settings: { model: 'gemini-pro', stream: false }
175
+ )
176
+ ```
177
+
178
+ ### Generate Content
179
+
180
+ #### Synchronous
181
+
182
+ ```ruby
183
+ result = client.stream_generate_content({
184
+ contents: { role: 'user', parts: { text: 'hi!' } }
185
+ })
186
+ ```
187
+
188
+ Result:
189
+ ```ruby
190
+ [{ 'candidates' =>
191
+ [{ 'content' => {
192
+ 'role' => 'model',
193
+ 'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
194
+ },
195
+ 'finishReason' => 'STOP',
196
+ 'safetyRatings' =>
197
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
198
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
199
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
200
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
201
+ 'usageMetadata' => {
202
+ 'promptTokenCount' => 2,
203
+ 'candidatesTokenCount' => 8,
204
+ 'totalTokenCount' => 10
205
+ } }]
206
+ ```
207
+
208
+ #### Streaming
209
+
210
+ You can set up the client to use streaming for all supported endpoints:
211
+ ```ruby
212
+ client = Gemini.new(
213
+ credentials: { file_path: 'google-credentials.json', project_id: 'PROJECT_ID', region: 'us-east4' },
214
+ settings: { model: 'gemini-pro', stream: true }
215
+ )
216
+ ```
217
+
218
+ Or, you can decide on a request basis:
219
+ ```ruby
220
+ client.stream_generate_content(
221
+ { contents: { role: 'user', parts: { text: 'hi!' } } },
222
+ stream: true
223
+ )
224
+ ```
225
+
226
+ With streaming enabled, you can use a block to receive the results:
227
+
228
+ ```ruby
229
+ client.stream_generate_content(
230
+ { contents: { role: 'user', parts: { text: 'hi!' } } }
231
+ ) do |event, parsed, raw|
232
+ puts event
233
+ end
234
+ ```
235
+
236
+ Event:
237
+ ```ruby
238
+ { 'candidates' =>
239
+ [{ 'content' => {
240
+ 'role' => 'model',
241
+ 'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
242
+ },
243
+ 'finishReason' => 'STOP',
244
+ 'safetyRatings' =>
245
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
246
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
247
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
248
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
249
+ 'usageMetadata' => {
250
+ 'promptTokenCount' => 2,
251
+ 'candidatesTokenCount' => 8,
252
+ 'totalTokenCount' => 10
253
+ } }
254
+ ```
255
+
256
+ #### Streaming Hang
257
+
258
+ Method calls will _hang_ until the stream finishes, so even without providing a block, you can get the final results of the stream events:
259
+
260
+ ```ruby
261
+ result = client.stream_generate_content(
262
+ { contents: { role: 'user', parts: { text: 'hi!' } } },
263
+ stream: true
264
+ )
265
+ ```
266
+
267
+ Result:
268
+ ```ruby
269
+ [{ 'candidates' =>
270
+ [{ 'content' => {
271
+ 'role' => 'model',
272
+ 'parts' => [{ 'text' => 'Hello! How may I assist you?' }]
273
+ },
274
+ 'finishReason' => 'STOP',
275
+ 'safetyRatings' =>
276
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
277
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
278
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
279
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
280
+ 'usageMetadata' => {
281
+ 'promptTokenCount' => 2,
282
+ 'candidatesTokenCount' => 8,
283
+ 'totalTokenCount' => 10
284
+ } }]
285
+ ```
286
+
287
+ ### Back-and-Forth Conversations
288
+
289
+ To maintain a back-and-forth conversation, you need to append the received responses and build a history for your requests:
290
+
291
+ ```rb
292
+ result = client.stream_generate_content(
293
+ { contents: [
294
+ { role: 'user', parts: { text: 'Hi! My name is Purple.' } },
295
+ { role: 'model', parts: { text: "Hello Purple! It's nice to meet you." } },
296
+ { role: 'user', parts: { text: "What's my name?" } }
297
+ ] }
298
+ )
299
+ ```
300
+
301
+ Result:
302
+ ```ruby
303
+ [{ 'candidates' =>
304
+ [{ 'content' =>
305
+ { 'role' => 'model',
306
+ 'parts' => [
307
+ { 'text' => "Purple.\n\nYou told me your name was Purple in your first message to me.\n\nIs there anything" }
308
+ ] },
309
+ 'safetyRatings' =>
310
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
311
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
312
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
313
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }] },
314
+ { 'candidates' =>
315
+ [{ 'content' => { 'role' => 'model', 'parts' => [{ 'text' => ' else I can help you with today, Purple?' }] },
316
+ 'finishReason' => 'STOP',
317
+ 'safetyRatings' =>
318
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
319
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
320
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
321
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
322
+ 'usageMetadata' => {
323
+ 'promptTokenCount' => 24,
324
+ 'candidatesTokenCount' => 31,
325
+ 'totalTokenCount' => 55
326
+ } }]
327
+ ```
328
+
329
+ ### Tools (Functions) Calling
330
+
331
+ > As of the writing of this README, only the `gemini-pro` model [supports](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/function-calling#supported_models) tools (functions) calls.
332
+
333
+ You can provide specifications for [tools (functions)](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/function-calling) using [JSON Schema](https://json-schema.org) to generate potential calls to them:
334
+
335
+ ```ruby
336
+ input = {
337
+ tools: {
338
+ function_declarations: [
339
+ {
340
+ name: 'date_and_time',
341
+ description: 'Returns the current date and time in the ISO 8601 format for a given timezone.',
342
+ parameters: {
343
+ type: 'object',
344
+ properties: {
345
+ timezone: {
346
+ type: 'string',
347
+ description: 'A string represents the timezone to be used for providing a datetime, following the IANA (Internet Assigned Numbers Authority) Time Zone Database. Examples include "Asia/Tokyo" and "Europe/Paris". If not provided, the default timezone is the user\'s current timezone.'
348
+ }
349
+ }
350
+ }
351
+ }
352
+ ]
353
+ },
354
+ contents: [
355
+ { role: 'user', parts: { text: 'What time is it?' } }
356
+ ]
357
+ }
358
+
359
+ result = client.stream_generate_content(input)
360
+ ```
361
+
362
+ Which may return a request to perform a call:
363
+ ```ruby
364
+ [{ 'candidates' =>
365
+ [{ 'content' => {
366
+ 'role' => 'model',
367
+ 'parts' => [{ 'functionCall' => {
368
+ 'name' => 'date_and_time',
369
+ 'args' => { 'timezone' => 'local' }
370
+ } }]
371
+ },
372
+ 'finishReason' => 'STOP',
373
+ 'safetyRatings' =>
374
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
375
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
376
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
377
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
378
+ 'usageMetadata' => { 'promptTokenCount' => 5, 'totalTokenCount' => 5 } }]
379
+ ```
380
+
381
+ Based on these results, you can perform the requested calls and provide their outputs:
382
+ ```ruby
383
+ gem 'tzinfo', '~> 2.0', '>= 2.0.6'
384
+ ```
385
+
386
+ ```ruby
387
+ require 'tzinfo'
388
+ require 'time'
389
+
390
+ function_calls = result.dig(0, 'candidates', 0, 'content', 'parts').filter do |part|
391
+ part.key?('functionCall')
392
+ end
393
+
394
+ function_parts = []
395
+
396
+ function_calls.each do |function_call|
397
+ next unless function_call['functionCall']['name'] == 'date_and_time'
398
+
399
+ timezone = function_call.dig('functionCall', 'args', 'timezone')
400
+
401
+ time = if !timezone.nil? && timezone != '' && timezone.downcase != 'local'
402
+ TZInfo::Timezone.get(timezone).now
403
+ else
404
+ Time.now
405
+ end
406
+
407
+ function_output = time.iso8601
408
+
409
+ function_parts << {
410
+ functionResponse: {
411
+ name: function_call['functionCall']['name'],
412
+ response: {
413
+ name: function_call['functionCall']['name'],
414
+ content: function_output
415
+ }
416
+ }
417
+ }
418
+ end
419
+
420
+ input[:contents] << result.dig(0, 'candidates', 0, 'content')
421
+ input[:contents] << { role: 'function', parts: function_parts }
422
+ ```
423
+
424
+ This will be equivalent to the following final input:
425
+ ```ruby
426
+ { tools: { function_declarations: [
427
+ { name: 'date_and_time',
428
+ description: 'Returns the current date and time in the ISO 8601 format for a given timezone.',
429
+ parameters: {
430
+ type: 'object',
431
+ properties: {
432
+ timezone: {
433
+ type: 'string',
434
+ description: "A string represents the timezone to be used for providing a datetime, following the IANA (Internet Assigned Numbers Authority) Time Zone Database. Examples include \"Asia/Tokyo\" and \"Europe/Paris\". If not provided, the default timezone is the user's current timezone."
435
+ }
436
+ }
437
+ } }
438
+ ] },
439
+ contents: [
440
+ { role: 'user', parts: { text: 'What time is it?' } },
441
+ { role: 'model',
442
+ parts: [
443
+ { functionCall: { name: 'date_and_time', args: { timezone: 'local' } } }
444
+ ] },
445
+ { role: 'function',
446
+ parts: [{ functionResponse: {
447
+ name: 'date_and_time',
448
+ response: {
449
+ name: 'date_and_time',
450
+ content: '2023-12-13T21:15:11-03:00'
451
+ }
452
+ } }] }
453
+ ] }
454
+ ```
455
+
456
+ With the input properly arranged, you can make another request:
457
+ ```ruby
458
+ result = client.stream_generate_content(input)
459
+ ```
460
+
461
+ Which will result in:
462
+ ```ruby
463
+ [{ 'candidates' =>
464
+ [{ 'content' => { 'role' => 'model', 'parts' => [{ 'text' => 'It is 21:15.' }] },
465
+ 'finishReason' => 'STOP',
466
+ 'safetyRatings' =>
467
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
468
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
469
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
470
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
471
+ 'usageMetadata' => { 'promptTokenCount' => 5, 'candidatesTokenCount' => 9, 'totalTokenCount' => 14 } }]
472
+ ```
473
+
474
+ ### New Functionalities and APIs
475
+
476
+ Google may launch a new endpoint that we haven't covered in the Gem yet. If that's the case, you may still be able to use it through the `request` method. For example, `stream_generate_content` is just a wrapper for `google/models/gemini-pro:streamGenerateContent`, which you can call directly like this:
477
+
478
+ ```ruby
479
+ result = client.request(
480
+ 'streamGenerateContent',
481
+ { contents: { role: 'user', parts: { text: 'hi!' } } }
482
+ )
483
+ ```
484
+
485
+ ## Development
486
+
487
+ ```bash
488
+ bundle
489
+ rubocop -A
490
+ ```
491
+
492
+ ### Purpose
493
+
494
+ This Gem is designed to provide low-level access to Gemini, enabling people to build abstractions on top of it. If you are interested in more high-level abstractions or more user-friendly tools, you may want to consider [Nano Bots](https://github.com/icebaker/ruby-nano-bots) 💎 🤖.
495
+
496
+ ### Publish to RubyGems
497
+
498
+ ```bash
499
+ gem build gemini-ai.gemspec
500
+
501
+ gem signin
502
+
503
+ gem push gemini-ai-1.0.0.gem
504
+ ```
505
+
506
+ ### Updating the README
507
+
508
+ Update the `template.md` file and then:
509
+
510
+ ```sh
511
+ bb tasks/generate-readme.clj
512
+ ```
513
+
514
+ Trick for automatically updating the `README.md` when `template.md` changes:
515
+
516
+ ```sh
517
+ sudo pacman -S inotify-tools # Arch / Manjaro
518
+ sudo apt-get install inotify-tools # Debian / Ubuntu / Raspberry Pi OS
519
+ sudo dnf install inotify-tools # Fedora / CentOS / RHEL
520
+
521
+ while inotifywait -e modify template.md; do bb tasks/generate-readme.clj; done
522
+ ```
523
+
524
+ Trick for Markdown Live Preview:
525
+ ```sh
526
+ pip install -U markdown_live_preview
527
+
528
+ mlp README.md -p 8076
529
+ ```
530
+
531
+ ## Resources and References
532
+
533
+ These resources and references may be useful throughout your learning process.
534
+
535
+ - [Getting Started with the Vertex AI Gemini API with cURL](https://github.com/GoogleCloudPlatform/generative-ai/blob/main/gemini/getting-started/intro_gemini_curl.ipynb)
536
+ - [Gemini API Documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini)
537
+ - [Vertex AI API Documentation](https://cloud.google.com/vertex-ai/docs/reference)
538
+ - [REST Documentation](https://cloud.google.com/vertex-ai/docs/reference/rest)
539
+ - [Google DeepMind Gemini](https://deepmind.google/technologies/gemini/)
540
+ - [Stream responses from Generative AI models](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/streaming)
541
+ - [Function calling](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/function-calling)
542
+
543
+ ## Disclaimer
544
+
545
+ This is not an official Google project, nor is it affiliated with Google in any way.
546
+
547
+ The software is distributed under the MIT License, which can be found at [https://github.com/gbaptista/gemini-ai/blob/main/LICENSE](https://github.com/gbaptista/gemini-ai/blob/main/LICENSE). This license includes a disclaimer of warranty. Moreover, the authors assume no responsibility for any damage or costs that may result from using this project. Use the Gemini AI Ruby Gem at your own risk.