lex-apollo 0.2.0
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 +7 -0
- data/CHANGELOG.md +25 -0
- data/README.md +135 -0
- data/lib/legion/extensions/apollo/actors/corroboration_checker.rb +22 -0
- data/lib/legion/extensions/apollo/actors/decay.rb +22 -0
- data/lib/legion/extensions/apollo/actors/expertise_aggregator.rb +22 -0
- data/lib/legion/extensions/apollo/actors/ingest.rb +25 -0
- data/lib/legion/extensions/apollo/actors/query_responder.rb +25 -0
- data/lib/legion/extensions/apollo/client.rb +30 -0
- data/lib/legion/extensions/apollo/helpers/confidence.rb +46 -0
- data/lib/legion/extensions/apollo/helpers/embedding.rb +22 -0
- data/lib/legion/extensions/apollo/helpers/graph_query.rb +77 -0
- data/lib/legion/extensions/apollo/helpers/similarity.rb +36 -0
- data/lib/legion/extensions/apollo/runners/expertise.rb +71 -0
- data/lib/legion/extensions/apollo/runners/knowledge.rb +213 -0
- data/lib/legion/extensions/apollo/runners/maintenance.rb +72 -0
- data/lib/legion/extensions/apollo/transport/exchanges/apollo.rb +19 -0
- data/lib/legion/extensions/apollo/transport/messages/ingest.rb +43 -0
- data/lib/legion/extensions/apollo/transport/messages/query.rb +43 -0
- data/lib/legion/extensions/apollo/transport/queues/ingest.rb +23 -0
- data/lib/legion/extensions/apollo/transport/queues/query.rb +23 -0
- data/lib/legion/extensions/apollo/version.rb +9 -0
- data/lib/legion/extensions/apollo.rb +25 -0
- data/spec/legion/extensions/apollo/actors/decay_spec.rb +45 -0
- data/spec/legion/extensions/apollo/actors/expertise_aggregator_spec.rb +41 -0
- data/spec/legion/extensions/apollo/actors/ingest_spec.rb +33 -0
- data/spec/legion/extensions/apollo/client_spec.rb +75 -0
- data/spec/legion/extensions/apollo/helpers/confidence_spec.rb +109 -0
- data/spec/legion/extensions/apollo/helpers/embedding_spec.rb +68 -0
- data/spec/legion/extensions/apollo/helpers/graph_query_spec.rb +69 -0
- data/spec/legion/extensions/apollo/helpers/similarity_spec.rb +58 -0
- data/spec/legion/extensions/apollo/runners/expertise_spec.rb +111 -0
- data/spec/legion/extensions/apollo/runners/knowledge_spec.rb +308 -0
- data/spec/legion/extensions/apollo/runners/maintenance_spec.rb +133 -0
- data/spec/legion/extensions/apollo/transport/messages/ingest_spec.rb +65 -0
- data/spec/legion/extensions/apollo/transport/messages/query_spec.rb +75 -0
- data/spec/spec_helper.rb +30 -0
- metadata +108 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative '../helpers/confidence'
|
|
5
|
+
require_relative '../helpers/embedding'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module Apollo
|
|
10
|
+
module Runners
|
|
11
|
+
module Knowledge
|
|
12
|
+
def store_knowledge(content:, content_type:, tags: [], source_agent: nil, context: {}, **)
|
|
13
|
+
content_type = content_type.to_sym
|
|
14
|
+
unless Helpers::Confidence::CONTENT_TYPES.include?(content_type)
|
|
15
|
+
raise ArgumentError, "invalid content_type: #{content_type}. Must be one of #{Helpers::Confidence::CONTENT_TYPES}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
action: :store,
|
|
20
|
+
content: content,
|
|
21
|
+
content_type: content_type,
|
|
22
|
+
tags: Array(tags),
|
|
23
|
+
source_agent: source_agent,
|
|
24
|
+
context: context
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def query_knowledge(query:, limit: 10, min_confidence: 0.3, status: [:confirmed], tags: nil, **)
|
|
29
|
+
{
|
|
30
|
+
action: :query,
|
|
31
|
+
query: query,
|
|
32
|
+
limit: limit,
|
|
33
|
+
min_confidence: min_confidence,
|
|
34
|
+
status: Array(status),
|
|
35
|
+
tags: tags
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def related_entries(entry_id:, relation_types: nil, depth: 2, **)
|
|
40
|
+
{
|
|
41
|
+
action: :traverse,
|
|
42
|
+
entry_id: entry_id,
|
|
43
|
+
relation_types: relation_types,
|
|
44
|
+
depth: depth
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def deprecate_entry(entry_id:, reason:, **)
|
|
49
|
+
{
|
|
50
|
+
action: :deprecate,
|
|
51
|
+
entry_id: entry_id,
|
|
52
|
+
reason: reason
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def handle_ingest(content:, content_type:, tags: [], source_agent: 'unknown', context: {}, **)
|
|
57
|
+
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
58
|
+
|
|
59
|
+
embedding = Helpers::Embedding.generate(text: content)
|
|
60
|
+
content_type_sym = content_type.to_s
|
|
61
|
+
tag_array = Array(tags)
|
|
62
|
+
|
|
63
|
+
corroborated, existing_id = find_corroboration(embedding, content_type_sym, source_agent)
|
|
64
|
+
|
|
65
|
+
unless corroborated
|
|
66
|
+
new_entry = Legion::Data::Model::ApolloEntry.create(
|
|
67
|
+
content: content,
|
|
68
|
+
content_type: content_type_sym,
|
|
69
|
+
confidence: Helpers::Confidence::INITIAL_CONFIDENCE,
|
|
70
|
+
source_agent: source_agent,
|
|
71
|
+
source_context: ::JSON.dump(context.is_a?(Hash) ? context : {}),
|
|
72
|
+
tags: Sequel.pg_array(tag_array),
|
|
73
|
+
status: 'candidate',
|
|
74
|
+
embedding: Sequel.lit("'[#{embedding.join(',')}]'::vector")
|
|
75
|
+
)
|
|
76
|
+
existing_id = new_entry.id
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
upsert_expertise(source_agent: source_agent, domain: tag_array.first || 'general')
|
|
80
|
+
|
|
81
|
+
Legion::Data::Model::ApolloAccessLog.create(
|
|
82
|
+
entry_id: existing_id, agent_id: source_agent, action: 'ingest'
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
{ success: true, entry_id: existing_id, status: corroborated ? 'corroborated' : 'candidate',
|
|
86
|
+
corroborated: corroborated }
|
|
87
|
+
rescue Sequel::Error => e
|
|
88
|
+
{ success: false, error: e.message }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def handle_query(query:, limit: 10, min_confidence: 0.3, status: [:confirmed], tags: nil, agent_id: 'unknown', **) # rubocop:disable Metrics/ParameterLists
|
|
92
|
+
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
93
|
+
|
|
94
|
+
embedding = Helpers::Embedding.generate(text: query)
|
|
95
|
+
sql = Helpers::GraphQuery.build_semantic_search_sql(
|
|
96
|
+
limit: limit, min_confidence: min_confidence,
|
|
97
|
+
statuses: Array(status).map(&:to_s), tags: tags
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
db = Legion::Data::Model::ApolloEntry.db
|
|
101
|
+
entries = db.fetch(sql, embedding: Sequel.lit("'[#{embedding.join(',')}]'::vector")).all
|
|
102
|
+
|
|
103
|
+
entries.each do |entry|
|
|
104
|
+
Legion::Data::Model::ApolloEntry.where(id: entry[:id]).update(
|
|
105
|
+
access_count: Sequel.expr(:access_count) + 1,
|
|
106
|
+
confidence: Helpers::Confidence.apply_retrieval_boost(
|
|
107
|
+
confidence: entry[:confidence]
|
|
108
|
+
),
|
|
109
|
+
updated_at: Time.now
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
if entries.any?
|
|
114
|
+
Legion::Data::Model::ApolloAccessLog.create(
|
|
115
|
+
entry_id: entries.first&.dig(:id), agent_id: agent_id, action: 'query'
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
formatted = entries.map do |entry|
|
|
120
|
+
{ id: entry[:id], content: entry[:content], content_type: entry[:content_type],
|
|
121
|
+
confidence: entry[:confidence], distance: entry[:distance],
|
|
122
|
+
tags: entry[:tags], source_agent: entry[:source_agent] }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
{ success: true, entries: formatted, count: formatted.size }
|
|
126
|
+
rescue Sequel::Error => e
|
|
127
|
+
{ success: false, error: e.message }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def retrieve_relevant(query: nil, limit: 5, min_confidence: 0.3, tags: nil, skip: false, **)
|
|
131
|
+
return { status: :skipped } if skip
|
|
132
|
+
|
|
133
|
+
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
134
|
+
|
|
135
|
+
return { success: true, entries: [], count: 0 } if query.nil? || query.to_s.strip.empty?
|
|
136
|
+
|
|
137
|
+
embedding = Helpers::Embedding.generate(text: query.to_s)
|
|
138
|
+
sql = Helpers::GraphQuery.build_semantic_search_sql(
|
|
139
|
+
limit: limit, min_confidence: min_confidence,
|
|
140
|
+
statuses: ['confirmed'], tags: tags
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
db = Legion::Data::Model::ApolloEntry.db
|
|
144
|
+
entries = db.fetch(sql, embedding: Sequel.lit("'[#{embedding.join(',')}]'::vector")).all
|
|
145
|
+
|
|
146
|
+
entries.each do |entry|
|
|
147
|
+
Legion::Data::Model::ApolloEntry.where(id: entry[:id]).update(
|
|
148
|
+
confidence: Helpers::Confidence.apply_retrieval_boost(confidence: entry[:confidence]),
|
|
149
|
+
updated_at: Time.now
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
formatted = entries.map do |entry|
|
|
154
|
+
{ id: entry[:id], content: entry[:content], content_type: entry[:content_type],
|
|
155
|
+
confidence: entry[:confidence], distance: entry[:distance],
|
|
156
|
+
tags: entry[:tags], source_agent: entry[:source_agent] }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
{ success: true, entries: formatted, count: formatted.size }
|
|
160
|
+
rescue Sequel::Error => e
|
|
161
|
+
{ success: false, error: e.message }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def find_corroboration(embedding, content_type_sym, source_agent)
|
|
167
|
+
existing = Legion::Data::Model::ApolloEntry
|
|
168
|
+
.where(content_type: content_type_sym)
|
|
169
|
+
.exclude(embedding: nil)
|
|
170
|
+
.limit(50)
|
|
171
|
+
|
|
172
|
+
existing.each do |entry|
|
|
173
|
+
next unless entry.embedding
|
|
174
|
+
|
|
175
|
+
sim = Helpers::Similarity.cosine_similarity(vec_a: embedding, vec_b: entry.embedding)
|
|
176
|
+
next unless Helpers::Similarity.above_corroboration_threshold?(similarity: sim)
|
|
177
|
+
|
|
178
|
+
entry.update(
|
|
179
|
+
confidence: Helpers::Confidence.apply_corroboration_boost(confidence: entry.confidence),
|
|
180
|
+
updated_at: Time.now
|
|
181
|
+
)
|
|
182
|
+
Legion::Data::Model::ApolloRelation.create(
|
|
183
|
+
from_entry_id: entry.id,
|
|
184
|
+
to_entry_id: entry.id,
|
|
185
|
+
relation_type: 'similar_to',
|
|
186
|
+
source_agent: source_agent,
|
|
187
|
+
weight: sim
|
|
188
|
+
)
|
|
189
|
+
return [true, entry.id]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
[false, nil]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def upsert_expertise(source_agent:, domain:)
|
|
196
|
+
expertise = Legion::Data::Model::ApolloExpertise
|
|
197
|
+
.where(agent_id: source_agent, domain: domain).first
|
|
198
|
+
if expertise
|
|
199
|
+
expertise.update(entry_count: expertise.entry_count + 1, last_active_at: Time.now)
|
|
200
|
+
else
|
|
201
|
+
Legion::Data::Model::ApolloExpertise.create(
|
|
202
|
+
agent_id: source_agent, domain: domain, proficiency: 0.0,
|
|
203
|
+
entry_count: 1, last_active_at: Time.now
|
|
204
|
+
)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../helpers/confidence'
|
|
4
|
+
require_relative '../helpers/similarity'
|
|
5
|
+
require_relative '../helpers/embedding'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module Apollo
|
|
10
|
+
module Runners
|
|
11
|
+
module Maintenance
|
|
12
|
+
def force_decay(factor: 0.5, **)
|
|
13
|
+
{ action: :force_decay, factor: factor }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def archive_stale(days: 90, **)
|
|
17
|
+
{ action: :archive_stale, days: days }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def resolve_dispute(entry_id:, resolution:, **)
|
|
21
|
+
{ action: :resolve_dispute, entry_id: entry_id, resolution: resolution }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_corroboration(**)
|
|
25
|
+
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
26
|
+
|
|
27
|
+
candidates = Legion::Data::Model::ApolloEntry.where(status: 'candidate').exclude(embedding: nil).all
|
|
28
|
+
confirmed = Legion::Data::Model::ApolloEntry.where(status: 'confirmed').exclude(embedding: nil).all
|
|
29
|
+
|
|
30
|
+
promoted = 0
|
|
31
|
+
|
|
32
|
+
candidates.each do |candidate|
|
|
33
|
+
match = confirmed.find do |conf|
|
|
34
|
+
next unless conf.content_type == candidate.content_type
|
|
35
|
+
|
|
36
|
+
sim = Helpers::Similarity.cosine_similarity(
|
|
37
|
+
vec_a: candidate.embedding, vec_b: conf.embedding
|
|
38
|
+
)
|
|
39
|
+
Helpers::Similarity.above_corroboration_threshold?(similarity: sim)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
next unless match
|
|
43
|
+
|
|
44
|
+
candidate.update(
|
|
45
|
+
status: 'confirmed',
|
|
46
|
+
confirmed_at: Time.now,
|
|
47
|
+
confidence: Helpers::Confidence.apply_corroboration_boost(confidence: candidate.confidence),
|
|
48
|
+
updated_at: Time.now
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
Legion::Data::Model::ApolloRelation.create(
|
|
52
|
+
from_entry_id: candidate.id,
|
|
53
|
+
to_entry_id: match.id,
|
|
54
|
+
relation_type: 'similar_to',
|
|
55
|
+
source_agent: 'system:corroboration',
|
|
56
|
+
weight: 1.0
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
promoted += 1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
{ success: true, promoted: promoted, scanned: candidates.size }
|
|
63
|
+
rescue Sequel::Error => e
|
|
64
|
+
{ success: false, error: e.message }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/transport/exchange' if defined?(Legion::Transport)
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Apollo
|
|
8
|
+
module Transport
|
|
9
|
+
module Exchanges
|
|
10
|
+
class Apollo < Legion::Transport::Exchange
|
|
11
|
+
def exchange_name
|
|
12
|
+
'apollo'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/transport/message' if defined?(Legion::Transport)
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Apollo
|
|
8
|
+
module Transport
|
|
9
|
+
module Messages
|
|
10
|
+
class Ingest < Legion::Transport::Message
|
|
11
|
+
def exchange
|
|
12
|
+
Exchanges::Apollo
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def routing_key
|
|
16
|
+
'apollo.ingest'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def message
|
|
20
|
+
{
|
|
21
|
+
content: @options[:content],
|
|
22
|
+
content_type: @options[:content_type],
|
|
23
|
+
tags: @options[:tags],
|
|
24
|
+
source_agent: @options[:source_agent],
|
|
25
|
+
context: @options[:context] || {}
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def type
|
|
30
|
+
'apollo_ingest'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def validate
|
|
34
|
+
raise TypeError, 'content is required' unless @options[:content].is_a?(String)
|
|
35
|
+
|
|
36
|
+
@valid = true
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/transport/message' if defined?(Legion::Transport)
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Apollo
|
|
8
|
+
module Transport
|
|
9
|
+
module Messages
|
|
10
|
+
class Query < Legion::Transport::Message
|
|
11
|
+
def exchange
|
|
12
|
+
Exchanges::Apollo
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def routing_key
|
|
16
|
+
'apollo.query'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def message
|
|
20
|
+
{
|
|
21
|
+
action: @options[:action],
|
|
22
|
+
query: @options[:query],
|
|
23
|
+
entry_id: @options[:entry_id],
|
|
24
|
+
limit: @options[:limit],
|
|
25
|
+
min_confidence: @options[:min_confidence],
|
|
26
|
+
status: @options[:status],
|
|
27
|
+
tags: @options[:tags],
|
|
28
|
+
relation_types: @options[:relation_types],
|
|
29
|
+
depth: @options[:depth],
|
|
30
|
+
reply_to: @options[:reply_to],
|
|
31
|
+
correlation_id: @options[:correlation_id]
|
|
32
|
+
}.compact
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def type
|
|
36
|
+
'apollo_query'
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/transport/queue' if defined?(Legion::Transport)
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Apollo
|
|
8
|
+
module Transport
|
|
9
|
+
module Queues
|
|
10
|
+
class Ingest < Legion::Transport::Queue
|
|
11
|
+
def queue_name
|
|
12
|
+
'apollo.ingest'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def queue_options
|
|
16
|
+
{ manual_ack: true, durable: true, arguments: { 'x-dead-letter-exchange': 'apollo.dlx' } }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/transport/queue' if defined?(Legion::Transport)
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Apollo
|
|
8
|
+
module Transport
|
|
9
|
+
module Queues
|
|
10
|
+
class Query < Legion::Transport::Queue
|
|
11
|
+
def queue_name
|
|
12
|
+
'apollo.query'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def queue_options
|
|
16
|
+
{ manual_ack: true, durable: true, arguments: { 'x-dead-letter-exchange': 'apollo.dlx' } }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/apollo/version'
|
|
4
|
+
require 'legion/extensions/apollo/helpers/confidence'
|
|
5
|
+
require 'legion/extensions/apollo/helpers/similarity'
|
|
6
|
+
require 'legion/extensions/apollo/helpers/graph_query'
|
|
7
|
+
require 'legion/extensions/apollo/runners/knowledge'
|
|
8
|
+
require 'legion/extensions/apollo/runners/expertise'
|
|
9
|
+
require 'legion/extensions/apollo/runners/maintenance'
|
|
10
|
+
|
|
11
|
+
if defined?(Legion::Transport)
|
|
12
|
+
require 'legion/extensions/apollo/transport/exchanges/apollo'
|
|
13
|
+
require 'legion/extensions/apollo/transport/queues/ingest'
|
|
14
|
+
require 'legion/extensions/apollo/transport/queues/query'
|
|
15
|
+
require 'legion/extensions/apollo/transport/messages/ingest'
|
|
16
|
+
require 'legion/extensions/apollo/transport/messages/query'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module Legion
|
|
20
|
+
module Extensions
|
|
21
|
+
module Apollo
|
|
22
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
unless defined?(Legion::Extensions::Actors::Every)
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Actors
|
|
9
|
+
class Every; end # rubocop:disable Lint/EmptyClass
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
$LOADED_FEATURES << 'legion/extensions/actors/every' unless $LOADED_FEATURES.include?('legion/extensions/actors/every')
|
|
15
|
+
|
|
16
|
+
require 'legion/extensions/apollo/runners/maintenance'
|
|
17
|
+
require 'legion/extensions/apollo/actors/decay'
|
|
18
|
+
|
|
19
|
+
RSpec.describe Legion::Extensions::Apollo::Actor::Decay do
|
|
20
|
+
subject(:actor) { described_class.new }
|
|
21
|
+
|
|
22
|
+
it 'uses Maintenance runner_class' do
|
|
23
|
+
expect(actor.runner_class).to eq(Legion::Extensions::Apollo::Runners::Maintenance)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'runs force_decay function' do
|
|
27
|
+
expect(actor.runner_function).to eq('force_decay')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'runs every 3600 seconds' do
|
|
31
|
+
expect(actor.time).to eq(3600)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'does not run immediately' do
|
|
35
|
+
expect(actor.run_now?).to be false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'does not use runner framework' do
|
|
39
|
+
expect(actor.use_runner?).to be false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'does not generate tasks' do
|
|
43
|
+
expect(actor.generate_task?).to be false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
unless defined?(Legion::Extensions::Actors::Every)
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Actors
|
|
9
|
+
class Every; end # rubocop:disable Lint/EmptyClass
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
$LOADED_FEATURES << 'legion/extensions/actors/every' unless $LOADED_FEATURES.include?('legion/extensions/actors/every')
|
|
15
|
+
|
|
16
|
+
require 'legion/extensions/apollo/runners/expertise'
|
|
17
|
+
require 'legion/extensions/apollo/actors/expertise_aggregator'
|
|
18
|
+
|
|
19
|
+
RSpec.describe Legion::Extensions::Apollo::Actor::ExpertiseAggregator do
|
|
20
|
+
subject(:actor) { described_class.new }
|
|
21
|
+
|
|
22
|
+
it 'uses Expertise runner_class' do
|
|
23
|
+
expect(actor.runner_class).to eq(Legion::Extensions::Apollo::Runners::Expertise)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'runs aggregate function' do
|
|
27
|
+
expect(actor.runner_function).to eq('aggregate')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'runs every 1800 seconds' do
|
|
31
|
+
expect(actor.time).to eq(1800)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'does not run immediately' do
|
|
35
|
+
expect(actor.run_now?).to be false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'does not generate tasks' do
|
|
39
|
+
expect(actor.generate_task?).to be false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
unless defined?(Legion::Extensions::Actors::Subscription)
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Actors
|
|
9
|
+
class Subscription; end # rubocop:disable Lint/EmptyClass
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
$LOADED_FEATURES << 'legion/extensions/actors/subscription' unless $LOADED_FEATURES.include?('legion/extensions/actors/subscription')
|
|
15
|
+
|
|
16
|
+
require 'legion/extensions/apollo/runners/knowledge'
|
|
17
|
+
require 'legion/extensions/apollo/actors/ingest'
|
|
18
|
+
|
|
19
|
+
RSpec.describe Legion::Extensions::Apollo::Actor::Ingest do
|
|
20
|
+
subject(:actor) { described_class.new }
|
|
21
|
+
|
|
22
|
+
it 'uses Knowledge runner_class as string' do
|
|
23
|
+
expect(actor.runner_class).to eq('Legion::Extensions::Apollo::Runners::Knowledge')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'runs handle_ingest function' do
|
|
27
|
+
expect(actor.runner_function).to eq('handle_ingest')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'does not generate tasks' do
|
|
31
|
+
expect(actor.generate_task?).to be false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'legion/extensions/apollo/client'
|
|
5
|
+
|
|
6
|
+
RSpec.describe Legion::Extensions::Apollo::Client do
|
|
7
|
+
let(:client) { described_class.new(agent_id: 'test-agent') }
|
|
8
|
+
|
|
9
|
+
describe '#initialize' do
|
|
10
|
+
it 'stores agent_id' do
|
|
11
|
+
expect(client.agent_id).to eq('test-agent')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'defaults agent_id to unknown' do
|
|
15
|
+
c = described_class.new
|
|
16
|
+
expect(c.agent_id).to eq('unknown')
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe 'Knowledge runner' do
|
|
21
|
+
it 'responds to store_knowledge' do
|
|
22
|
+
expect(client).to respond_to(:store_knowledge)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'injects source_agent from client agent_id' do
|
|
26
|
+
result = client.store_knowledge(content: 'test fact', content_type: :fact)
|
|
27
|
+
expect(result[:source_agent]).to eq('test-agent')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'allows source_agent override' do
|
|
31
|
+
result = client.store_knowledge(content: 'test', content_type: :fact, source_agent: 'other')
|
|
32
|
+
expect(result[:source_agent]).to eq('other')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'responds to query_knowledge' do
|
|
36
|
+
expect(client).to respond_to(:query_knowledge)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'responds to related_entries' do
|
|
40
|
+
expect(client).to respond_to(:related_entries)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'responds to deprecate_entry' do
|
|
44
|
+
expect(client).to respond_to(:deprecate_entry)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe 'Expertise runner' do
|
|
49
|
+
it 'responds to get_expertise' do
|
|
50
|
+
expect(client).to respond_to(:get_expertise)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'responds to domains_at_risk' do
|
|
54
|
+
expect(client).to respond_to(:domains_at_risk)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'responds to agent_profile' do
|
|
58
|
+
expect(client).to respond_to(:agent_profile)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe 'Maintenance runner' do
|
|
63
|
+
it 'responds to force_decay' do
|
|
64
|
+
expect(client).to respond_to(:force_decay)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'responds to archive_stale' do
|
|
68
|
+
expect(client).to respond_to(:archive_stale)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it 'responds to resolve_dispute' do
|
|
72
|
+
expect(client).to respond_to(:resolve_dispute)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|