lex-llm-gemini 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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +7 -5
- data/README.md +39 -0
- data/lex-llm-gemini.gemspec +4 -1
- data/lib/legion/extensions/llm/gemini/provider.rb +313 -0
- data/lib/legion/extensions/llm/gemini/version.rb +1 -1
- data/lib/legion/extensions/llm/gemini.rb +10 -3
- metadata +46 -5
- data/Gemfile.lock +0 -161
- data/lib/legion/extensions/llm/gemini/provider_settings.rb +0 -55
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b03f32f8b23f0f05443b2e0a32d2a6860c287628b1f2bd9f5ee0ad38b458d311
|
|
4
|
+
data.tar.gz: 1eb0389046f1c40f8b8e74c1ed306d871bdc6030492ead96aeec07293167bf10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9647f4d3d4bfa69924576f8cab5ff4b1523ace82b33d8ec60fcf9c57e7877f184f2f9920b6a7a5d46c5a3822dadddd0982f7bc215c7bd970d3c58744282d2773
|
|
7
|
+
data.tar.gz: 905d32ea8573ee0903794c19547d7068c1c27aab9ad5f23b12fb57780908cf55a0f7e639604421a14558b64671557ad30dc8e08033ad57bbd4f1f762795b8ed9
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1 - 2026-04-27
|
|
4
|
+
|
|
5
|
+
- Add the Gemini LexLLM provider class with generateContent, streaming, model listing, and embedContent helpers.
|
|
6
|
+
- Use shared `Legion::Extensions::Llm.provider_settings` defaults from `lex-llm`.
|
|
7
|
+
- Remove the committed `Gemfile.lock`.
|
|
8
|
+
|
|
3
9
|
## 0.1.0 - 2026-04-26
|
|
4
10
|
|
|
5
11
|
- Initial Legion LLM Gemini provider extension scaffold.
|
data/Gemfile
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
source 'https://rubygems.org'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
group :test do
|
|
6
|
+
if ENV['LEX_LLM_PATH'] && File.directory?(ENV.fetch('LEX_LLM_PATH'))
|
|
7
|
+
gem 'lex-llm', path: ENV.fetch('LEX_LLM_PATH')
|
|
8
|
+
else
|
|
9
|
+
gem 'lex-llm', git: 'https://github.com/LegionIO/lex-llm',
|
|
10
|
+
branch: ENV.fetch('LEX_LLM_BRANCH', 'main')
|
|
11
|
+
end
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
gemspec
|
data/README.md
CHANGED
|
@@ -3,3 +3,42 @@
|
|
|
3
3
|
LegionIO LLM provider extension for Gemini.
|
|
4
4
|
|
|
5
5
|
This gem lives under `Legion::Extensions::Llm::Gemini` and depends on `lex-llm` for shared provider-neutral routing, fleet, and schema primitives.
|
|
6
|
+
|
|
7
|
+
## What It Provides
|
|
8
|
+
|
|
9
|
+
- `LexLLM::Provider` registration as `:gemini`
|
|
10
|
+
- Gemini-native chat requests through `POST /v1beta/{model=models/*}:generateContent`
|
|
11
|
+
- streaming chat support through `POST /v1beta/{model=models/*}:streamGenerateContent?alt=sse`
|
|
12
|
+
- model discovery through `GET /v1beta/models`
|
|
13
|
+
- embeddings through `POST /v1beta/{model=models/*}:embedContent`
|
|
14
|
+
- shared fleet/default settings via `Legion::Extensions::Llm.provider_settings`
|
|
15
|
+
|
|
16
|
+
## Defaults
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
Legion::Extensions::Llm::Gemini.default_settings
|
|
20
|
+
# {
|
|
21
|
+
# provider_family: :gemini,
|
|
22
|
+
# instances: {
|
|
23
|
+
# default: {
|
|
24
|
+
# endpoint: "https://generativelanguage.googleapis.com/v1beta",
|
|
25
|
+
# tier: :frontier,
|
|
26
|
+
# transport: :http,
|
|
27
|
+
# credentials: { api_key: "env://GEMINI_API_KEY" },
|
|
28
|
+
# usage: { inference: true, embedding: true },
|
|
29
|
+
# limits: { concurrency: 4 }
|
|
30
|
+
# }
|
|
31
|
+
# }
|
|
32
|
+
# }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
LexLLM.configure do |config|
|
|
39
|
+
config.gemini_api_key = ENV.fetch("GEMINI_API_KEY")
|
|
40
|
+
config.gemini_api_base = "https://generativelanguage.googleapis.com/v1beta"
|
|
41
|
+
config.default_model = "gemini-2.0-flash"
|
|
42
|
+
config.default_embedding_model = "gemini-embedding-001"
|
|
43
|
+
end
|
|
44
|
+
```
|
data/lex-llm-gemini.gemspec
CHANGED
|
@@ -24,5 +24,8 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.require_paths = ['lib']
|
|
25
25
|
|
|
26
26
|
spec.add_dependency 'faraday', '>= 2.0'
|
|
27
|
-
spec.add_dependency '
|
|
27
|
+
spec.add_dependency 'legion-json', '>= 1.0'
|
|
28
|
+
spec.add_dependency 'legion-logging', '>= 1.0'
|
|
29
|
+
spec.add_dependency 'legion-settings', '>= 1.3.14'
|
|
30
|
+
spec.add_dependency 'lex-llm', '>= 0.1.1'
|
|
28
31
|
end
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lex_llm'
|
|
4
|
+
require 'legion/json'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Llm
|
|
9
|
+
module Gemini
|
|
10
|
+
# Gemini provider implementation for the LexLLM base provider contract.
|
|
11
|
+
class Provider < LexLLM::Provider # rubocop:disable Metrics/ClassLength
|
|
12
|
+
class << self
|
|
13
|
+
def slug = 'gemini'
|
|
14
|
+
def configuration_options = %i[gemini_api_key gemini_api_base]
|
|
15
|
+
def configuration_requirements = %i[gemini_api_key]
|
|
16
|
+
def capabilities = Capabilities
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Capability predicates for Gemini API models.
|
|
20
|
+
module Capabilities
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def chat?(model) = supported?(model, 'generateContent')
|
|
24
|
+
def streaming?(model) = supported?(model, 'streamGenerateContent')
|
|
25
|
+
def embeddings?(model) = supported?(model, 'embedContent')
|
|
26
|
+
def vision?(model) = model_id(model).match?(/gemini|flash|pro/)
|
|
27
|
+
def functions?(model) = chat?(model)
|
|
28
|
+
|
|
29
|
+
def critical_capabilities_for(model)
|
|
30
|
+
[
|
|
31
|
+
('streaming' if streaming?(model)),
|
|
32
|
+
('embeddings' if embeddings?(model)),
|
|
33
|
+
('function_calling' if functions?(model)),
|
|
34
|
+
('vision' if vision?(model))
|
|
35
|
+
].compact
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def supported?(model, action)
|
|
39
|
+
methods = generation_methods(model)
|
|
40
|
+
return model_id(model).include?('embedding') if action == 'embedContent' && methods.empty?
|
|
41
|
+
return true if methods.empty? && action != 'embedContent'
|
|
42
|
+
|
|
43
|
+
methods.include?(action)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def generation_methods(model)
|
|
47
|
+
metadata = metadata_for(model)
|
|
48
|
+
Array(metadata[:supported_generation_methods] || metadata['supported_generation_methods'])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def model_id(model)
|
|
52
|
+
return model.fetch('name', '').delete_prefix('models/') if model.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
model.respond_to?(:id) ? model.id.to_s : model.to_s
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def metadata_for(model)
|
|
58
|
+
return model if model.is_a?(Hash)
|
|
59
|
+
return model.metadata if model.respond_to?(:metadata)
|
|
60
|
+
|
|
61
|
+
{}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def api_base
|
|
66
|
+
config.gemini_api_base || 'https://generativelanguage.googleapis.com/v1beta'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def headers
|
|
70
|
+
{ 'x-goog-api-key' => config.gemini_api_key }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def completion_url = generate_content_url(model: @model)
|
|
74
|
+
def stream_url = stream_generate_content_url(model: @model)
|
|
75
|
+
def models_url = 'models'
|
|
76
|
+
def embedding_url(model:) = embed_content_url(model:)
|
|
77
|
+
|
|
78
|
+
def generate_content_url(model:)
|
|
79
|
+
"#{model_path(model)}:generateContent"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def stream_generate_content_url(model:)
|
|
83
|
+
"#{model_path(model)}:streamGenerateContent?alt=sse"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def embed_content_url(model:)
|
|
87
|
+
"#{model_path(model)}:embedContent"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def model_path(model)
|
|
93
|
+
value = model.to_s
|
|
94
|
+
value.start_with?('models/') ? value : "models/#{value}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument
|
|
98
|
+
def render_payload(messages, tools:, temperature:, model:, stream:, schema:, thinking:, tool_prefs:)
|
|
99
|
+
@model = model.id
|
|
100
|
+
payload = { contents: format_messages(messages), generationConfig: generation_config(temperature, schema) }
|
|
101
|
+
payload[:systemInstruction] = system_instruction(messages)
|
|
102
|
+
payload[:tools] = format_tools(tools) unless tools.empty?
|
|
103
|
+
payload[:toolConfig] = tool_config(tool_prefs) if tool_prefs
|
|
104
|
+
payload.compact
|
|
105
|
+
end
|
|
106
|
+
# rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument
|
|
107
|
+
|
|
108
|
+
def generation_config(temperature, schema)
|
|
109
|
+
{
|
|
110
|
+
temperature: temperature,
|
|
111
|
+
responseMimeType: ('application/json' if schema),
|
|
112
|
+
responseSchema: schema_hash(schema)
|
|
113
|
+
}.compact
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def schema_hash(schema)
|
|
117
|
+
return unless schema
|
|
118
|
+
|
|
119
|
+
schema.respond_to?(:to_h) ? schema.to_h.fetch(:schema, schema.to_h) : schema
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def system_instruction(messages)
|
|
123
|
+
system_messages = messages.select { |message| message.role == :system }
|
|
124
|
+
parts = system_messages.flat_map { |message| content_parts(message.content) }
|
|
125
|
+
return nil if parts.empty?
|
|
126
|
+
|
|
127
|
+
{ parts: parts }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def format_messages(messages)
|
|
131
|
+
messages.reject { |message| message.role == :system }.map do |message|
|
|
132
|
+
{ role: gemini_role(message), parts: message_parts(message) }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def gemini_role(message)
|
|
137
|
+
return 'model' if message.role == :assistant
|
|
138
|
+
return 'function' if message.tool_result?
|
|
139
|
+
|
|
140
|
+
message.role.to_s
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def message_parts(message)
|
|
144
|
+
return tool_call_parts(message) if message.tool_call?
|
|
145
|
+
return tool_result_parts(message) if message.tool_result?
|
|
146
|
+
|
|
147
|
+
content_parts(message.content)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def content_parts(content)
|
|
151
|
+
return Array(content.value) if content.is_a?(LexLLM::Content::Raw)
|
|
152
|
+
return [{ text: Legion::JSON.generate(content) }] if content.is_a?(Hash) || content.is_a?(Array)
|
|
153
|
+
return [{ text: content.to_s }] unless content.is_a?(LexLLM::Content)
|
|
154
|
+
|
|
155
|
+
parts = []
|
|
156
|
+
parts << { text: content.text } if content.text
|
|
157
|
+
content.attachments.each { |attachment| parts << attachment_part(attachment) }
|
|
158
|
+
parts
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def attachment_part(attachment)
|
|
162
|
+
if attachment.text?
|
|
163
|
+
{ text: attachment.for_llm }
|
|
164
|
+
else
|
|
165
|
+
{ inline_data: { mime_type: attachment.mime_type, data: attachment.encoded } }
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def tool_call_parts(message)
|
|
170
|
+
message.tool_calls.values.map do |tool_call|
|
|
171
|
+
{ functionCall: { name: tool_call.name, args: tool_call.arguments } }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def tool_result_parts(message)
|
|
176
|
+
[{
|
|
177
|
+
functionResponse: {
|
|
178
|
+
name: message.tool_call_id,
|
|
179
|
+
response: { name: message.tool_call_id, content: content_parts(message.content) }
|
|
180
|
+
}
|
|
181
|
+
}]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def format_tools(tools)
|
|
185
|
+
[{
|
|
186
|
+
functionDeclarations: tools.values.map do |tool|
|
|
187
|
+
declaration = { name: tool.name, description: tool.description }
|
|
188
|
+
declaration[:parameters] = tool.params_schema if tool.params_schema
|
|
189
|
+
declaration
|
|
190
|
+
end
|
|
191
|
+
}]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def tool_config(tool_prefs)
|
|
195
|
+
choice = tool_prefs[:choice] || tool_prefs['choice']
|
|
196
|
+
return unless choice
|
|
197
|
+
|
|
198
|
+
{ functionCallingConfig: { mode: choice.to_s } }
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def parse_completion_response(response)
|
|
202
|
+
body = response.body
|
|
203
|
+
parts = response_parts(body)
|
|
204
|
+
|
|
205
|
+
LexLLM::Message.new(
|
|
206
|
+
role: :assistant,
|
|
207
|
+
content: text_content(parts),
|
|
208
|
+
tool_calls: parse_tool_calls(parts),
|
|
209
|
+
input_tokens: body.dig('usageMetadata', 'promptTokenCount'),
|
|
210
|
+
output_tokens: output_tokens(body),
|
|
211
|
+
cached_tokens: body.dig('usageMetadata', 'cachedContentTokenCount'),
|
|
212
|
+
thinking_tokens: body.dig('usageMetadata', 'thoughtsTokenCount'),
|
|
213
|
+
model_id: body['modelVersion'] || @model,
|
|
214
|
+
raw: body
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def build_chunk(data)
|
|
219
|
+
parts = response_parts(data)
|
|
220
|
+
|
|
221
|
+
LexLLM::Chunk.new(
|
|
222
|
+
role: :assistant,
|
|
223
|
+
content: text_content(parts),
|
|
224
|
+
tool_calls: parse_tool_calls(parts),
|
|
225
|
+
input_tokens: data.dig('usageMetadata', 'promptTokenCount'),
|
|
226
|
+
output_tokens: output_tokens(data),
|
|
227
|
+
cached_tokens: data.dig('usageMetadata', 'cachedContentTokenCount'),
|
|
228
|
+
thinking_tokens: data.dig('usageMetadata', 'thoughtsTokenCount'),
|
|
229
|
+
model_id: data['modelVersion'] || @model,
|
|
230
|
+
raw: data
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def response_parts(body)
|
|
235
|
+
body.dig('candidates', 0, 'content', 'parts') || []
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def text_content(parts)
|
|
239
|
+
text = parts.reject { |part| part['thought'] }.filter_map { |part| part['text'] }.join
|
|
240
|
+
text.empty? ? nil : text
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def output_tokens(body)
|
|
244
|
+
candidates = body.dig('usageMetadata', 'candidatesTokenCount') || 0
|
|
245
|
+
thoughts = body.dig('usageMetadata', 'thoughtsTokenCount') || 0
|
|
246
|
+
total = candidates + thoughts
|
|
247
|
+
total.positive? ? total : nil
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def parse_tool_calls(parts)
|
|
251
|
+
tool_calls = parts.each_with_object({}) do |part, result|
|
|
252
|
+
function_call = part['functionCall']
|
|
253
|
+
next unless function_call
|
|
254
|
+
|
|
255
|
+
id = SecureRandom.uuid
|
|
256
|
+
result[id] = LexLLM::ToolCall.new(
|
|
257
|
+
id: id,
|
|
258
|
+
name: function_call['name'],
|
|
259
|
+
arguments: function_call['args'] || {}
|
|
260
|
+
)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
tool_calls.empty? ? nil : tool_calls
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def parse_list_models_response(response, provider, capabilities)
|
|
267
|
+
Array(response.body['models']).map do |model_data|
|
|
268
|
+
model_id = model_data.fetch('name').delete_prefix('models/')
|
|
269
|
+
methods = Array(model_data['supportedGenerationMethods'])
|
|
270
|
+
|
|
271
|
+
LexLLM::Model::Info.new(
|
|
272
|
+
id: model_id,
|
|
273
|
+
name: model_data['displayName'] || model_id,
|
|
274
|
+
provider: provider,
|
|
275
|
+
context_window: model_data['inputTokenLimit'],
|
|
276
|
+
max_output_tokens: model_data['outputTokenLimit'],
|
|
277
|
+
capabilities: capabilities.critical_capabilities_for(model_data),
|
|
278
|
+
modalities: modalities_for(methods),
|
|
279
|
+
metadata: {
|
|
280
|
+
version: model_data['version'],
|
|
281
|
+
description: model_data['description'],
|
|
282
|
+
supported_generation_methods: methods
|
|
283
|
+
}
|
|
284
|
+
)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def modalities_for(methods)
|
|
289
|
+
return { input: %w[text], output: %w[embeddings] } if methods.include?('embedContent')
|
|
290
|
+
|
|
291
|
+
{ input: %w[text image audio video], output: %w[text] }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def render_embedding_payload(text, model:, dimensions:)
|
|
295
|
+
{
|
|
296
|
+
model: model_path(model),
|
|
297
|
+
content: { parts: [{ text: text.to_s }] },
|
|
298
|
+
outputDimensionality: dimensions
|
|
299
|
+
}.compact
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def parse_embedding_response(response, model:, **)
|
|
303
|
+
LexLLM::Embedding.new(
|
|
304
|
+
vectors: response.body.dig('embedding', 'values'),
|
|
305
|
+
model: model,
|
|
306
|
+
input_tokens: response.body.dig('usageMetadata', 'promptTokenCount').to_i
|
|
307
|
+
)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/llm'
|
|
4
|
-
require 'legion/extensions/llm/gemini/
|
|
4
|
+
require 'legion/extensions/llm/gemini/provider'
|
|
5
5
|
require 'legion/extensions/llm/gemini/version'
|
|
6
6
|
|
|
7
7
|
module Legion
|
|
@@ -14,10 +14,10 @@ module Legion
|
|
|
14
14
|
PROVIDER_FAMILY = :gemini
|
|
15
15
|
|
|
16
16
|
def self.default_settings
|
|
17
|
-
|
|
17
|
+
::Legion::Extensions::Llm.provider_settings(
|
|
18
18
|
family: PROVIDER_FAMILY,
|
|
19
19
|
instance: {
|
|
20
|
-
endpoint: 'https://generativelanguage.googleapis.com',
|
|
20
|
+
endpoint: 'https://generativelanguage.googleapis.com/v1beta',
|
|
21
21
|
tier: :frontier,
|
|
22
22
|
transport: :http,
|
|
23
23
|
credentials: { api_key: 'env://GEMINI_API_KEY' },
|
|
@@ -26,7 +26,14 @@ module Legion
|
|
|
26
26
|
}
|
|
27
27
|
)
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
def self.provider_class
|
|
31
|
+
Provider
|
|
32
|
+
end
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
35
|
end
|
|
32
36
|
end
|
|
37
|
+
|
|
38
|
+
LexLLM::Provider.register(Legion::Extensions::Llm::Gemini::PROVIDER_FAMILY,
|
|
39
|
+
Legion::Extensions::Llm::Gemini::Provider)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm-gemini
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LegionIO
|
|
@@ -23,20 +23,62 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: legion-json
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: legion-logging
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: legion-settings
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 1.3.14
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: 1.3.14
|
|
26
68
|
- !ruby/object:Gem::Dependency
|
|
27
69
|
name: lex-llm
|
|
28
70
|
requirement: !ruby/object:Gem::Requirement
|
|
29
71
|
requirements:
|
|
30
72
|
- - ">="
|
|
31
73
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.1.
|
|
74
|
+
version: 0.1.1
|
|
33
75
|
type: :runtime
|
|
34
76
|
prerelease: false
|
|
35
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
78
|
requirements:
|
|
37
79
|
- - ">="
|
|
38
80
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.1.
|
|
81
|
+
version: 0.1.1
|
|
40
82
|
description: Gemini provider integration for the LegionIO LLM routing framework.
|
|
41
83
|
email:
|
|
42
84
|
- matthewdiverson@gmail.com
|
|
@@ -51,12 +93,11 @@ files:
|
|
|
51
93
|
- ".rubocop.yml"
|
|
52
94
|
- CHANGELOG.md
|
|
53
95
|
- Gemfile
|
|
54
|
-
- Gemfile.lock
|
|
55
96
|
- LICENSE
|
|
56
97
|
- README.md
|
|
57
98
|
- lex-llm-gemini.gemspec
|
|
58
99
|
- lib/legion/extensions/llm/gemini.rb
|
|
59
|
-
- lib/legion/extensions/llm/gemini/
|
|
100
|
+
- lib/legion/extensions/llm/gemini/provider.rb
|
|
60
101
|
- lib/legion/extensions/llm/gemini/version.rb
|
|
61
102
|
- lib/lex_llm_gemini.rb
|
|
62
103
|
homepage: https://github.com/LegionIO/lex-llm-gemini
|
data/Gemfile.lock
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
GIT
|
|
2
|
-
remote: https://github.com/LegionIO/lex-llm
|
|
3
|
-
revision: 6b2901bf332806ca369399ad08f09da82825b121
|
|
4
|
-
branch: lex-llm-routing-base-20260425
|
|
5
|
-
specs:
|
|
6
|
-
lex-llm (0.1.0)
|
|
7
|
-
base64
|
|
8
|
-
event_stream_parser (~> 1)
|
|
9
|
-
faraday (>= 1.10.0)
|
|
10
|
-
faraday-multipart (>= 1)
|
|
11
|
-
faraday-net_http (>= 1)
|
|
12
|
-
faraday-retry (>= 1)
|
|
13
|
-
marcel (~> 1)
|
|
14
|
-
ruby_llm-schema (~> 0)
|
|
15
|
-
zeitwerk (~> 2)
|
|
16
|
-
|
|
17
|
-
PATH
|
|
18
|
-
remote: .
|
|
19
|
-
specs:
|
|
20
|
-
lex-llm-gemini (0.1.0)
|
|
21
|
-
faraday (>= 2.0)
|
|
22
|
-
lex-llm (>= 0.1.0)
|
|
23
|
-
|
|
24
|
-
GEM
|
|
25
|
-
remote: https://rubygems.org/
|
|
26
|
-
specs:
|
|
27
|
-
ast (2.4.3)
|
|
28
|
-
base64 (0.3.0)
|
|
29
|
-
diff-lcs (1.6.2)
|
|
30
|
-
event_stream_parser (1.0.0)
|
|
31
|
-
faraday (2.14.1)
|
|
32
|
-
faraday-net_http (>= 2.0, < 3.5)
|
|
33
|
-
json
|
|
34
|
-
logger
|
|
35
|
-
faraday-multipart (1.2.0)
|
|
36
|
-
multipart-post (~> 2.0)
|
|
37
|
-
faraday-net_http (3.4.2)
|
|
38
|
-
net-http (~> 0.5)
|
|
39
|
-
faraday-retry (2.4.0)
|
|
40
|
-
faraday (~> 2.0)
|
|
41
|
-
json (2.19.4)
|
|
42
|
-
language_server-protocol (3.17.0.5)
|
|
43
|
-
lint_roller (1.1.0)
|
|
44
|
-
logger (1.7.0)
|
|
45
|
-
marcel (1.1.0)
|
|
46
|
-
multipart-post (2.4.1)
|
|
47
|
-
net-http (0.9.1)
|
|
48
|
-
uri (>= 0.11.1)
|
|
49
|
-
parallel (2.1.0)
|
|
50
|
-
parser (3.3.11.1)
|
|
51
|
-
ast (~> 2.4.1)
|
|
52
|
-
racc
|
|
53
|
-
prism (1.9.0)
|
|
54
|
-
racc (1.8.1)
|
|
55
|
-
rainbow (3.1.1)
|
|
56
|
-
rake (13.4.2)
|
|
57
|
-
regexp_parser (2.12.0)
|
|
58
|
-
rspec (3.13.2)
|
|
59
|
-
rspec-core (~> 3.13.0)
|
|
60
|
-
rspec-expectations (~> 3.13.0)
|
|
61
|
-
rspec-mocks (~> 3.13.0)
|
|
62
|
-
rspec-core (3.13.6)
|
|
63
|
-
rspec-support (~> 3.13.0)
|
|
64
|
-
rspec-expectations (3.13.5)
|
|
65
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
66
|
-
rspec-support (~> 3.13.0)
|
|
67
|
-
rspec-mocks (3.13.8)
|
|
68
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
69
|
-
rspec-support (~> 3.13.0)
|
|
70
|
-
rspec-support (3.13.7)
|
|
71
|
-
rubocop (1.86.1)
|
|
72
|
-
json (~> 2.3)
|
|
73
|
-
language_server-protocol (~> 3.17.0.2)
|
|
74
|
-
lint_roller (~> 1.1.0)
|
|
75
|
-
parallel (>= 1.10)
|
|
76
|
-
parser (>= 3.3.0.2)
|
|
77
|
-
rainbow (>= 2.2.2, < 4.0)
|
|
78
|
-
regexp_parser (>= 2.9.3, < 3.0)
|
|
79
|
-
rubocop-ast (>= 1.49.0, < 2.0)
|
|
80
|
-
ruby-progressbar (~> 1.7)
|
|
81
|
-
unicode-display_width (>= 2.4.0, < 4.0)
|
|
82
|
-
rubocop-ast (1.49.1)
|
|
83
|
-
parser (>= 3.3.7.2)
|
|
84
|
-
prism (~> 1.7)
|
|
85
|
-
rubocop-performance (1.26.1)
|
|
86
|
-
lint_roller (~> 1.1)
|
|
87
|
-
rubocop (>= 1.75.0, < 2.0)
|
|
88
|
-
rubocop-ast (>= 1.47.1, < 2.0)
|
|
89
|
-
rubocop-rake (0.7.1)
|
|
90
|
-
lint_roller (~> 1.1)
|
|
91
|
-
rubocop (>= 1.72.1)
|
|
92
|
-
rubocop-rspec (3.9.0)
|
|
93
|
-
lint_roller (~> 1.1)
|
|
94
|
-
rubocop (~> 1.81)
|
|
95
|
-
ruby-progressbar (1.13.0)
|
|
96
|
-
ruby_llm-schema (0.3.0)
|
|
97
|
-
unicode-display_width (3.2.0)
|
|
98
|
-
unicode-emoji (~> 4.1)
|
|
99
|
-
unicode-emoji (4.2.0)
|
|
100
|
-
uri (1.1.1)
|
|
101
|
-
zeitwerk (2.7.5)
|
|
102
|
-
|
|
103
|
-
PLATFORMS
|
|
104
|
-
arm64-darwin-25
|
|
105
|
-
ruby
|
|
106
|
-
|
|
107
|
-
DEPENDENCIES
|
|
108
|
-
bundler (>= 2.0)
|
|
109
|
-
lex-llm!
|
|
110
|
-
lex-llm-gemini!
|
|
111
|
-
rake (>= 13.0)
|
|
112
|
-
rspec (~> 3.12)
|
|
113
|
-
rubocop (>= 1.0)
|
|
114
|
-
rubocop-performance
|
|
115
|
-
rubocop-rake (>= 0.6)
|
|
116
|
-
rubocop-rspec
|
|
117
|
-
|
|
118
|
-
CHECKSUMS
|
|
119
|
-
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
120
|
-
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
121
|
-
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
122
|
-
event_stream_parser (1.0.0) sha256=a2683bab70126286f8184dc88f7968ffc4028f813161fb073ec90d171f7de3c8
|
|
123
|
-
faraday (2.14.1) sha256=a43cceedc1e39d188f4d2cdd360a8aaa6a11da0c407052e426ba8d3fb42ef61c
|
|
124
|
-
faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757
|
|
125
|
-
faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
|
|
126
|
-
faraday-retry (2.4.0) sha256=7b79c48fb7e56526faf247b12d94a680071ff40c9fda7cf1ec1549439ad11ebe
|
|
127
|
-
json (2.19.4) sha256=670a7d333fb3b18ca5b29cb255eb7bef099e40d88c02c80bd42a3f30fe5239ac
|
|
128
|
-
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
129
|
-
lex-llm (0.1.0)
|
|
130
|
-
lex-llm-gemini (0.1.0)
|
|
131
|
-
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
132
|
-
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
133
|
-
marcel (1.1.0) sha256=fdcfcfa33cc52e93c4308d40e4090a5d4ea279e160a7f6af988260fa970e0bee
|
|
134
|
-
multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8
|
|
135
|
-
net-http (0.9.1) sha256=25ba0b67c63e89df626ed8fac771d0ad24ad151a858af2cc8e6a716ca4336996
|
|
136
|
-
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
|
137
|
-
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
|
|
138
|
-
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
139
|
-
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
140
|
-
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
141
|
-
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
|
142
|
-
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
|
|
143
|
-
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
144
|
-
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
145
|
-
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
146
|
-
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
147
|
-
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
148
|
-
rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
|
|
149
|
-
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
|
150
|
-
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
|
|
151
|
-
rubocop-rake (0.7.1) sha256=3797f2b6810c3e9df7376c26d5f44f3475eda59eb1adc38e6f62ecf027cbae4d
|
|
152
|
-
rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
|
|
153
|
-
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
|
154
|
-
ruby_llm-schema (0.3.0) sha256=a591edc5ca1b7f0304f0e2261de61ba4b3bea17be09f5cf7558153adfda3dec6
|
|
155
|
-
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
|
|
156
|
-
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
|
|
157
|
-
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
|
|
158
|
-
zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
|
|
159
|
-
|
|
160
|
-
BUNDLED WITH
|
|
161
|
-
4.0.6
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
module Gemini
|
|
7
|
-
# Builds provider defaults while inheriting shared lex-llm defaults.
|
|
8
|
-
module ProviderSettings
|
|
9
|
-
module_function
|
|
10
|
-
|
|
11
|
-
def build(family:, instance:)
|
|
12
|
-
deep_merge(
|
|
13
|
-
base_settings,
|
|
14
|
-
{
|
|
15
|
-
enabled: true,
|
|
16
|
-
provider_family: family,
|
|
17
|
-
discovery: { enabled: true, interval_seconds: 300 },
|
|
18
|
-
instances: {
|
|
19
|
-
default: {
|
|
20
|
-
enabled: true,
|
|
21
|
-
credentials: nil,
|
|
22
|
-
fleet: { enabled: false, consumer_priority: 0, prefetch: 1 }
|
|
23
|
-
}.merge(instance)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def base_settings
|
|
30
|
-
return {} unless ::Legion::Extensions::Llm.respond_to?(:default_settings)
|
|
31
|
-
|
|
32
|
-
deep_dup(::Legion::Extensions::Llm.default_settings)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def deep_dup(value)
|
|
36
|
-
case value
|
|
37
|
-
when Hash
|
|
38
|
-
value.to_h { |key, inner_value| [key, deep_dup(inner_value)] }
|
|
39
|
-
when Array
|
|
40
|
-
value.map { |inner_value| deep_dup(inner_value) }
|
|
41
|
-
else
|
|
42
|
-
value
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def deep_merge(left, right)
|
|
47
|
-
left.merge(right) do |_key, left_value, right_value|
|
|
48
|
-
left_value.is_a?(Hash) && right_value.is_a?(Hash) ? deep_merge(left_value, right_value) : right_value
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|