gemini_craft 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb9a603d6cf3b6c09120f38cc52368bed245f3d65f5189984e14814b91e1c207
4
- data.tar.gz: f04c2337fd5a66396a80f2b712269705e624b275e494ddacd0e38d40ecf72d03
3
+ metadata.gz: 3cb129b8cc115605e729b88b19abac03e7809dc4d7f652dbd2c33a3afa14c602
4
+ data.tar.gz: 3da67f63ceca11564f2f2a11715adf3eeae491bc93d150111ad8768065c33edd
5
5
  SHA512:
6
- metadata.gz: cc32823fac5b9369b87f645126d5845386777bc31db783d08b8961fbd3dcda9d2b7a6ace8648d6cfbeffb22ed54025fc6cea1e3e35f57e283d3fba78338c6293
7
- data.tar.gz: a5cd1b48fe3f4ba7a1b40b4b62822e8975e172683b3f4af7ca5d056e1759f33b5d6df5c4624d5dc35af2645048baaeb8ccd5e892efed94288c95264e7389b40e
6
+ metadata.gz: 3012eda7aa4d40600343f993ad397ed7fe9474f790f1e3a56f74b6c1b3e5be2da3541e51d874a0e4a4e7e7e688b6e8e47b84fb12655ed138566133a8c8867513
7
+ data.tar.gz: f1b10bf9dd7cf3098b8f65893af8bfd81c3c1b8ded920028fcb895f6f354ca93b4d553cc9ea9046ce2594915bff592157e0c9ecd9e0b692e9a7357ef8059e634
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module GeminiCraft
6
+ # Simple in-memory cache for API responses
7
+ class Cache
8
+ # Initialize the cache
9
+ # @param config [GeminiCraft::Configuration] Configuration object
10
+ def initialize(config)
11
+ @config = config
12
+ @store = {}
13
+ @timestamps = {}
14
+ end
15
+
16
+ # Get a value from the cache
17
+ # @param key [String] Cache key
18
+ # @return [String, nil] Cached value or nil if not found/expired
19
+ def get(key)
20
+ return nil unless @store.key?(key)
21
+
22
+ # Check if the entry has expired
23
+ if Time.now.to_i - @timestamps[key] > @config.cache_ttl
24
+ @store.delete(key)
25
+ @timestamps.delete(key)
26
+ return nil
27
+ end
28
+
29
+ @store[key]
30
+ end
31
+
32
+ # Set a value in the cache
33
+ # @param key [String] Cache key
34
+ # @param value [String] Value to cache
35
+ def set(key, value)
36
+ @store[key] = value
37
+ @timestamps[key] = Time.now.to_i
38
+ end
39
+
40
+ # Clear the entire cache
41
+ def clear
42
+ @store.clear
43
+ @timestamps.clear
44
+ end
45
+
46
+ # Remove expired entries from the cache
47
+ def cleanup
48
+ current_time = Time.now.to_i
49
+ @timestamps.each do |key, timestamp|
50
+ if current_time - timestamp > @config.cache_ttl
51
+ @store.delete(key)
52
+ @timestamps.delete(key)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/retry"
5
+ require "json"
6
+
7
+ module GeminiCraft
8
+ # Client for interacting with the Gemini API
9
+ class Client
10
+ attr_reader :config, :cache
11
+
12
+ # Initialize a new client
13
+ def initialize
14
+ @config = GeminiCraft.configuration
15
+ @config.validate!
16
+ @cache = Cache.new(@config)
17
+ end
18
+
19
+ # Generate content using Gemini
20
+ # @param text [String] The text prompt to send to Gemini
21
+ # @param system_instruction [String, nil] Optional system instruction to guide the model
22
+ # @param options [Hash] Additional options for the request
23
+ # @return [String] The generated content
24
+ def generate_content(text, system_instruction = nil, options = {})
25
+ # Create cache key from the request parameters
26
+ cache_key = generate_cache_key(text, system_instruction, options)
27
+
28
+ # Check cache if enabled
29
+ if @config.cache_enabled && (cached_response = @cache.get(cache_key))
30
+ return cached_response
31
+ end
32
+
33
+ # Prepare request payload
34
+ payload = build_payload(text, system_instruction, options)
35
+
36
+ # Send request to API
37
+ response = make_request("models/#{@config.model}:generateContent", payload)
38
+
39
+ # Process response
40
+ content = extract_content(response)
41
+
42
+ # Cache response if enabled
43
+ @cache.set(cache_key, content) if @config.cache_enabled
44
+
45
+ content
46
+ end
47
+
48
+ private
49
+
50
+ # Build the API request payload
51
+ # @param text [String] The text prompt
52
+ # @param system_instruction [String, nil] Optional system instruction
53
+ # @param options [Hash] Additional options
54
+ # @return [Hash] The request payload
55
+ def build_payload(text, system_instruction, options)
56
+ payload = {
57
+ contents: [
58
+ {
59
+ parts: [
60
+ {
61
+ text: text
62
+ }
63
+ ]
64
+ }
65
+ ]
66
+ }
67
+
68
+ # Add system instruction if provided
69
+ if system_instruction
70
+ payload[:system_instruction] = {
71
+ parts: [
72
+ {
73
+ text: system_instruction
74
+ }
75
+ ]
76
+ }
77
+ end
78
+
79
+ # Merge additional options if provided
80
+ payload.merge!(options) if options && !options.empty?
81
+
82
+ payload
83
+ end
84
+
85
+ # Make a request to the Gemini API
86
+ # @param endpoint [String] API endpoint
87
+ # @param payload [Hash] Request payload
88
+ # @return [Hash] Parsed response
89
+ # @raise [GeminiCraft::APIError] If the API returns an error
90
+ def make_request(endpoint, payload)
91
+ response = connection.post(endpoint) do |req|
92
+ req.params["key"] = @config.api_key
93
+ req.headers["Content-Type"] = "application/json"
94
+ req.body = JSON.generate(payload)
95
+ end
96
+
97
+ handle_response(response)
98
+ rescue Faraday::Error => e
99
+ raise APIError, "API request failed: #{e.message}"
100
+ end
101
+
102
+ # Set up a Faraday connection with retry logic
103
+ # @return [Faraday::Connection] Configured connection
104
+ def connection
105
+ Faraday.new(url: @config.api_base_url) do |faraday|
106
+ faraday.options.timeout = @config.timeout
107
+ faraday.request :retry, max: @config.max_retries, interval: 0.5,
108
+ interval_randomness: 0.5, backoff_factor: 2,
109
+ exceptions: [Faraday::TimeoutError, Faraday::ConnectionFailed]
110
+ faraday.adapter Faraday.default_adapter
111
+ end
112
+ end
113
+
114
+ # Handle and parse the API response
115
+ # @param response [Faraday::Response] The API response
116
+ # @return [Hash] Parsed response body
117
+ # @raise [GeminiCraft::APIError] If the API returns an error
118
+ def handle_response(response)
119
+ case response.status
120
+ when 200
121
+ JSON.parse(response.body)
122
+ when 400..499
123
+ error_body = begin
124
+ JSON.parse(response.body)
125
+ rescue StandardError
126
+ { "error" => response.body }
127
+ end
128
+ raise APIError, "API client error (#{response.status}): #{error_body["error"]["message"] || "Unknown error"}"
129
+ when 500..599
130
+ raise APIError, "API server error (#{response.status}): The server encountered an error"
131
+ else
132
+ raise APIError, "Unknown API error (#{response.status})"
133
+ end
134
+ end
135
+
136
+ # Extract content from the response
137
+ # @param response [Hash] Parsed API response
138
+ # @return [String] The extracted content
139
+ # @raise [GeminiCraft::ResponseError] If the response format is unexpected
140
+ def extract_content(response)
141
+ candidates = response["candidates"]
142
+ return "" if candidates.nil? || candidates.empty?
143
+
144
+ content = candidates[0]["content"]
145
+ return "" if content.nil?
146
+
147
+ parts = content["parts"]
148
+ return "" if parts.nil? || parts.empty?
149
+
150
+ text = parts[0]["text"]
151
+ text || ""
152
+ rescue StandardError => e
153
+ raise ResponseError, "Failed to extract content from response: #{e.message}"
154
+ end
155
+
156
+ # Generate a cache key from request parameters
157
+ # @param text [String] The text prompt
158
+ # @param system_instruction [String, nil] Optional system instruction
159
+ # @param options [Hash] Additional options
160
+ # @return [String] A unique cache key
161
+ def generate_cache_key(text, system_instruction, options)
162
+ key_parts = [
163
+ @config.model,
164
+ text,
165
+ system_instruction,
166
+ options.to_s
167
+ ]
168
+
169
+ # Create a deterministic string from the key parts
170
+ Digest::SHA256.hexdigest(key_parts.join("--"))
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeminiCraft
4
+ # Configuration for the GeminiCraft gem
5
+ class Configuration
6
+ attr_accessor :api_key, :api_base_url, :model, :timeout, :cache_enabled, :cache_ttl, :max_retries
7
+
8
+ # Initialize a new configuration with default values
9
+ def initialize
10
+ @api_key = ENV["GEMINI_API_KEY"]
11
+ @api_base_url = "https://generativelanguage.googleapis.com/v1beta"
12
+ @model = "gemini-2.0-flash"
13
+ @timeout = 30
14
+ @cache_enabled = false
15
+ @cache_ttl = 3600 # 1 hour in seconds
16
+ @max_retries = 3
17
+ end
18
+
19
+ # Validate that the configuration has required parameters
20
+ # @raise [GeminiCraft::ConfigurationError] if the configuration is invalid
21
+ def validate!
22
+ raise ConfigurationError, "API key must be configured" unless api_key
23
+ raise ConfigurationError, "Model must be configured" unless model
24
+ end
25
+ end
26
+
27
+ # Error raised when configuration is invalid
28
+ class ConfigurationError < Error; end
29
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GeminiCraft
4
+ # Base error class for all GeminiCraft errors
5
+ class Error < StandardError; end
6
+
7
+ # Error raised when there's an issue with API requests
8
+ class APIError < Error; end
9
+
10
+ # Error raised when the response cannot be processed
11
+ class ResponseError < Error; end
12
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GeminiCraft
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemini_craft
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shobhit Jain
@@ -158,19 +158,16 @@ executables: []
158
158
  extensions: []
159
159
  extra_rdoc_files: []
160
160
  files:
161
- - ".rspec"
162
- - ".rubocop.yml"
163
161
  - CHANGELOG.md
164
162
  - CODE_OF_CONDUCT.md
165
- - Gemfile
166
163
  - LICENSE.txt
167
164
  - README.md
168
- - Rakefile
169
- - bin/console
170
- - bin/setup
171
165
  - lib/gemini_craft.rb
166
+ - lib/gemini_craft/cache.rb
167
+ - lib/gemini_craft/client.rb
168
+ - lib/gemini_craft/configuration.rb
169
+ - lib/gemini_craft/error.rb
172
170
  - lib/gemini_craft/version.rb
173
- - sig/gemini_craft.rbs
174
171
  homepage: https://github.com/shobhits7/gemini_craft
175
172
  licenses:
176
173
  - MIT
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,8 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.6.0
3
-
4
- Style/StringLiterals:
5
- EnforcedStyle: double_quotes
6
-
7
- Style/StringLiteralsInInterpolation:
8
- EnforcedStyle: double_quotes
data/Gemfile DELETED
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in gemini_craft.gemspec
6
- gemspec
7
-
8
- gem "irb"
9
- gem "rake", "~> 13.0"
10
-
11
- gem "rspec", "~> 3.0"
12
-
13
- gem "rubocop", "~> 1.21"
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- require "rubocop/rake_task"
9
-
10
- RuboCop::RakeTask.new
11
-
12
- task default: %i[spec rubocop]
data/bin/console DELETED
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "gemini_craft"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- require "irb"
11
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
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
data/sig/gemini_craft.rbs DELETED
@@ -1,4 +0,0 @@
1
- module GeminiCraft
2
- VERSION: String
3
- # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
- end