Class: Legion::Extensions::Agentic::Inference::Abductive::Helpers::AbductionEngine

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb

Constant Summary

Constants included from Constants

Constants::CONTRADICTION_PENALTY, Constants::DECAY_RATE, Constants::DEFAULT_PLAUSIBILITY, Constants::EVIDENCE_BOOST, Constants::EXPLANATORY_POWER_WEIGHT, Constants::HYPOTHESIS_STATES, Constants::MAX_EXPLANATIONS, Constants::MAX_HISTORY, Constants::MAX_HYPOTHESES, Constants::MAX_OBSERVATIONS, Constants::PLAUSIBILITY_CEILING, Constants::PLAUSIBILITY_FLOOR, Constants::PRIOR_WEIGHT, Constants::QUALITY_LABELS, Constants::SIMPLICITY_WEIGHT, Constants::STALE_THRESHOLD, Constants::SURPRISE_LEVELS

Instance Method Summary collapse

Constructor Details

#initializeAbductionEngine

Returns a new instance of AbductionEngine.



12
13
14
15
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 12

def initialize
  @observations = {}
  @hypotheses   = {}
end

Instance Method Details

#add_evidence(hypothesis_id:, supporting:) ⇒ Object



59
60
61
62
63
64
65
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 59

def add_evidence(hypothesis_id:, supporting:)
  hyp = @hypotheses[hypothesis_id]
  return { found: false } unless hyp

  hyp.add_evidence(supporting: supporting)
  { found: true, hypothesis_id: hypothesis_id, state: hyp.state, plausibility: hyp.plausibility }
end

#best_explanation(observation_id:) ⇒ Object



67
68
69
70
71
72
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 67

def best_explanation(observation_id:)
  candidates = active_hypotheses_for(observation_id)
  return nil if candidates.empty?

  candidates.max_by(&:overall_score)
end

#competing_hypotheses(observation_id:) ⇒ Object



74
75
76
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 74

def competing_hypotheses(observation_id:)
  active_hypotheses_for(observation_id).sort_by { |h| -h.overall_score }
end

#decay_staleObject



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 98

def decay_stale
  cutoff = Time.now.utc - Constants::STALE_THRESHOLD
  decayed = 0
  @hypotheses.each_value do |hyp|
    next if hyp.state == :refuted
    next if hyp.last_evaluated_at >= cutoff

    hyp.plausibility = (hyp.plausibility - Constants::DECAY_RATE).clamp(
      Constants::PLAUSIBILITY_FLOOR,
      Constants::PLAUSIBILITY_CEILING
    )
    decayed += 1
  end
  decayed
end

#evaluate_hypothesis(hypothesis_id:) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 44

def evaluate_hypothesis(hypothesis_id:)
  hyp = @hypotheses[hypothesis_id]
  return { found: false } unless hyp

  hyp.instance_variable_set(:@last_evaluated_at, Time.now.utc)
  ranked = ranked_hypotheses_for_observations(hyp.observation_ids)
  rank = ranked.index { |h| h.id == hypothesis_id }.to_i + 1

  {
    score:         hyp.overall_score,
    rank:          rank,
    quality_label: hyp.quality_label
  }
end

#find_by_domain(domain:) ⇒ Object



86
87
88
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 86

def find_by_domain(domain:)
  @hypotheses.values.select { |h| h.domain == domain && h.state != :refuted }
end

#generate_hypothesis(content:, observation_ids:, domain:, simplicity:, explanatory_power:, prior_probability: Constants::DEFAULT_PLAUSIBILITY) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 29

def generate_hypothesis(content:, observation_ids:, domain:, simplicity:,
                        explanatory_power:, prior_probability: Constants::DEFAULT_PLAUSIBILITY)
  hyp = Hypothesis.new(
    content:           content,
    observation_ids:   observation_ids,
    domain:            domain,
    simplicity:        simplicity,
    explanatory_power: explanatory_power,
    prior_probability: prior_probability
  )
  prune_hypotheses if @hypotheses.size >= Constants::MAX_HYPOTHESES
  @hypotheses[hyp.id] = hyp
  hyp
end

#prune_refutedObject



114
115
116
117
118
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 114

def prune_refuted
  before = @hypotheses.size
  @hypotheses.delete_if { |_, h| h.state == :refuted }
  before - @hypotheses.size
end

#record_observation(content:, domain:, surprise_level: :notable, context: {}) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 17

def record_observation(content:, domain:, surprise_level: :notable, context: {})
  obs = Observation.new(
    content:        content,
    domain:         domain,
    surprise_level: surprise_level,
    context:        context
  )
  prune_observations if @observations.size >= Constants::MAX_OBSERVATIONS
  @observations[obs.id] = obs
  obs
end

#refute_hypothesis(hypothesis_id:) ⇒ Object



78
79
80
81
82
83
84
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 78

def refute_hypothesis(hypothesis_id:)
  hyp = @hypotheses[hypothesis_id]
  return { found: false } unless hyp

  hyp.refute!
  { found: true, hypothesis_id: hypothesis_id, state: hyp.state }
end

#to_hObject



120
121
122
123
124
125
126
127
128
129
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 120

def to_h
  {
    observation_count: @observations.size,
    hypothesis_count:  @hypotheses.size,
    supported_count:   @hypotheses.values.count { |h| h.state == :supported },
    refuted_count:     @hypotheses.values.count { |h| h.state == :refuted },
    candidate_count:   @hypotheses.values.count { |h| h.state == :candidate },
    unexplained_count: unexplained_observations.size
  }
end

#unexplained_observationsObject



90
91
92
93
94
95
96
# File 'lib/legion/extensions/agentic/inference/abductive/helpers/abduction_engine.rb', line 90

def unexplained_observations
  @observations.values.reject do |obs|
    @hypotheses.values.any? do |h|
      h.state == :supported && h.observation_ids.include?(obs.id)
    end
  end
end