lex-cognitive-empathy 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1f590431e81d898d029f4728f145da572298e5d07f3a7b9c105d0ffd05519a8c
4
+ data.tar.gz: e7ad498fdb8d1ae424fd5cac5b3463031358e0b74a6573f8d970c4dac68a042e
5
+ SHA512:
6
+ metadata.gz: e5bcfb9c8f0a05cc9bdea9d19fc031a461cb4ae5b05a159ee22eedfbb671797dde95ab424b94e804f18eb208bb78e73f8476642df35fc4764568b395a003f90b
7
+ data.tar.gz: 659247e264da0939ade120322f814014b604c966ec4cac8921b46bfae5d1deca84ccfc5c263a51693d432a61fa93efee60f4fb0de06f5c1c8150375b769bbdf2
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEmpathy
6
+ class Client
7
+ include Runners::CognitiveEmpathy
8
+
9
+ def initialize(engine: nil)
10
+ @engine = engine || Helpers::EmpathyEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEmpathy
6
+ module Helpers
7
+ module Constants
8
+ MAX_PERSPECTIVES = 50
9
+ MAX_INTERACTIONS = 200
10
+ MAX_HISTORY = 200
11
+
12
+ DEFAULT_ACCURACY = 0.5
13
+ ACCURACY_FLOOR = 0.1
14
+ ACCURACY_CEILING = 0.95
15
+ CONTAGION_RATE = 0.15
16
+ CONTAGION_DECAY = 0.05
17
+ ACCURACY_ALPHA = 0.1
18
+
19
+ PERSPECTIVE_TYPES = %i[cognitive affective motivational situational].freeze
20
+ EMPATHIC_STATES = %i[detached observing resonating immersed].freeze
21
+
22
+ ACCURACY_LABELS = {
23
+ (0.8..) => :excellent,
24
+ (0.6...0.8) => :good,
25
+ (0.4...0.6) => :moderate,
26
+ (0.2...0.4) => :poor,
27
+ (..0.2) => :blind
28
+ }.freeze
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEmpathy
6
+ module Helpers
7
+ class EmpathyEngine
8
+ include Constants
9
+
10
+ attr_reader :perspectives, :contagion_level, :history
11
+
12
+ def initialize
13
+ @perspectives = {}
14
+ @contagion_level = 0.0
15
+ @counter = 0
16
+ @history = []
17
+ end
18
+
19
+ def take_perspective(agent_id:, perspective_type:, predicted_state:, confidence:)
20
+ return nil if @perspectives.size >= MAX_PERSPECTIVES
21
+ return nil unless PERSPECTIVE_TYPES.include?(perspective_type)
22
+
23
+ @counter += 1
24
+ id = :"perspective_#{@counter}"
25
+ perspective = Perspective.new(
26
+ id: id,
27
+ agent_id: agent_id,
28
+ perspective_type: perspective_type,
29
+ predicted_state: predicted_state,
30
+ confidence: confidence
31
+ )
32
+ @perspectives[id] = perspective
33
+ record_event(:perspective_taken, id: id, agent_id: agent_id)
34
+ perspective
35
+ end
36
+
37
+ def record_outcome(perspective_id:, actual_state:)
38
+ perspective = @perspectives[perspective_id]
39
+ return nil unless perspective
40
+
41
+ perspective.record_actual(actual_state: actual_state)
42
+ record_event(:outcome_recorded, id: perspective_id, accuracy: perspective.accuracy)
43
+ perspective
44
+ end
45
+
46
+ def empathic_accuracy(agent_id:)
47
+ agent_perspectives = perspectives_for(agent_id: agent_id)
48
+ resolved = agent_perspectives.select(&:resolved?)
49
+ return DEFAULT_ACCURACY if resolved.empty?
50
+
51
+ resolved.sum(&:accuracy) / resolved.size
52
+ end
53
+
54
+ def overall_accuracy
55
+ resolved = @perspectives.values.select(&:resolved?)
56
+ return DEFAULT_ACCURACY if resolved.empty?
57
+
58
+ resolved.sum(&:accuracy) / resolved.size
59
+ end
60
+
61
+ def emotional_contagion(emotion_valence:, intensity:)
62
+ intensity_f = intensity.to_f.clamp(0.0, 1.0)
63
+ absorption = CONTAGION_RATE * intensity_f
64
+ @contagion_level = (@contagion_level + absorption).clamp(0.0, 1.0)
65
+ record_event(:contagion, valence: emotion_valence, intensity: intensity_f,
66
+ level: @contagion_level)
67
+ @contagion_level
68
+ end
69
+
70
+ def contagion_decay
71
+ @contagion_level = [@contagion_level - CONTAGION_DECAY, 0.0].max
72
+ end
73
+
74
+ def empathic_state
75
+ return :immersed if @contagion_level >= 0.75
76
+ return :resonating if @contagion_level >= 0.45
77
+ return :observing if @contagion_level >= 0.15
78
+
79
+ :detached
80
+ end
81
+
82
+ def perspectives_for(agent_id:)
83
+ @perspectives.values.select { |p| p.agent_id == agent_id }
84
+ end
85
+
86
+ def most_accurate_agent
87
+ agent_accuracies = build_agent_accuracies
88
+ return nil if agent_accuracies.empty?
89
+
90
+ agent_accuracies.max_by { |_, acc| acc }&.first
91
+ end
92
+
93
+ def least_accurate_agent
94
+ agent_accuracies = build_agent_accuracies
95
+ return nil if agent_accuracies.empty?
96
+
97
+ agent_accuracies.min_by { |_, acc| acc }&.first
98
+ end
99
+
100
+ def tick
101
+ contagion_decay
102
+ prune_old_perspectives
103
+ self
104
+ end
105
+
106
+ def to_h
107
+ {
108
+ perspective_count: @perspectives.size,
109
+ resolved_count: @perspectives.values.count(&:resolved?),
110
+ overall_accuracy: overall_accuracy.round(4),
111
+ contagion_level: @contagion_level.round(4),
112
+ empathic_state: empathic_state,
113
+ history_size: @history.size
114
+ }
115
+ end
116
+
117
+ private
118
+
119
+ def build_agent_accuracies
120
+ agent_ids = @perspectives.values.map(&:agent_id).uniq
121
+ accuracies = {}
122
+ agent_ids.each do |aid|
123
+ resolved = perspectives_for(agent_id: aid).select(&:resolved?)
124
+ next if resolved.empty?
125
+
126
+ accuracies[aid] = resolved.sum(&:accuracy) / resolved.size
127
+ end
128
+ accuracies
129
+ end
130
+
131
+ def prune_old_perspectives
132
+ resolved = @perspectives.select { |_, p| p.resolved? }
133
+ return unless resolved.size > MAX_PERSPECTIVES / 2
134
+
135
+ oldest_keys = resolved.keys.first(resolved.size - (MAX_PERSPECTIVES / 4))
136
+ oldest_keys.each { |k| @perspectives.delete(k) }
137
+ end
138
+
139
+ def record_event(type, **details)
140
+ @history << { type: type, at: Time.now.utc }.merge(details)
141
+ @history.shift while @history.size > MAX_HISTORY
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEmpathy
6
+ module Helpers
7
+ class Perspective
8
+ include Constants
9
+
10
+ attr_reader :id, :agent_id, :perspective_type, :predicted_state, :actual_state,
11
+ :confidence, :accuracy
12
+
13
+ def initialize(id:, agent_id:, perspective_type: :cognitive, predicted_state: {}, confidence: 0.5)
14
+ @id = id
15
+ @agent_id = agent_id
16
+ @perspective_type = perspective_type
17
+ @predicted_state = predicted_state
18
+ @confidence = confidence.to_f.clamp(0.0, 1.0)
19
+ @actual_state = nil
20
+ @accuracy = DEFAULT_ACCURACY
21
+ @created_at = Time.now.utc
22
+ @resolved_at = nil
23
+ end
24
+
25
+ def record_actual(actual_state:)
26
+ @actual_state = actual_state
27
+ @resolved_at = Time.now.utc
28
+ error = compute_error(predicted_state, actual_state)
29
+ observed_accuracy = (1.0 - error).clamp(ACCURACY_FLOOR, ACCURACY_CEILING)
30
+ @accuracy = ((1.0 - ACCURACY_ALPHA) * @accuracy) + (ACCURACY_ALPHA * observed_accuracy)
31
+ @accuracy = @accuracy.clamp(ACCURACY_FLOOR, ACCURACY_CEILING)
32
+ self
33
+ end
34
+
35
+ def accurate?
36
+ @accuracy > 0.6
37
+ end
38
+
39
+ def resolved?
40
+ !@actual_state.nil?
41
+ end
42
+
43
+ def to_h
44
+ {
45
+ id: @id,
46
+ agent_id: @agent_id,
47
+ perspective_type: @perspective_type,
48
+ predicted_state: @predicted_state,
49
+ actual_state: @actual_state,
50
+ confidence: @confidence.round(4),
51
+ accuracy: @accuracy.round(4),
52
+ accurate: accurate?,
53
+ resolved: resolved?,
54
+ created_at: @created_at,
55
+ resolved_at: @resolved_at
56
+ }
57
+ end
58
+
59
+ private
60
+
61
+ def compute_error(predicted, actual)
62
+ return 0.0 if predicted.empty? && actual.empty?
63
+ return 1.0 if predicted.empty? || actual.empty?
64
+
65
+ keys = (predicted.keys | actual.keys)
66
+ return 1.0 if keys.empty?
67
+
68
+ total_error = keys.sum do |k|
69
+ p_val = numeric_value(predicted[k])
70
+ a_val = numeric_value(actual[k])
71
+ (p_val - a_val).abs
72
+ end
73
+
74
+ (total_error / keys.size).clamp(0.0, 1.0)
75
+ end
76
+
77
+ def numeric_value(val)
78
+ return val.to_f if val.is_a?(Numeric)
79
+ return 1.0 if val == true
80
+ return 0.0 if val == false || val.nil?
81
+
82
+ 0.5
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEmpathy
6
+ module Runners
7
+ module CognitiveEmpathy
8
+ include Helpers::Constants
9
+ include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
10
+
11
+ def take_empathic_perspective(agent_id:, perspective_type:, predicted_state:, confidence: 0.5, **)
12
+ perspective = engine.take_perspective(
13
+ agent_id: agent_id,
14
+ perspective_type: perspective_type,
15
+ predicted_state: predicted_state,
16
+ confidence: confidence
17
+ )
18
+ return { success: false, reason: :limit_or_invalid_type } unless perspective
19
+
20
+ { success: true, perspective_id: perspective.id, agent_id: agent_id,
21
+ perspective_type: perspective_type }
22
+ end
23
+
24
+ def record_empathic_outcome(perspective_id:, actual_state:, **)
25
+ perspective = engine.record_outcome(perspective_id: perspective_id, actual_state: actual_state)
26
+ return { success: false, reason: :not_found } unless perspective
27
+
28
+ { success: true, perspective_id: perspective_id,
29
+ accuracy: perspective.accuracy.round(4), accurate: perspective.accurate? }
30
+ end
31
+
32
+ def empathic_accuracy_for(agent_id:, **)
33
+ accuracy = engine.empathic_accuracy(agent_id: agent_id)
34
+ label = accuracy_label(accuracy)
35
+ { success: true, agent_id: agent_id, accuracy: accuracy.round(4), label: label }
36
+ end
37
+
38
+ def overall_empathic_accuracy(**)
39
+ accuracy = engine.overall_accuracy
40
+ label = accuracy_label(accuracy)
41
+ { success: true, accuracy: accuracy.round(4), label: label }
42
+ end
43
+
44
+ def apply_emotional_contagion(emotion_valence:, intensity:, **)
45
+ level = engine.emotional_contagion(emotion_valence: emotion_valence, intensity: intensity)
46
+ { success: true, contagion_level: level.round(4), empathic_state: engine.empathic_state }
47
+ end
48
+
49
+ def current_empathic_state(**)
50
+ { success: true, empathic_state: engine.empathic_state,
51
+ contagion_level: engine.contagion_level.round(4) }
52
+ end
53
+
54
+ def perspectives_for_agent(agent_id:, **)
55
+ list = engine.perspectives_for(agent_id: agent_id).map(&:to_h)
56
+ { success: true, agent_id: agent_id, perspectives: list, count: list.size }
57
+ end
58
+
59
+ def empathic_blind_spots(**)
60
+ least = engine.least_accurate_agent
61
+ most = engine.most_accurate_agent
62
+ { success: true, least_accurate_agent: least, most_accurate_agent: most,
63
+ overall_accuracy: engine.overall_accuracy.round(4) }
64
+ end
65
+
66
+ def update_cognitive_empathy(**)
67
+ engine.tick
68
+ { success: true }.merge(engine.to_h)
69
+ end
70
+
71
+ def cognitive_empathy_stats(**)
72
+ { success: true }.merge(engine.to_h)
73
+ end
74
+
75
+ private
76
+
77
+ def engine
78
+ @engine ||= Helpers::EmpathyEngine.new
79
+ end
80
+
81
+ def accuracy_label(accuracy)
82
+ ACCURACY_LABELS.each { |range, lbl| return lbl if range.cover?(accuracy) }
83
+ :blind
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveEmpathy
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-cognitive-empathy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Esity
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: legion-gaia
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: Perspective-taking, emotional contagion modeling, and empathic accuracy
27
+ tracking for LegionIO agentic extensions
28
+ email:
29
+ - matthewdiverson@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/legion/extensions/cognitive_empathy/client.rb
35
+ - lib/legion/extensions/cognitive_empathy/helpers/constants.rb
36
+ - lib/legion/extensions/cognitive_empathy/helpers/empathy_engine.rb
37
+ - lib/legion/extensions/cognitive_empathy/helpers/perspective.rb
38
+ - lib/legion/extensions/cognitive_empathy/runners/cognitive_empathy.rb
39
+ - lib/legion/extensions/cognitive_empathy/version.rb
40
+ homepage: https://github.com/LegionIO/lex-cognitive-empathy
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ homepage_uri: https://github.com/LegionIO/lex-cognitive-empathy
45
+ source_code_uri: https://github.com/LegionIO/lex-cognitive-empathy
46
+ documentation_uri: https://github.com/LegionIO/lex-cognitive-empathy/blob/master/README.md
47
+ changelog_uri: https://github.com/LegionIO/lex-cognitive-empathy/blob/master/CHANGELOG.md
48
+ bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-empathy/issues
49
+ rubygems_mfa_required: 'true'
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '3.4'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 3.6.9
65
+ specification_version: 4
66
+ summary: Cognitive empathy engine for LegionIO
67
+ test_files: []