legion-apollo 0.3.0 → 0.3.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/CHANGELOG.md +8 -0
- data/lib/legion/apollo/runners/request.rb +14 -0
- data/lib/legion/apollo/runners.rb +3 -0
- data/lib/legion/apollo/version.rb +1 -1
- data/lib/legion/apollo.rb +150 -15
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 487b9b2441548d8c6e112f287119bc230f85d3a329e39b9b0ca5ffad0d483c23
|
|
4
|
+
data.tar.gz: 1fcd2fc638ea141c8ff48a9e13ff1d56a8e469ade43a6a28242f1d036e0a131a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f4aa0535295bdded7f8fb087b344461cc269f477cd772e0c66c18706ae7f128e4eecac8c7f56c7ac29b808836d2f1c05eafeb088bbbe7f334aa13a256f45aaec
|
|
7
|
+
data.tar.gz: 7faa330d5b94f70cd0083fbc49b0ccf329c63604f7de35f7196bd552ff356efd2d7c11856efa7138f2313fdb56b3aae4bdc36c07ba402877dd57c19be6604017
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.1] - 2026-03-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `scope:` param on `query`/`retrieve`/`ingest` — `:global` (default), `:local` (SQLite only), `:all` (merged global + local)
|
|
7
|
+
- `Legion::Apollo::Runners::Request` shim — GAIA `knowledge_retrieval` phase now resolves to merged retrieval without any changes to `legion-gaia`
|
|
8
|
+
- Merge helpers: `query_merged`, `normalize_local_entries`, `normalize_global_entries`, `dedup_and_rank`
|
|
9
|
+
- Ingest routing: `ingest_local` and `ingest_all` private helpers
|
|
10
|
+
|
|
3
11
|
## [0.3.0] - 2026-03-25
|
|
4
12
|
|
|
5
13
|
### Added
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Apollo
|
|
5
|
+
module Runners
|
|
6
|
+
# GAIA knowledge_retrieval shim — delegates to Legion::Apollo.retrieve with scope: :all.
|
|
7
|
+
module Request
|
|
8
|
+
def self.retrieve(text:, limit: 5, **)
|
|
9
|
+
Legion::Apollo.retrieve(text: text, limit: limit, scope: :all, **)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/legion/apollo.rb
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'digest'
|
|
3
4
|
require_relative 'apollo/version'
|
|
4
5
|
require_relative 'apollo/settings'
|
|
5
6
|
require_relative 'apollo/local'
|
|
7
|
+
require_relative 'apollo/runners'
|
|
6
8
|
|
|
7
9
|
module Legion
|
|
8
10
|
# Apollo client library — query, ingest, and retrieve with smart routing.
|
|
9
11
|
# Routes to a co-located lex-apollo service when available, falls back to
|
|
10
12
|
# RabbitMQ transport, and degrades gracefully when neither is present.
|
|
13
|
+
# Supports scope: :global (default), :local (SQLite only), :all (merged).
|
|
11
14
|
module Apollo # rubocop:disable Metrics/ModuleLength
|
|
12
15
|
class << self # rubocop:disable Metrics/ClassLength
|
|
13
16
|
def start
|
|
@@ -36,39 +39,49 @@ module Legion
|
|
|
36
39
|
Legion::Apollo::Local
|
|
37
40
|
end
|
|
38
41
|
|
|
39
|
-
def query(text:, limit: nil, min_confidence: nil, tags: nil, **opts) # rubocop:disable Metrics/MethodLength
|
|
42
|
+
def query(text:, limit: nil, min_confidence: nil, tags: nil, scope: :global, **opts) # rubocop:disable Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/ParameterLists
|
|
40
43
|
return not_started_error unless started?
|
|
41
44
|
|
|
42
|
-
limit
|
|
45
|
+
limit ||= apollo_setting(:default_limit, 5)
|
|
43
46
|
min_confidence ||= apollo_setting(:min_confidence, 0.3)
|
|
44
47
|
|
|
45
48
|
payload = { text: text, limit: limit, min_confidence: min_confidence, tags: tags, **opts }
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
publish_query(payload)
|
|
50
|
+
case scope
|
|
51
|
+
when :local then query_local(payload)
|
|
52
|
+
when :all then query_merged(payload)
|
|
51
53
|
else
|
|
52
|
-
|
|
54
|
+
if co_located_reader?
|
|
55
|
+
direct_query(payload)
|
|
56
|
+
elsif transport_available?
|
|
57
|
+
publish_query(payload)
|
|
58
|
+
else
|
|
59
|
+
{ success: false, error: :no_path_available }
|
|
60
|
+
end
|
|
53
61
|
end
|
|
54
62
|
end
|
|
55
63
|
|
|
56
|
-
def ingest(content:, tags: [], **opts)
|
|
64
|
+
def ingest(content:, tags: [], scope: :global, **opts) # rubocop:disable Metrics/MethodLength
|
|
57
65
|
return not_started_error unless started?
|
|
58
66
|
|
|
59
67
|
payload = { content: content, tags: Array(tags).first(apollo_setting(:max_tags, 20)), **opts }
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
publish_ingest(payload)
|
|
69
|
+
case scope
|
|
70
|
+
when :local then ingest_local(payload)
|
|
71
|
+
when :all then ingest_all(payload)
|
|
65
72
|
else
|
|
66
|
-
|
|
73
|
+
if co_located_writer?
|
|
74
|
+
direct_ingest(payload)
|
|
75
|
+
elsif transport_available?
|
|
76
|
+
publish_ingest(payload)
|
|
77
|
+
else
|
|
78
|
+
{ success: false, error: :no_path_available }
|
|
79
|
+
end
|
|
67
80
|
end
|
|
68
81
|
end
|
|
69
82
|
|
|
70
|
-
def retrieve(text:, limit: 5, **)
|
|
71
|
-
query(text: text, limit: limit, **)
|
|
83
|
+
def retrieve(text:, limit: 5, scope: :global, **)
|
|
84
|
+
query(text: text, limit: limit, scope: scope, **)
|
|
72
85
|
end
|
|
73
86
|
|
|
74
87
|
def transport_available?
|
|
@@ -150,6 +163,128 @@ module Legion
|
|
|
150
163
|
{ success: false, error: e.message }
|
|
151
164
|
end
|
|
152
165
|
|
|
166
|
+
def query_local(payload)
|
|
167
|
+
return { success: false, error: :no_path_available } unless Legion::Apollo::Local.started?
|
|
168
|
+
|
|
169
|
+
result = Legion::Apollo::Local.query(**payload.slice(:text, :limit, :min_confidence, :tags))
|
|
170
|
+
return result unless result[:success]
|
|
171
|
+
|
|
172
|
+
entries = normalize_local_entries(Array(result[:results]))
|
|
173
|
+
{ success: true, entries: entries, count: entries.size, mode: :local }
|
|
174
|
+
rescue StandardError => e
|
|
175
|
+
{ success: false, error: e.message }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def query_merged(payload) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
179
|
+
entries = []
|
|
180
|
+
attempted = false
|
|
181
|
+
any_success = false
|
|
182
|
+
errors = []
|
|
183
|
+
|
|
184
|
+
if co_located_reader?
|
|
185
|
+
attempted = true
|
|
186
|
+
global = direct_query(payload)
|
|
187
|
+
if global[:success]
|
|
188
|
+
any_success = true
|
|
189
|
+
entries.concat(normalize_global_entries(Array(global[:entries]))) if global[:entries]
|
|
190
|
+
else
|
|
191
|
+
errors << global[:error]
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if Legion::Apollo::Local.started?
|
|
196
|
+
attempted = true
|
|
197
|
+
local = Legion::Apollo::Local.query(**payload.slice(:text, :limit, :min_confidence, :tags))
|
|
198
|
+
if local[:success]
|
|
199
|
+
any_success = true
|
|
200
|
+
entries.concat(normalize_local_entries(Array(local[:results]))) if local[:results]
|
|
201
|
+
else
|
|
202
|
+
errors << local[:error]
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
return { success: false, error: :no_path_available } unless attempted
|
|
207
|
+
|
|
208
|
+
unless any_success
|
|
209
|
+
combined_error = errors.compact.map(&:to_s).reject(&:empty?).join('; ')
|
|
210
|
+
combined_error = :upstream_query_failed if combined_error.empty?
|
|
211
|
+
return { success: false, error: combined_error }
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
ranked = dedup_and_rank(entries, limit: payload[:limit])
|
|
215
|
+
{ success: true, entries: ranked, count: ranked.size, mode: :merged }
|
|
216
|
+
rescue StandardError => e
|
|
217
|
+
{ success: false, error: e.message }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def normalize_local_entries(entries) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
221
|
+
entries.map do |e|
|
|
222
|
+
hash = e[:content_hash] || Digest::MD5.hexdigest(e[:content].to_s.strip.downcase.gsub(/\s+/, ' '))
|
|
223
|
+
tags = if e[:tags].is_a?(String)
|
|
224
|
+
begin
|
|
225
|
+
::JSON.parse(e[:tags])
|
|
226
|
+
rescue StandardError
|
|
227
|
+
[]
|
|
228
|
+
end
|
|
229
|
+
else
|
|
230
|
+
Array(e[:tags])
|
|
231
|
+
end
|
|
232
|
+
{ id: e[:id], content: e[:content], content_hash: hash,
|
|
233
|
+
confidence: e[:confidence] || 0.5, content_type: 'fact', tags: tags, source: :local }
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def normalize_global_entries(entries)
|
|
238
|
+
entries.map do |e|
|
|
239
|
+
hash = e[:content_hash] || Digest::MD5.hexdigest(e[:content].to_s.strip.downcase.gsub(/\s+/, ' '))
|
|
240
|
+
{ id: e[:id], content: e[:content], content_hash: hash,
|
|
241
|
+
confidence: e[:confidence] || 0.5, content_type: e[:content_type] || 'fact',
|
|
242
|
+
tags: Array(e[:tags]), source: :global }
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def dedup_and_rank(entries, limit:)
|
|
247
|
+
sorted = entries
|
|
248
|
+
.sort_by { |e| -(e[:confidence] || 0) }
|
|
249
|
+
.uniq { |e| e[:content_hash] }
|
|
250
|
+
|
|
251
|
+
limit ? sorted.first(limit) : sorted
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def ingest_local(payload)
|
|
255
|
+
return { success: false, error: :no_path_available } unless Legion::Apollo::Local.started?
|
|
256
|
+
|
|
257
|
+
Legion::Apollo::Local.ingest(**payload)
|
|
258
|
+
rescue StandardError => e
|
|
259
|
+
{ success: false, error: e.message }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def ingest_all(payload) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
263
|
+
results = []
|
|
264
|
+
|
|
265
|
+
if co_located_writer?
|
|
266
|
+
results << direct_ingest(payload)
|
|
267
|
+
elsif transport_available?
|
|
268
|
+
results << publish_ingest(payload)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
results << Legion::Apollo::Local.ingest(**payload) if Legion::Apollo::Local.started?
|
|
272
|
+
|
|
273
|
+
return { success: false, error: :no_path_available } if results.empty?
|
|
274
|
+
|
|
275
|
+
overall_success = results.any? { |r| r.respond_to?(:[]) && r[:success] }
|
|
276
|
+
|
|
277
|
+
if overall_success
|
|
278
|
+
{ success: true, mode: :all, results: results }
|
|
279
|
+
else
|
|
280
|
+
errors = results.select { |r| r.respond_to?(:[]) }.map { |r| r[:error] }.compact.uniq
|
|
281
|
+
error_value = errors.length <= 1 ? errors.first : errors
|
|
282
|
+
{ success: false, mode: :all, results: results, error: error_value }
|
|
283
|
+
end
|
|
284
|
+
rescue StandardError => e
|
|
285
|
+
{ success: false, error: e.message }
|
|
286
|
+
end
|
|
287
|
+
|
|
153
288
|
def apollo_setting(key, default)
|
|
154
289
|
return default unless defined?(Legion::Settings) && !Legion::Settings[:apollo].nil?
|
|
155
290
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legion-apollo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -76,6 +76,8 @@ files:
|
|
|
76
76
|
- lib/legion/apollo/messages/ingest.rb
|
|
77
77
|
- lib/legion/apollo/messages/query.rb
|
|
78
78
|
- lib/legion/apollo/messages/writeback.rb
|
|
79
|
+
- lib/legion/apollo/runners.rb
|
|
80
|
+
- lib/legion/apollo/runners/request.rb
|
|
79
81
|
- lib/legion/apollo/settings.rb
|
|
80
82
|
- lib/legion/apollo/version.rb
|
|
81
83
|
homepage: https://github.com/LegionIO/legion-apollo
|