gemini_craft 0.1.0 → 0.1.2
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 +4 -4
- data/lib/gemini_craft/cache.rb +57 -0
- data/lib/gemini_craft/client.rb +173 -0
- data/lib/gemini_craft/configuration.rb +29 -0
- data/lib/gemini_craft/error.rb +12 -0
- data/lib/gemini_craft/version.rb +1 -1
- metadata +6 -8
- data/.rspec +0 -3
- data/.rubocop.yml +0 -8
- data/Gemfile +0 -13
- data/Rakefile +0 -12
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/sig/gemini_craft.rbs +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 272eb66f29c87c7681a0f703ac2d3d697ef6ce23a6f067565179b623adfa6d85
|
4
|
+
data.tar.gz: 6ccdc9d479c0fa4f2d28b15d9e45c72115ae841f28d2163cff367875b565dfb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9344da16d0a82761e10bcff5717ea5ca0c4b9dc31386e7814668875abcc2d2e3d916a95c18ef80154feaf4a659d7667a8e97f0f99c4ab39f70fe78f8487fa27
|
7
|
+
data.tar.gz: bd822c34dc245184c0fc7c4843898d93fc20c4dae4a0771df7be3ef9885712ecd4c1874446646952984bc639de4e340856f3d5f60249634a354b3bf1f899ea5b
|
@@ -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.fetch("GEMINI_API_KEY", nil)
|
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
|
data/lib/gemini_craft/version.rb
CHANGED
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.
|
4
|
+
version: 0.1.2
|
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
|
@@ -178,6 +175,7 @@ metadata:
|
|
178
175
|
homepage_uri: https://github.com/shobhits7/gemini_craft
|
179
176
|
source_code_uri: https://github.com/shobhits7/gemini_craft
|
180
177
|
changelog_uri: https://github.com/shobhits7/gemini_craft/blob/main/CHANGELOG.md
|
178
|
+
rubygems_mfa_required: 'true'
|
181
179
|
post_install_message:
|
182
180
|
rdoc_options: []
|
183
181
|
require_paths:
|
data/.rspec
DELETED
data/.rubocop.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
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
data/sig/gemini_craft.rbs
DELETED