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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63a18f390ea4ed531450615fbff3877951d39b6a4d60bdf5fbee72a2572f3817
4
- data.tar.gz: 9220f43c2c962e936d82257b8e944fb9b82fd7e1c306fbb8462a3323e6e37de6
3
+ metadata.gz: 487b9b2441548d8c6e112f287119bc230f85d3a329e39b9b0ca5ffad0d483c23
4
+ data.tar.gz: 1fcd2fc638ea141c8ff48a9e13ff1d56a8e469ade43a6a28242f1d036e0a131a
5
5
  SHA512:
6
- metadata.gz: '08e857932f343c0b7a5d8ec98abb1e2c2bbb4c4ba3953ef9ae33927b6a0f1861901f8646f900fba9fc233f99737805b355fa0f0176c11771fc664fe97ee54214'
7
- data.tar.gz: 1d2bc1a4f9d9bf637731c48061bda8e442316feeb4a0eb29ccf0546d20ea1363be795c737079730706707659b1beef7cb81de63087ff4944084c25a9320e9eda
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
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'runners/request'
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module Apollo
5
- VERSION = '0.3.0'
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  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 ||= apollo_setting(:default_limit, 5)
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
- if co_located_reader?
48
- direct_query(payload)
49
- elsif transport_available?
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
- { success: false, error: :no_path_available }
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
- if co_located_writer?
62
- direct_ingest(payload)
63
- elsif transport_available?
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
- { success: false, error: :no_path_available }
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.0
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