lex-apollo 0.4.21 → 0.4.23

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -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 +56 -30
  13. data/lib/legion/extensions/apollo/gaia_integration.rb +13 -11
  14. data/lib/legion/extensions/apollo/helpers/capability.rb +12 -13
  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 +18 -14
  22. data/lib/legion/extensions/apollo/runners/knowledge.rb +77 -62
  23. data/lib/legion/extensions/apollo/runners/maintenance.rb +5 -4
  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 +84 -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 +19 -9
  34. data/spec/legion/extensions/apollo/runners/request_spec.rb +4 -4
  35. data/spec/spec_helper.rb +4 -0
  36. metadata +2 -1
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require 'legion/json'
4
+ require 'legion/settings'
4
5
  require_relative '../helpers/confidence'
6
+ require_relative '../helpers/data_models'
5
7
 
6
8
  module Legion
7
9
  module Extensions
@@ -26,9 +28,9 @@ module Legion
26
28
 
27
29
  def store_knowledge(content:, content_type:, tags: [], source_agent: nil, context: {}, **)
28
30
  content_type = normalize_content_type(content_type)
29
- log.debug("Apollo Knowledge.store_knowledge content_type=#{content_type} tags=#{Array(tags).size} source_agent=#{source_agent || 'nil'} data_available=#{defined?(Legion::Data::Model::ApolloEntry) ? true : false}") # rubocop:disable Layout/LineLength
31
+ log.debug("Apollo Knowledge.store_knowledge content_type=#{content_type} tags=#{Array(tags).size} source_agent=#{source_agent || 'nil'} data_available=#{Helpers::DataModels.apollo_entry_available?}") # rubocop:disable Layout/LineLength
30
32
 
31
- if defined?(Legion::Data::Model::ApolloEntry)
33
+ if Helpers::DataModels.apollo_entry_available?
32
34
  return handle_ingest(content: content, content_type: content_type,
33
35
  tags: Array(tags), source_agent: source_agent, context: context, **)
34
36
  end
@@ -44,8 +46,8 @@ module Legion
44
46
  end
45
47
 
46
48
  def query_knowledge(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: %i[confirmed candidate], tags: nil, **) # rubocop:disable Layout/LineLength
47
- log.debug("Apollo Knowledge.query_knowledge query_length=#{query.to_s.length} limit=#{limit} statuses=#{Array(status).join(',')} tags=#{Array(tags).size} data_available=#{defined?(Legion::Data::Model::ApolloEntry) ? true : false}") # rubocop:disable Layout/LineLength
48
- if defined?(Legion::Data::Model::ApolloEntry)
49
+ log.debug("Apollo Knowledge.query_knowledge query_length=#{query.to_s.length} limit=#{limit} statuses=#{Array(status).join(',')} tags=#{Array(tags).size} data_available=#{Helpers::DataModels.apollo_entry_available?}") # rubocop:disable Layout/LineLength
50
+ if Helpers::DataModels.apollo_entry_available?
49
51
  return handle_query(query: query, limit: limit, min_confidence: min_confidence,
50
52
  status: status, tags: tags, **)
51
53
  end
@@ -61,8 +63,8 @@ module Legion
61
63
  end
62
64
 
63
65
  def related_entries(entry_id:, relation_types: nil, depth: Helpers::GraphQuery.default_depth, **)
64
- log.debug("Apollo Knowledge.related_entries entry_id=#{entry_id} depth=#{depth} relation_types=#{Array(relation_types).join(',')} data_available=#{defined?(Legion::Data::Model::ApolloEntry) ? true : false}") # rubocop:disable Layout/LineLength
65
- return handle_traverse(entry_id: entry_id, depth: depth, relation_types: relation_types, **) if defined?(Legion::Data::Model::ApolloEntry)
66
+ log.debug("Apollo Knowledge.related_entries entry_id=#{entry_id} depth=#{depth} relation_types=#{Array(relation_types).join(',')} data_available=#{Helpers::DataModels.apollo_entry_available?}") # rubocop:disable Layout/LineLength
67
+ return handle_traverse(entry_id: entry_id, depth: depth, relation_types: relation_types, **) if Helpers::DataModels.apollo_entry_available?
66
68
 
67
69
  {
68
70
  action: :traverse,
@@ -84,6 +86,7 @@ module Legion
84
86
  return { status: :skipped } if skip
85
87
 
86
88
  content = normalize_text_input(content)
89
+ content_type = normalize_content_type(content_type.nil? ? :observation : content_type)
87
90
  log.debug("Apollo Knowledge.handle_ingest content_length=#{content.length} content_type=#{content_type} tags=#{Array(tags).size} source_agent=#{source_agent} source_channel=#{source_channel || 'nil'}") # rubocop:disable Layout/LineLength
88
91
  early_error = ingest_early_return_error(content: content, content_type: content_type, tags: tags)
89
92
  return early_error if early_error
@@ -116,7 +119,7 @@ module Legion
116
119
 
117
120
  upsert_expertise(source_agent: metadata[:source_agent], domain: metadata[:domain])
118
121
 
119
- Legion::Data::Model::ApolloAccessLog.create(
122
+ Helpers::DataModels.apollo_access_log.create(
120
123
  entry_id: existing_id, agent_id: metadata[:source_agent], action: 'ingest'
121
124
  )
122
125
 
@@ -131,8 +134,9 @@ module Legion
131
134
  end
132
135
 
133
136
  def handle_query(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: UNSET, tags: nil, domain: nil, agent_id: 'unknown', **) # rubocop:disable Layout/LineLength
134
- return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
137
+ return { success: false, error: 'apollo_data_not_available' } unless Helpers::DataModels.apollo_entry_available?
135
138
 
139
+ entry_model = Helpers::DataModels.apollo_entry
136
140
  query = normalize_text_input(query)
137
141
  status_defaulted = status.equal?(UNSET)
138
142
  requested_status = status_defaulted ? DEFAULT_QUERY_STATUS : status
@@ -148,26 +152,16 @@ module Legion
148
152
  statuses: Array(requested_status).map(&:to_s), tags: tags, domain: domain
149
153
  )
150
154
 
151
- db = Legion::Data::Model::ApolloEntry.db
155
+ db = entry_model.db
152
156
  entries = db.fetch(sql, embedding: Sequel.lit("'[#{embedding.join(',')}]'::vector")).all
153
157
 
154
158
  entries = entries.reject { |e| e[:distance].respond_to?(:nan?) && e[:distance].nan? }
155
159
 
156
160
  entries.each do |entry|
157
- Legion::Data::Model::ApolloEntry.where(id: entry[:id]).update(
158
- access_count: Sequel.expr(:access_count) + 1,
159
- confidence: Helpers::Confidence.apply_retrieval_boost(
160
- confidence: entry[:confidence]
161
- ),
162
- updated_at: Time.now
163
- )
161
+ boost_entry_after_query(entry_model, entry)
164
162
  end
165
163
 
166
- if entries.any?
167
- Legion::Data::Model::ApolloAccessLog.create(
168
- entry_id: entries.first&.dig(:id), agent_id: agent_id, action: 'query'
169
- )
170
- end
164
+ record_query_access(entry_id: entries.first&.dig(:id), agent_id: agent_id) if entries.any?
171
165
 
172
166
  formatted = entries.map do |entry|
173
167
  { id: entry[:id], content: entry[:content], content_type: entry[:content_type],
@@ -184,7 +178,7 @@ module Legion
184
178
  end
185
179
 
186
180
  def handle_traverse(entry_id:, depth: Helpers::GraphQuery.default_depth, relation_types: nil, agent_id: 'unknown', **)
187
- return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
181
+ return { success: false, error: 'apollo_data_not_available' } unless Helpers::DataModels.apollo_entry_available?
188
182
 
189
183
  log.debug("Apollo Knowledge.handle_traverse entry_id=#{entry_id} depth=#{depth} relation_types=#{Array(relation_types).join(',')} agent_id=#{agent_id}") # rubocop:disable Layout/LineLength
190
184
  # Whitelist relation_types to prevent SQL injection (they are string-interpolated in build_traversal_sql)
@@ -194,11 +188,11 @@ module Legion
194
188
  end
195
189
 
196
190
  sql = Helpers::GraphQuery.build_traversal_sql(depth: depth, relation_types: relation_types)
197
- db = Legion::Data::Model::ApolloEntry.db
191
+ db = Helpers::DataModels.apollo_entry.db
198
192
  entries = db.fetch(sql, entry_id: entry_id).all
199
193
 
200
194
  if entries.any? && agent_id != 'unknown'
201
- Legion::Data::Model::ApolloAccessLog.create(
195
+ Helpers::DataModels.apollo_access_log.create(
202
196
  entry_id: entry_id, agent_id: agent_id, action: 'query'
203
197
  )
204
198
  end
@@ -217,13 +211,13 @@ module Legion
217
211
  end
218
212
 
219
213
  def redistribute_knowledge(agent_id:, min_confidence: Helpers::Confidence.apollo_setting(:query, :redistribute_min_confidence, default: 0.5), **)
220
- return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
214
+ return { success: false, error: 'apollo_data_not_available' } unless Helpers::DataModels.apollo_entry_available?
221
215
 
222
216
  log.debug("Apollo Knowledge.redistribute_knowledge agent_id=#{agent_id} min_confidence=#{min_confidence}")
223
- entries = Legion::Data::Model::ApolloEntry
224
- .where(source_agent: agent_id, status: 'confirmed')
225
- .where { confidence > min_confidence }
226
- .all
217
+ entries = Helpers::DataModels.apollo_entry
218
+ .where(source_agent: agent_id, status: 'confirmed')
219
+ .where { confidence > min_confidence }
220
+ .all
227
221
 
228
222
  return { success: true, redistributed: 0 } if entries.empty?
229
223
 
@@ -254,7 +248,7 @@ module Legion
254
248
  def retrieve_relevant(query: nil, limit: Helpers::Confidence.apollo_setting(:query, :retrieval_limit, default: 5), min_confidence: Helpers::GraphQuery.default_query_min_confidence, tags: nil, domain: nil, skip: false, **) # rubocop:disable Layout/LineLength
255
249
  return { status: :skipped } if skip
256
250
 
257
- return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
251
+ return { success: false, error: 'apollo_data_not_available' } unless Helpers::DataModels.apollo_entry_available?
258
252
 
259
253
  query = normalize_text_input(query)
260
254
  log.debug("Apollo Knowledge.retrieve_relevant query_length=#{query.length} limit=#{limit} min_confidence=#{min_confidence} tags=#{Array(tags).size} domain=#{domain || 'nil'}") # rubocop:disable Layout/LineLength
@@ -266,12 +260,12 @@ module Legion
266
260
  statuses: %w[confirmed candidate], tags: tags, domain: domain
267
261
  )
268
262
 
269
- db = Legion::Data::Model::ApolloEntry.db
263
+ db = Helpers::DataModels.apollo_entry.db
270
264
  entries = db.fetch(sql, embedding: Sequel.lit("'[#{embedding.join(',')}]'::vector")).all
271
265
  entries = entries.reject { |e| e[:distance].respond_to?(:nan?) && e[:distance].nan? }
272
266
 
273
267
  entries.each do |entry|
274
- Legion::Data::Model::ApolloEntry.where(id: entry[:id]).update(
268
+ Helpers::DataModels.apollo_entry.where(id: entry[:id]).update(
275
269
  confidence: Helpers::Confidence.apply_retrieval_boost(confidence: entry[:confidence]),
276
270
  updated_at: Time.now
277
271
  )
@@ -366,7 +360,7 @@ module Legion
366
360
  return { success: false, error: 'content_type is required' }
367
361
  end
368
362
 
369
- return nil if defined?(Legion::Data::Model::ApolloEntry)
363
+ return nil if Helpers::DataModels.apollo_entry_available?
370
364
 
371
365
  log.warn('[apollo][handle_ingest] early-return: apollo_data_not_available ' \
372
366
  "content_type=#{content_type}")
@@ -382,7 +376,7 @@ module Legion
382
376
  def embed_text(text)
383
377
  text = normalize_text_input(text)
384
378
  log.debug("Apollo Knowledge.embed_text text_length=#{text.length}")
385
- result = Legion::LLM::Embeddings.generate(text: text)
379
+ result = Legion::LLM::Call::Embeddings.generate(text: text)
386
380
  vector = result.is_a?(Hash) ? result[:vector] : result
387
381
  if vector.is_a?(Array) && vector.any?
388
382
  log.debug("Apollo Knowledge.embed_text vector_dimensions=#{vector.length}")
@@ -392,7 +386,7 @@ module Legion
392
386
  Array.new(1024, 0.0)
393
387
  end
394
388
  rescue StandardError => e
395
- log.warn("Apollo Knowledge.embed_text failed: #{e.message}")
389
+ handle_exception(e, level: :warn, operation: 'apollo.knowledge.embed_text')
396
390
  Array.new(1024, 0.0)
397
391
  end
398
392
 
@@ -405,7 +399,7 @@ module Legion
405
399
 
406
400
  sanitize_for_postgres(result)
407
401
  rescue StandardError => e
408
- log.warn("Apollo Knowledge.normalize_text_input failed: #{e.message}")
402
+ handle_exception(e, level: :warn, operation: 'apollo.knowledge.normalize_text_input')
409
403
  ''
410
404
  end
411
405
 
@@ -429,10 +423,10 @@ module Legion
429
423
  def active_duplicate_for_hash(hash)
430
424
  return nil unless hash
431
425
 
432
- existing = Legion::Data::Model::ApolloEntry
433
- .where(content_hash: hash)
434
- .exclude(status: 'archived')
435
- .first
426
+ existing = Helpers::DataModels.apollo_entry
427
+ .where(content_hash: hash)
428
+ .exclude(status: 'archived')
429
+ .first
436
430
  existing&.update(confidence: [existing.confidence + Helpers::Confidence.retrieval_boost, 1.0].min)
437
431
  log.debug("Apollo Knowledge.active_duplicate_for_hash matched entry_id=#{existing.id}") if existing
438
432
  existing
@@ -452,14 +446,14 @@ module Legion
452
446
  end
453
447
 
454
448
  def create_candidate_entry(content:, content_type:, context:, metadata:, content_hash:, embedding:)
455
- new_entry = Legion::Data::Model::ApolloEntry.create(
449
+ new_entry = Helpers::DataModels.apollo_entry.create(
456
450
  content: content,
457
451
  content_type: content_type,
458
452
  confidence: Helpers::Confidence.initial_confidence,
459
453
  source_agent: metadata[:source_agent],
460
454
  source_provider: metadata[:source_provider],
461
455
  source_channel: metadata[:source_channel],
462
- source_context: ::JSON.dump(context.is_a?(Hash) ? context : {}),
456
+ source_context: json_dump(context.is_a?(Hash) ? context : {}),
463
457
  tags: Sequel.pg_array(metadata[:tags]),
464
458
  status: 'candidate',
465
459
  knowledge_domain: metadata[:domain],
@@ -478,7 +472,7 @@ module Legion
478
472
 
479
473
  def list_entries_chronologically(query:, limit:, status:, status_defaulted:, tags:, domain:)
480
474
  log.debug("Apollo Knowledge.list_entries_chronologically limit=#{limit} statuses=#{Array(status).join(',')} status_defaulted=#{status_defaulted} tags=#{Array(tags).size} domain=#{domain || 'nil'}") # rubocop:disable Layout/LineLength
481
- dataset = Legion::Data::Model::ApolloEntry.exclude(status: 'archived')
475
+ dataset = Helpers::DataModels.apollo_entry.exclude(status: 'archived')
482
476
  requested = Array(status).map(&:to_s).reject(&:empty?)
483
477
  dataset = dataset.where(status: requested) unless status_defaulted || requested.empty?
484
478
  dataset = dataset.where(Sequel.lit('tags && ?', Sequel.pg_array(Array(tags)))) if tags && !Array(tags).empty?
@@ -501,12 +495,28 @@ module Legion
501
495
  knowledge_domain: entry[:knowledge_domain] }
502
496
  end
503
497
 
498
+ def record_query_access(entry_id:, agent_id:)
499
+ Helpers::DataModels.apollo_access_log.create(
500
+ entry_id: entry_id, agent_id: agent_id, action: 'query'
501
+ )
502
+ end
503
+
504
+ def boost_entry_after_query(entry_model, entry)
505
+ entry_model.where(id: entry[:id]).update(
506
+ access_count: Sequel.expr(:access_count) + 1,
507
+ confidence: Helpers::Confidence.apply_retrieval_boost(
508
+ confidence: entry[:confidence]
509
+ ),
510
+ updated_at: Time.now
511
+ )
512
+ end
513
+
514
+ def settings
515
+ Legion::Extensions::Apollo.settings
516
+ end
517
+
504
518
  def allowed_domains_for(target_domain)
505
- rules = if defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :domain_isolation)
506
- Legion::Settings.dig(:apollo, :domain_isolation)
507
- else
508
- DOMAIN_ISOLATION
509
- end
519
+ rules = settings[:domain_isolation]
510
520
 
511
521
  allowed = rules[target_domain]
512
522
  return :all if allowed == :all || allowed.nil?
@@ -515,13 +525,13 @@ module Legion
515
525
  end
516
526
 
517
527
  def detect_contradictions(entry_id, embedding, content)
518
- return [] unless embedding && defined?(Legion::Data::Model::ApolloEntry)
528
+ return [] unless embedding && Helpers::DataModels.apollo_entry_available?
519
529
 
520
530
  sim_limit = Helpers::Confidence.apollo_setting(:contradiction, :similar_limit, default: 10)
521
531
  sim_threshold = Helpers::Confidence.apollo_setting(:contradiction, :similarity_threshold, default: 0.7)
522
532
  rel_weight = Helpers::Confidence.apollo_setting(:contradiction, :relation_weight, default: 0.8)
523
533
 
524
- db = Legion::Data::Model::ApolloEntry.db
534
+ db = Helpers::DataModels.apollo_entry.db
525
535
  log.debug("Apollo Knowledge.detect_contradictions entry_id=#{entry_id} similar_limit=#{sim_limit} threshold=#{sim_threshold}")
526
536
  similar = db.fetch(
527
537
  "SELECT id, content, embedding FROM apollo_entries WHERE id != :entry_id AND embedding IS NOT NULL ORDER BY embedding <=> :embedding LIMIT #{sim_limit}", # rubocop:disable Layout/LineLength
@@ -538,13 +548,13 @@ module Legion
538
548
  next unless sim > sim_threshold
539
549
  next unless llm_detects_conflict?(content, existing[:content])
540
550
 
541
- Legion::Data::Model::ApolloRelation.create(
551
+ Helpers::DataModels.apollo_relation.create(
542
552
  from_entry_id: entry_id, to_entry_id: existing[:id],
543
553
  relation_type: 'contradicts', source_agent: 'system:contradiction',
544
554
  weight: rel_weight
545
555
  )
546
556
 
547
- Legion::Data::Model::ApolloEntry.where(id: [entry_id, existing[:id]]).update(status: 'disputed')
557
+ Helpers::DataModels.apollo_entry.where(id: [entry_id, existing[:id]]).update(status: 'disputed')
548
558
  contradictions << existing[:id]
549
559
  end
550
560
  log.info("Apollo Knowledge.detect_contradictions entry_id=#{entry_id} contradictions=#{contradictions.size}") if contradictions.any?
@@ -569,17 +579,17 @@ module Legion
569
579
  )
570
580
  result[:data]&.dig(:contradicts) == true
571
581
  rescue StandardError => e
572
- log.warn("Apollo Knowledge.llm_detects_conflict? failed: #{e.message}")
582
+ handle_exception(e, level: :warn, operation: 'apollo.knowledge.llm_detects_conflict')
573
583
  false
574
584
  end
575
585
 
576
586
  def find_corroboration(embedding, content_type_sym, source_agent, source_channel = nil)
577
587
  scan_limit = Helpers::Confidence.apollo_setting(:corroboration, :scan_limit, default: 50)
578
588
  log.debug("Apollo Knowledge.find_corroboration content_type=#{content_type_sym} source_agent=#{source_agent} source_channel=#{source_channel || 'nil'} scan_limit=#{scan_limit}") # rubocop:disable Layout/LineLength
579
- existing = Legion::Data::Model::ApolloEntry
580
- .where(content_type: content_type_sym)
581
- .exclude(embedding: nil)
582
- .limit(scan_limit)
589
+ existing = Helpers::DataModels.apollo_entry
590
+ .where(content_type: content_type_sym)
591
+ .exclude(embedding: nil)
592
+ .limit(scan_limit)
583
593
 
584
594
  existing.each do |entry|
585
595
  next unless entry.embedding
@@ -601,7 +611,7 @@ module Legion
601
611
  confidence: Helpers::Confidence.apply_corroboration_boost(confidence: entry.confidence, weight: weight),
602
612
  updated_at: Time.now
603
613
  )
604
- Legion::Data::Model::ApolloRelation.create(
614
+ Helpers::DataModels.apollo_relation.create(
605
615
  from_entry_id: entry.id,
606
616
  to_entry_id: entry.id,
607
617
  relation_type: 'similar_to',
@@ -632,12 +642,12 @@ module Legion
632
642
 
633
643
  def upsert_expertise(source_agent:, domain:)
634
644
  log.debug("Apollo Knowledge.upsert_expertise source_agent=#{source_agent} domain=#{domain}")
635
- expertise = Legion::Data::Model::ApolloExpertise
636
- .where(agent_id: source_agent, domain: domain).first
645
+ expertise = Helpers::DataModels.apollo_expertise
646
+ .where(agent_id: source_agent, domain: domain).first
637
647
  if expertise
638
648
  expertise.update(entry_count: expertise.entry_count + 1, last_active_at: Time.now)
639
649
  else
640
- Legion::Data::Model::ApolloExpertise.create(
650
+ Helpers::DataModels.apollo_expertise.create(
641
651
  agent_id: source_agent, domain: domain,
642
652
  proficiency: Helpers::Confidence.apollo_setting(:expertise, :initial_proficiency, default: 0.0),
643
653
  entry_count: 1, last_active_at: Time.now
@@ -646,6 +656,11 @@ module Legion
646
656
  end
647
657
 
648
658
  include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
659
+ include Legion::JSON::Helper
660
+ include Legion::Settings::Helper
661
+ extend Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
662
+ extend Legion::JSON::Helper
663
+ extend Legion::Settings::Helper
649
664
  end
650
665
  end
651
666
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../helpers/confidence'
4
+ require_relative '../helpers/data_models'
4
5
  require_relative '../helpers/similarity'
5
6
 
6
7
  module Legion
@@ -65,13 +66,13 @@ module Legion
65
66
  end
66
67
 
67
68
  def check_corroboration(**) # rubocop:disable Metrics/CyclomaticComplexity
68
- unless defined?(Legion::Data::Model::ApolloEntry)
69
+ unless Helpers::DataModels.apollo_entry_available?
69
70
  log.warn('Apollo Maintenance.check_corroboration skipped: apollo_data_not_available')
70
71
  return { success: false, error: 'apollo_data_not_available' }
71
72
  end
72
73
 
73
- candidates = Legion::Data::Model::ApolloEntry.where(status: 'candidate').exclude(embedding: nil).all
74
- confirmed = Legion::Data::Model::ApolloEntry.where(status: 'confirmed').exclude(embedding: nil).all
74
+ candidates = Helpers::DataModels.apollo_entry.where(status: 'candidate').exclude(embedding: nil).all
75
+ confirmed = Helpers::DataModels.apollo_entry.where(status: 'confirmed').exclude(embedding: nil).all
75
76
  log.debug("Apollo Maintenance.check_corroboration candidates=#{candidates.size} confirmed=#{confirmed.size}")
76
77
 
77
78
  promoted = 0
@@ -104,7 +105,7 @@ module Legion
104
105
  updated_at: Time.now
105
106
  )
106
107
 
107
- Legion::Data::Model::ApolloRelation.create(
108
+ Helpers::DataModels.apollo_relation.create(
108
109
  from_entry_id: candidate.id,
109
110
  to_entry_id: match.id,
110
111
  relation_type: 'similar_to',
@@ -1,10 +1,14 @@
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
6
8
  module Runners
7
9
  module Request
10
+ include Legion::Logging::Helper
11
+ extend Legion::Logging::Helper
8
12
  extend self
9
13
 
10
14
  def self.data_required?
@@ -67,7 +71,7 @@ module Legion
67
71
  end
68
72
 
69
73
  def local_service_available?
70
- defined?(Legion::Data::Model::ApolloEntry) &&
74
+ Helpers::DataModels.apollo_entry_available? &&
71
75
  defined?(Knowledge)
72
76
  end
73
77
 
@@ -81,6 +85,7 @@ module Legion
81
85
  Transport::Messages::Query.new(payload).publish
82
86
  { success: true, dispatched: :transport, payload: payload }
83
87
  rescue StandardError => e
88
+ handle_exception(e, level: :warn, operation: 'apollo.request.publish_query')
84
89
  { success: false, error: e.message }
85
90
  end
86
91
 
@@ -88,6 +93,7 @@ module Legion
88
93
  Transport::Messages::Ingest.new(payload).publish
89
94
  { success: true, dispatched: :transport, payload: payload }
90
95
  rescue StandardError => e
96
+ handle_exception(e, level: :warn, operation: 'apollo.request.publish_ingest')
91
97
  { success: false, error: e.message }
92
98
  end
93
99
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module Apollo
6
- VERSION = '0.4.21'
6
+ VERSION = '0.4.23'
7
7
  end
8
8
  end
9
9
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/logging'
4
+ require 'legion/settings'
5
+ require 'legion/json'
3
6
  require 'legion/extensions/apollo/version'
4
7
  require 'legion/extensions/apollo/helpers/confidence'
5
8
  require 'legion/extensions/apollo/helpers/similarity'
@@ -7,6 +10,7 @@ require 'legion/extensions/apollo/helpers/graph_query'
7
10
  require 'legion/extensions/apollo/helpers/tag_normalizer'
8
11
  require 'legion/extensions/apollo/helpers/capability'
9
12
  require 'legion/extensions/apollo/helpers/writeback'
13
+ require 'legion/extensions/apollo/helpers/data_models'
10
14
  require 'legion/extensions/apollo/runners/knowledge'
11
15
  require 'legion/extensions/apollo/runners/expertise'
12
16
  require 'legion/extensions/apollo/runners/maintenance'
@@ -32,11 +36,103 @@ end
32
36
  module Legion
33
37
  module Extensions
34
38
  module Apollo
39
+ extend Legion::Logging::Helper
40
+ extend Legion::Settings::Helper
35
41
  extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core, false
36
42
 
37
43
  def self.remote_invocable?
38
44
  false
39
45
  end
46
+
47
+ def self.default_settings # rubocop:disable Metrics/MethodLength
48
+ {
49
+ confidence: {
50
+ initial: 0.5,
51
+ corroboration_boost: 0.3,
52
+ retrieval_boost: 0.02,
53
+ write_gate: 0.6,
54
+ novelty_gate: 0.3,
55
+ corroboration_similarity: 0.9
56
+ },
57
+ power_law_alpha: 0.05,
58
+ decay_threshold: 0.05,
59
+ stale_days: 90,
60
+ decay_min_age_hours: 168,
61
+ graph: {
62
+ spread_factor: 0.6,
63
+ default_depth: 2,
64
+ min_activation: 0.1
65
+ },
66
+ query: {
67
+ default_limit: 10,
68
+ default_min_confidence: 0.3,
69
+ retrieval_limit: 5,
70
+ redistribute_min_confidence: 0.5,
71
+ mesh_export_min_confidence: 0.5,
72
+ mesh_export_limit: 100
73
+ },
74
+ gas: {
75
+ relate_confidence_gate: 0.7,
76
+ synthesis_confidence_cap: 0.7,
77
+ max_anticipations: 3,
78
+ similar_entries_limit: 3,
79
+ fallback_confidence: 0.5
80
+ },
81
+ maintenance: {
82
+ force_decay_factor: 0.5
83
+ },
84
+ contradiction: {
85
+ similar_limit: 10,
86
+ similarity_threshold: 0.7,
87
+ relation_weight: 0.8
88
+ },
89
+ corroboration: {
90
+ relation_weight: 1.0,
91
+ scan_limit: 50,
92
+ same_provider_weight: 0.5
93
+ },
94
+ expertise: {
95
+ initial_proficiency: 0.0,
96
+ min_agents_at_risk: 2,
97
+ proficiency_cap: 1.0
98
+ },
99
+ entity_extractor: {
100
+ min_confidence: 0.7
101
+ },
102
+ entity_watchdog: {
103
+ concept_keywords: [],
104
+ types: %w[person service repository concept],
105
+ detect_confidence: 0.5,
106
+ exists_min_confidence: 0.1,
107
+ min_confidence: 0.7,
108
+ dedup_threshold: 0.92,
109
+ lookback_seconds: 300,
110
+ log_limit: 50
111
+ },
112
+ domain_isolation: {
113
+ 'claims_optimization' => ['claims_optimization'],
114
+ 'clinical_care' => %w[clinical_care general],
115
+ 'general' => :all
116
+ },
117
+ embedding: {
118
+ provider: nil,
119
+ model: nil
120
+ },
121
+ data: {
122
+ apollo_write: false
123
+ },
124
+ writeback: {
125
+ enabled: true,
126
+ min_content_length: 50
127
+ },
128
+ actors: {
129
+ decay_interval: 3600,
130
+ expertise_interval: 1800,
131
+ corroboration_interval: 900,
132
+ entity_watchdog_interval: 120
133
+ }
134
+ }
135
+ end
40
136
  end
41
137
  end
42
138
  end
@@ -53,7 +53,7 @@ RSpec.describe Legion::Extensions::Apollo::Actor::WritebackVectorize do
53
53
  { vector: Array.new(1024, 0.1), model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 }
54
54
  end
55
55
  end
56
- stub_const('Legion::LLM::Embeddings', embeddings_mod)
56
+ stub_const('Legion::LLM::Call::Embeddings', embeddings_mod)
57
57
  end
58
58
 
59
59
  describe '#runner_function' do
@@ -66,7 +66,7 @@ RSpec.describe Legion::Extensions::Apollo::Actor::WritebackVectorize do
66
66
  let(:payload) { { content: 'test content', content_type: 'observation', tags: %w[test] } }
67
67
 
68
68
  before do
69
- allow(Legion::LLM::Embeddings).to receive(:generate)
69
+ allow(Legion::LLM::Call::Embeddings).to receive(:generate)
70
70
  .and_return({ vector: [0.1] * 1024, model: 'test', provider: :ollama, dimensions: 1024, tokens: 0 })
71
71
  allow(Legion::Extensions::Apollo::Helpers::Capability).to receive(:can_write?).and_return(false)
72
72
  end
@@ -92,7 +92,7 @@ RSpec.describe Legion::Extensions::Apollo::Actor::WritebackVectorize do
92
92
  end
93
93
 
94
94
  it 'returns error hash on failure' do
95
- allow(Legion::LLM::Embeddings).to receive(:generate).and_raise(RuntimeError, 'boom')
95
+ allow(Legion::LLM::Call::Embeddings).to receive(:generate).and_raise(RuntimeError, 'boom')
96
96
 
97
97
  result = actor.handle_vectorize(payload)
98
98
  expect(result[:success]).to be false