legion-llm 0.5.21 → 0.5.22
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/CHANGELOG.md +12 -0
- data/legion-llm.gemspec +1 -0
- data/lib/legion/llm/embeddings.rb +64 -6
- data/lib/legion/llm/settings.rb +1 -1
- data/lib/legion/llm/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7e7ff9c35609294ace1f203076485aaba0a811ba0adbe7d01197ce91de7a06ea
|
|
4
|
+
data.tar.gz: c493d891ded32632daf9b7e367fa115efc9903cc63d408c8c0799bb8bc242ca1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2cd3766d938ba6814f2a14af190441a0d9b0e011538da22744729b678313ff03045fc84ae4e1ecab7dcff9ac31a1d4d92afdff25d3b5e7ca322a0303ef54c0fa
|
|
7
|
+
data.tar.gz: 3a03dd436e31b0d02290e23af4cadc755136c89321af205a83c0c18dac4cac9a8f320875a578ac6405dafd409a0b3bc516431d1dff34670731ab803618df36ae
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.5.22] - 2026-03-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Auto-chunking for oversized Ollama embedding inputs via `lex-knowledge` Chunker with character-split fallback
|
|
9
|
+
- `average_vectors` for document-level embedding from multiple chunks
|
|
10
|
+
- Per-model Ollama context limits (`OLLAMA_CONTEXT_CHARS`): mxbai-embed-large 2048, nomic-embed-text 32768
|
|
11
|
+
- `lex-knowledge` added as a dependency for semantic chunking
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- `handle_embed_failure` no longer permanently mutates `@embedding_provider` — failover is per-request only
|
|
15
|
+
- `ollama_preferred` order corrected: `mxbai-embed-large` (1024 dims) first, `nomic-embed-text` (768 dims) second
|
|
16
|
+
|
|
5
17
|
## [0.5.21] - 2026-03-31
|
|
6
18
|
|
|
7
19
|
### Added
|
data/legion-llm.gemspec
CHANGED
|
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
33
33
|
spec.add_dependency 'lex-bedrock'
|
|
34
34
|
spec.add_dependency 'lex-claude'
|
|
35
35
|
spec.add_dependency 'lex-gemini'
|
|
36
|
+
spec.add_dependency 'lex-knowledge'
|
|
36
37
|
spec.add_dependency 'lex-openai'
|
|
37
38
|
spec.add_dependency 'ruby_llm', '~> 1.13'
|
|
38
39
|
spec.add_dependency 'tzinfo', '>= 2.0'
|
|
@@ -16,6 +16,14 @@ module Legion
|
|
|
16
16
|
|
|
17
17
|
TARGET_DIMENSION = 1024
|
|
18
18
|
|
|
19
|
+
OLLAMA_CONTEXT_CHARS = {
|
|
20
|
+
'mxbai-embed-large' => 2048,
|
|
21
|
+
'bge-large' => 2048,
|
|
22
|
+
'snowflake-arctic-embed' => 2048,
|
|
23
|
+
'nomic-embed-text' => 32_768
|
|
24
|
+
}.freeze
|
|
25
|
+
OLLAMA_DEFAULT_CONTEXT_CHARS = 2048
|
|
26
|
+
|
|
19
27
|
class << self
|
|
20
28
|
def generate(text:, model: nil, provider: nil, dimensions: nil)
|
|
21
29
|
return { vector: nil, model: model, provider: provider, error: 'LLM not started' } unless LLM.started?
|
|
@@ -97,8 +105,6 @@ module Legion
|
|
|
97
105
|
fallback = find_fallback_provider(failed_provider)
|
|
98
106
|
if fallback
|
|
99
107
|
Legion::Logging.info "Embedding failover: #{failed_provider} -> #{fallback[:provider]}" if defined?(Legion::Logging)
|
|
100
|
-
LLM.instance_variable_set(:@embedding_provider, fallback[:provider])
|
|
101
|
-
LLM.instance_variable_set(:@embedding_model, fallback[:model])
|
|
102
108
|
generate(text: text, model: fallback[:model], provider: fallback[:provider])
|
|
103
109
|
else
|
|
104
110
|
{ vector: nil, model: failed_model, provider: failed_provider, error: error.message }
|
|
@@ -177,6 +183,9 @@ module Legion
|
|
|
177
183
|
end
|
|
178
184
|
|
|
179
185
|
def generate_ollama(text:, model:)
|
|
186
|
+
max_chars = ollama_context_chars(model)
|
|
187
|
+
return generate_ollama_chunked(text: text, model: model, max_chars: max_chars) if text.length > max_chars
|
|
188
|
+
|
|
180
189
|
result = ollama_embed_request(model: model, input: text)
|
|
181
190
|
vector = result['embeddings']&.first
|
|
182
191
|
vector = apply_dimension_enforcement(vector, :ollama) if vector
|
|
@@ -185,14 +194,63 @@ module Legion
|
|
|
185
194
|
{ vector: vector, model: model, provider: :ollama, dimensions: vector&.size || 0, tokens: 0 }
|
|
186
195
|
end
|
|
187
196
|
|
|
197
|
+
def generate_ollama_chunked(text:, model:, max_chars:)
|
|
198
|
+
chunks = chunk_text(text, max_chars: max_chars)
|
|
199
|
+
vectors = chunks.filter_map do |chunk|
|
|
200
|
+
result = ollama_embed_request(model: model, input: chunk[:content])
|
|
201
|
+
result['embeddings']&.first
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
return { vector: nil, model: model, provider: :ollama, error: 'all chunks failed embedding' } if vectors.empty?
|
|
205
|
+
|
|
206
|
+
avg = average_vectors(vectors)
|
|
207
|
+
avg = apply_dimension_enforcement(avg, :ollama)
|
|
208
|
+
return dimension_error(model, :ollama, avg) if avg.is_a?(String)
|
|
209
|
+
|
|
210
|
+
{ vector: avg, model: model, provider: :ollama, dimensions: avg.size, tokens: 0, chunks: vectors.size }
|
|
211
|
+
end
|
|
212
|
+
|
|
188
213
|
def generate_ollama_batch(texts:, model:)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
214
|
+
max_chars = ollama_context_chars(model)
|
|
215
|
+
texts.each_with_index.map do |text, i|
|
|
216
|
+
if text.length > max_chars
|
|
217
|
+
result = generate_ollama_chunked(text: text, model: model, max_chars: max_chars)
|
|
218
|
+
build_batch_entry(result[:vector], model, :ollama, i)
|
|
219
|
+
else
|
|
220
|
+
result = ollama_embed_request(model: model, input: text)
|
|
221
|
+
vec = result['embeddings']&.first
|
|
222
|
+
build_batch_entry(vec, model, :ollama, i)
|
|
223
|
+
end
|
|
193
224
|
end
|
|
194
225
|
end
|
|
195
226
|
|
|
227
|
+
def chunk_text(text, max_chars:)
|
|
228
|
+
if defined?(Legion::Extensions::Knowledge::Helpers::Chunker)
|
|
229
|
+
chunker = Legion::Extensions::Knowledge::Helpers::Chunker
|
|
230
|
+
max_tokens = max_chars / chunker::CHARS_PER_TOKEN
|
|
231
|
+
sections = [{ content: text, heading: nil, section_path: nil, source_file: nil }]
|
|
232
|
+
chunker.chunk(sections: sections, max_tokens: max_tokens)
|
|
233
|
+
else
|
|
234
|
+
text.chars.each_slice(max_chars).map { |s| { content: s.join } }
|
|
235
|
+
end
|
|
236
|
+
rescue StandardError
|
|
237
|
+
text.chars.each_slice(max_chars).map { |s| { content: s.join } }
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def average_vectors(vectors)
|
|
241
|
+
return vectors.first if vectors.size == 1
|
|
242
|
+
|
|
243
|
+
dim = vectors.first.size
|
|
244
|
+
sum = Array.new(dim, 0.0)
|
|
245
|
+
vectors.each { |v| v.each_with_index { |val, i| sum[i] += val } }
|
|
246
|
+
sum.map { |s| s / vectors.size }
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def ollama_context_chars(model)
|
|
250
|
+
base = model.to_s.split(':').first
|
|
251
|
+
OLLAMA_CONTEXT_CHARS[base] || OLLAMA_DEFAULT_CONTEXT_CHARS
|
|
252
|
+
end
|
|
253
|
+
|
|
196
254
|
def ollama_embed_request(model:, input:)
|
|
197
255
|
base_url = Legion::Settings.dig(:llm, :providers, :ollama, :base_url) || 'http://localhost:11434'
|
|
198
256
|
conn = Faraday.new(url: base_url) do |f|
|
data/lib/legion/llm/settings.rb
CHANGED
|
@@ -150,7 +150,7 @@ module Legion
|
|
|
150
150
|
bedrock: 'amazon.titan-embed-text-v2:0',
|
|
151
151
|
openai: 'text-embedding-3-small'
|
|
152
152
|
},
|
|
153
|
-
ollama_preferred: %w[
|
|
153
|
+
ollama_preferred: %w[mxbai-embed-large nomic-embed-text bge-large snowflake-arctic-embed]
|
|
154
154
|
}
|
|
155
155
|
end
|
|
156
156
|
|
data/lib/legion/llm/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -121,6 +121,20 @@ dependencies:
|
|
|
121
121
|
- - ">="
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
123
|
version: '0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: lex-knowledge
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
type: :runtime
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - ">="
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '0'
|
|
124
138
|
- !ruby/object:Gem::Dependency
|
|
125
139
|
name: lex-openai
|
|
126
140
|
requirement: !ruby/object:Gem::Requirement
|