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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fcb22d66eb9b08e01ececa39900c455be2aa64f358a2976878c0b2934b71670a
|
|
4
|
+
data.tar.gz: 1d2d41cf8835c04e14827e22caaed2a848d0c8fc4c41cef3aeecc927041849b3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6bff39d97c42ca8085b7937066e38d52c50fe302cd1d04e80d8a4aca6762eb5c28b33eb05a0ea16adf391b86bffa3d0936f3d83caf3d936d5703e8c08bd0140c
|
|
7
|
+
data.tar.gz: 694442a97667f0355bba359938c7bd9317f9d2308a6ef169a54c59bc63e097c7786625a525b33defe44581bc20593cba259ff68be019c6c55708b6ad7f31e43a
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.2.0] - 2026-03-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `Helpers::Embedding` — embedding generation wrapper with legion-llm + zero-vector fallback
|
|
7
|
+
- `Knowledge.handle_ingest` — server-side ingest: embedding, corroboration check, entry creation, expertise upsert
|
|
8
|
+
- `Knowledge.handle_query` — server-side query: semantic search via pgvector, retrieval boost, access logging
|
|
9
|
+
- `Knowledge.retrieve_relevant` — GAIA tick phase handler for knowledge_retrieval (phase 4)
|
|
10
|
+
- `Maintenance.check_corroboration` — periodic scan promoting candidates to confirmed via similarity threshold
|
|
11
|
+
- `Expertise.aggregate` — periodic proficiency recalculation using log2-weighted average confidence
|
|
12
|
+
|
|
13
|
+
## [0.1.0] - 2026-03-15
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Initial scaffold with helpers, runners, actors, transport, and standalone client
|
|
17
|
+
- Confidence helper with decay, boost, and write gate logic
|
|
18
|
+
- Similarity helper with cosine distance and corroboration classification
|
|
19
|
+
- Graph query builder for recursive CTE traversal and pgvector semantic search
|
|
20
|
+
- Knowledge, Expertise, and Maintenance runners (client-side RMQ payloads)
|
|
21
|
+
- Ingest, QueryResponder, Decay, ExpertiseAggregator, CorroborationChecker actors
|
|
22
|
+
- Transport layer: apollo exchange, ingest/query queues, ingest/query messages
|
|
23
|
+
- Standalone Client with agent_id injection
|
|
24
|
+
- GAIA tick integration: knowledge_retrieval phase (phase 4)
|
|
25
|
+
- legion-data migration 012 with PostgreSQL+pgvector tables (guarded)
|
data/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# lex-apollo
|
|
2
|
+
|
|
3
|
+
Shared durable knowledge store for the GAIA cognitive mesh. Agents publish confirmed knowledge via RabbitMQ; a dedicated Apollo service persists to PostgreSQL+pgvector. Supports semantic search, concept graph traversal, and expertise tracking.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`lex-apollo` operates in two modes:
|
|
8
|
+
|
|
9
|
+
- **Client mode**: Any agent loads this gem and calls runners. Runners publish to RabbitMQ — no direct database access required.
|
|
10
|
+
- **Service mode**: A dedicated Apollo process runs the actors, subscribes to queues, generates embeddings, and writes to PostgreSQL+pgvector.
|
|
11
|
+
|
|
12
|
+
The backing store is Azure Database for PostgreSQL Flexible Server with the pgvector extension.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add to your Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem 'lex-apollo'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Standalone Client
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require 'legion/extensions/apollo'
|
|
28
|
+
|
|
29
|
+
client = Legion::Extensions::Apollo::Client.new(agent_id: 'my-agent-001')
|
|
30
|
+
|
|
31
|
+
# Store a confirmed knowledge entry
|
|
32
|
+
client.store_knowledge(
|
|
33
|
+
domain: 'networking',
|
|
34
|
+
content: 'BGP route reflectors reduce full-mesh IBGP complexity',
|
|
35
|
+
confidence: 0.9,
|
|
36
|
+
source_agent_id: 'my-agent-001',
|
|
37
|
+
tags: ['bgp', 'routing', 'ibgp']
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Query for relevant knowledge
|
|
41
|
+
client.query_knowledge(
|
|
42
|
+
query: 'BGP route reflector configuration',
|
|
43
|
+
domain: 'networking',
|
|
44
|
+
min_confidence: 0.6,
|
|
45
|
+
limit: 10
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Get related entries (concept graph traversal)
|
|
49
|
+
client.related_entries(entry_id: 'entry-uuid', max_hops: 2)
|
|
50
|
+
|
|
51
|
+
# Deprecate a stale entry
|
|
52
|
+
client.deprecate_entry(entry_id: 'entry-uuid', reason: 'superseded by RFC 7938')
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Expertise Queries
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# Get proficiency scores for a domain
|
|
59
|
+
client.get_expertise(domain: 'networking', agent_id: 'my-agent-001')
|
|
60
|
+
|
|
61
|
+
# Find domains where knowledge coverage is thin
|
|
62
|
+
client.domains_at_risk(min_entries: 5, min_confidence: 0.7)
|
|
63
|
+
|
|
64
|
+
# Full agent knowledge profile
|
|
65
|
+
client.agent_profile(agent_id: 'my-agent-001')
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Maintenance
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Force confidence decay cycle
|
|
72
|
+
client.force_decay(domain: 'networking')
|
|
73
|
+
|
|
74
|
+
# Archive entries below confidence threshold
|
|
75
|
+
client.archive_stale(max_confidence: 0.2)
|
|
76
|
+
|
|
77
|
+
# Resolve a corroboration dispute
|
|
78
|
+
client.resolve_dispute(entry_id: 'entry-uuid', resolution: :accept)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Architecture
|
|
82
|
+
|
|
83
|
+
### Client Mode
|
|
84
|
+
|
|
85
|
+
Runners build structured payloads and publish to the `apollo` exchange via RabbitMQ. No PostgreSQL or pgvector dependency is needed in the calling agent. Transport requires `Legion::Transport` to be loaded (the `if defined?(Legion::Transport)` guard in the entry point handles this automatically).
|
|
86
|
+
|
|
87
|
+
### Service Mode
|
|
88
|
+
|
|
89
|
+
Five actors run in the dedicated Apollo service process:
|
|
90
|
+
|
|
91
|
+
| Actor | Type | Interval | Purpose |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `Ingest` | Subscription | on-message | Receive knowledge, generate embeddings, persist to PostgreSQL |
|
|
94
|
+
| `QueryResponder` | Subscription | on-message | Handle semantic queries, return results via RPC |
|
|
95
|
+
| `Decay` | Interval | 3600s | Confidence decay cycle across all entries |
|
|
96
|
+
| `ExpertiseAggregator` | Interval | 1800s | Recalculate domain proficiency scores |
|
|
97
|
+
| `CorroborationChecker` | Interval | 900s | Scan pending entries for auto-confirm threshold |
|
|
98
|
+
|
|
99
|
+
### GAIA Tick Integration
|
|
100
|
+
|
|
101
|
+
Apollo is wired into the GAIA tick cycle at the `knowledge_retrieval` phase (phase 4), which fires after `memory_retrieval` and before `working_memory_integration`. It activates only when local memory lacks high-confidence matches for the current tick context.
|
|
102
|
+
|
|
103
|
+
## Confidence Model
|
|
104
|
+
|
|
105
|
+
Entries have a confidence score between 0.0 and 1.0:
|
|
106
|
+
|
|
107
|
+
- New entries start at the caller-supplied confidence value
|
|
108
|
+
- Corroboration from multiple agents boosts confidence
|
|
109
|
+
- Entries below `WRITE_GATE_THRESHOLD` are rejected on ingest
|
|
110
|
+
- Confidence decays hourly; entries below `ARCHIVE_THRESHOLD` are archived
|
|
111
|
+
|
|
112
|
+
See `helpers/confidence.rb` for decay constants and boost logic.
|
|
113
|
+
|
|
114
|
+
## Requirements
|
|
115
|
+
|
|
116
|
+
### Client mode
|
|
117
|
+
- Ruby >= 3.4
|
|
118
|
+
- RabbitMQ (via `legion-transport`)
|
|
119
|
+
|
|
120
|
+
### Service mode
|
|
121
|
+
- PostgreSQL with pgvector extension
|
|
122
|
+
- RabbitMQ
|
|
123
|
+
- `legion-data` for database connection management
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
bundle install
|
|
129
|
+
bundle exec rspec
|
|
130
|
+
bundle exec rubocop
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
require_relative '../runners/maintenance'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Apollo
|
|
9
|
+
module Actor
|
|
10
|
+
class CorroborationChecker < Legion::Extensions::Actors::Every
|
|
11
|
+
def runner_class = Legion::Extensions::Apollo::Runners::Maintenance
|
|
12
|
+
def runner_function = 'check_corroboration'
|
|
13
|
+
def time = 900
|
|
14
|
+
def run_now? = false
|
|
15
|
+
def use_runner? = false
|
|
16
|
+
def check_subtask? = false
|
|
17
|
+
def generate_task? = false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
require_relative '../runners/maintenance'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Apollo
|
|
9
|
+
module Actor
|
|
10
|
+
class Decay < Legion::Extensions::Actors::Every
|
|
11
|
+
def runner_class = Legion::Extensions::Apollo::Runners::Maintenance
|
|
12
|
+
def runner_function = 'force_decay'
|
|
13
|
+
def time = 3600
|
|
14
|
+
def run_now? = false
|
|
15
|
+
def use_runner? = false
|
|
16
|
+
def check_subtask? = false
|
|
17
|
+
def generate_task? = false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
require_relative '../runners/expertise'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Apollo
|
|
9
|
+
module Actor
|
|
10
|
+
class ExpertiseAggregator < Legion::Extensions::Actors::Every
|
|
11
|
+
def runner_class = Legion::Extensions::Apollo::Runners::Expertise
|
|
12
|
+
def runner_function = 'aggregate'
|
|
13
|
+
def time = 1800
|
|
14
|
+
def run_now? = false
|
|
15
|
+
def use_runner? = false
|
|
16
|
+
def check_subtask? = false
|
|
17
|
+
def generate_task? = false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/subscription' if defined?(Legion::Extensions::Actors::Subscription)
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Apollo
|
|
8
|
+
module Actor
|
|
9
|
+
class Ingest < Legion::Extensions::Actors::Subscription
|
|
10
|
+
def runner_class = 'Legion::Extensions::Apollo::Runners::Knowledge'
|
|
11
|
+
def runner_function = 'handle_ingest'
|
|
12
|
+
def check_subtask? = false
|
|
13
|
+
def generate_task? = false
|
|
14
|
+
|
|
15
|
+
def enabled?
|
|
16
|
+
defined?(Legion::Extensions::Apollo::Runners::Knowledge) &&
|
|
17
|
+
defined?(Legion::Transport)
|
|
18
|
+
rescue StandardError
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/subscription' if defined?(Legion::Extensions::Actors::Subscription)
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Apollo
|
|
8
|
+
module Actor
|
|
9
|
+
class QueryResponder < Legion::Extensions::Actors::Subscription
|
|
10
|
+
def runner_class = 'Legion::Extensions::Apollo::Runners::Knowledge'
|
|
11
|
+
def runner_function = 'handle_query'
|
|
12
|
+
def check_subtask? = false
|
|
13
|
+
def generate_task? = false
|
|
14
|
+
|
|
15
|
+
def enabled?
|
|
16
|
+
defined?(Legion::Extensions::Apollo::Runners::Knowledge) &&
|
|
17
|
+
defined?(Legion::Transport)
|
|
18
|
+
rescue StandardError
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'helpers/confidence'
|
|
4
|
+
require_relative 'helpers/similarity'
|
|
5
|
+
require_relative 'helpers/graph_query'
|
|
6
|
+
require_relative 'runners/knowledge'
|
|
7
|
+
require_relative 'runners/expertise'
|
|
8
|
+
require_relative 'runners/maintenance'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module Apollo
|
|
13
|
+
class Client
|
|
14
|
+
include Runners::Knowledge
|
|
15
|
+
include Runners::Expertise
|
|
16
|
+
include Runners::Maintenance
|
|
17
|
+
|
|
18
|
+
attr_reader :agent_id
|
|
19
|
+
|
|
20
|
+
def initialize(agent_id: 'unknown', **)
|
|
21
|
+
@agent_id = agent_id
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def store_knowledge(source_agent: nil, **)
|
|
25
|
+
super(**, source_agent: source_agent || @agent_id)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Apollo
|
|
6
|
+
module Helpers
|
|
7
|
+
module Confidence
|
|
8
|
+
INITIAL_CONFIDENCE = 0.5
|
|
9
|
+
CORROBORATION_BOOST = 0.3
|
|
10
|
+
RETRIEVAL_BOOST = 0.02
|
|
11
|
+
HOURLY_DECAY_FACTOR = 0.998
|
|
12
|
+
DECAY_THRESHOLD = 0.1
|
|
13
|
+
CORROBORATION_SIMILARITY_THRESHOLD = 0.9
|
|
14
|
+
WRITE_CONFIDENCE_GATE = 0.6
|
|
15
|
+
WRITE_NOVELTY_GATE = 0.3
|
|
16
|
+
STALE_DAYS = 90
|
|
17
|
+
CONTENT_TYPES = %i[fact concept procedure association observation].freeze
|
|
18
|
+
STATUSES = %w[candidate confirmed disputed decayed archived].freeze
|
|
19
|
+
RELATION_TYPES = %w[is_a has_a part_of causes similar_to contradicts supersedes depends_on].freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def apply_decay(confidence:, factor: HOURLY_DECAY_FACTOR, **)
|
|
24
|
+
[confidence * factor, 0.0].max
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def apply_retrieval_boost(confidence:, **)
|
|
28
|
+
[confidence + RETRIEVAL_BOOST, 1.0].min
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def apply_corroboration_boost(confidence:, **)
|
|
32
|
+
[confidence + CORROBORATION_BOOST, 1.0].min
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def decayed?(confidence:, **)
|
|
36
|
+
confidence < DECAY_THRESHOLD
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def meets_write_gate?(confidence:, novelty:, **)
|
|
40
|
+
confidence > WRITE_CONFIDENCE_GATE && novelty > WRITE_NOVELTY_GATE
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Apollo
|
|
6
|
+
module Helpers
|
|
7
|
+
module Embedding
|
|
8
|
+
DIMENSION = 1536
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def generate(text:, **)
|
|
13
|
+
return Array.new(DIMENSION, 0.0) unless defined?(Legion::LLM) && Legion::LLM.started?
|
|
14
|
+
|
|
15
|
+
result = Legion::LLM.embed(text: text)
|
|
16
|
+
result.is_a?(Array) && result.size == DIMENSION ? result : Array.new(DIMENSION, 0.0)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Apollo
|
|
6
|
+
module Helpers
|
|
7
|
+
module GraphQuery
|
|
8
|
+
SPREAD_FACTOR = 0.6
|
|
9
|
+
DEFAULT_DEPTH = 2
|
|
10
|
+
MIN_ACTIVATION = 0.1
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def build_traversal_sql(depth: DEFAULT_DEPTH, relation_types: nil, min_activation: MIN_ACTIVATION, **)
|
|
15
|
+
type_filter = if relation_types&.any?
|
|
16
|
+
types = relation_types.map { |t| "'#{t}'" }.join(', ')
|
|
17
|
+
"AND r.relation_type IN (#{types})"
|
|
18
|
+
else
|
|
19
|
+
''
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
<<~SQL
|
|
23
|
+
WITH RECURSIVE graph AS (
|
|
24
|
+
SELECT e.id, e.content, e.content_type, e.confidence, e.tags, e.source_agent,
|
|
25
|
+
0 AS depth, 1.0::float AS activation
|
|
26
|
+
FROM apollo_entries e
|
|
27
|
+
WHERE e.id = $entry_id
|
|
28
|
+
|
|
29
|
+
UNION ALL
|
|
30
|
+
|
|
31
|
+
SELECT e.id, e.content, e.content_type, e.confidence, e.tags, e.source_agent,
|
|
32
|
+
g.depth + 1,
|
|
33
|
+
(g.activation * #{SPREAD_FACTOR} * r.weight)::float
|
|
34
|
+
FROM graph g
|
|
35
|
+
JOIN apollo_relations r ON r.from_entry_id = g.id #{type_filter}
|
|
36
|
+
JOIN apollo_entries e ON e.id = r.to_entry_id
|
|
37
|
+
WHERE g.depth < #{depth}
|
|
38
|
+
AND g.activation * #{SPREAD_FACTOR} * r.weight > #{min_activation}
|
|
39
|
+
)
|
|
40
|
+
SELECT DISTINCT ON (id) id, content, content_type, confidence, tags, source_agent,
|
|
41
|
+
depth, activation
|
|
42
|
+
FROM graph
|
|
43
|
+
ORDER BY id, activation DESC
|
|
44
|
+
SQL
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def build_semantic_search_sql(limit: 10, min_confidence: 0.3, statuses: nil, tags: nil, **)
|
|
48
|
+
conditions = ["e.confidence >= #{min_confidence}"]
|
|
49
|
+
|
|
50
|
+
if statuses&.any?
|
|
51
|
+
status_list = statuses.map { |s| "'#{s}'" }.join(', ')
|
|
52
|
+
conditions << "e.status IN (#{status_list})"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if tags&.any?
|
|
56
|
+
tag_list = tags.map { |t| "'#{t}'" }.join(', ')
|
|
57
|
+
conditions << "e.tags && ARRAY[#{tag_list}]::text[]"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
where_clause = conditions.join(' AND ')
|
|
61
|
+
|
|
62
|
+
<<~SQL
|
|
63
|
+
SELECT e.id, e.content, e.content_type, e.confidence, e.tags, e.source_agent,
|
|
64
|
+
e.access_count, e.created_at,
|
|
65
|
+
(e.embedding <=> $embedding) AS distance
|
|
66
|
+
FROM apollo_entries e
|
|
67
|
+
WHERE #{where_clause}
|
|
68
|
+
AND e.embedding IS NOT NULL
|
|
69
|
+
ORDER BY e.embedding <=> $embedding
|
|
70
|
+
LIMIT #{limit}
|
|
71
|
+
SQL
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'confidence'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Apollo
|
|
8
|
+
module Helpers
|
|
9
|
+
module Similarity
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def cosine_similarity(vec_a:, vec_b:, **)
|
|
13
|
+
dot = vec_a.zip(vec_b).sum { |x, y| x * y }
|
|
14
|
+
mag_a = Math.sqrt(vec_a.sum { |x| x**2 })
|
|
15
|
+
mag_b = Math.sqrt(vec_b.sum { |x| x**2 })
|
|
16
|
+
return 0.0 if mag_a.zero? || mag_b.zero?
|
|
17
|
+
|
|
18
|
+
dot / (mag_a * mag_b)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def above_corroboration_threshold?(similarity:, **)
|
|
22
|
+
similarity >= Confidence::CORROBORATION_SIMILARITY_THRESHOLD
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def classify_match(similarity:, same_content_type: true, contradicts: false, **)
|
|
26
|
+
if above_corroboration_threshold?(similarity: similarity) && same_content_type
|
|
27
|
+
contradicts ? :contradiction : :corroboration
|
|
28
|
+
else
|
|
29
|
+
:novel
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Apollo
|
|
6
|
+
module Runners
|
|
7
|
+
module Expertise
|
|
8
|
+
def get_expertise(domain:, min_proficiency: 0.0, **)
|
|
9
|
+
{ action: :expertise_query, domain: domain, min_proficiency: min_proficiency }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def domains_at_risk(min_agents: 2, **)
|
|
13
|
+
{ action: :domains_at_risk, min_agents: min_agents }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def agent_profile(agent_id:, **)
|
|
17
|
+
{ action: :agent_profile, agent_id: agent_id }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def aggregate(**)
|
|
21
|
+
return { success: false, error: 'apollo_data_not_available' } unless defined?(Legion::Data::Model::ApolloEntry)
|
|
22
|
+
|
|
23
|
+
entries = Legion::Data::Model::ApolloEntry
|
|
24
|
+
.select(:source_agent, :tags, :confidence)
|
|
25
|
+
.exclude(source_agent: nil)
|
|
26
|
+
.all
|
|
27
|
+
|
|
28
|
+
groups = {}
|
|
29
|
+
entries.each do |entry|
|
|
30
|
+
agent = entry.source_agent
|
|
31
|
+
domain = entry.tags.is_a?(Array) ? (entry.tags.first || 'general') : 'general'
|
|
32
|
+
key = "#{agent}:#{domain}"
|
|
33
|
+
groups[key] ||= { agent_id: agent, domain: domain, confidences: [] }
|
|
34
|
+
groups[key][:confidences] << entry.confidence.to_f
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
agent_set = Set.new
|
|
38
|
+
domain_set = Set.new
|
|
39
|
+
|
|
40
|
+
groups.each_value do |group|
|
|
41
|
+
avg = group[:confidences].sum / group[:confidences].size
|
|
42
|
+
count = group[:confidences].size
|
|
43
|
+
proficiency = [avg * Math.log2(count + 1), 1.0].min
|
|
44
|
+
|
|
45
|
+
existing = Legion::Data::Model::ApolloExpertise
|
|
46
|
+
.where(agent_id: group[:agent_id], domain: group[:domain]).first
|
|
47
|
+
|
|
48
|
+
if existing
|
|
49
|
+
existing.update(proficiency: proficiency, entry_count: count, last_active_at: Time.now)
|
|
50
|
+
else
|
|
51
|
+
Legion::Data::Model::ApolloExpertise.create(
|
|
52
|
+
agent_id: group[:agent_id], domain: group[:domain],
|
|
53
|
+
proficiency: proficiency, entry_count: count, last_active_at: Time.now
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
agent_set << group[:agent_id]
|
|
58
|
+
domain_set << group[:domain]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
{ success: true, agents: agent_set.size, domains: domain_set.size }
|
|
62
|
+
rescue Sequel::Error => e
|
|
63
|
+
{ success: false, error: e.message }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|