lex-apollo 0.4.7 → 0.4.8
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 +11 -0
- data/lib/legion/extensions/apollo/actors/writeback_vectorize.rb +3 -1
- data/lib/legion/extensions/apollo/helpers/writeback.rb +3 -2
- data/lib/legion/extensions/apollo/runners/knowledge.rb +11 -4
- data/lib/legion/extensions/apollo/runners/maintenance.rb +0 -1
- data/lib/legion/extensions/apollo/version.rb +1 -1
- data/spec/legion/extensions/apollo/actors/writeback_vectorize_spec.rb +12 -3
- data/spec/legion/extensions/apollo/runners/knowledge_spec.rb +19 -11
- data/spec/legion/extensions/apollo/runners/maintenance_spec.rb +0 -1
- data/spec/legion/extensions/apollo/runners/request_spec.rb +13 -7
- metadata +1 -3
- data/lib/legion/extensions/apollo/helpers/embedding.rb +0 -104
- data/spec/legion/extensions/apollo/helpers/embedding_spec.rb +0 -127
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef4d63cf3e8b7a9ce86f11af1150b851a911fa7383ca2233d753bc328be31cb5
|
|
4
|
+
data.tar.gz: 8d4e3f73d3d7d04c1dff338eb226a3385d0977d57d77c2378d41723b3fff80e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fae193470f3da5dedcdb5ee91310ac824064aa3ebd60de9f21e9d9a6c5eb770db23ad21603cb6c8d7337fb7fb84b1f85f9c3155f1ff38cac2bd426a3270eaabf
|
|
7
|
+
data.tar.gz: 6d6827a8defe1e41724a3033193f774034830cddf28bd34096622410f736d233a79853567f71b5e2f7547e48194ff3d23ce685633b55f832747ad37026c54df5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.8] - 2026-03-25
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Removed `Helpers::Embedding` direct Ollama bypass; all embedding generation now routes through `Legion::LLM::Embeddings.generate`
|
|
7
|
+
- `Runners::Knowledge`, `Helpers::Writeback`, and `Actor::WritebackVectorize` all use `Legion::LLM::Embeddings.generate` and extract `[:vector]` from the result hash
|
|
8
|
+
- Zero-vector fallback (`Array.new(1024, 0.0)`) preserved when LLM is unavailable or returns nil vector
|
|
9
|
+
|
|
10
|
+
### Removed
|
|
11
|
+
- `lib/legion/extensions/apollo/helpers/embedding.rb` — direct Faraday/Ollama embedding bypass (superseded by `Legion::LLM` provider routing)
|
|
12
|
+
- `spec/legion/extensions/apollo/helpers/embedding_spec.rb` — tests for removed helper
|
|
13
|
+
|
|
3
14
|
## [0.4.7] - 2026-03-25
|
|
4
15
|
|
|
5
16
|
### Added
|
|
@@ -14,7 +14,9 @@ module Legion
|
|
|
14
14
|
|
|
15
15
|
def handle_vectorize(payload)
|
|
16
16
|
payload = symbolize(payload)
|
|
17
|
-
|
|
17
|
+
result = Legion::LLM::Embeddings.generate(text: payload[:content])
|
|
18
|
+
vector = result.is_a?(Hash) ? result[:vector] : result
|
|
19
|
+
embedding = vector.is_a?(Array) && vector.any? ? vector : Array.new(1024, 0.0)
|
|
18
20
|
enriched = payload.merge(embedding: embedding)
|
|
19
21
|
|
|
20
22
|
if Helpers::Capability.can_write?
|
|
@@ -64,8 +64,9 @@ module Legion
|
|
|
64
64
|
can_write = Helpers::Capability.can_write?
|
|
65
65
|
|
|
66
66
|
if can_embed
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
result = Legion::LLM::Embeddings.generate(text: payload[:content])
|
|
68
|
+
vector = result.is_a?(Hash) ? result[:vector] : result
|
|
69
|
+
payload[:embedding] = vector.is_a?(Array) && vector.any? ? vector : Array.new(1024, 0.0)
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
if can_write && can_embed
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require_relative '../helpers/confidence'
|
|
5
|
-
require_relative '../helpers/embedding'
|
|
6
5
|
|
|
7
6
|
module Legion
|
|
8
7
|
module Extensions
|
|
@@ -75,7 +74,7 @@ module Legion
|
|
|
75
74
|
end
|
|
76
75
|
end
|
|
77
76
|
|
|
78
|
-
embedding =
|
|
77
|
+
embedding = embed_text(content)
|
|
79
78
|
content_type_sym = content_type.to_s
|
|
80
79
|
tag_array = defined?(Helpers::TagNormalizer) ? Helpers::TagNormalizer.normalize_all(tags) : Array(tags)
|
|
81
80
|
domain = knowledge_domain || tag_array.first || 'general'
|
|
@@ -119,7 +118,7 @@ module Legion
|
|
|
119
118
|
def handle_query(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: [:confirmed], tags: nil, domain: nil, agent_id: 'unknown', **) # rubocop:disable Metrics/ParameterLists, Layout/LineLength
|
|
120
119
|
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
121
120
|
|
|
122
|
-
embedding =
|
|
121
|
+
embedding = embed_text(query)
|
|
123
122
|
sql = Helpers::GraphQuery.build_semantic_search_sql(
|
|
124
123
|
limit: limit, min_confidence: min_confidence,
|
|
125
124
|
statuses: Array(status).map(&:to_s), tags: tags, domain: domain
|
|
@@ -228,7 +227,7 @@ module Legion
|
|
|
228
227
|
|
|
229
228
|
return { success: true, entries: [], count: 0 } if query.nil? || query.to_s.strip.empty?
|
|
230
229
|
|
|
231
|
-
embedding =
|
|
230
|
+
embedding = embed_text(query.to_s)
|
|
232
231
|
sql = Helpers::GraphQuery.build_semantic_search_sql(
|
|
233
232
|
limit: limit, min_confidence: min_confidence,
|
|
234
233
|
statuses: ['confirmed'], tags: tags, domain: domain
|
|
@@ -310,6 +309,14 @@ module Legion
|
|
|
310
309
|
|
|
311
310
|
private
|
|
312
311
|
|
|
312
|
+
def embed_text(text)
|
|
313
|
+
result = Legion::LLM::Embeddings.generate(text: text)
|
|
314
|
+
vector = result.is_a?(Hash) ? result[:vector] : result
|
|
315
|
+
vector.is_a?(Array) && vector.any? ? vector : Array.new(1024, 0.0)
|
|
316
|
+
rescue StandardError
|
|
317
|
+
Array.new(1024, 0.0)
|
|
318
|
+
end
|
|
319
|
+
|
|
313
320
|
def allowed_domains_for(target_domain)
|
|
314
321
|
rules = if defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :domain_isolation)
|
|
315
322
|
Legion::Settings.dig(:apollo, :domain_isolation)
|
|
@@ -39,7 +39,6 @@ unless defined?(Legion::Transport::Message)
|
|
|
39
39
|
$LOADED_FEATURES << 'legion/transport/exchange' unless $LOADED_FEATURES.include?('legion/transport/exchange')
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
require 'legion/extensions/apollo/helpers/embedding'
|
|
43
42
|
require 'legion/extensions/apollo/helpers/capability'
|
|
44
43
|
require 'legion/extensions/apollo/transport/exchanges/apollo'
|
|
45
44
|
require 'legion/extensions/apollo/transport/messages/writeback'
|
|
@@ -48,6 +47,15 @@ require 'legion/extensions/apollo/actors/writeback_vectorize'
|
|
|
48
47
|
RSpec.describe Legion::Extensions::Apollo::Actor::WritebackVectorize do
|
|
49
48
|
subject(:actor) { described_class.new }
|
|
50
49
|
|
|
50
|
+
before do
|
|
51
|
+
embeddings_mod = Module.new do
|
|
52
|
+
def self.generate(*, **)
|
|
53
|
+
{ vector: Array.new(1024, 0.1), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
stub_const('Legion::LLM::Embeddings', embeddings_mod)
|
|
57
|
+
end
|
|
58
|
+
|
|
51
59
|
describe '#runner_function' do
|
|
52
60
|
it 'returns handle_vectorize' do
|
|
53
61
|
expect(actor.runner_function).to eq('handle_vectorize')
|
|
@@ -58,7 +66,8 @@ RSpec.describe Legion::Extensions::Apollo::Actor::WritebackVectorize do
|
|
|
58
66
|
let(:payload) { { content: 'test content', content_type: 'observation', tags: %w[test] } }
|
|
59
67
|
|
|
60
68
|
before do
|
|
61
|
-
allow(Legion::
|
|
69
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
70
|
+
.and_return({ vector: [0.1] * 1024, model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
62
71
|
allow(Legion::Extensions::Apollo::Helpers::Capability).to receive(:can_write?).and_return(false)
|
|
63
72
|
end
|
|
64
73
|
|
|
@@ -83,7 +92,7 @@ RSpec.describe Legion::Extensions::Apollo::Actor::WritebackVectorize do
|
|
|
83
92
|
end
|
|
84
93
|
|
|
85
94
|
it 'returns error hash on failure' do
|
|
86
|
-
allow(Legion::
|
|
95
|
+
allow(Legion::LLM::Embeddings).to receive(:generate).and_raise(RuntimeError, 'boom')
|
|
87
96
|
|
|
88
97
|
result = actor.handle_vectorize(payload)
|
|
89
98
|
expect(result[:success]).to be false
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
require 'legion/extensions/apollo/helpers/confidence'
|
|
5
5
|
require 'legion/extensions/apollo/helpers/similarity'
|
|
6
|
-
require 'legion/extensions/apollo/helpers/embedding'
|
|
7
6
|
require 'legion/extensions/apollo/helpers/graph_query'
|
|
8
7
|
require 'legion/extensions/apollo/helpers/tag_normalizer'
|
|
9
8
|
require 'legion/extensions/apollo/helpers/writeback'
|
|
@@ -16,6 +15,15 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Knowledge do
|
|
|
16
15
|
obj
|
|
17
16
|
end
|
|
18
17
|
|
|
18
|
+
before do
|
|
19
|
+
embeddings_mod = Module.new do
|
|
20
|
+
def self.generate(*, **)
|
|
21
|
+
{ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
stub_const('Legion::LLM::Embeddings', embeddings_mod)
|
|
25
|
+
end
|
|
26
|
+
|
|
19
27
|
describe '#store_knowledge' do
|
|
20
28
|
it 'returns a message payload hash' do
|
|
21
29
|
result = runner.store_knowledge(
|
|
@@ -119,8 +127,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Knowledge do
|
|
|
119
127
|
stub_const('Legion::Data::Model::ApolloRelation', mock_relation_class)
|
|
120
128
|
stub_const('Legion::Data::Model::ApolloExpertise', mock_expertise_class)
|
|
121
129
|
stub_const('Legion::Data::Model::ApolloAccessLog', mock_access_log_class)
|
|
122
|
-
allow(Legion::
|
|
123
|
-
.and_return(Array.new(
|
|
130
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
131
|
+
.and_return({ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
124
132
|
|
|
125
133
|
# Corroboration lookup chain
|
|
126
134
|
allow(mock_entry_class).to receive(:where).and_return(double(exclude: double(limit: empty_dataset)))
|
|
@@ -249,8 +257,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Knowledge do
|
|
|
249
257
|
context 'when Sequel raises an error' do
|
|
250
258
|
before do
|
|
251
259
|
stub_const('Legion::Data::Model::ApolloEntry', Class.new)
|
|
252
|
-
allow(Legion::
|
|
253
|
-
.and_return(Array.new(
|
|
260
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
261
|
+
.and_return({ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
254
262
|
allow(Legion::Data::Model::ApolloEntry).to receive(:where)
|
|
255
263
|
.and_raise(Sequel::Error, 'connection lost')
|
|
256
264
|
end
|
|
@@ -289,8 +297,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Knowledge do
|
|
|
289
297
|
before do
|
|
290
298
|
stub_const('Legion::Data::Model::ApolloEntry', mock_entry_class)
|
|
291
299
|
stub_const('Legion::Data::Model::ApolloAccessLog', mock_access_log_class)
|
|
292
|
-
allow(Legion::
|
|
293
|
-
.and_return(Array.new(
|
|
300
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
301
|
+
.and_return({ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
294
302
|
allow(mock_entry_class).to receive(:db).and_return(mock_db)
|
|
295
303
|
allow(mock_db).to receive(:fetch).and_return(double(all: sample_entries))
|
|
296
304
|
allow(mock_entry_class).to receive(:where).and_return(double(update: true))
|
|
@@ -324,8 +332,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Knowledge do
|
|
|
324
332
|
|
|
325
333
|
before do
|
|
326
334
|
stub_const('Legion::Data::Model::ApolloEntry', mock_entry_class)
|
|
327
|
-
allow(Legion::
|
|
328
|
-
.and_return(Array.new(
|
|
335
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
336
|
+
.and_return({ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
329
337
|
allow(mock_entry_class).to receive(:db).and_return(mock_db)
|
|
330
338
|
allow(mock_db).to receive(:fetch).and_return(double(all: []))
|
|
331
339
|
end
|
|
@@ -380,8 +388,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Knowledge do
|
|
|
380
388
|
|
|
381
389
|
before do
|
|
382
390
|
stub_const('Legion::Data::Model::ApolloEntry', mock_entry_class)
|
|
383
|
-
allow(Legion::
|
|
384
|
-
.and_return(Array.new(
|
|
391
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
392
|
+
.and_return({ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
385
393
|
allow(mock_entry_class).to receive(:db).and_return(mock_db)
|
|
386
394
|
allow(mock_db).to receive(:fetch).and_return(double(all: sample_entries))
|
|
387
395
|
allow(mock_entry_class).to receive(:where).and_return(double(update: true))
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
require 'legion/extensions/apollo/helpers/confidence'
|
|
5
5
|
require 'legion/extensions/apollo/helpers/similarity'
|
|
6
|
-
require 'legion/extensions/apollo/helpers/embedding'
|
|
7
6
|
require 'legion/extensions/apollo/runners/maintenance'
|
|
8
7
|
|
|
9
8
|
RSpec.describe Legion::Extensions::Apollo::Runners::Maintenance do
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
require 'legion/extensions/apollo/helpers/confidence'
|
|
5
5
|
require 'legion/extensions/apollo/helpers/similarity'
|
|
6
|
-
require 'legion/extensions/apollo/helpers/embedding'
|
|
7
6
|
require 'legion/extensions/apollo/helpers/graph_query'
|
|
8
7
|
require 'legion/extensions/apollo/runners/knowledge'
|
|
9
8
|
require 'legion/extensions/apollo/runners/request'
|
|
@@ -12,6 +11,13 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Request do
|
|
|
12
11
|
before do
|
|
13
12
|
# Clear cached knowledge_host between examples
|
|
14
13
|
described_class.instance_variable_set(:@knowledge_host, nil)
|
|
14
|
+
|
|
15
|
+
embeddings_mod = Module.new do
|
|
16
|
+
def self.generate(*, **)
|
|
17
|
+
{ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
stub_const('Legion::LLM::Embeddings', embeddings_mod)
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
describe '.data_required?' do
|
|
@@ -33,8 +39,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Request do
|
|
|
33
39
|
before do
|
|
34
40
|
stub_const('Legion::Data::Model::ApolloEntry', mock_entry_class)
|
|
35
41
|
stub_const('Legion::Data::Model::ApolloAccessLog', mock_access_log_class)
|
|
36
|
-
allow(Legion::
|
|
37
|
-
.and_return(Array.new(
|
|
42
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
43
|
+
.and_return({ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
38
44
|
allow(mock_entry_class).to receive(:db).and_return(mock_db)
|
|
39
45
|
allow(mock_db).to receive(:fetch).and_return(double(all: sample_entries))
|
|
40
46
|
allow(mock_entry_class).to receive(:where).and_return(double(update: true))
|
|
@@ -97,8 +103,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Request do
|
|
|
97
103
|
|
|
98
104
|
before do
|
|
99
105
|
stub_const('Legion::Data::Model::ApolloEntry', mock_entry_class)
|
|
100
|
-
allow(Legion::
|
|
101
|
-
.and_return(Array.new(
|
|
106
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
107
|
+
.and_return({ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
102
108
|
allow(mock_entry_class).to receive(:db).and_return(mock_db)
|
|
103
109
|
allow(mock_db).to receive(:fetch).and_return(double(all: []))
|
|
104
110
|
allow(mock_entry_class).to receive(:where).and_return(double(update: true))
|
|
@@ -136,8 +142,8 @@ RSpec.describe Legion::Extensions::Apollo::Runners::Request do
|
|
|
136
142
|
stub_const('Legion::Data::Model::ApolloEntry', mock_entry_class)
|
|
137
143
|
stub_const('Legion::Data::Model::ApolloExpertise', mock_expertise_class)
|
|
138
144
|
stub_const('Legion::Data::Model::ApolloAccessLog', mock_access_log_class)
|
|
139
|
-
allow(Legion::
|
|
140
|
-
.and_return(Array.new(
|
|
145
|
+
allow(Legion::LLM::Embeddings).to receive(:generate)
|
|
146
|
+
.and_return({ vector: Array.new(1024, 0.0), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
|
|
141
147
|
allow(mock_entry_class).to receive(:where).and_return(double(exclude: double(limit: double(each: nil), first: nil)))
|
|
142
148
|
allow(mock_entry_class).to receive(:exclude)
|
|
143
149
|
.and_return(double(exclude: double(limit: double(all: []))))
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-apollo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -160,7 +160,6 @@ files:
|
|
|
160
160
|
- lib/legion/extensions/apollo/gaia_integration.rb
|
|
161
161
|
- lib/legion/extensions/apollo/helpers/capability.rb
|
|
162
162
|
- lib/legion/extensions/apollo/helpers/confidence.rb
|
|
163
|
-
- lib/legion/extensions/apollo/helpers/embedding.rb
|
|
164
163
|
- lib/legion/extensions/apollo/helpers/entity_watchdog.rb
|
|
165
164
|
- lib/legion/extensions/apollo/helpers/graph_query.rb
|
|
166
165
|
- lib/legion/extensions/apollo/helpers/similarity.rb
|
|
@@ -197,7 +196,6 @@ files:
|
|
|
197
196
|
- spec/legion/extensions/apollo/gaia_integration_spec.rb
|
|
198
197
|
- spec/legion/extensions/apollo/helpers/capability_spec.rb
|
|
199
198
|
- spec/legion/extensions/apollo/helpers/confidence_spec.rb
|
|
200
|
-
- spec/legion/extensions/apollo/helpers/embedding_spec.rb
|
|
201
199
|
- spec/legion/extensions/apollo/helpers/entity_watchdog_spec.rb
|
|
202
200
|
- spec/legion/extensions/apollo/helpers/graph_query_spec.rb
|
|
203
201
|
- spec/legion/extensions/apollo/helpers/similarity_spec.rb
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Apollo
|
|
6
|
-
module Helpers
|
|
7
|
-
module Embedding
|
|
8
|
-
DEFAULT_DIMENSION = 1024
|
|
9
|
-
|
|
10
|
-
LOCAL_EMBEDDING_MODELS = %w[mxbai-embed-large bge-large snowflake-arctic-embed].freeze
|
|
11
|
-
|
|
12
|
-
module_function
|
|
13
|
-
|
|
14
|
-
def generate(text:, **)
|
|
15
|
-
unless defined?(Legion::LLM) && Legion::LLM.started?
|
|
16
|
-
Legion::Logging.debug('[apollo] embedding fallback: LLM not started') if defined?(Legion::Logging)
|
|
17
|
-
return zero_vector
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
local_model = detect_local_model
|
|
21
|
-
vector = if local_model
|
|
22
|
-
ollama_embed(text, local_model)
|
|
23
|
-
else
|
|
24
|
-
opts = cloud_embedding_opts
|
|
25
|
-
result = Legion::LLM.embed(text, **opts)
|
|
26
|
-
result.is_a?(Hash) ? result[:vector] : result
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
if vector.is_a?(Array) && vector.any?
|
|
30
|
-
@dimension = vector.size
|
|
31
|
-
vector
|
|
32
|
-
else
|
|
33
|
-
Legion::Logging.warn('[apollo] embedding fallback: LLM returned no vector') if defined?(Legion::Logging)
|
|
34
|
-
zero_vector
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def dimension
|
|
39
|
-
@dimension || configured_dimension
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def configured_dimension
|
|
43
|
-
return DEFAULT_DIMENSION unless defined?(Legion::Settings) && !Legion::Settings[:apollo].nil?
|
|
44
|
-
|
|
45
|
-
Legion::Settings[:apollo].dig(:embedding, :dimension) || DEFAULT_DIMENSION
|
|
46
|
-
rescue StandardError
|
|
47
|
-
DEFAULT_DIMENSION
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def ollama_embed(text, model)
|
|
51
|
-
require 'faraday'
|
|
52
|
-
base_url = ollama_base_url
|
|
53
|
-
Legion::Logging.debug("[apollo] embedding via local Ollama: #{model}") if defined?(Legion::Logging)
|
|
54
|
-
conn = Faraday.new(url: base_url) { |f| f.options.timeout = 10 }
|
|
55
|
-
response = conn.post('/api/embed', { model: model, input: text }.to_json,
|
|
56
|
-
'Content-Type' => 'application/json')
|
|
57
|
-
return nil unless response.success?
|
|
58
|
-
|
|
59
|
-
parsed = ::JSON.parse(response.body)
|
|
60
|
-
parsed['embeddings']&.first
|
|
61
|
-
rescue StandardError => e
|
|
62
|
-
Legion::Logging.warn("[apollo] local Ollama embed failed: #{e.message}") if defined?(Legion::Logging)
|
|
63
|
-
nil
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def ollama_base_url
|
|
67
|
-
return 'http://localhost:11434' unless defined?(Legion::Settings)
|
|
68
|
-
|
|
69
|
-
Legion::Settings[:llm].dig(:providers, :ollama, :base_url) || 'http://localhost:11434'
|
|
70
|
-
rescue StandardError
|
|
71
|
-
'http://localhost:11434'
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def cloud_embedding_opts
|
|
75
|
-
return {} unless defined?(Legion::Settings) && !Legion::Settings[:apollo].nil?
|
|
76
|
-
|
|
77
|
-
embedding = Legion::Settings[:apollo][:embedding] || {}
|
|
78
|
-
opts = {}
|
|
79
|
-
opts[:provider] = embedding[:provider].to_sym if embedding[:provider]
|
|
80
|
-
opts[:model] = embedding[:model] if embedding[:model]
|
|
81
|
-
opts
|
|
82
|
-
rescue StandardError
|
|
83
|
-
{}
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def detect_local_model
|
|
87
|
-
return nil unless defined?(Legion::LLM::Discovery::Ollama)
|
|
88
|
-
|
|
89
|
-
LOCAL_EMBEDDING_MODELS.find do |m|
|
|
90
|
-
Legion::LLM::Discovery::Ollama.model_available?(m) ||
|
|
91
|
-
Legion::LLM::Discovery::Ollama.model_available?("#{m}:latest")
|
|
92
|
-
end
|
|
93
|
-
rescue StandardError
|
|
94
|
-
nil
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def zero_vector
|
|
98
|
-
Array.new(dimension, 0.0)
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
require 'legion/extensions/apollo/helpers/embedding'
|
|
5
|
-
|
|
6
|
-
RSpec.describe Legion::Extensions::Apollo::Helpers::Embedding do
|
|
7
|
-
describe '.generate' do
|
|
8
|
-
context 'when Legion::LLM is not defined' do
|
|
9
|
-
before do
|
|
10
|
-
hide_const('Legion::LLM') if defined?(Legion::LLM)
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
it 'returns a zero vector of the correct dimension' do
|
|
14
|
-
result = described_class.generate(text: 'hello world')
|
|
15
|
-
expect(result).to eq(Array.new(1024, 0.0))
|
|
16
|
-
expect(result.size).to eq(1024)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
context 'when Legion::LLM is defined but not started' do
|
|
21
|
-
before do
|
|
22
|
-
stub_const('Legion::LLM', Module.new { def self.started? = false })
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
it 'returns a zero vector' do
|
|
26
|
-
result = described_class.generate(text: 'hello world')
|
|
27
|
-
expect(result).to eq(Array.new(1024, 0.0))
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
context 'when Legion::LLM is available and started' do
|
|
32
|
-
let(:mock_vector) { Array.new(1024) { rand(-1.0..1.0) } }
|
|
33
|
-
|
|
34
|
-
before do
|
|
35
|
-
llm = Module.new do
|
|
36
|
-
define_method(:started?) { true }
|
|
37
|
-
define_method(:embed) { |_text, **| nil }
|
|
38
|
-
extend self
|
|
39
|
-
end
|
|
40
|
-
stub_const('Legion::LLM', llm)
|
|
41
|
-
allow(Legion::LLM).to receive(:embed).and_return({ vector: mock_vector, model: 'text-embedding-3-small' })
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
it 'returns the vector from the LLM response hash' do
|
|
45
|
-
result = described_class.generate(text: 'hello world')
|
|
46
|
-
expect(result).to eq(mock_vector)
|
|
47
|
-
expect(Legion::LLM).to have_received(:embed).with('hello world', **{})
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
context 'when Legion::LLM returns a short embedding' do
|
|
52
|
-
before do
|
|
53
|
-
llm = Module.new do
|
|
54
|
-
define_method(:started?) { true }
|
|
55
|
-
define_method(:embed) { |_text, **| nil }
|
|
56
|
-
extend self
|
|
57
|
-
end
|
|
58
|
-
stub_const('Legion::LLM', llm)
|
|
59
|
-
allow(Legion::LLM).to receive(:embed).and_return({ vector: [0.1, 0.2], model: 'test' })
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
it 'accepts the embedding and updates dimension' do
|
|
63
|
-
result = described_class.generate(text: 'hello world')
|
|
64
|
-
expect(result).to eq([0.1, 0.2])
|
|
65
|
-
expect(described_class.dimension).to eq(2)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
context 'when Legion::LLM returns nil vector' do
|
|
70
|
-
before do
|
|
71
|
-
llm = Module.new do
|
|
72
|
-
define_method(:started?) { true }
|
|
73
|
-
define_method(:embed) { |_text, **| nil }
|
|
74
|
-
extend self
|
|
75
|
-
end
|
|
76
|
-
stub_const('Legion::LLM', llm)
|
|
77
|
-
allow(Legion::LLM).to receive(:embed).and_return({ vector: nil, model: 'test', error: 'failed' })
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
it 'returns a zero vector as fallback' do
|
|
81
|
-
result = described_class.generate(text: 'hello world')
|
|
82
|
-
expect(result).to be_an(Array)
|
|
83
|
-
expect(result.all?(&:zero?)).to be true
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
context 'when Legion::LLM returns nil' do
|
|
88
|
-
before do
|
|
89
|
-
llm = Module.new do
|
|
90
|
-
define_method(:started?) { true }
|
|
91
|
-
define_method(:embed) { |_text, **| nil }
|
|
92
|
-
extend self
|
|
93
|
-
end
|
|
94
|
-
stub_const('Legion::LLM', llm)
|
|
95
|
-
allow(Legion::LLM).to receive(:embed).and_return(nil)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
it 'returns a zero vector as fallback' do
|
|
99
|
-
result = described_class.generate(text: 'hello world')
|
|
100
|
-
expect(result).to be_an(Array)
|
|
101
|
-
expect(result.all?(&:zero?)).to be true
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
describe '.configured_dimension' do
|
|
107
|
-
context 'when Settings has apollo.embedding.dimension' do
|
|
108
|
-
before do
|
|
109
|
-
stub_const('Legion::Settings', { apollo: { embedding: { dimension: 768 } } })
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
it 'returns the configured dimension' do
|
|
113
|
-
expect(described_class.configured_dimension).to eq(768)
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
context 'when Settings has no apollo key' do
|
|
118
|
-
before do
|
|
119
|
-
stub_const('Legion::Settings', { apollo: nil })
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
it 'returns the default dimension' do
|
|
123
|
-
expect(described_class.configured_dimension).to eq(1024)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|