lex-apollo 0.4.3 → 0.4.4
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 +15 -0
- data/lib/legion/extensions/apollo/actors/corroboration_checker.rb +1 -1
- data/lib/legion/extensions/apollo/actors/decay.rb +1 -1
- data/lib/legion/extensions/apollo/actors/entity_watchdog.rb +6 -4
- data/lib/legion/extensions/apollo/actors/expertise_aggregator.rb +1 -1
- data/lib/legion/extensions/apollo/helpers/confidence.rb +26 -5
- data/lib/legion/extensions/apollo/helpers/entity_watchdog.rb +1 -1
- data/lib/legion/extensions/apollo/helpers/graph_query.rb +12 -4
- data/lib/legion/extensions/apollo/runners/entity_extractor.rb +1 -1
- data/lib/legion/extensions/apollo/runners/expertise.rb +4 -3
- data/lib/legion/extensions/apollo/runners/gas.rb +28 -20
- data/lib/legion/extensions/apollo/runners/knowledge.rb +20 -13
- data/lib/legion/extensions/apollo/runners/maintenance.rb +5 -19
- data/lib/legion/extensions/apollo/version.rb +1 -1
- data/spec/legion/extensions/apollo/runners/decay_cycle_spec.rb +5 -7
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 98c5141f161198efe7dbdd37179e46bf36ec1c58404efa645897bc256975d3f8
|
|
4
|
+
data.tar.gz: c2ac6728af5ada2ab45eb8fff0a0e80d6bceac5464305bca18bd836c81fc0ffb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45328d181484cabb6a17aceb9d6d1a816a623fbe1dc8f5c80972b9151a2250f244d88ba88551cf0cecac70229732546a35abe32189a2ff75a4026010279a810e
|
|
7
|
+
data.tar.gz: 1eab2286abce601fe8a95b75c0da2edb0017bebb39e09abe05a1c7dbf72658e7955147b2ab1e1b6ed743788ed61a86c4871919e7ab53762ef3e2e40906eea00f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.4] - 2026-03-24
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- All ~35 hardcoded numeric thresholds, limits, intervals, and confidence values are now configurable via `Legion::Settings[:apollo]`
|
|
7
|
+
- `Helpers::Confidence` gains `apollo_setting(*keys, default:)` lookup helper and accessor methods for every constant (initial_confidence, corroboration_boost, retrieval_boost, power_law_alpha, decay_threshold, write gates, stale_days)
|
|
8
|
+
- `Helpers::GraphQuery` gains `spread_factor`, `default_depth`, `min_activation`, `default_query_limit`, `default_query_min_confidence` settings-aware accessors
|
|
9
|
+
- `Runners::Gas` gains `relate_confidence_gate`, `synthesis_confidence_cap`, `max_anticipations`, `similar_entries_limit`, `fallback_confidence` settings-aware accessors
|
|
10
|
+
- `Runners::Knowledge` method defaults (query limits, min_confidence, contradiction/corroboration params) now read from Settings
|
|
11
|
+
- `Runners::Maintenance` removed duplicate private helpers, delegates to `Helpers::Confidence` methods
|
|
12
|
+
- `Runners::Expertise` proficiency cap and min_agents_at_risk configurable
|
|
13
|
+
- Actor intervals (Decay, ExpertiseAggregator, CorroborationChecker, EntityWatchdog) configurable via `apollo.actors.*_interval`
|
|
14
|
+
- EntityWatchdog lookback_seconds, log_limit, exists_min_confidence configurable
|
|
15
|
+
- EntityExtractor min_confidence configurable via `apollo.entity_extractor.min_confidence`
|
|
16
|
+
- Helpers::EntityWatchdog detect_confidence configurable
|
|
17
|
+
|
|
3
18
|
## [0.4.3] - 2026-03-24
|
|
4
19
|
|
|
5
20
|
### Fixed
|
|
@@ -10,7 +10,7 @@ module Legion
|
|
|
10
10
|
class CorroborationChecker < Legion::Extensions::Actors::Every
|
|
11
11
|
def runner_class = Legion::Extensions::Apollo::Runners::Maintenance
|
|
12
12
|
def runner_function = 'check_corroboration'
|
|
13
|
-
def time = 900
|
|
13
|
+
def time = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :actors, :corroboration_interval)) || 900
|
|
14
14
|
def run_now? = false
|
|
15
15
|
def use_runner? = false
|
|
16
16
|
def check_subtask? = false
|
|
@@ -10,7 +10,7 @@ module Legion
|
|
|
10
10
|
class Decay < Legion::Extensions::Actors::Every
|
|
11
11
|
def runner_class = Legion::Extensions::Apollo::Runners::Maintenance
|
|
12
12
|
def runner_function = 'run_decay_cycle'
|
|
13
|
-
def time = 3600
|
|
13
|
+
def time = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :actors, :decay_interval)) || 3600
|
|
14
14
|
def run_now? = false
|
|
15
15
|
def use_runner? = false
|
|
16
16
|
def check_subtask? = false
|
|
@@ -18,7 +18,7 @@ module Legion
|
|
|
18
18
|
|
|
19
19
|
def runner_class = self.class
|
|
20
20
|
def runner_function = 'scan_and_ingest'
|
|
21
|
-
def time = 120
|
|
21
|
+
def time = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :actors, :entity_watchdog_interval)) || 120
|
|
22
22
|
def run_now? = false
|
|
23
23
|
def use_runner? = false
|
|
24
24
|
def check_subtask? = false
|
|
@@ -62,11 +62,13 @@ module Legion
|
|
|
62
62
|
def recent_task_log_texts
|
|
63
63
|
return [] unless defined?(Legion::Data) && defined?(Legion::Data::Model::TaskLog)
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
lookback = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :entity_watchdog, :lookback_seconds)) || TASK_LOG_LOOKBACK_SECONDS
|
|
66
|
+
log_limit = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :entity_watchdog, :log_limit)) || TASK_LOG_LIMIT
|
|
67
|
+
cutoff = Time.now - lookback
|
|
66
68
|
logs = Legion::Data::Model::TaskLog
|
|
67
69
|
.where { created_at >= cutoff }
|
|
68
70
|
.order(Sequel.desc(:created_at))
|
|
69
|
-
.limit(
|
|
71
|
+
.limit(log_limit)
|
|
70
72
|
.select_map(:message)
|
|
71
73
|
logs.map(&:to_s).reject(&:empty?).uniq
|
|
72
74
|
rescue StandardError
|
|
@@ -77,7 +79,7 @@ module Legion
|
|
|
77
79
|
result = retrieve_relevant(
|
|
78
80
|
query: entity[:name].to_s,
|
|
79
81
|
limit: 1,
|
|
80
|
-
min_confidence: 0.1,
|
|
82
|
+
min_confidence: Helpers::Confidence.apollo_setting(:entity_watchdog, :exists_min_confidence, default: 0.1),
|
|
81
83
|
tags: [entity[:type].to_s]
|
|
82
84
|
)
|
|
83
85
|
return false unless result[:success] && result[:count].positive?
|
|
@@ -10,7 +10,7 @@ module Legion
|
|
|
10
10
|
class ExpertiseAggregator < Legion::Extensions::Actors::Every
|
|
11
11
|
def runner_class = Legion::Extensions::Apollo::Runners::Expertise
|
|
12
12
|
def runner_function = 'aggregate'
|
|
13
|
-
def time = 1800
|
|
13
|
+
def time = (defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :actors, :expertise_interval)) || 1800
|
|
14
14
|
def run_now? = false
|
|
15
15
|
def use_runner? = false
|
|
16
16
|
def check_subtask? = false
|
|
@@ -20,7 +20,28 @@ module Legion
|
|
|
20
20
|
|
|
21
21
|
module_function
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def apollo_setting(*keys, default:)
|
|
24
|
+
return default unless defined?(Legion::Settings) && !Legion::Settings[:apollo].nil?
|
|
25
|
+
|
|
26
|
+
Legion::Settings[:apollo].dig(*keys) || default
|
|
27
|
+
rescue StandardError
|
|
28
|
+
default
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initial_confidence = apollo_setting(:confidence, :initial, default: INITIAL_CONFIDENCE)
|
|
32
|
+
def corroboration_boost = apollo_setting(:confidence, :corroboration_boost, default: CORROBORATION_BOOST)
|
|
33
|
+
def retrieval_boost = apollo_setting(:confidence, :retrieval_boost, default: RETRIEVAL_BOOST)
|
|
34
|
+
def power_law_alpha = apollo_setting(:power_law_alpha, default: POWER_LAW_ALPHA)
|
|
35
|
+
def decay_threshold = apollo_setting(:decay_threshold, default: DECAY_THRESHOLD)
|
|
36
|
+
def write_confidence_gate = apollo_setting(:confidence, :write_gate, default: WRITE_CONFIDENCE_GATE)
|
|
37
|
+
def write_novelty_gate = apollo_setting(:confidence, :novelty_gate, default: WRITE_NOVELTY_GATE)
|
|
38
|
+
def stale_days = apollo_setting(:stale_days, default: STALE_DAYS)
|
|
39
|
+
|
|
40
|
+
def corroboration_similarity_threshold
|
|
41
|
+
apollo_setting(:confidence, :corroboration_similarity, default: CORROBORATION_SIMILARITY_THRESHOLD)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def apply_decay(confidence:, age_hours: nil, alpha: power_law_alpha, **)
|
|
24
45
|
if age_hours
|
|
25
46
|
[confidence * ((age_hours.clamp(0, Float::INFINITY) + 2.0)**(-alpha)) / ((age_hours.clamp(0, Float::INFINITY) + 1.0)**(-alpha)), 0.0].max
|
|
26
47
|
else
|
|
@@ -30,19 +51,19 @@ module Legion
|
|
|
30
51
|
end
|
|
31
52
|
|
|
32
53
|
def apply_retrieval_boost(confidence:, **)
|
|
33
|
-
[confidence +
|
|
54
|
+
[confidence + retrieval_boost, 1.0].min
|
|
34
55
|
end
|
|
35
56
|
|
|
36
57
|
def apply_corroboration_boost(confidence:, weight: 1.0, **)
|
|
37
|
-
[confidence + (
|
|
58
|
+
[confidence + (corroboration_boost * weight), 1.0].min
|
|
38
59
|
end
|
|
39
60
|
|
|
40
61
|
def decayed?(confidence:, **)
|
|
41
|
-
confidence <
|
|
62
|
+
confidence < decay_threshold
|
|
42
63
|
end
|
|
43
64
|
|
|
44
65
|
def meets_write_gate?(confidence:, novelty:, **)
|
|
45
|
-
confidence >
|
|
66
|
+
confidence > write_confidence_gate && novelty > write_novelty_gate
|
|
46
67
|
end
|
|
47
68
|
end
|
|
48
69
|
end
|
|
@@ -23,7 +23,7 @@ module Legion
|
|
|
23
23
|
next unless pattern
|
|
24
24
|
|
|
25
25
|
text.scan(pattern).each do |match|
|
|
26
|
-
entities << { type: type_sym, value: match.strip, confidence: 0.5 }
|
|
26
|
+
entities << { type: type_sym, value: match.strip, confidence: Confidence.apollo_setting(:entity_watchdog, :detect_confidence, default: 0.5) }
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -11,7 +11,15 @@ module Legion
|
|
|
11
11
|
|
|
12
12
|
module_function
|
|
13
13
|
|
|
14
|
-
def
|
|
14
|
+
def spread_factor = Confidence.apollo_setting(:graph, :spread_factor, default: SPREAD_FACTOR)
|
|
15
|
+
def default_depth = Confidence.apollo_setting(:graph, :default_depth, default: DEFAULT_DEPTH)
|
|
16
|
+
def min_activation = Confidence.apollo_setting(:graph, :min_activation, default: MIN_ACTIVATION)
|
|
17
|
+
|
|
18
|
+
def default_query_limit = Confidence.apollo_setting(:query, :default_limit, default: 10)
|
|
19
|
+
def default_query_min_confidence = Confidence.apollo_setting(:query, :default_min_confidence, default: 0.3)
|
|
20
|
+
|
|
21
|
+
def build_traversal_sql(depth: default_depth, relation_types: nil, min_activation: self.min_activation, **)
|
|
22
|
+
sf = spread_factor
|
|
15
23
|
type_filter = if relation_types&.any?
|
|
16
24
|
types = relation_types.map { |t| "'#{t}'" }.join(', ')
|
|
17
25
|
"AND r.relation_type IN (#{types})"
|
|
@@ -30,12 +38,12 @@ module Legion
|
|
|
30
38
|
|
|
31
39
|
SELECT e.id, e.content, e.content_type, e.confidence, e.tags, e.source_agent,
|
|
32
40
|
g.depth + 1,
|
|
33
|
-
(g.activation * #{
|
|
41
|
+
(g.activation * #{sf} * r.weight)::float
|
|
34
42
|
FROM graph g
|
|
35
43
|
JOIN apollo_relations r ON r.from_entry_id = g.id #{type_filter}
|
|
36
44
|
JOIN apollo_entries e ON e.id = r.to_entry_id
|
|
37
45
|
WHERE g.depth < #{depth}
|
|
38
|
-
AND g.activation * #{
|
|
46
|
+
AND g.activation * #{sf} * r.weight > #{min_activation}
|
|
39
47
|
)
|
|
40
48
|
SELECT DISTINCT ON (id) id, content, content_type, confidence, tags, source_agent,
|
|
41
49
|
depth, activation
|
|
@@ -44,7 +52,7 @@ module Legion
|
|
|
44
52
|
SQL
|
|
45
53
|
end
|
|
46
54
|
|
|
47
|
-
def build_semantic_search_sql(limit:
|
|
55
|
+
def build_semantic_search_sql(limit: default_query_limit, min_confidence: default_query_min_confidence, statuses: nil, tags: nil, domain: nil, **)
|
|
48
56
|
conditions = ["e.confidence >= #{min_confidence}"]
|
|
49
57
|
|
|
50
58
|
if statuses&.any?
|
|
@@ -8,7 +8,7 @@ module Legion
|
|
|
8
8
|
DEFAULT_ENTITY_TYPES = %w[person service repository concept].freeze
|
|
9
9
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
10
10
|
|
|
11
|
-
def extract_entities(text:, entity_types: nil, min_confidence: DEFAULT_MIN_CONFIDENCE, **)
|
|
11
|
+
def extract_entities(text:, entity_types: nil, min_confidence: Helpers::Confidence.apollo_setting(:entity_extractor, :min_confidence, default: DEFAULT_MIN_CONFIDENCE), **) # rubocop:disable Layout/LineLength
|
|
12
12
|
return { success: true, entities: [], source: :empty } if text.to_s.strip.empty?
|
|
13
13
|
|
|
14
14
|
return { success: true, entities: [], source: :unavailable } unless defined?(Legion::LLM) && Legion::LLM.started?
|
|
@@ -5,11 +5,11 @@ module Legion
|
|
|
5
5
|
module Apollo
|
|
6
6
|
module Runners
|
|
7
7
|
module Expertise
|
|
8
|
-
def get_expertise(domain:, min_proficiency: 0.0, **)
|
|
8
|
+
def get_expertise(domain:, min_proficiency: Helpers::Confidence.apollo_setting(:expertise, :initial_proficiency, default: 0.0), **)
|
|
9
9
|
{ action: :expertise_query, domain: domain, min_proficiency: min_proficiency }
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def domains_at_risk(min_agents: 2, **)
|
|
12
|
+
def domains_at_risk(min_agents: Helpers::Confidence.apollo_setting(:expertise, :min_agents_at_risk, default: 2), **)
|
|
13
13
|
{ action: :domains_at_risk, min_agents: min_agents }
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -40,7 +40,8 @@ module Legion
|
|
|
40
40
|
groups.each_value do |group|
|
|
41
41
|
avg = group[:confidences].sum / group[:confidences].size
|
|
42
42
|
count = group[:confidences].size
|
|
43
|
-
|
|
43
|
+
cap = Helpers::Confidence.apollo_setting(:expertise, :proficiency_cap, default: 1.0)
|
|
44
|
+
proficiency = [avg * Math.log2(count + 1), cap].min
|
|
44
45
|
|
|
45
46
|
existing = Legion::Data::Model::ApolloExpertise
|
|
46
47
|
.where(agent_id: group[:agent_id], domain: group[:domain]).first
|
|
@@ -5,8 +5,23 @@ module Legion
|
|
|
5
5
|
module Apollo
|
|
6
6
|
module Runners
|
|
7
7
|
module Gas
|
|
8
|
+
RELATION_TYPES = %w[
|
|
9
|
+
similar_to contradicts depends_on causes
|
|
10
|
+
part_of supersedes supports_by extends
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
RELATE_CONFIDENCE_GATE = 0.7
|
|
14
|
+
SYNTHESIS_CONFIDENCE_CAP = 0.7
|
|
15
|
+
MAX_ANTICIPATIONS = 3
|
|
16
|
+
|
|
8
17
|
module_function
|
|
9
18
|
|
|
19
|
+
def relate_confidence_gate = Helpers::Confidence.apollo_setting(:gas, :relate_confidence_gate, default: RELATE_CONFIDENCE_GATE)
|
|
20
|
+
def synthesis_confidence_cap = Helpers::Confidence.apollo_setting(:gas, :synthesis_confidence_cap, default: SYNTHESIS_CONFIDENCE_CAP)
|
|
21
|
+
def max_anticipations = Helpers::Confidence.apollo_setting(:gas, :max_anticipations, default: MAX_ANTICIPATIONS)
|
|
22
|
+
def similar_entries_limit = Helpers::Confidence.apollo_setting(:gas, :similar_entries_limit, default: 3)
|
|
23
|
+
def fallback_confidence = Helpers::Confidence.apollo_setting(:gas, :fallback_confidence, default: 0.5)
|
|
24
|
+
|
|
10
25
|
def process(audit_event)
|
|
11
26
|
return { phases_completed: 0, reason: 'no content' } unless processable?(audit_event)
|
|
12
27
|
|
|
@@ -57,13 +72,6 @@ module Legion
|
|
|
57
72
|
[]
|
|
58
73
|
end
|
|
59
74
|
|
|
60
|
-
RELATION_TYPES = %w[
|
|
61
|
-
similar_to contradicts depends_on causes
|
|
62
|
-
part_of supersedes supports_by extends
|
|
63
|
-
].freeze
|
|
64
|
-
|
|
65
|
-
RELATE_CONFIDENCE_GATE = 0.7
|
|
66
|
-
|
|
67
75
|
# Phase 3: Relate - classify relationships between new and existing entries
|
|
68
76
|
def phase_relate(facts, _entities)
|
|
69
77
|
return [] unless defined?(Runners::Knowledge)
|
|
@@ -81,8 +89,6 @@ module Legion
|
|
|
81
89
|
relations
|
|
82
90
|
end
|
|
83
91
|
|
|
84
|
-
SYNTHESIS_CONFIDENCE_CAP = 0.7
|
|
85
|
-
|
|
86
92
|
# Phase 4: Synthesize - generate derivative knowledge
|
|
87
93
|
def phase_synthesize(facts, _relations)
|
|
88
94
|
return [] if facts.length < 2
|
|
@@ -115,8 +121,6 @@ module Legion
|
|
|
115
121
|
{ deposited: deposited }
|
|
116
122
|
end
|
|
117
123
|
|
|
118
|
-
MAX_ANTICIPATIONS = 3
|
|
119
|
-
|
|
120
124
|
# Phase 6: Anticipate - pre-cache likely follow-up questions
|
|
121
125
|
def phase_anticipate(facts, _synthesis)
|
|
122
126
|
return [] if facts.empty?
|
|
@@ -128,9 +132,11 @@ module Legion
|
|
|
128
132
|
end
|
|
129
133
|
|
|
130
134
|
def fetch_similar_entries(facts)
|
|
135
|
+
lim = similar_entries_limit
|
|
136
|
+
min_conf = Helpers::GraphQuery.default_query_min_confidence
|
|
131
137
|
entries = []
|
|
132
138
|
facts.each do |fact|
|
|
133
|
-
result = Runners::Knowledge.retrieve_relevant(query: fact[:content], limit:
|
|
139
|
+
result = Runners::Knowledge.retrieve_relevant(query: fact[:content], limit: lim, min_confidence: min_conf)
|
|
134
140
|
entries.concat(result[:entries]) if result[:success] && result[:entries]&.any?
|
|
135
141
|
rescue StandardError
|
|
136
142
|
next
|
|
@@ -139,13 +145,14 @@ module Legion
|
|
|
139
145
|
end
|
|
140
146
|
|
|
141
147
|
def classify_relation(fact, entry)
|
|
148
|
+
fb_conf = fallback_confidence
|
|
142
149
|
if llm_available?
|
|
143
150
|
llm_classify_relation(fact, entry)
|
|
144
151
|
else
|
|
145
|
-
{ from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence:
|
|
152
|
+
{ from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: fb_conf }
|
|
146
153
|
end
|
|
147
154
|
rescue StandardError
|
|
148
|
-
{ from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence:
|
|
155
|
+
{ from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: fb_conf }
|
|
149
156
|
end
|
|
150
157
|
|
|
151
158
|
def llm_classify_relation(fact, entry)
|
|
@@ -190,7 +197,7 @@ module Legion
|
|
|
190
197
|
|
|
191
198
|
conf = best[:confidence] || best['confidence'] || 0
|
|
192
199
|
rtype = best[:relation_type] || best['relation_type']
|
|
193
|
-
return fallback_relation(fact, entry) if conf <
|
|
200
|
+
return fallback_relation(fact, entry) if conf < relate_confidence_gate || !RELATION_TYPES.include?(rtype)
|
|
194
201
|
|
|
195
202
|
{ from_content: fact[:content], to_id: entry[:id], relation_type: rtype, confidence: conf }
|
|
196
203
|
rescue StandardError
|
|
@@ -198,7 +205,7 @@ module Legion
|
|
|
198
205
|
end
|
|
199
206
|
|
|
200
207
|
def fallback_relation(fact, entry)
|
|
201
|
-
{ from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence:
|
|
208
|
+
{ from_content: fact[:content], to_id: entry[:id], relation_type: 'similar_to', confidence: fallback_confidence }
|
|
202
209
|
end
|
|
203
210
|
|
|
204
211
|
def llm_synthesize(facts)
|
|
@@ -249,13 +256,14 @@ module Legion
|
|
|
249
256
|
def build_synthesis_entry(item, facts)
|
|
250
257
|
source_indices = item[:source_indices] || item['source_indices'] || []
|
|
251
258
|
source_confs = source_indices.filter_map { |i| facts[i]&.dig(:confidence) }
|
|
252
|
-
|
|
259
|
+
fb = fallback_confidence
|
|
260
|
+
geo_mean = source_confs.empty? ? fb : geometric_mean(source_confs)
|
|
253
261
|
|
|
254
262
|
{
|
|
255
263
|
content: item[:content] || item['content'],
|
|
256
264
|
content_type: (item[:content_type] || item['content_type'] || 'inference').to_sym,
|
|
257
265
|
status: :candidate,
|
|
258
|
-
confidence: [geo_mean,
|
|
266
|
+
confidence: [geo_mean, synthesis_confidence_cap].min,
|
|
259
267
|
source_indices: source_indices
|
|
260
268
|
}
|
|
261
269
|
end
|
|
@@ -294,7 +302,7 @@ module Legion
|
|
|
294
302
|
content = result.respond_to?(:message) ? result.message[:content] : result.to_s
|
|
295
303
|
parsed = Legion::JSON.load(content)
|
|
296
304
|
questions = parsed.is_a?(Hash) ? (parsed[:questions] || parsed['questions'] || []) : []
|
|
297
|
-
questions = questions.first(
|
|
305
|
+
questions = questions.first(max_anticipations)
|
|
298
306
|
|
|
299
307
|
questions.map do |q|
|
|
300
308
|
promote_to_pattern_store(question: q, facts: facts)
|
|
@@ -310,7 +318,7 @@ module Legion
|
|
|
310
318
|
Legion::Extensions::Agentic::TBI::PatternStore.promote_candidate(
|
|
311
319
|
intent: question,
|
|
312
320
|
resolution: { source: 'gas_anticipate', facts: facts.map { |f| f[:content] } },
|
|
313
|
-
confidence:
|
|
321
|
+
confidence: fallback_confidence
|
|
314
322
|
)
|
|
315
323
|
rescue StandardError
|
|
316
324
|
nil
|
|
@@ -31,7 +31,7 @@ module Legion
|
|
|
31
31
|
}
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def query_knowledge(query:, limit:
|
|
34
|
+
def query_knowledge(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: [:confirmed], tags: nil, **) # rubocop:disable Layout/LineLength
|
|
35
35
|
{
|
|
36
36
|
action: :query,
|
|
37
37
|
query: query,
|
|
@@ -42,7 +42,7 @@ module Legion
|
|
|
42
42
|
}
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def related_entries(entry_id:, relation_types: nil, depth:
|
|
45
|
+
def related_entries(entry_id:, relation_types: nil, depth: Helpers::GraphQuery.default_depth, **)
|
|
46
46
|
{
|
|
47
47
|
action: :traverse,
|
|
48
48
|
entry_id: entry_id,
|
|
@@ -73,7 +73,7 @@ module Legion
|
|
|
73
73
|
new_entry = Legion::Data::Model::ApolloEntry.create(
|
|
74
74
|
content: content,
|
|
75
75
|
content_type: content_type_sym,
|
|
76
|
-
confidence: Helpers::Confidence
|
|
76
|
+
confidence: Helpers::Confidence.initial_confidence,
|
|
77
77
|
source_agent: source_agent,
|
|
78
78
|
source_provider: source_provider || derive_provider_from_agent(source_agent),
|
|
79
79
|
source_channel: source_channel,
|
|
@@ -100,7 +100,7 @@ module Legion
|
|
|
100
100
|
{ success: false, error: e.message }
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
def handle_query(query:, limit:
|
|
103
|
+
def handle_query(query:, limit: Helpers::GraphQuery.default_query_limit, min_confidence: Helpers::GraphQuery.default_query_min_confidence, status: [:confirmed], tags: nil, domain: nil, agent_id: 'unknown', **) # rubocop:disable Metrics/ParameterLists, Layout/LineLength
|
|
104
104
|
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
105
105
|
|
|
106
106
|
embedding = Helpers::Embedding.generate(text: query)
|
|
@@ -140,7 +140,7 @@ module Legion
|
|
|
140
140
|
{ success: false, error: e.message }
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
-
def redistribute_knowledge(agent_id:, min_confidence: 0.5, **)
|
|
143
|
+
def redistribute_knowledge(agent_id:, min_confidence: Helpers::Confidence.apollo_setting(:query, :redistribute_min_confidence, default: 0.5), **)
|
|
144
144
|
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
145
145
|
|
|
146
146
|
entries = Legion::Data::Model::ApolloEntry
|
|
@@ -173,7 +173,7 @@ module Legion
|
|
|
173
173
|
{ success: false, error: e.message }
|
|
174
174
|
end
|
|
175
175
|
|
|
176
|
-
def retrieve_relevant(query: nil, limit: 5, min_confidence:
|
|
176
|
+
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 Metrics/ParameterLists, Layout/LineLength
|
|
177
177
|
return { status: :skipped } if skip
|
|
178
178
|
|
|
179
179
|
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
@@ -208,7 +208,7 @@ module Legion
|
|
|
208
208
|
{ success: false, error: e.message }
|
|
209
209
|
end
|
|
210
210
|
|
|
211
|
-
def prepare_mesh_export(target_domain:, min_confidence: 0.5, limit: 100, **)
|
|
211
|
+
def prepare_mesh_export(target_domain:, min_confidence: Helpers::Confidence.apollo_setting(:query, :mesh_export_min_confidence, default: 0.5), limit: Helpers::Confidence.apollo_setting(:query, :mesh_export_limit, default: 100), **) # rubocop:disable Layout/LineLength
|
|
212
212
|
unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
|
|
213
213
|
return { success: false, error: 'apollo_data_not_available' }
|
|
214
214
|
end
|
|
@@ -277,21 +277,25 @@ module Legion
|
|
|
277
277
|
def detect_contradictions(entry_id, embedding, content)
|
|
278
278
|
return [] unless embedding && defined?(Legion::Data::Model::ApolloEntry)
|
|
279
279
|
|
|
280
|
+
sim_limit = Helpers::Confidence.apollo_setting(:contradiction, :similar_limit, default: 10)
|
|
281
|
+
sim_threshold = Helpers::Confidence.apollo_setting(:contradiction, :similarity_threshold, default: 0.7)
|
|
282
|
+
rel_weight = Helpers::Confidence.apollo_setting(:contradiction, :relation_weight, default: 0.8)
|
|
283
|
+
|
|
280
284
|
similar = Legion::Data::Model::ApolloEntry
|
|
281
285
|
.exclude(id: entry_id)
|
|
282
286
|
.exclude(embedding: nil)
|
|
283
|
-
.limit(
|
|
287
|
+
.limit(sim_limit).all
|
|
284
288
|
|
|
285
289
|
contradictions = []
|
|
286
290
|
similar.each do |existing|
|
|
287
291
|
sim = Helpers::Similarity.cosine_similarity(vec_a: embedding, vec_b: existing.embedding)
|
|
288
|
-
next unless sim >
|
|
292
|
+
next unless sim > sim_threshold
|
|
289
293
|
next unless llm_detects_conflict?(content, existing.content)
|
|
290
294
|
|
|
291
295
|
Legion::Data::Model::ApolloRelation.create(
|
|
292
296
|
from_entry_id: entry_id, to_entry_id: existing.id,
|
|
293
297
|
relation_type: 'contradicts', source_agent: 'system:contradiction',
|
|
294
|
-
weight:
|
|
298
|
+
weight: rel_weight
|
|
295
299
|
)
|
|
296
300
|
|
|
297
301
|
Legion::Data::Model::ApolloEntry.where(id: [entry_id, existing.id]).update(status: 'disputed')
|
|
@@ -319,10 +323,11 @@ module Legion
|
|
|
319
323
|
end
|
|
320
324
|
|
|
321
325
|
def find_corroboration(embedding, content_type_sym, source_agent, source_channel = nil)
|
|
326
|
+
scan_limit = Helpers::Confidence.apollo_setting(:corroboration, :scan_limit, default: 50)
|
|
322
327
|
existing = Legion::Data::Model::ApolloEntry
|
|
323
328
|
.where(content_type: content_type_sym)
|
|
324
329
|
.exclude(embedding: nil)
|
|
325
|
-
.limit(
|
|
330
|
+
.limit(scan_limit)
|
|
326
331
|
|
|
327
332
|
existing.each do |entry|
|
|
328
333
|
next unless entry.embedding
|
|
@@ -330,7 +335,8 @@ module Legion
|
|
|
330
335
|
sim = Helpers::Similarity.cosine_similarity(vec_a: embedding, vec_b: entry.embedding)
|
|
331
336
|
next unless Helpers::Similarity.above_corroboration_threshold?(similarity: sim)
|
|
332
337
|
|
|
333
|
-
|
|
338
|
+
same_provider_wt = Helpers::Confidence.apollo_setting(:corroboration, :same_provider_weight, default: 0.5)
|
|
339
|
+
weight = same_source_provider?(source_agent, entry) ? same_provider_wt : 1.0
|
|
334
340
|
|
|
335
341
|
# Reject corroboration entirely if same channel (same data source)
|
|
336
342
|
if source_channel && entry.respond_to?(:source_channel)
|
|
@@ -377,7 +383,8 @@ module Legion
|
|
|
377
383
|
expertise.update(entry_count: expertise.entry_count + 1, last_active_at: Time.now)
|
|
378
384
|
else
|
|
379
385
|
Legion::Data::Model::ApolloExpertise.create(
|
|
380
|
-
agent_id: source_agent, domain: domain,
|
|
386
|
+
agent_id: source_agent, domain: domain,
|
|
387
|
+
proficiency: Helpers::Confidence.apollo_setting(:expertise, :initial_proficiency, default: 0.0),
|
|
381
388
|
entry_count: 1, last_active_at: Time.now
|
|
382
389
|
)
|
|
383
390
|
end
|
|
@@ -9,11 +9,11 @@ module Legion
|
|
|
9
9
|
module Apollo
|
|
10
10
|
module Runners
|
|
11
11
|
module Maintenance
|
|
12
|
-
def force_decay(factor: 0.5, **)
|
|
12
|
+
def force_decay(factor: Helpers::Confidence.apollo_setting(:maintenance, :force_decay_factor, default: 0.5), **)
|
|
13
13
|
{ action: :force_decay, factor: factor }
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def archive_stale(days:
|
|
16
|
+
def archive_stale(days: Helpers::Confidence.stale_days, **)
|
|
17
17
|
{ action: :archive_stale, days: days }
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -22,17 +22,13 @@ module Legion
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def run_decay_cycle(alpha: nil, min_confidence: nil, **)
|
|
25
|
-
alpha ||=
|
|
26
|
-
min_confidence ||= decay_threshold
|
|
25
|
+
alpha ||= Helpers::Confidence.power_law_alpha
|
|
26
|
+
min_confidence ||= Helpers::Confidence.decay_threshold
|
|
27
27
|
|
|
28
28
|
return { decayed: 0, archived: 0 } unless defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && Legion::Data.connection
|
|
29
29
|
|
|
30
30
|
conn = Legion::Data.connection
|
|
31
31
|
|
|
32
|
-
# Power-law: per-cycle decay factor decreases as entries age
|
|
33
|
-
# Factor = (hours_old / (hours_old + 1)) ^ alpha
|
|
34
|
-
# Recent entries (small hours_old) get a factor closer to 0 (more decay)
|
|
35
|
-
# Old entries (large hours_old) get a factor closer to 1 (less decay)
|
|
36
32
|
hours_expr = Sequel.lit(
|
|
37
33
|
'GREATEST(EXTRACT(EPOCH FROM (NOW() - COALESCE(updated_at, created_at))) / 3600.0, 1.0)'
|
|
38
34
|
)
|
|
@@ -79,7 +75,6 @@ module Legion
|
|
|
79
75
|
both_known = known_provider?(candidate_provider) && known_provider?(match_provider)
|
|
80
76
|
next if both_known && candidate_provider == match_provider
|
|
81
77
|
|
|
82
|
-
# Also reject if same source_channel (same data pipeline)
|
|
83
78
|
candidate_channel = candidate.respond_to?(:source_channel) ? candidate.source_channel : nil
|
|
84
79
|
match_channel = match.respond_to?(:source_channel) ? match.source_channel : nil
|
|
85
80
|
next if candidate_channel && match_channel && candidate_channel == match_channel
|
|
@@ -96,7 +91,7 @@ module Legion
|
|
|
96
91
|
to_entry_id: match.id,
|
|
97
92
|
relation_type: 'similar_to',
|
|
98
93
|
source_agent: 'system:corroboration',
|
|
99
|
-
weight: 1.0
|
|
94
|
+
weight: Helpers::Confidence.apollo_setting(:corroboration, :relation_weight, default: 1.0)
|
|
100
95
|
)
|
|
101
96
|
|
|
102
97
|
promoted += 1
|
|
@@ -109,19 +104,10 @@ module Legion
|
|
|
109
104
|
|
|
110
105
|
private
|
|
111
106
|
|
|
112
|
-
def decay_alpha
|
|
113
|
-
(defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :power_law_alpha)) ||
|
|
114
|
-
Helpers::Confidence::POWER_LAW_ALPHA
|
|
115
|
-
end
|
|
116
|
-
|
|
117
107
|
def known_provider?(provider)
|
|
118
108
|
!provider.nil? && !provider.to_s.empty? && provider.to_s != 'unknown'
|
|
119
109
|
end
|
|
120
110
|
|
|
121
|
-
def decay_threshold
|
|
122
|
-
(defined?(Legion::Settings) && Legion::Settings.dig(:apollo, :decay_threshold)) || 0.1
|
|
123
|
-
end
|
|
124
|
-
|
|
125
111
|
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
126
112
|
end
|
|
127
113
|
end
|
|
@@ -12,15 +12,13 @@ RSpec.describe 'Apollo Decay Cycle' do
|
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
describe '
|
|
16
|
-
it 'returns POWER_LAW_ALPHA
|
|
17
|
-
expect(
|
|
15
|
+
describe 'configurable decay parameters' do
|
|
16
|
+
it 'returns POWER_LAW_ALPHA as default' do
|
|
17
|
+
expect(Legion::Extensions::Apollo::Helpers::Confidence.power_law_alpha).to eq(0.5)
|
|
18
18
|
end
|
|
19
|
-
end
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
expect(maintenance.send(:decay_threshold)).to eq(0.1)
|
|
20
|
+
it 'returns default decay threshold' do
|
|
21
|
+
expect(Legion::Extensions::Apollo::Helpers::Confidence.decay_threshold).to eq(0.1)
|
|
24
22
|
end
|
|
25
23
|
end
|
|
26
24
|
end
|