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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08ccdbe9c1f4187acdcae49df633370d7fecb9866fa441aa1e95a3f932d5c9e6'
4
- data.tar.gz: df48f1ba0ef83a0fb26ba67865bf0f69ee415a349f2fc0e4ec40dd70f7f70815
3
+ metadata.gz: 7e7ff9c35609294ace1f203076485aaba0a811ba0adbe7d01197ce91de7a06ea
4
+ data.tar.gz: c493d891ded32632daf9b7e367fa115efc9903cc63d408c8c0799bb8bc242ca1
5
5
  SHA512:
6
- metadata.gz: 9237a7a67d3b843bef628817cbd679fdce2f690e35b35f817e38b39c2e46918cb87d1c81b0b3c5a48589f54a163cbd7f3ef5956e73ba3c96f4f0ee3c1f803c9d
7
- data.tar.gz: 996f35c9bb47ff5046bfd10fe48440a783480b81956b6e0b7f1f10bd860fd4aaab9dd9fcda3378761ac2942ea7f9c886d0bd19722cee9b46936f7ae8c662aa9d
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
- result = ollama_embed_request(model: model, input: texts)
190
- vectors = result['embeddings'] || []
191
- vectors.each_with_index.map do |vec, i|
192
- build_batch_entry(vec, model, :ollama, i)
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|
@@ -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[nomic-embed-text mxbai-embed-large bge-large snowflake-arctic-embed]
153
+ ollama_preferred: %w[mxbai-embed-large nomic-embed-text bge-large snowflake-arctic-embed]
154
154
  }
155
155
  end
156
156
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module LLM
5
- VERSION = '0.5.21'
5
+ VERSION = '0.5.22'
6
6
  end
7
7
  end
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.21
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