lex-apollo 0.4.3 → 0.4.5
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 +30 -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/api.rb +157 -0
- 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 +63 -21
- data/lib/legion/extensions/apollo/runners/maintenance.rb +5 -19
- data/lib/legion/extensions/apollo/runners/request.rb +97 -0
- data/lib/legion/extensions/apollo/version.rb +1 -1
- data/lib/legion/extensions/apollo.rb +3 -0
- data/spec/legion/extensions/apollo/api_spec.rb +28 -0
- data/spec/legion/extensions/apollo/contradiction_spec.rb +32 -0
- data/spec/legion/extensions/apollo/runners/decay_cycle_spec.rb +5 -7
- data/spec/legion/extensions/apollo/runners/knowledge_spec.rb +81 -2
- data/spec/legion/extensions/apollo/runners/request_spec.rb +186 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 692cd508a98259d83ceaf2c6b752c7c87ddeb219145291bd568fa76430e41577
|
|
4
|
+
data.tar.gz: b02962423b3dff2950af8b51b0cf5035a8db72f6c2ef80c4f1e54f9f0989e25c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c4989d252540ae09177a73f389a1f4d1a09c51aee09acdf88bdfbb9ffb3e5d7c6522e8cf0953117518011f199f97f1e76dacb6d88aa2f10a48ecfe1da064497
|
|
7
|
+
data.tar.gz: a806f1485d4f3ef587f2fbd015180c027934ac87b080c4e0264ccb12a478708d89d0aa313ac3c9b2f20ed3c64ec779e2468ec7b457967c7cb6c1ce780324a884
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.5] - 2026-03-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `handle_traverse` method in Knowledge runner: executes recursive CTE graph traversal SQL, returns formatted entries with depth and activation scores (closes #3)
|
|
7
|
+
- `Runners::Request` client-tier runner with `data_required? false`: routes `query`, `retrieve`, `ingest`, `traverse` locally when DB is available or via transport when it isn't (closes #1)
|
|
8
|
+
- REST API (`Api` Sinatra app) with 8 endpoints: health, query, ingest, traverse, retrieve, deprecate, expertise, stats (closes #5)
|
|
9
|
+
- Conditional `require 'legion/extensions/apollo/api'` when Sinatra is available
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Contradiction detection now orders by embedding distance (`<=>` pgvector operator) before LIMIT, scanning the most semantically similar entries instead of arbitrary rows (closes #4)
|
|
13
|
+
- Whitelisted `relation_types` in `handle_traverse` against `RELATION_TYPES` constant to prevent SQL injection
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- GAIA phase wiring updated: `knowledge_retrieval` now targets `Request#retrieve` instead of `Knowledge#retrieve_relevant` (in legion-gaia)
|
|
17
|
+
|
|
18
|
+
## [0.4.4] - 2026-03-24
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- All ~35 hardcoded numeric thresholds, limits, intervals, and confidence values are now configurable via `Legion::Settings[:apollo]`
|
|
22
|
+
- `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)
|
|
23
|
+
- `Helpers::GraphQuery` gains `spread_factor`, `default_depth`, `min_activation`, `default_query_limit`, `default_query_min_confidence` settings-aware accessors
|
|
24
|
+
- `Runners::Gas` gains `relate_confidence_gate`, `synthesis_confidence_cap`, `max_anticipations`, `similar_entries_limit`, `fallback_confidence` settings-aware accessors
|
|
25
|
+
- `Runners::Knowledge` method defaults (query limits, min_confidence, contradiction/corroboration params) now read from Settings
|
|
26
|
+
- `Runners::Maintenance` removed duplicate private helpers, delegates to `Helpers::Confidence` methods
|
|
27
|
+
- `Runners::Expertise` proficiency cap and min_agents_at_risk configurable
|
|
28
|
+
- Actor intervals (Decay, ExpertiseAggregator, CorroborationChecker, EntityWatchdog) configurable via `apollo.actors.*_interval`
|
|
29
|
+
- EntityWatchdog lookback_seconds, log_limit, exists_min_confidence configurable
|
|
30
|
+
- EntityExtractor min_confidence configurable via `apollo.entity_extractor.min_confidence`
|
|
31
|
+
- Helpers::EntityWatchdog detect_confidence configurable
|
|
32
|
+
|
|
3
33
|
## [0.4.3] - 2026-03-24
|
|
4
34
|
|
|
5
35
|
### 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
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sinatra/base' unless defined?(Sinatra)
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Apollo
|
|
9
|
+
class Api < Sinatra::Base
|
|
10
|
+
set :host_authorization, permitted: :any
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
content_type :json
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
helpers do
|
|
17
|
+
def json_body
|
|
18
|
+
body = request.body.read
|
|
19
|
+
return {} if body.empty?
|
|
20
|
+
|
|
21
|
+
::JSON.parse(body, symbolize_names: true)
|
|
22
|
+
rescue ::JSON::ParserError
|
|
23
|
+
halt 400, { error: 'invalid JSON' }.to_json
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def runner
|
|
27
|
+
@runner ||= begin
|
|
28
|
+
obj = Object.new
|
|
29
|
+
obj.extend(Runners::Knowledge)
|
|
30
|
+
obj
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def expertise_runner
|
|
35
|
+
@expertise_runner ||= begin
|
|
36
|
+
obj = Object.new
|
|
37
|
+
obj.extend(Runners::Expertise)
|
|
38
|
+
obj
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Health check
|
|
44
|
+
get '/api/apollo/health' do
|
|
45
|
+
available = defined?(Legion::Data::Model::ApolloEntry) ? true : false
|
|
46
|
+
{ status: available ? 'ok' : 'degraded', data_available: available }.to_json
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Query knowledge (semantic search)
|
|
50
|
+
post '/api/apollo/query' do
|
|
51
|
+
req = json_body
|
|
52
|
+
halt 400, { error: 'query is required' }.to_json unless req[:query]
|
|
53
|
+
|
|
54
|
+
result = runner.handle_query(
|
|
55
|
+
query: req[:query],
|
|
56
|
+
limit: req[:limit] || 10,
|
|
57
|
+
min_confidence: req[:min_confidence] || 0.3,
|
|
58
|
+
status: req[:status] || [:confirmed],
|
|
59
|
+
tags: req[:tags],
|
|
60
|
+
domain: req[:domain],
|
|
61
|
+
agent_id: req[:agent_id] || 'api'
|
|
62
|
+
)
|
|
63
|
+
status result[:success] ? 200 : 500
|
|
64
|
+
result.to_json
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Ingest knowledge
|
|
68
|
+
post '/api/apollo/ingest' do
|
|
69
|
+
req = json_body
|
|
70
|
+
halt 400, { error: 'content is required' }.to_json unless req[:content]
|
|
71
|
+
halt 400, { error: 'content_type is required' }.to_json unless req[:content_type]
|
|
72
|
+
|
|
73
|
+
result = runner.handle_ingest(
|
|
74
|
+
content: req[:content],
|
|
75
|
+
content_type: req[:content_type],
|
|
76
|
+
tags: req[:tags] || [],
|
|
77
|
+
source_agent: req[:source_agent] || 'api',
|
|
78
|
+
source_provider: req[:source_provider],
|
|
79
|
+
source_channel: req[:source_channel],
|
|
80
|
+
knowledge_domain: req[:knowledge_domain],
|
|
81
|
+
context: req[:context] || {}
|
|
82
|
+
)
|
|
83
|
+
status result[:success] ? 201 : 500
|
|
84
|
+
result.to_json
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Graph traversal
|
|
88
|
+
post '/api/apollo/traverse' do
|
|
89
|
+
req = json_body
|
|
90
|
+
halt 400, { error: 'entry_id is required' }.to_json unless req[:entry_id]
|
|
91
|
+
|
|
92
|
+
result = runner.handle_traverse(
|
|
93
|
+
entry_id: req[:entry_id],
|
|
94
|
+
depth: req[:depth] || 2,
|
|
95
|
+
relation_types: req[:relation_types],
|
|
96
|
+
agent_id: req[:agent_id] || 'api'
|
|
97
|
+
)
|
|
98
|
+
status result[:success] ? 200 : 500
|
|
99
|
+
result.to_json
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Retrieve relevant (GAIA-compatible)
|
|
103
|
+
post '/api/apollo/retrieve' do
|
|
104
|
+
req = json_body
|
|
105
|
+
halt 400, { error: 'query is required' }.to_json unless req[:query]
|
|
106
|
+
|
|
107
|
+
result = runner.retrieve_relevant(
|
|
108
|
+
query: req[:query],
|
|
109
|
+
limit: req[:limit] || 5,
|
|
110
|
+
min_confidence: req[:min_confidence] || 0.3,
|
|
111
|
+
tags: req[:tags],
|
|
112
|
+
domain: req[:domain]
|
|
113
|
+
)
|
|
114
|
+
status result[:success] ? 200 : 500
|
|
115
|
+
result.to_json
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Deprecate entry
|
|
119
|
+
post '/api/apollo/entries/:id/deprecate' do
|
|
120
|
+
result = runner.deprecate_entry(
|
|
121
|
+
entry_id: params[:id],
|
|
122
|
+
reason: json_body[:reason] || 'deprecated via API'
|
|
123
|
+
)
|
|
124
|
+
result.to_json
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Domains at risk — must be declared before /:agent_id to avoid routing conflict
|
|
128
|
+
get '/api/apollo/expertise/at-risk' do
|
|
129
|
+
result = expertise_runner.domains_at_risk
|
|
130
|
+
result.to_json
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Expertise for an agent
|
|
134
|
+
get '/api/apollo/expertise/:agent_id' do
|
|
135
|
+
result = expertise_runner.agent_profile(agent_id: params[:agent_id])
|
|
136
|
+
result.to_json
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Statistics
|
|
140
|
+
get '/api/apollo/stats' do
|
|
141
|
+
stats = {}
|
|
142
|
+
if defined?(Legion::Data::Model::ApolloEntry)
|
|
143
|
+
stats[:total_entries] = Legion::Data::Model::ApolloEntry.count
|
|
144
|
+
stats[:by_status] = Legion::Data::Model::ApolloEntry.group_and_count(:status).all
|
|
145
|
+
.to_h { |r| [r[:status], r[:count]] }
|
|
146
|
+
stats[:by_content_type] = Legion::Data::Model::ApolloEntry.group_and_count(:content_type).all
|
|
147
|
+
.to_h { |r| [r[:content_type], r[:count]] }
|
|
148
|
+
stats[:total_relations] = Legion::Data::Model::ApolloRelation.count if defined?(Legion::Data::Model::ApolloRelation)
|
|
149
|
+
else
|
|
150
|
+
stats[:error] = 'apollo_data_not_available'
|
|
151
|
+
end
|
|
152
|
+
stats.to_json
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -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
|