lex-apollo 0.4.22 → 0.4.24

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/lib/legion/extensions/apollo/actors/corroboration_checker.rb +3 -1
  4. data/lib/legion/extensions/apollo/actors/decay.rb +3 -1
  5. data/lib/legion/extensions/apollo/actors/entity_watchdog.rb +10 -21
  6. data/lib/legion/extensions/apollo/actors/expertise_aggregator.rb +3 -1
  7. data/lib/legion/extensions/apollo/actors/gas_subscriber.rb +1 -1
  8. data/lib/legion/extensions/apollo/actors/ingest.rb +1 -1
  9. data/lib/legion/extensions/apollo/actors/query_responder.rb +1 -1
  10. data/lib/legion/extensions/apollo/actors/writeback_store.rb +1 -1
  11. data/lib/legion/extensions/apollo/actors/writeback_vectorize.rb +2 -2
  12. data/lib/legion/extensions/apollo/api.rb +28 -22
  13. data/lib/legion/extensions/apollo/gaia_integration.rb +16 -13
  14. data/lib/legion/extensions/apollo/helpers/capability.rb +19 -17
  15. data/lib/legion/extensions/apollo/helpers/confidence.rb +5 -8
  16. data/lib/legion/extensions/apollo/helpers/data_models.rb +61 -0
  17. data/lib/legion/extensions/apollo/helpers/entity_watchdog.rb +8 -15
  18. data/lib/legion/extensions/apollo/helpers/similarity.rb +5 -6
  19. data/lib/legion/extensions/apollo/helpers/writeback.rb +13 -14
  20. data/lib/legion/extensions/apollo/runners/expertise.rb +10 -8
  21. data/lib/legion/extensions/apollo/runners/gas.rb +47 -27
  22. data/lib/legion/extensions/apollo/runners/knowledge.rb +95 -80
  23. data/lib/legion/extensions/apollo/runners/maintenance.rb +7 -5
  24. data/lib/legion/extensions/apollo/runners/request.rb +7 -1
  25. data/lib/legion/extensions/apollo/version.rb +1 -1
  26. data/lib/legion/extensions/apollo.rb +96 -0
  27. data/spec/legion/extensions/apollo/actors/writeback_vectorize_spec.rb +3 -3
  28. data/spec/legion/extensions/apollo/api_spec.rb +20 -0
  29. data/spec/legion/extensions/apollo/helpers/capability_spec.rb +4 -4
  30. data/spec/legion/extensions/apollo/runners/gas_anticipate_spec.rb +0 -3
  31. data/spec/legion/extensions/apollo/runners/gas_relate_spec.rb +0 -4
  32. data/spec/legion/extensions/apollo/runners/gas_synthesize_spec.rb +0 -11
  33. data/spec/legion/extensions/apollo/runners/knowledge_spec.rb +25 -15
  34. data/spec/legion/extensions/apollo/runners/maintenance_spec.rb +8 -8
  35. data/spec/legion/extensions/apollo/runners/request_spec.rb +8 -8
  36. data/spec/spec_helper.rb +4 -0
  37. metadata +2 -1
@@ -12,9 +12,8 @@ module Legion
12
12
  }.freeze
13
13
 
14
14
  class << self
15
- def log
16
- Legion::Logging
17
- end
15
+ include Legion::Logging::Helper
16
+ include Legion::Settings::Helper
18
17
 
19
18
  def detect_entities(text:, types: nil)
20
19
  return [] if text.nil? || text.empty?
@@ -23,11 +22,13 @@ module Legion
23
22
  entities = []
24
23
 
25
24
  types.each do |type_sym|
26
- pattern = type_sym == :concept ? concept_pattern : ENTITY_PATTERNS[type_sym]
25
+ entity_type = type_sym == :repository ? :repo : type_sym
26
+ pattern = entity_type == :concept ? concept_pattern : ENTITY_PATTERNS[entity_type]
27
27
  next unless pattern
28
28
 
29
29
  text.scan(pattern).each do |match|
30
- entities << { type: type_sym, value: match.strip, confidence: Confidence.apollo_setting(:entity_watchdog, :detect_confidence, default: 0.5) }
30
+ entities << { type: entity_type, value: match.strip,
31
+ confidence: Confidence.apollo_setting(:entity_watchdog, :detect_confidence, default: 0.5) }
31
32
  end
32
33
  end
33
34
 
@@ -55,11 +56,7 @@ module Legion
55
56
  end
56
57
 
57
58
  def concept_pattern
58
- keywords = if defined?(Legion::Settings)
59
- Legion::Settings.dig(:apollo, :entity_watchdog, :concept_keywords) || []
60
- else
61
- []
62
- end
59
+ keywords = settings[:entity_watchdog][:concept_keywords]
63
60
  return nil if keywords.empty?
64
61
 
65
62
  Regexp.new("\\b(?:#{keywords.map { |k| Regexp.escape(k) }.join('|')})\\b", Regexp::IGNORECASE)
@@ -68,11 +65,7 @@ module Legion
68
65
  private
69
66
 
70
67
  def default_types
71
- if defined?(Legion::Settings)
72
- Legion::Settings.dig(:apollo, :entity_watchdog, :types) || %w[person service repo concept]
73
- else
74
- %w[person service repo concept]
75
- end
68
+ settings[:entity_watchdog][:types]
76
69
  end
77
70
 
78
71
  def find_existing(_entity)
@@ -7,11 +7,10 @@ module Legion
7
7
  module Apollo
8
8
  module Helpers
9
9
  module Similarity
10
- module_function
10
+ extend Legion::Logging::Helper
11
+ extend Legion::JSON::Helper
11
12
 
12
- def log
13
- Legion::Logging
14
- end
13
+ module_function
15
14
 
16
15
  def cosine_similarity(vec_a:, vec_b:, **)
17
16
  vec_a = parse_vector(vec_a)
@@ -30,9 +29,9 @@ module Legion
30
29
  return vec if vec.is_a?(Array)
31
30
  return nil unless vec.is_a?(String)
32
31
 
33
- ::JSON.parse(vec)
32
+ json_parse(vec)
34
33
  rescue StandardError => e
35
- log.warn("Apollo Similarity.parse_vector failed: #{e.message}")
34
+ handle_exception(e, level: :warn, operation: 'apollo.similarity.parse_vector')
36
35
  nil
37
36
  end
38
37
 
@@ -8,16 +8,15 @@ module Legion
8
8
  module Apollo
9
9
  module Helpers
10
10
  module Writeback
11
+ extend Legion::Logging::Helper
12
+ extend Legion::Settings::Helper
13
+
11
14
  RESEARCH_TOOLS = %w[read_file search_files search_content run_command].freeze
12
15
  MAX_CONTENT_LENGTH = 4000
13
16
  MIN_CONTENT_LENGTH = 50
14
17
 
15
18
  module_function
16
19
 
17
- def log
18
- Legion::Logging
19
- end
20
-
21
20
  def evaluate_and_route(request:, response:, enrichments: {})
22
21
  return unless writeback_enabled?
23
22
  return unless should_capture?(request, response, enrichments)
@@ -25,7 +24,7 @@ module Legion
25
24
  payload = build_payload(request: request, response: response)
26
25
  route_payload(payload)
27
26
  rescue StandardError => e
28
- log.warn("apollo writeback failed: #{e.message}")
27
+ handle_exception(e, level: :warn, operation: 'apollo.writeback.evaluate_and_route')
29
28
  end
30
29
 
31
30
  def should_capture?(_request, response, enrichments)
@@ -68,7 +67,7 @@ module Legion
68
67
  can_write = Helpers::Capability.can_write?
69
68
 
70
69
  if can_embed
71
- result = Legion::LLM::Embeddings.generate(text: payload[:content])
70
+ result = Legion::LLM::Call::Embeddings.generate(text: payload[:content])
72
71
  vector = result.is_a?(Hash) ? result[:vector] : result
73
72
  payload[:embedding] = vector.is_a?(Array) && vector.any? ? vector : Array.new(1024, 0.0)
74
73
  end
@@ -87,7 +86,7 @@ module Legion
87
86
  Runners::Knowledge.handle_ingest(**payload)
88
87
  end
89
88
  rescue StandardError => e
90
- log.warn("apollo direct write failed, falling back to transport: #{e.message}")
89
+ handle_exception(e, level: :warn, operation: 'apollo.writeback.write_directly')
91
90
  publish_to_transport(payload, has_embedding: !payload[:embedding].nil?)
92
91
  end
93
92
 
@@ -98,20 +97,20 @@ module Legion
98
97
  **payload, has_embedding: has_embedding
99
98
  ).publish
100
99
  rescue StandardError => e
101
- log.warn("apollo writeback publish failed: #{e.message}")
100
+ handle_exception(e, level: :warn, operation: 'apollo.writeback.publish_to_transport')
102
101
  end
103
102
 
104
103
  def writeback_enabled?
105
- Legion::Settings.dig(:apollo, :writeback, :enabled) != false
104
+ settings[:writeback][:enabled] != false
106
105
  rescue StandardError => e
107
- log.warn("Apollo Writeback.writeback_enabled? failed: #{e.message}")
106
+ handle_exception(e, level: :warn, operation: 'apollo.writeback.writeback_enabled')
108
107
  true
109
108
  end
110
109
 
111
110
  def min_content_length
112
- Legion::Settings.dig(:apollo, :writeback, :min_content_length) || MIN_CONTENT_LENGTH
111
+ settings[:writeback][:min_content_length]
113
112
  rescue StandardError => e
114
- log.warn("Apollo Writeback.min_content_length failed: #{e.message}")
113
+ handle_exception(e, level: :warn, operation: 'apollo.writeback.min_content_length')
115
114
  MIN_CONTENT_LENGTH
116
115
  end
117
116
 
@@ -132,7 +131,7 @@ module Legion
132
131
 
133
132
  request.caller.dig(:requested_by, :identity) || 'unknown'
134
133
  rescue StandardError => e
135
- log.warn("Apollo Writeback.extract_identity failed: #{e.message}")
134
+ handle_exception(e, level: :warn, operation: 'apollo.writeback.extract_identity')
136
135
  'unknown'
137
136
  end
138
137
 
@@ -142,7 +141,7 @@ module Legion
142
141
  user_msgs = Array(request.messages).select { |m| m[:role] == 'user' || m['role'] == 'user' }
143
142
  (user_msgs.last || {})[:content] || ''
144
143
  rescue StandardError => e
145
- log.warn("Apollo Writeback.extract_user_query failed: #{e.message}")
144
+ handle_exception(e, level: :warn, operation: 'apollo.writeback.extract_user_query')
146
145
  ''
147
146
  end
148
147
 
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../helpers/data_models'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Apollo
@@ -18,15 +20,15 @@ module Legion
18
20
  end
19
21
 
20
22
  def aggregate(**)
21
- unless defined?(Legion::Data::Model::ApolloEntry)
23
+ unless Helpers::DataModels.apollo_entry_available?
22
24
  log.warn('Apollo Expertise.aggregate skipped: apollo_data_not_available')
23
25
  return { success: false, error: 'apollo_data_not_available' }
24
26
  end
25
27
 
26
- entries = Legion::Data::Model::ApolloEntry
27
- .select(:source_agent, :tags, :confidence)
28
- .exclude(source_agent: nil)
29
- .all
28
+ entries = Helpers::DataModels.apollo_entry
29
+ .select(:source_agent, :tags, :confidence)
30
+ .exclude(source_agent: nil)
31
+ .all
30
32
  log.debug("Apollo Expertise.aggregate entries=#{entries.size}")
31
33
 
32
34
  agent_set = Set.new
@@ -58,13 +60,13 @@ module Legion
58
60
  def upsert_expertise_group(group)
59
61
  count = group[:confidences].size
60
62
  proficiency = expertise_proficiency(group[:confidences])
61
- existing = Legion::Data::Model::ApolloExpertise
62
- .where(agent_id: group[:agent_id], domain: group[:domain]).first
63
+ existing = Helpers::DataModels.apollo_expertise
64
+ .where(agent_id: group[:agent_id], domain: group[:domain]).first
63
65
 
64
66
  if existing
65
67
  existing.update(proficiency: proficiency, entry_count: count, last_active_at: Time.now)
66
68
  else
67
- Legion::Data::Model::ApolloExpertise.create(
69
+ Helpers::DataModels.apollo_expertise.create(
68
70
  agent_id: group[:agent_id], domain: group[:domain],
69
71
  proficiency: proficiency, entry_count: count, last_active_at: Time.now
70
72
  )
@@ -6,7 +6,11 @@ module Legion
6
6
  module Runners
7
7
  module Gas
8
8
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
9
+ include Legion::Logging::Helper
10
+ include Legion::JSON::Helper
9
11
  extend Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
12
+ extend Legion::Logging::Helper
13
+ extend Legion::JSON::Helper
10
14
 
11
15
  RELATION_TYPES = %w[
12
16
  similar_to contradicts depends_on causes
@@ -20,7 +24,7 @@ module Legion
20
24
  module_function
21
25
 
22
26
  def json_load(str)
23
- ::JSON.parse(str, symbolize_names: true)
27
+ json_parse(str)
24
28
  end
25
29
 
26
30
  def relate_confidence_gate = Helpers::Confidence.apollo_setting(:gas, :relate_confidence_gate, default: RELATE_CONFIDENCE_GATE)
@@ -56,7 +60,7 @@ module Legion
56
60
  log.info("GAS process complete facts=#{result[:facts]} entities=#{result[:entities]} relations=#{result[:relations]} synthesis=#{result[:synthesis]} anticipations=#{result[:anticipations]}") # rubocop:disable Layout/LineLength
57
61
  result
58
62
  rescue StandardError => e
59
- log.error("GAS pipeline error: #{e.message}")
63
+ handle_exception(e, level: :error, operation: 'apollo.gas.process')
60
64
  { phases_completed: 0, error: e.message }
61
65
  end
62
66
 
@@ -88,7 +92,7 @@ module Legion
88
92
  log.debug("GAS phase_extract success=#{result[:success]} entities=#{entities.size}")
89
93
  entities
90
94
  rescue StandardError => e
91
- log.warn("GAS phase_extract failed: #{e.message}")
95
+ handle_exception(e, level: :warn, operation: 'apollo.gas.phase_extract')
92
96
  []
93
97
  end
94
98
 
@@ -131,7 +135,7 @@ module Legion
131
135
  log.debug("GAS phase_synthesize synthesis=#{synthesis.size}")
132
136
  synthesis
133
137
  rescue StandardError => e
134
- log.warn("GAS phase_synthesize failed: #{e.message}")
138
+ handle_exception(e, level: :warn, operation: 'apollo.gas.phase_synthesize')
135
139
  []
136
140
  end
137
141
 
@@ -155,7 +159,7 @@ module Legion
155
159
  )
156
160
  deposited += 1
157
161
  rescue StandardError => e
158
- log.warn("GAS deposit error: #{e.message}")
162
+ handle_exception(e, level: :warn, operation: 'apollo.gas.phase_deposit_fact')
159
163
  end
160
164
  log.info("GAS phase_deposit deposited=#{deposited} facts=#{facts.size}")
161
165
  { deposited: deposited }
@@ -176,7 +180,7 @@ module Legion
176
180
  log.debug("GAS phase_anticipate anticipations=#{anticipations.size}")
177
181
  anticipations
178
182
  rescue StandardError => e
179
- log.warn("GAS phase_anticipate failed: #{e.message}")
183
+ handle_exception(e, level: :warn, operation: 'apollo.gas.phase_anticipate')
180
184
  []
181
185
  end
182
186
 
@@ -188,7 +192,7 @@ module Legion
188
192
  result = Runners::Knowledge.retrieve_relevant(query: fact[:content], limit: lim, min_confidence: min_conf)
189
193
  entries.concat(result[:entries]) if result[:success] && result[:entries]&.any?
190
194
  rescue StandardError => e
191
- log.warn("GAS fetch_similar_entries failed for fact: #{e.message}")
195
+ handle_exception(e, level: :warn, operation: 'apollo.gas.fetch_similar_entries')
192
196
  next
193
197
  end
194
198
  unique = entries.uniq { |e| e[:id] }
@@ -204,11 +208,11 @@ module Legion
204
208
  { from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: fb_conf }
205
209
  end
206
210
  rescue StandardError => e
207
- log.warn("GAS classify_relation failed: #{e.message}")
211
+ handle_exception(e, level: :warn, operation: 'apollo.gas.classify_relation')
208
212
  { from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: fallback_confidence }
209
213
  end
210
214
 
211
- def llm_classify_relation(fact, entry)
215
+ def llm_classify_relation(fact, entry) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
212
216
  prompt = <<~PROMPT
213
217
  Classify the relationship between these two knowledge entries.
214
218
  Valid types: #{RELATION_TYPES.join(', ')}
@@ -241,9 +245,13 @@ module Legion
241
245
  phase: 'gas_relate'
242
246
  )
243
247
 
244
- content = result.respond_to?(:message) ? result.message[:content] : result.to_s
245
- parsed = json_load(content)
246
- rels = parsed.is_a?(Hash) ? (parsed[:relations] || parsed['relations'] || []) : []
248
+ rels = if result.is_a?(Hash) && result[:data].is_a?(Hash)
249
+ result[:data][:relations] || result.dig(:data, 'relations') || []
250
+ else
251
+ content = result.respond_to?(:message) ? result.message[:content] : result.to_s
252
+ parsed = json_load(content)
253
+ parsed.is_a?(Hash) ? (parsed[:relations] || parsed['relations'] || []) : []
254
+ end
247
255
  best = rels.max_by { |r| r[:confidence] || r['confidence'] || 0 }
248
256
 
249
257
  return fallback_relation(fact, entry) unless best
@@ -254,7 +262,7 @@ module Legion
254
262
 
255
263
  { from_content: fact[:content], to_id: entry[:id], relation_type: rtype, confidence: conf }
256
264
  rescue StandardError => e
257
- log.warn("GAS llm_classify_relation failed: #{e.message}")
265
+ handle_exception(e, level: :warn, operation: 'apollo.gas.llm_classify_relation')
258
266
  fallback_relation(fact, entry)
259
267
  end
260
268
 
@@ -298,13 +306,17 @@ module Legion
298
306
  phase: 'gas_synthesize'
299
307
  )
300
308
 
301
- content = result.respond_to?(:message) ? result.message[:content] : result.to_s
302
- parsed = json_load(content)
303
- items = parsed.is_a?(Hash) ? (parsed[:synthesis] || parsed['synthesis'] || []) : []
309
+ items = if result.is_a?(Hash) && result[:data].is_a?(Hash)
310
+ result[:data][:synthesis] || result.dig(:data, 'synthesis') || []
311
+ else
312
+ content = result.respond_to?(:message) ? result.message[:content] : result.to_s
313
+ parsed = json_load(content)
314
+ parsed.is_a?(Hash) ? (parsed[:synthesis] || parsed['synthesis'] || []) : []
315
+ end
304
316
 
305
317
  items.map { |item| build_synthesis_entry(item, facts) }
306
318
  rescue StandardError => e
307
- log.warn("GAS llm_synthesize failed: #{e.message}")
319
+ handle_exception(e, level: :warn, operation: 'apollo.gas.llm_synthesize')
308
320
  []
309
321
  end
310
322
 
@@ -354,9 +366,13 @@ module Legion
354
366
  phase: 'gas_anticipate'
355
367
  )
356
368
 
357
- content = result.respond_to?(:message) ? result.message[:content] : result.to_s
358
- parsed = json_load(content)
359
- questions = parsed.is_a?(Hash) ? (parsed[:questions] || parsed['questions'] || []) : []
369
+ questions = if result.is_a?(Hash) && result[:data].is_a?(Hash)
370
+ result[:data][:questions] || result.dig(:data, 'questions') || []
371
+ else
372
+ content = result.respond_to?(:message) ? result.message[:content] : result.to_s
373
+ parsed = json_load(content)
374
+ parsed.is_a?(Hash) ? (parsed[:questions] || parsed['questions'] || []) : []
375
+ end
360
376
  questions = questions.first(max_anticipations)
361
377
 
362
378
  questions.map do |q|
@@ -364,7 +380,7 @@ module Legion
364
380
  { question: q }
365
381
  end
366
382
  rescue StandardError => e
367
- log.warn("GAS llm_anticipate failed: #{e.message}")
383
+ handle_exception(e, level: :warn, operation: 'apollo.gas.llm_anticipate')
368
384
  []
369
385
  end
370
386
 
@@ -377,14 +393,14 @@ module Legion
377
393
  confidence: fallback_confidence
378
394
  )
379
395
  rescue StandardError => e
380
- log.warn("GAS promote_to_pattern_store failed: #{e.message}")
396
+ handle_exception(e, level: :warn, operation: 'apollo.gas.promote_to_pattern_store')
381
397
  nil
382
398
  end
383
399
 
384
400
  def llm_available?
385
401
  defined?(Legion::LLM::Pipeline::GaiaCaller)
386
402
  rescue StandardError => e
387
- log.warn("GAS llm_available? check failed: #{e.message}")
403
+ handle_exception(e, level: :warn, operation: 'apollo.gas.llm_available')
388
404
  false
389
405
  end
390
406
 
@@ -422,9 +438,13 @@ module Legion
422
438
  phase: 'gas_comprehend'
423
439
  )
424
440
 
425
- content = result.respond_to?(:message) ? result.message[:content] : result.to_s
426
- parsed = json_load(content)
427
- facts_array = parsed.is_a?(Hash) ? (parsed[:facts] || parsed['facts'] || []) : Array(parsed)
441
+ facts_array = if result.is_a?(Hash) && result[:data].is_a?(Hash)
442
+ result[:data][:facts] || result.dig(:data, 'facts') || []
443
+ else
444
+ content = result.respond_to?(:message) ? result.message[:content] : result.to_s
445
+ parsed = json_load(content)
446
+ parsed.is_a?(Hash) ? (parsed[:facts] || parsed['facts'] || []) : Array(parsed)
447
+ end
428
448
  facts_array.map do |f|
429
449
  {
430
450
  content: f[:content] || f['content'],
@@ -432,7 +452,7 @@ module Legion
432
452
  }
433
453
  end
434
454
  rescue StandardError => e
435
- log.warn("GAS llm_comprehend failed: #{e.message}")
455
+ handle_exception(e, level: :warn, operation: 'apollo.gas.llm_comprehend')
436
456
  mechanical_comprehend(messages, response)
437
457
  end
438
458
  end