ruby-gemini-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d7b4f9c65b80cb5b10a2a1440b29b78b40ca6161ae7297642ef1f33b4dfb3501
4
+ data.tar.gz: 3580107b3720e3299cca98c5f9f039b61bc4c891915dd94aca25d648452d333f
5
+ SHA512:
6
+ metadata.gz: c46abb842e6cefdbe2a59bf6e646f5136b6d68eac4f8de6f12bb224483fa66eb1709a6935daf447927ec0cf75cad763376357e37cfbb528d75db813fac010a4d
7
+ data.tar.gz: 58c436b5d93829a68e996a7f152478c5fca9170850346ce1ce4614ad8be52b258f724005de7b02d19f49c22ca71effdeb1762be525b41fd1634e1b74f88eba72
@@ -0,0 +1,48 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ push:
5
+ branches: [ "master" ]
6
+ pull_request:
7
+ branches: [ "master" ]
8
+
9
+ jobs:
10
+ build:
11
+ name: Build + Publish
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+ packages: write
16
+
17
+ steps:
18
+ - uses: actions/checkout@v3
19
+ - name: Set up Ruby 2.6.7
20
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
21
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
22
+ # uses: ruby/setup-ruby@v1
23
+ uses: ruby/setup-ruby@55283cc23133118229fd3f97f9336ee23a179fcf # v1.146.0
24
+ with:
25
+ ruby-version: 2.6.7
26
+
27
+ - name: Publish to GPR
28
+ run: |
29
+ mkdir -p $HOME/.gem
30
+ touch $HOME/.gem/credentials
31
+ chmod 0600 $HOME/.gem/credentials
32
+ printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
33
+ gem build *.gemspec
34
+ gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
35
+ env:
36
+ GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
37
+ OWNER: ${{ github.repository_owner }}
38
+
39
+ - name: Publish to RubyGems
40
+ run: |
41
+ mkdir -p $HOME/.gem
42
+ touch $HOME/.gem/credentials
43
+ chmod 0600 $HOME/.gem/credentials
44
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
45
+ gem build *.gemspec
46
+ gem push *.gem
47
+ env:
48
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
data/.gitignore ADDED
@@ -0,0 +1,56 @@
1
+ # Ignore bundler config.
2
+ /.bundle
3
+
4
+ # Ignore local development configuration.
5
+ /config/*.yml
6
+
7
+ # Ignore test and spec directories.
8
+ /spec/tmp
9
+ /spec/fixtures
10
+ /spec/reports
11
+ /spec/examples
12
+
13
+ # Ignore coverage report generated by SimpleCov.
14
+ /coverage
15
+
16
+ # Ignore environment-specific files.
17
+ .env
18
+ .env.local
19
+ .env.development.local
20
+ .env.test.local
21
+ .env.production.local
22
+
23
+ # Ignore local database files.
24
+ /db/*.sqlite3
25
+ /db/*.sqlite3-journal
26
+
27
+ # Ignore the output of 'gem build'.
28
+ /*.gem
29
+
30
+ # rspec failure tracking
31
+ .rspec_status
32
+
33
+ # IDE
34
+ .idea
35
+ .idea/
36
+ .idea/*
37
+ .vscode
38
+ .vs/
39
+ *.iml
40
+ *.sublime-project
41
+ *.sublime-workspace
42
+ /.vscode
43
+
44
+ # Mac
45
+ .DS_Store
46
+
47
+ # Ignore system and application-specific files.
48
+ *.log
49
+ *.swp
50
+ *.bak
51
+ Thumbs.db
52
+
53
+ # Ignore gem artifacts.
54
+ /pkg
55
+ *.gem
56
+
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Include gem dependencies from ruby-geminiai.gemspec
4
+ gemspec
5
+
6
+ group :test, :development do
7
+ gem "byebug", "~> 11.1.3"
8
+ gem "dotenv", "~> 2.8.1"
9
+ gem "rake", "~> 13.1"
10
+ gem "rspec", "~> 3.12"
11
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,69 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ ruby-gemini-ai (0.1.0)
5
+ event_stream_parser (>= 0.3.0, < 2.0.0)
6
+ faraday (>= 1)
7
+ googleauth (~> 1.8)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.8.6)
13
+ public_suffix (>= 2.0.2, < 6.0)
14
+ byebug (11.1.3)
15
+ diff-lcs (1.5.1)
16
+ dotenv (2.8.1)
17
+ event_stream_parser (1.0.0)
18
+ faraday (2.9.0)
19
+ faraday-net_http (>= 2.0, < 3.2)
20
+ faraday-net_http (3.1.0)
21
+ net-http
22
+ google-cloud-env (2.1.1)
23
+ faraday (>= 1.0, < 3.a)
24
+ googleauth (1.11.0)
25
+ faraday (>= 1.0, < 3.a)
26
+ google-cloud-env (~> 2.1)
27
+ jwt (>= 1.4, < 3.0)
28
+ multi_json (~> 1.11)
29
+ os (>= 0.9, < 2.0)
30
+ signet (>= 0.16, < 2.a)
31
+ jwt (2.7.1)
32
+ multi_json (1.15.0)
33
+ net-http (0.4.1)
34
+ uri
35
+ os (1.1.4)
36
+ public_suffix (5.0.4)
37
+ rake (13.1.0)
38
+ rspec (3.13.0)
39
+ rspec-core (~> 3.13.0)
40
+ rspec-expectations (~> 3.13.0)
41
+ rspec-mocks (~> 3.13.0)
42
+ rspec-core (3.13.0)
43
+ rspec-support (~> 3.13.0)
44
+ rspec-expectations (3.13.0)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.13.0)
47
+ rspec-mocks (3.13.0)
48
+ diff-lcs (>= 1.2.0, < 2.0)
49
+ rspec-support (~> 3.13.0)
50
+ rspec-support (3.13.0)
51
+ signet (0.19.0)
52
+ addressable (~> 2.8)
53
+ faraday (>= 0.17.5, < 3.a)
54
+ jwt (>= 1.5, < 3.0)
55
+ multi_json (~> 1.10)
56
+ uri (0.13.0)
57
+
58
+ PLATFORMS
59
+ arm64-darwin-22
60
+
61
+ DEPENDENCIES
62
+ byebug (~> 11.1.3)
63
+ dotenv (~> 2.8.1)
64
+ rake (~> 13.1)
65
+ rspec (~> 3.12)
66
+ ruby-gemini-ai!
67
+
68
+ BUNDLED WITH
69
+ 2.3.22
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 GeminiAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,381 @@
1
+ # Ruby GeminiAi
2
+
3
+ This guide explains how to seamlessly integrate the powerful Gemini AI API into your Ruby projects. Utilize Gemini's cutting-edge language capabilities for generating text, translating languages, and more.
4
+
5
+ # Table of Contents
6
+
7
+ - [Installation](#installation)
8
+ - [Bundler](#bundler)
9
+ - [Gem Install](#gem-install)
10
+ - [Credentials](#credentials)
11
+ - [Option 1: API Key (Generative Language API)](#option-1-api-key-generative-language-api)
12
+ - [Option 2: Service Account Credentials File (Vertex AI API)](#option-2-service-account-credentials-file-vertex-ai-api)
13
+ - [Option 3: Application Default Credentials (Vertex AI API)](#option-3-application-default-credentials-vertex-ai-api)
14
+ - [Usage](#usage)
15
+ - [Quickstart](#quickstart)
16
+ - [With Config](#with-config)
17
+ - [Verbose Logging](#verbose-logging)
18
+ - [Methods](#methods)
19
+ - [stream_generate_content](#stream_generate_content)
20
+ - [generate_content](#generate_content)
21
+ - [Development](#development)
22
+ - [Compatibility](#compatibility)
23
+ - [License](#license)
24
+ - [Resources and References](#resources-and-references)
25
+ - [Additional Notes](#additional-notes)
26
+
27
+ ## Installation
28
+
29
+ ### Bundler
30
+
31
+ Add this line to your application's Gemfile:
32
+
33
+ ```ruby
34
+ gem "ruby-gemini-ai"
35
+ ```
36
+
37
+ And then execute:
38
+
39
+ ```bash
40
+ $ bundle install
41
+ ```
42
+
43
+ ### Gem install
44
+
45
+ Or install with:
46
+
47
+ ```bash
48
+ $ gem install ruby-gemini-ai
49
+ ```
50
+
51
+ and require with:
52
+
53
+ ```ruby
54
+ require "gemini-ai"
55
+ ```
56
+
57
+ ## Credentials
58
+
59
+ - [Option 1: API Key (Generative Language API)](#option-1-api-key-generative-language-api)
60
+ - [Option 2: Service Account Credentials File (Vertex AI API)](#option-2-service-account-credentials-file-vertex-ai-api)
61
+ - [Option 3: Application Default Credentials (Vertex AI API)](#option-3-application-default-credentials-vertex-ai-api)
62
+
63
+ #### Option 1: API Key (Generative Language API):
64
+
65
+ Obtain an API Key from your Google Cloud project: [Google Cloud](https://console.cloud.google.com) through the Google Cloud Console: [https://console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials).
66
+
67
+ Enable the Generative Language API service in your Google Cloud Console. which can be done [here](https://console.cloud.google.com/apis/library/generativelanguage.googleapis.com).
68
+
69
+ Alternatively, you can generate an API Key through Google AI Studio [here](https://makersuite.google.com/app/apikey), which will automatically create a project for you.
70
+
71
+ #### Option 2: Service Account Credentials File (Vertex AI API)
72
+
73
+ For the Vertex AI API, create a [Google Cloud](https://console.cloud.google.com) Project and a [_Service Account_](https://cloud.google.com/iam/docs/service-account-overview). Enable the [Vertex AI] (https://cloud.google.com/vertex-ai) API for your project [here](https://console.cloud.google.com/apis/library/aiplatform.googleapis.com).
74
+
75
+ Generate credentials for your Service Account [here](https://console.cloud.google.com/apis/credentials) and download a JSON file named google-credentials.json.
76
+
77
+ ```json
78
+ {
79
+ "type": "service_account",
80
+ "project_id": "YOUR_PROJECT_ID",
81
+ "private_key_id": "a00...",
82
+ "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
83
+ "client_email": "PROJECT_ID@PROJECT_ID.iam.gserviceaccount.com",
84
+ "client_id": "000...",
85
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
86
+ "token_uri": "https://oauth2.googleapis.com/token",
87
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
88
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/..."
89
+ }
90
+ ```
91
+
92
+ Ensure the necessary [policies](https://cloud.google.com/iam/docs/policies) (`roles/aiplatform.user` and possibly `roles/ml.admin`) are in place use the Vertex AI API.
93
+
94
+ 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_.
95
+
96
+ Alternatively, you can add them through the [gcloud CLI](https://cloud.google.com/sdk/gcloud) as follows:
97
+
98
+ ```sh
99
+ gcloud projects add-iam-policy-binding PROJECT_ID \
100
+ --member='serviceAccount:PROJECT_ID@PROJECT_ID.iam.gserviceaccount.com' \
101
+ --role='roles/aiplatform.user'
102
+ ```
103
+
104
+ Some people reported having trouble accessing the API, and adding the role `roles/ml.admin` fixed it:
105
+
106
+ ```sh
107
+ gcloud projects add-iam-policy-binding PROJECT_ID \
108
+ --member='serviceAccount:PROJECT_ID@PROJECT_ID.iam.gserviceaccount.com' \
109
+ --role='roles/ml.admin'
110
+ ```
111
+
112
+ If you are not using a _Service Account_:
113
+ ```sh
114
+ gcloud projects add-iam-policy-binding PROJECT_ID \
115
+ --member='user:YOUR@MAIL.COM' \
116
+ --role='roles/aiplatform.user'
117
+
118
+ gcloud projects add-iam-policy-binding PROJECT_ID \
119
+ --member='user:YOUR@MAIL.COM' \
120
+ --role='roles/ml.admin'
121
+ ```
122
+
123
+ #### Option 3: Application Default Credentials (Vertex AI API)
124
+
125
+
126
+ Similar to [Option 2](#option-2-service-account-credentials-file-vertex-ai-api), but you don't need to download a `google-credentials.json`. These automatically find credentials based on your environment. [_Application Default Credentials_](https://cloud.google.com/docs/authentication/application-default-credentials).
127
+
128
+ Generate them using the gcloud CLI before local development. [gcloud CLI](https://cloud.google.com/sdk/gcloud):
129
+
130
+ ```sh
131
+ gcloud auth application-default login
132
+ ```
133
+
134
+ For more details about alternative methods and different environments, check the official documentation:
135
+ [Set up Application Default Credentials](https://cloud.google.com/docs/authentication/provide-credentials-adc)
136
+
137
+
138
+ ## Usage
139
+
140
+ ### Quickstart
141
+
142
+ For a quick test you can pass your token directly to a new client:
143
+
144
+ ```ruby
145
+ client = GeminiAi::Client.new(api_key: "gemini_api_key")
146
+ ```
147
+
148
+ ### With Config
149
+
150
+ We can configure Gemini with Ruby using three options.
151
+
152
+ **Option 1**, API KEY
153
+
154
+ For a more robust setup, you can configure the gem with your API keys, for example in an `gemini.rb` initializer file. Never hardcode secrets into your codebase - instead use something like [dotenv](https://github.com/motdotla/dotenv) to pass the keys safely into your environments.
155
+
156
+ ```ruby
157
+ GeminiAi.configure do |config|
158
+ config.api_key = ENV.fetch("GEMINI_API_KEY")
159
+ config.service = ENV.fetch("GEMINI_API_SERVICE")
160
+ end
161
+ ```
162
+
163
+ **Option 2**, Service Account
164
+
165
+ For the Service Account, provide a `google-credentials.json` file and a `REGION`:
166
+
167
+ ```ruby
168
+ GeminiAi.configure do |config|
169
+ config.service = 'vertex-ai-api'
170
+ config.region = 'us-east4'
171
+ config.file_path = 'google-credentials.json'
172
+ end
173
+ ```
174
+
175
+ **Option 3**, Default Credentials
176
+
177
+ For _Application Default Credentials_, omit both the `api_key` and the `file_path`:
178
+
179
+ ```ruby
180
+ GeminiAi.configure do |config|
181
+ config.region = 'us-east4'
182
+ config.service = 'vertex-ai-api'
183
+ end
184
+ ```
185
+
186
+ Then you can create a client like this:
187
+
188
+ ```ruby
189
+ client = GeminiAi::Client.new
190
+ ```
191
+
192
+
193
+ ## Methods
194
+
195
+ ### stream_generate_content(contents, model):
196
+
197
+ - Streams generated text in real-time.
198
+ - contents (hash): User input and role information.
199
+ - model (string): Optional model name (e.g., gemini-pro).
200
+ - Returns an array of candidates objects with generated text and safety ratings.
201
+
202
+ ```ruby
203
+ client = GeminiAi::Client.new
204
+ # Assuming you configured with your API key or credentials
205
+
206
+ contents = {
207
+ contents: {
208
+ role: 'user',
209
+ parts: {
210
+ text: 'Write a poem about the ocean.'
211
+ }
212
+ }
213
+ }
214
+
215
+ stream = client.stream_generate_content(contents, model: 'gemini-pro')
216
+ ```
217
+
218
+ In this case, the result will be an array with all the received events:
219
+
220
+ ```ruby
221
+ [{ 'candidates' =>
222
+ [{ 'content' => {
223
+ 'role' => 'model',
224
+ 'parts' => [{ 'text' => 'exmaple poem content.......' }]
225
+ },
226
+ 'finishReason' => 'STOP',
227
+ 'safetyRatings' =>
228
+ [{ 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
229
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
230
+ { 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
231
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
232
+ 'usageMetadata' => {
233
+ 'promptTokenCount' => 2,
234
+ 'candidatesTokenCount' => 8,
235
+ 'totalTokenCount' => 10
236
+ } }]
237
+ ```
238
+
239
+ #### with stream
240
+
241
+ ```ruby
242
+ client = GeminiAi::Client.new
243
+ # Assuming you configured with your API key or credentials
244
+
245
+ contents = {
246
+ contents: {
247
+ role: 'user',
248
+ parts: {
249
+ text: 'Write a poem about the ocean.'
250
+ }
251
+ }
252
+ }
253
+
254
+ client.stream_generate_content(contents, model: 'gemini-pro', stream: true) do |part_text, event, parsed, raw|
255
+ puts text
256
+ end
257
+ ```
258
+ OR
259
+
260
+ ```ruby
261
+ client = GeminiAi::Client.new
262
+ # Assuming you configured with your API key or credentials
263
+
264
+ contents = {
265
+ contents: {
266
+ role: 'user',
267
+ parts: {
268
+ text: 'Write a poem about the ocean.'
269
+ }
270
+ }
271
+ }
272
+
273
+ # Assuming you have a block or procedure (proc) defined
274
+ stream_proc = Proc.new do |part_text, event, parsed, raw|
275
+ puts part_text
276
+ end
277
+
278
+ client.stream_generate_content(contents, model: 'gemini-pro', stream: true, &stream_proc)
279
+ ```
280
+
281
+
282
+ In this case, the result will be an array with all the received events:
283
+
284
+ ```ruby
285
+ 'exmaple poem content.......'
286
+ ```
287
+
288
+ ### generate_content(contents, model)
289
+
290
+ - Generates text in a single request.
291
+ - contents (hash): User input and role information.
292
+ - model (string): Optional model name (e.g., gemini-pro).
293
+ - Returns a hash with generated text, safety ratings, and prompt feedback
294
+
295
+ ```ruby
296
+ result = client.generate_content(
297
+ { contents: { role: 'user', parts: { text: 'hi!' } } }, model: 'gemini-pro'
298
+ )
299
+ ```
300
+
301
+ ```ruby
302
+ client = GeminiAi::Client.new
303
+ # Assuming you configured with your API key or credentials
304
+
305
+ contents = {
306
+ contents: {
307
+ role: 'user',
308
+ parts: {
309
+ text: 'Write a poem about the ocean.'
310
+ }
311
+ }
312
+ }
313
+
314
+ stream = client.generate_content(contents, model: 'gemini-pro')
315
+ ```
316
+
317
+
318
+ Result:
319
+ ```ruby
320
+ { 'candidates' =>
321
+ [{ 'content' => { 'parts' => [{ 'text' => 'exampled poem.......' }], 'role' => 'model' },
322
+ 'finishReason' => 'STOP',
323
+ 'index' => 0,
324
+ 'safetyRatings' =>
325
+ [{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
326
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
327
+ { 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
328
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] }],
329
+ 'promptFeedback' =>
330
+ { 'safetyRatings' =>
331
+ [{ 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'probability' => 'NEGLIGIBLE' },
332
+ { 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'probability' => 'NEGLIGIBLE' },
333
+ { 'category' => 'HARM_CATEGORY_HARASSMENT', 'probability' => 'NEGLIGIBLE' },
334
+ { 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'probability' => 'NEGLIGIBLE' }] } }
335
+ ```
336
+
337
+ #### Verbose Logging
338
+
339
+ You can pass [Faraday middleware](https://lostisland.github.io/faraday/#/middleware/index) to the client in a block, eg. to enable verbose logging with Ruby's [Logger](https://ruby-doc.org/3.2.2/stdlibs/logger/Logger.html):
340
+
341
+ ```ruby
342
+ client = GeminiAi::Client.new do |f|
343
+ f.response :logger, Logger.new($stdout), bodies: true
344
+ end
345
+ ```
346
+
347
+ ## Development
348
+
349
+ 1) Clone the repository.
350
+ 2) Run **bin/setup** to install dependencies.
351
+ 3) Use **bin/console** for interactive exploration.
352
+ 4) Run **bundle exec** rake install to install the gem locally.
353
+
354
+ ## Compatibility
355
+
356
+ ruby-gemini-ai gem is compatible with Ruby versions 2.6.7 and higher.
357
+
358
+ ## License
359
+
360
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
361
+
362
+ ## Resources and References
363
+
364
+ Explore the following curated list of resources and references to enhance your understanding throughout the learning process:
365
+
366
+ - [Google AI for Developers](https://ai.google.dev): Stay updated on the latest developments and resources in the field of Artificial Intelligence by visiting Google AI for Developers.
367
+ - [Get started with the Gemini API](https://ai.google.dev/docs): Initiate your journey into the Gemini API with comprehensive guides and documentation provided by Google.
368
+ - [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): Explore hands-on examples and tutorials using cURL to kickstart your experience with the Vertex AI Gemini API.
369
+ - [Gemini API Documentation](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini): Refer to the official Gemini API documentation for detailed information on model references and implementation guidelines.
370
+ - [Vertex AI API Documentation](https://cloud.google.com/vertex-ai/docs/reference): Dive into the Vertex AI API documentation to gain a comprehensive understanding of Vertex AI services.
371
+ - [REST Documentation](https://cloud.google.com/vertex-ai/docs/reference/rest): Explore the RESTful API documentation for Vertex AI to facilitate seamless integration with your applications.
372
+ - [Google DeepMind Gemini](https://deepmind.google/technologies/gemini/): Gain insights into Google DeepMind's Gemini technology, a cutting-edge advancement in the field of Artificial Intelligence.
373
+ - [Stream responses from Generative AI models](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/streaming): Learn how to effectively stream responses from Generative AI models by consulting this specific guide within the Vertex AI documentation.
374
+ - [Function calling](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/function-calling): Understand the intricacies of function calling in the context of Generative AI models with this guide from the Vertex AI documentation.
375
+
376
+ These resources collectively provide a comprehensive foundation for your exploration of the Gemini API and Vertex AI services.
377
+
378
+ ## Additional Notes
379
+
380
+ - As of now, only generate_content is supported with the `vertex-ai-api` service.
381
+ - For detailed API documentation and advanced usage, refer to the official Gemini AI documentation Consider adding examples and error handling for a more user-friendly experience.
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gemini-ai"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'event_stream_parser'
4
+ require 'faraday'
5
+ require 'json'
6
+ require 'googleauth'
7
+ require 'uri'
8
+
9
+ require_relative 'errors'
10
+
11
+ module GeminiAi
12
+ class Client
13
+ include GeminiAi::HTTP
14
+
15
+ CONFIG_KEYS = %i[ api_key region file_path version service ].freeze
16
+ attr_accessor :authentication, :authorizer, :project_id, :service_version
17
+ attr_reader *CONFIG_KEYS, :faraday_middleware
18
+
19
+ def initialize(config = {}, &faraday_middleware)
20
+ CONFIG_KEYS.each do |key|
21
+ # Set instance variables like service authentication etc
22
+ instance_variable_set("@#{key}", config[key] || GeminiAi.configuration.send(key))
23
+ end
24
+ @service_version = @version || DEFAULT_SERVICE_VERSION
25
+ case
26
+ when @api_key
27
+ @authentication = :api_key
28
+ when @file_path
29
+ @authentication = :service_account
30
+ @authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
31
+ json_key_io: File.open(@file_path),
32
+ scope: 'https://www.googleapis.com/auth/cloud-platform'
33
+ )
34
+ else
35
+ @authentication = :default_credentials
36
+ @authorizer = Google::Auth.get_application_default
37
+ end
38
+
39
+ if @authentication == :service_account || @authentication == :default_credentials
40
+ @project_id = @authorizer.project_id || @authorizer.quota_project_id
41
+ raise Errors::MissingProjectIdError, 'Could not determine project_id, which is required.' if @project_id.nil?
42
+ end
43
+ @faraday_middleware = faraday_middleware
44
+ end
45
+
46
+ def stream_generate_content(payload, model: nil, stream: nil, &callback)
47
+ path = build_request_url('streamGenerateContent', model, stream)
48
+ json_post(path: path, parameters: payload, &callback)
49
+ end
50
+
51
+ def generate_content(payload, model: nil, stream: nil, &callback)
52
+ path = build_request_url('generateContent', model, stream)
53
+ json_post(path: path, parameters: payload, &callback)
54
+ end
55
+
56
+ private
57
+
58
+ def build_request_url(path, model, stream)
59
+ base_url = case @service
60
+ when 'vertex-ai-api'
61
+ "https://#{@region}-aiplatform.googleapis.com/#{service_version}/projects/#{@project_id}/locations/#{@region}/publishers/google/models/#{model}"
62
+ when 'generative-language-api'
63
+ "https://generativelanguage.googleapis.com/#{service_version}/models/#{model}"
64
+ end
65
+
66
+ params = {}
67
+ params[:alt] = 'sse' if stream
68
+ params[:key] = @api_key if @authentication == :api_key
69
+
70
+ uri = URI("#{base_url}:#{path}")
71
+ uri.query = URI.encode_www_form(params) if params.present?
72
+ uri.to_s
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,10 @@
1
+ module Ruby
2
+ module GeminiAI
3
+ VERSION = ::GeminiAI::VERSION
4
+
5
+ Error = ::GeminiAI::Error
6
+ ConfigurationError = ::GeminiAI::ConfigurationError
7
+ Configuration = ::GeminiAI::Configuration
8
+ MiddlewareErrors = ::GeminiAI::MiddlewareErrors
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ module GeminiAi
2
+ module Errors
3
+ class GeminiAiError < StandardError; end
4
+ class MissingProjectIdError < GeminiAiError; end
5
+ class UnsupportedServiceError < GeminiAiError; end
6
+ class BlockWithoutServerSentEventsError < GeminiAiError; end
7
+
8
+ class RequestError < GeminiAiError
9
+ attr_reader :request, :payload
10
+
11
+ def initialize(message = nil, request: nil, payload: nil)
12
+ @request = request
13
+ @payload = payload
14
+ super(message)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,117 @@
1
+ require "event_stream_parser"
2
+
3
+ require_relative "http_headers"
4
+
5
+ module GeminiAi
6
+ module HTTP
7
+ include HTTPHeaders
8
+
9
+ def json_post(path:, parameters:, &callback)
10
+ conn.post(path) do |req|
11
+ configure_json_post_request(req, parameters, &callback)
12
+ end&.body
13
+ end
14
+
15
+ private
16
+
17
+ def parse_json(response)
18
+ return unless response
19
+ return response unless response.is_a?(String)
20
+
21
+ # Convert a multiline string of JSON objects to a JSON array.
22
+ response = response.gsub("}\n{", "},{").prepend("[").concat("]")
23
+
24
+ JSON.parse(response)
25
+ end
26
+
27
+ #===================================================================================================
28
+
29
+ def to_json_stream(&callback)
30
+ partial_json = ''
31
+ parser = EventStreamParser::Parser.new
32
+
33
+ proc do |chunk, bytes, env|
34
+ if env && env.status != 200
35
+ raise_error = Faraday::Response::RaiseError.new
36
+ raise_error.on_complete(env.merge(body: chunk))
37
+ end
38
+
39
+ parser.feed(chunk) do |type, data, id, reconnection_time|
40
+ partial_json += data
41
+ parsed_json = parse_partial_json(partial_json)
42
+
43
+ if parsed_json
44
+ parsed_json_text = extract_text_from_json(parsed_json)
45
+ result = {
46
+ text: parsed_json_text,
47
+ event: parsed_json,
48
+ parsed: { type: type, data: data, id: id, reconnection_time: reconnection_time },
49
+ raw: { chunk: chunk, bytes: bytes, env: env }
50
+ }
51
+
52
+ callback.call(result[:text], result[:event], result[:parsed], result[:raw]) unless callback.nil?
53
+
54
+ partial_json = ''
55
+
56
+ if parsed_json['candidates']
57
+ parsed_json['candidates'].find do |candidate|
58
+ !candidate['finishReason'].nil? && candidate['finishReason'] != ''
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ def extract_text_from_json(parsed_json)
68
+ parsed_json["candidates"].map do |candidate|
69
+ candidate["content"]["parts"].map { |part| part["text"] }
70
+ end.join(" ")
71
+ end
72
+
73
+ def parse_partial_json(response)
74
+ return unless response
75
+ return response unless response.is_a?(String)
76
+ response.to_s.lstrip.start_with?('{', '[') ? JSON.parse(response) : nil
77
+ rescue JSON::ParserError
78
+ nil
79
+ end
80
+
81
+ #===================================================================================================
82
+
83
+ def conn(multipart: false)
84
+ connection = Faraday.new do |f|
85
+ f.options[:timeout] = @request_timeout
86
+ f.request(:multipart) if multipart
87
+ f.use MiddlewareErrors
88
+ f.response :raise_error
89
+ f.response :json
90
+ end
91
+
92
+ @faraday_middleware&.call(connection)
93
+
94
+ connection
95
+ end
96
+
97
+ def configure_json_post_request(req, parameters, &callback)
98
+ req_parameters = parameters.dup
99
+ if req.params["alt"].eql?("sse")
100
+ req.options.on_data = to_json_stream(&callback)
101
+ elsif req.params["alt"]
102
+ raise ArgumentError, "The alt parameter value must be a sse"
103
+ end
104
+
105
+ req.headers = headers
106
+ req.body = req_parameters.to_json
107
+ end
108
+
109
+ def try_parse_json(maybe_json)
110
+ JSON.parse(maybe_json)
111
+ rescue JSON::ParserError
112
+ maybe_json
113
+ end
114
+ end
115
+ end
116
+
117
+
@@ -0,0 +1,16 @@
1
+ module GeminiAi
2
+ module HTTPHeaders
3
+
4
+ private
5
+
6
+ def headers
7
+ default_headers = {
8
+ "Content-Type" => "application/json"
9
+ }
10
+ if @authentication == :service_account || @authentication == :default_credentials
11
+ default_headers.merge!("Authorization": "Bearer #{@authorizer.fetch_access_token!['access_token']}")
12
+ end
13
+ default_headers
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module GeminiAi
2
+ VERSION = "0.1.0".freeze
3
+ end
data/lib/gemini-ai.rb ADDED
@@ -0,0 +1,87 @@
1
+ require "faraday"
2
+
3
+ require_relative "gemini-ai/http"
4
+ require_relative "gemini-ai/client"
5
+ require_relative "gemini-ai/errors"
6
+ require_relative "gemini-ai/version"
7
+
8
+ module GeminiAi
9
+ class Error < StandardError; end
10
+
11
+ class ConfigurationError < Error; end
12
+
13
+ class MiddlewareErrors < Faraday::Middleware
14
+ def call(env)
15
+ @app.call(env)
16
+ rescue Faraday::Error => e
17
+ raise e unless e.response.is_a?(Hash)
18
+
19
+ logger = Logger.new($stdout)
20
+ logger.formatter = proc do |_severity, _datetime, _progname, msg|
21
+ "\033[31mGemini AI HTTP Error (spotted in ruby-gemini-ai #{VERSION}): #{msg}\n\033[0m"
22
+ end
23
+ logger.error(e.response[:body])
24
+
25
+ raise e
26
+ end
27
+ end
28
+
29
+ class Configuration
30
+ attr_writer :api_key, :region, :file_path, :version, :service
31
+ DEFAULT_SERVICE_VERSION = 'v1'.freeze
32
+ DEFAULT_SERVICE = 'generative-language-api'.freeze
33
+
34
+ def initialize
35
+ @api_key = nil
36
+ @region = nil
37
+ @file_path = nil
38
+ @version = DEFAULT_SERVICE_VERSION
39
+ @service = DEFAULT_SERVICE
40
+ end
41
+
42
+ def api_key
43
+ @api_key
44
+ end
45
+
46
+ def service
47
+ unless %w[vertex-ai-api generative-language-api].include?(@service)
48
+ raise Errors::UnsupportedServiceError, "Unsupported service: #{@service}"
49
+ end
50
+ @service
51
+ end
52
+
53
+ def version
54
+ @version
55
+ end
56
+
57
+ def file_path
58
+ @file_path
59
+ end
60
+
61
+ def region
62
+ @region
63
+ end
64
+
65
+ end
66
+
67
+ class << self
68
+ attr_writer :configuration
69
+ end
70
+
71
+ def self.configuration
72
+ @configuration ||= GeminiAi::Configuration.new
73
+ end
74
+
75
+ def self.configure
76
+ yield(configuration)
77
+ end
78
+
79
+ def self.parsed_response(response, join_val: " ")
80
+ response.flat_map do |entry|
81
+ entry["candidates"].map do |candidate|
82
+ candidate.dig("content", "parts").map { |part| part["text"] }.join(join_val)
83
+ end
84
+ end.join(join_val)
85
+ end
86
+
87
+ end
@@ -0,0 +1,2 @@
1
+ require_relative "../gemini-ai"
2
+ require_relative "../gemini-ai/compatibility"
@@ -0,0 +1,31 @@
1
+ require_relative "lib/gemini-ai/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "ruby-gemini-ai"
5
+ spec.version = GeminiAi::VERSION
6
+ spec.authors = ["Saurav Saini"]
7
+ spec.email = ["sainisaurav019@gmai.com"]
8
+
9
+ spec.summary = "Gemini API"
10
+ spec.homepage = "https://github.com/sauravsaini98/ruby-gemini-ai"
11
+ spec.license = "MIT"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
13
+
14
+ spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["source_code_uri"] = "https://github.com/sauravsaini98/ruby-gemini-ai"
16
+ spec.metadata["rubygems_mfa_required"] = "true"
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.bindir = "exe"
24
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_dependency "event_stream_parser", ">= 0.3.0", "< 2.0.0"
28
+ spec.add_dependency "faraday", ">= 1"
29
+ spec.add_dependency 'googleauth', '~> 1.8'
30
+
31
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-gemini-ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Saurav Saini
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-02-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: event_stream_parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 0.3.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: faraday
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1'
47
+ - !ruby/object:Gem::Dependency
48
+ name: googleauth
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.8'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.8'
61
+ description:
62
+ email:
63
+ - sainisaurav019@gmai.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files: []
67
+ files:
68
+ - ".github/workflows/gem-push.yml"
69
+ - ".gitignore"
70
+ - ".rspec"
71
+ - Gemfile
72
+ - Gemfile.lock
73
+ - LICENSE
74
+ - README.md
75
+ - bin/console
76
+ - bin/setup
77
+ - lib/gemini-ai.rb
78
+ - lib/gemini-ai/client.rb
79
+ - lib/gemini-ai/compatibility.rb
80
+ - lib/gemini-ai/errors.rb
81
+ - lib/gemini-ai/http.rb
82
+ - lib/gemini-ai/http_headers.rb
83
+ - lib/gemini-ai/version.rb
84
+ - lib/ruby/gemini-ai.rb
85
+ - ruby-gemini-ai.gemspec
86
+ homepage: https://github.com/sauravsaini98/ruby-gemini-ai
87
+ licenses:
88
+ - MIT
89
+ metadata:
90
+ homepage_uri: https://github.com/sauravsaini98/ruby-gemini-ai
91
+ source_code_uri: https://github.com/sauravsaini98/ruby-gemini-ai
92
+ rubygems_mfa_required: 'true'
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 2.6.0
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 3.2.3
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Gemini API
112
+ test_files: []