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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/lib/legion/extensions/apollo/actors/corroboration_checker.rb +3 -1
- data/lib/legion/extensions/apollo/actors/decay.rb +3 -1
- data/lib/legion/extensions/apollo/actors/entity_watchdog.rb +10 -21
- data/lib/legion/extensions/apollo/actors/expertise_aggregator.rb +3 -1
- data/lib/legion/extensions/apollo/actors/gas_subscriber.rb +1 -1
- data/lib/legion/extensions/apollo/actors/ingest.rb +1 -1
- data/lib/legion/extensions/apollo/actors/query_responder.rb +1 -1
- data/lib/legion/extensions/apollo/actors/writeback_store.rb +1 -1
- data/lib/legion/extensions/apollo/actors/writeback_vectorize.rb +2 -2
- data/lib/legion/extensions/apollo/api.rb +56 -30
- data/lib/legion/extensions/apollo/gaia_integration.rb +13 -11
- data/lib/legion/extensions/apollo/helpers/capability.rb +12 -13
- data/lib/legion/extensions/apollo/helpers/confidence.rb +5 -8
- data/lib/legion/extensions/apollo/helpers/data_models.rb +61 -0
- data/lib/legion/extensions/apollo/helpers/entity_watchdog.rb +8 -15
- data/lib/legion/extensions/apollo/helpers/similarity.rb +5 -6
- data/lib/legion/extensions/apollo/helpers/writeback.rb +13 -14
- data/lib/legion/extensions/apollo/runners/expertise.rb +10 -8
- data/lib/legion/extensions/apollo/runners/gas.rb +18 -14
- data/lib/legion/extensions/apollo/runners/knowledge.rb +77 -62
- data/lib/legion/extensions/apollo/runners/maintenance.rb +5 -4
- data/lib/legion/extensions/apollo/runners/request.rb +7 -1
- data/lib/legion/extensions/apollo/version.rb +1 -1
- data/lib/legion/extensions/apollo.rb +96 -0
- data/spec/legion/extensions/apollo/actors/writeback_vectorize_spec.rb +3 -3
- data/spec/legion/extensions/apollo/api_spec.rb +84 -0
- data/spec/legion/extensions/apollo/helpers/capability_spec.rb +4 -4
- data/spec/legion/extensions/apollo/runners/gas_anticipate_spec.rb +0 -3
- data/spec/legion/extensions/apollo/runners/gas_relate_spec.rb +0 -4
- data/spec/legion/extensions/apollo/runners/gas_synthesize_spec.rb +0 -11
- data/spec/legion/extensions/apollo/runners/knowledge_spec.rb +19 -9
- data/spec/legion/extensions/apollo/runners/request_spec.rb +4 -4
- data/spec/spec_helper.rb +4 -0
- 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=#{
|
|
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
|
|
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=#{
|
|
48
|
-
if
|
|
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=#{
|
|
65
|
-
return handle_traverse(entry_id: entry_id, depth: depth, relation_types: relation_types, **) if
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 &&
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
|
|
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 =
|
|
636
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
74
|
-
confirmed =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|