lex-llm-gemini 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/.gitignore +2 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +3 -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: 1134a4cb3dc92ae34c6a8dfe5c307c2c16c8ead8ad4795eacb070b44561ada54
|
|
4
|
+
data.tar.gz: a83a9fab31f175beed47cd196da8930f3f8f20bd4ff38205996997c1ca774b05
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b682d787c4b406727f3f8758a5e81d5df9298435fd0047f62e0914c1367a2be8f9769b42e134ec3589a4eba46729d549a699442fb6fb2f02387069da63457d2
|
|
7
|
+
data.tar.gz: f41e2b8dcf0a0d45a6ac4c0eeda4c743034669190d346e3d31211ed6308ee173590b590b7adeaa2eae8767d8b104fecfbc5931b128cf979edd9f6add1e7a05b9
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 - 2026-04-28
|
|
4
|
+
|
|
5
|
+
- Replace fork-era namespace references with the standard Legion::Extensions::Llm provider contract.
|
|
6
|
+
- Remove GitHub-based lex-llm Gemfile fallback so test installs use only a guarded local path or released gem dependency.
|
|
7
|
+
- Require lex-llm >= 0.1.3 for the cleaned Legion-native base extension.
|
|
8
|
+
|
|
9
|
+
## 0.1.1 - 2026-04-27
|
|
10
|
+
|
|
11
|
+
- Add the Gemini Legion::Extensions::Llm provider class with generateContent, streaming, model listing, and embedContent helpers.
|
|
12
|
+
- Use shared `Legion::Extensions::Llm.provider_settings` defaults from `lex-llm`.
|
|
13
|
+
- Remove the committed `Gemfile.lock`.
|
|
14
|
+
|
|
3
15
|
## 0.1.0 - 2026-04-26
|
|
4
16
|
|
|
5
17
|
- Initial Legion LLM Gemini provider extension scaffold.
|
data/Gemfile
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
source 'https://rubygems.org'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
gem 'lex-llm', git: 'https://github.com/LegionIO/lex-llm',
|
|
9
|
-
branch: ENV.fetch('LEX_LLM_BRANCH', 'lex-llm-routing-base-20260425')
|
|
5
|
+
group :test do
|
|
6
|
+
lex_llm_path = ENV.fetch('LEX_LLM_PATH', File.expand_path('../lex-llm', __dir__))
|
|
7
|
+
gem 'lex-llm', path: lex_llm_path if File.directory?(lex_llm_path)
|
|
10
8
|
end
|
|
11
9
|
|
|
12
10
|
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
|
+
- `Legion::Extensions::Llm::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
|
+
Legion::Extensions::Llm.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.3'
|
|
28
31
|
end
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/llm'
|
|
4
|
+
require 'legion/json'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Llm
|
|
9
|
+
module Gemini
|
|
10
|
+
# Gemini provider implementation for the Legion::Extensions::Llm base provider contract.
|
|
11
|
+
class Provider < Legion::Extensions::Llm::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?(Legion::Extensions::Llm::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?(Legion::Extensions::Llm::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
|
+
Legion::Extensions::Llm::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
|
+
Legion::Extensions::Llm::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] = Legion::Extensions::Llm::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
|
+
Legion::Extensions::Llm::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
|
+
Legion::Extensions::Llm::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
|
+
Legion::Extensions::Llm::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.2
|
|
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.3
|
|
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.3
|
|
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
|