lex-agentic-language 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.
Files changed (126) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-language.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/language/conceptual_blending/client.rb +25 -0
  8. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/blend.rb +91 -0
  9. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/blending_engine.rb +171 -0
  10. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/constants.rb +35 -0
  11. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/mental_space.rb +51 -0
  12. data/lib/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending.rb +106 -0
  13. data/lib/legion/extensions/agentic/language/conceptual_blending/version.rb +13 -0
  14. data/lib/legion/extensions/agentic/language/conceptual_blending.rb +20 -0
  15. data/lib/legion/extensions/agentic/language/conceptual_metaphor/client.rb +19 -0
  16. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/constants.rb +49 -0
  17. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor.rb +109 -0
  18. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_engine.rb +154 -0
  19. data/lib/legion/extensions/agentic/language/conceptual_metaphor/runners/conceptual_metaphor.rb +107 -0
  20. data/lib/legion/extensions/agentic/language/conceptual_metaphor/version.rb +13 -0
  21. data/lib/legion/extensions/agentic/language/conceptual_metaphor.rb +19 -0
  22. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/client.rb +23 -0
  23. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/constants.rb +32 -0
  24. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame.rb +109 -0
  25. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame_engine.rb +139 -0
  26. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame_instance.rb +51 -0
  27. data/lib/legion/extensions/agentic/language/frame_semantics/runners/frame_semantics.rb +108 -0
  28. data/lib/legion/extensions/agentic/language/frame_semantics/version.rb +13 -0
  29. data/lib/legion/extensions/agentic/language/frame_semantics.rb +20 -0
  30. data/lib/legion/extensions/agentic/language/grammar/client.rb +29 -0
  31. data/lib/legion/extensions/agentic/language/grammar/helpers/constants.rb +38 -0
  32. data/lib/legion/extensions/agentic/language/grammar/helpers/construal.rb +68 -0
  33. data/lib/legion/extensions/agentic/language/grammar/helpers/construction.rb +68 -0
  34. data/lib/legion/extensions/agentic/language/grammar/helpers/grammar_engine.rb +119 -0
  35. data/lib/legion/extensions/agentic/language/grammar/runners/cognitive_grammar.rb +100 -0
  36. data/lib/legion/extensions/agentic/language/grammar/version.rb +13 -0
  37. data/lib/legion/extensions/agentic/language/grammar.rb +20 -0
  38. data/lib/legion/extensions/agentic/language/inner_speech/client.rb +19 -0
  39. data/lib/legion/extensions/agentic/language/inner_speech/helpers/constants.rb +53 -0
  40. data/lib/legion/extensions/agentic/language/inner_speech/helpers/inner_voice.rb +141 -0
  41. data/lib/legion/extensions/agentic/language/inner_speech/helpers/speech_stream.rb +128 -0
  42. data/lib/legion/extensions/agentic/language/inner_speech/helpers/utterance.rb +90 -0
  43. data/lib/legion/extensions/agentic/language/inner_speech/runners/inner_speech.rb +87 -0
  44. data/lib/legion/extensions/agentic/language/inner_speech/version.rb +13 -0
  45. data/lib/legion/extensions/agentic/language/inner_speech.rb +20 -0
  46. data/lib/legion/extensions/agentic/language/language/client.rb +26 -0
  47. data/lib/legion/extensions/agentic/language/language/helpers/constants.rb +43 -0
  48. data/lib/legion/extensions/agentic/language/language/helpers/lexicon.rb +63 -0
  49. data/lib/legion/extensions/agentic/language/language/helpers/summarizer.rb +167 -0
  50. data/lib/legion/extensions/agentic/language/language/runners/language.rb +134 -0
  51. data/lib/legion/extensions/agentic/language/language/version.rb +13 -0
  52. data/lib/legion/extensions/agentic/language/language.rb +19 -0
  53. data/lib/legion/extensions/agentic/language/narrative_reasoning/client.rb +28 -0
  54. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative.rb +123 -0
  55. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine.rb +122 -0
  56. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event.rb +41 -0
  57. data/lib/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning.rb +122 -0
  58. data/lib/legion/extensions/agentic/language/narrative_reasoning/version.rb +13 -0
  59. data/lib/legion/extensions/agentic/language/narrative_reasoning.rb +18 -0
  60. data/lib/legion/extensions/agentic/language/narrator/client.rb +27 -0
  61. data/lib/legion/extensions/agentic/language/narrator/helpers/constants.rb +69 -0
  62. data/lib/legion/extensions/agentic/language/narrator/helpers/journal.rb +68 -0
  63. data/lib/legion/extensions/agentic/language/narrator/helpers/llm_enhancer.rb +105 -0
  64. data/lib/legion/extensions/agentic/language/narrator/helpers/prose.rb +122 -0
  65. data/lib/legion/extensions/agentic/language/narrator/helpers/synthesizer.rb +138 -0
  66. data/lib/legion/extensions/agentic/language/narrator/runners/narrator.rb +196 -0
  67. data/lib/legion/extensions/agentic/language/narrator/version.rb +13 -0
  68. data/lib/legion/extensions/agentic/language/narrator.rb +21 -0
  69. data/lib/legion/extensions/agentic/language/pragmatic_inference/client.rb +28 -0
  70. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/constants.rb +52 -0
  71. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine.rb +164 -0
  72. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/utterance.rb +84 -0
  73. data/lib/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference.rb +136 -0
  74. data/lib/legion/extensions/agentic/language/pragmatic_inference/version.rb +13 -0
  75. data/lib/legion/extensions/agentic/language/pragmatic_inference.rb +18 -0
  76. data/lib/legion/extensions/agentic/language/version.rb +11 -0
  77. data/lib/legion/extensions/agentic/language.rb +28 -0
  78. data/spec/legion/extensions/agentic/language/conceptual_blending/client_spec.rb +78 -0
  79. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/blend_spec.rb +141 -0
  80. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/blending_engine_spec.rb +211 -0
  81. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/mental_space_spec.rb +85 -0
  82. data/spec/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending_spec.rb +162 -0
  83. data/spec/legion/extensions/agentic/language/conceptual_metaphor/client_spec.rb +29 -0
  84. data/spec/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_engine_spec.rb +166 -0
  85. data/spec/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_spec.rb +133 -0
  86. data/spec/legion/extensions/agentic/language/conceptual_metaphor/runners/conceptual_metaphor_spec.rb +133 -0
  87. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_engine_spec.rb +227 -0
  88. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_instance_spec.rb +83 -0
  89. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_spec.rb +213 -0
  90. data/spec/legion/extensions/agentic/language/frame_semantics/runners/frame_semantics_spec.rb +155 -0
  91. data/spec/legion/extensions/agentic/language/grammar/client_spec.rb +121 -0
  92. data/spec/legion/extensions/agentic/language/grammar/cognitive_grammar_spec.rb +18 -0
  93. data/spec/legion/extensions/agentic/language/grammar/helpers/constants_spec.rb +67 -0
  94. data/spec/legion/extensions/agentic/language/grammar/helpers/construal_spec.rb +124 -0
  95. data/spec/legion/extensions/agentic/language/grammar/helpers/construction_spec.rb +155 -0
  96. data/spec/legion/extensions/agentic/language/grammar/helpers/grammar_engine_spec.rb +206 -0
  97. data/spec/legion/extensions/agentic/language/grammar/runners/cognitive_grammar_spec.rb +189 -0
  98. data/spec/legion/extensions/agentic/language/inner_speech/client_spec.rb +39 -0
  99. data/spec/legion/extensions/agentic/language/inner_speech/helpers/inner_voice_spec.rb +185 -0
  100. data/spec/legion/extensions/agentic/language/inner_speech/helpers/speech_stream_spec.rb +158 -0
  101. data/spec/legion/extensions/agentic/language/inner_speech/helpers/utterance_spec.rb +121 -0
  102. data/spec/legion/extensions/agentic/language/inner_speech/runners/inner_speech_spec.rb +102 -0
  103. data/spec/legion/extensions/agentic/language/language/client_spec.rb +20 -0
  104. data/spec/legion/extensions/agentic/language/language/helpers/constants_spec.rb +31 -0
  105. data/spec/legion/extensions/agentic/language/language/helpers/lexicon_spec.rb +116 -0
  106. data/spec/legion/extensions/agentic/language/language/helpers/summarizer_spec.rb +224 -0
  107. data/spec/legion/extensions/agentic/language/language/runners/language_spec.rb +169 -0
  108. data/spec/legion/extensions/agentic/language/narrative_reasoning/client_spec.rb +19 -0
  109. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine_spec.rb +182 -0
  110. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event_spec.rb +61 -0
  111. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_spec.rb +168 -0
  112. data/spec/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning_spec.rb +174 -0
  113. data/spec/legion/extensions/agentic/language/narrator/client_spec.rb +24 -0
  114. data/spec/legion/extensions/agentic/language/narrator/helpers/journal_spec.rb +95 -0
  115. data/spec/legion/extensions/agentic/language/narrator/helpers/llm_enhancer_spec.rb +107 -0
  116. data/spec/legion/extensions/agentic/language/narrator/helpers/prose_spec.rb +134 -0
  117. data/spec/legion/extensions/agentic/language/narrator/helpers/synthesizer_spec.rb +89 -0
  118. data/spec/legion/extensions/agentic/language/narrator/runners/narrator_llm_spec.rb +74 -0
  119. data/spec/legion/extensions/agentic/language/narrator/runners/narrator_spec.rb +126 -0
  120. data/spec/legion/extensions/agentic/language/pragmatic_inference/client_spec.rb +19 -0
  121. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/constants_spec.rb +73 -0
  122. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine_spec.rb +185 -0
  123. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/utterance_spec.rb +111 -0
  124. data/spec/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference_spec.rb +231 -0
  125. data/spec/spec_helper.rb +33 -0
  126. metadata +210 -0
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::PragmaticInference::Helpers::Constants do
4
+ describe 'MAXIMS' do
5
+ it 'contains four Gricean maxims' do
6
+ expect(described_class::MAXIMS).to eq(%i[quality quantity relevance manner])
7
+ end
8
+ end
9
+
10
+ describe 'VIOLATION_TYPES' do
11
+ it 'contains expected violation types' do
12
+ expect(described_class::VIOLATION_TYPES).to include(:none, :flouting, :violating, :opting_out)
13
+ end
14
+ end
15
+
16
+ describe 'SPEECH_ACTS' do
17
+ it 'contains expected speech act types' do
18
+ expect(described_class::SPEECH_ACTS).to include(:assert, :question, :request, :promise, :warn, :inform, :suggest)
19
+ end
20
+ end
21
+
22
+ describe 'MAXIM_DESCRIPTIONS' do
23
+ it 'has a description for each maxim' do
24
+ described_class::MAXIMS.each do |maxim|
25
+ expect(described_class::MAXIM_DESCRIPTIONS[maxim]).to be_a(String)
26
+ end
27
+ end
28
+ end
29
+
30
+ describe '.valid_maxim?' do
31
+ it 'returns true for valid maxims' do
32
+ expect(described_class.valid_maxim?(:quality)).to be true
33
+ expect(described_class.valid_maxim?(:manner)).to be true
34
+ end
35
+
36
+ it 'returns false for invalid maxims' do
37
+ expect(described_class.valid_maxim?(:sincerity)).to be false
38
+ end
39
+ end
40
+
41
+ describe '.valid_violation_type?' do
42
+ it 'returns true for valid types' do
43
+ expect(described_class.valid_violation_type?(:flouting)).to be true
44
+ end
45
+
46
+ it 'returns false for invalid types' do
47
+ expect(described_class.valid_violation_type?(:unknown)).to be false
48
+ end
49
+ end
50
+
51
+ describe '.valid_speech_act?' do
52
+ it 'returns true for valid speech acts' do
53
+ expect(described_class.valid_speech_act?(:assert)).to be true
54
+ end
55
+
56
+ it 'returns false for invalid speech acts' do
57
+ expect(described_class.valid_speech_act?(:perform)).to be false
58
+ end
59
+ end
60
+
61
+ describe 'numeric constants' do
62
+ it 'has correct confidence bounds' do
63
+ expect(described_class::CONFIDENCE_FLOOR).to eq(0.0)
64
+ expect(described_class::CONFIDENCE_CEILING).to eq(1.0)
65
+ expect(described_class::DEFAULT_CONFIDENCE).to eq(0.5)
66
+ end
67
+
68
+ it 'has positive reinforcement and decay rates' do
69
+ expect(described_class::REINFORCEMENT_RATE).to be > 0
70
+ expect(described_class::DECAY_RATE).to be > 0
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::PragmaticInference::Helpers::PragmaticEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:quality_violating_scores) { { quality: 0.1, quantity: 0.9, relevance: 0.8, manner: 0.9 } }
7
+ let(:compliant_scores) { { quality: 0.9, quantity: 0.8, relevance: 0.9, manner: 0.95 } }
8
+
9
+ def add_utterance(speaker: 'alice', speech_act: :assert, scores: compliant_scores)
10
+ engine.analyze_utterance(
11
+ content: 'test utterance',
12
+ speaker: speaker,
13
+ speech_act: speech_act,
14
+ maxim_scores: scores
15
+ )
16
+ end
17
+
18
+ describe '#analyze_utterance' do
19
+ it 'stores and returns an Utterance' do
20
+ utterance = add_utterance
21
+ expect(utterance).to be_a(Legion::Extensions::Agentic::Language::PragmaticInference::Helpers::Utterance)
22
+ end
23
+
24
+ it 'stores the utterance by id' do
25
+ utterance = add_utterance
26
+ expect(engine.utterances[utterance.id]).to eq(utterance)
27
+ end
28
+
29
+ it 'records history entry' do
30
+ add_utterance
31
+ expect(engine.history).not_to be_empty
32
+ end
33
+
34
+ it 'trims utterances when at capacity' do
35
+ stub_const('Legion::Extensions::Agentic::Language::PragmaticInference::Helpers::Constants::MAX_UTTERANCES', 3)
36
+ 3.times { add_utterance }
37
+ initial_ids = engine.utterances.keys.dup
38
+ add_utterance
39
+ expect(engine.utterances.keys).not_to include(initial_ids.first)
40
+ end
41
+ end
42
+
43
+ describe '#detect_violations' do
44
+ it 'returns violations for low-scoring maxims' do
45
+ utterance = add_utterance(scores: quality_violating_scores)
46
+ violations = engine.detect_violations(utterance_id: utterance.id)
47
+ expect(violations.map { |v| v[:maxim] }).to include(:quality)
48
+ end
49
+
50
+ it 'returns empty array for compliant utterance' do
51
+ utterance = add_utterance(scores: compliant_scores)
52
+ violations = engine.detect_violations(utterance_id: utterance.id)
53
+ expect(violations).to be_empty
54
+ end
55
+
56
+ it 'returns empty array for unknown utterance id' do
57
+ expect(engine.detect_violations(utterance_id: 'nonexistent')).to eq([])
58
+ end
59
+
60
+ it 'classifies severe violation as :violating' do
61
+ utterance = add_utterance(scores: { quality: 0.05, quantity: 0.9, relevance: 0.9, manner: 0.9 })
62
+ violations = engine.detect_violations(utterance_id: utterance.id)
63
+ quality_violation = violations.find { |v| v[:maxim] == :quality }
64
+ expect(quality_violation[:violation_type]).to eq(:violating)
65
+ end
66
+
67
+ it 'classifies moderate violation as :flouting' do
68
+ utterance = add_utterance(scores: { quality: 0.2, quantity: 0.9, relevance: 0.9, manner: 0.9 })
69
+ violations = engine.detect_violations(utterance_id: utterance.id)
70
+ quality_violation = violations.find { |v| v[:maxim] == :quality }
71
+ expect(quality_violation[:violation_type]).to eq(:flouting)
72
+ end
73
+
74
+ it 'classifies mild violation as :opting_out' do
75
+ utterance = add_utterance(scores: { quality: 0.4, quantity: 0.9, relevance: 0.9, manner: 0.9 })
76
+ violations = engine.detect_violations(utterance_id: utterance.id)
77
+ quality_violation = violations.find { |v| v[:maxim] == :quality }
78
+ expect(quality_violation[:violation_type]).to eq(:opting_out)
79
+ end
80
+ end
81
+
82
+ describe '#generate_implicature' do
83
+ it 'adds an implicature to the utterance' do
84
+ utterance = add_utterance
85
+ result = engine.generate_implicature(utterance_id: utterance.id, inferred_meaning: 'indirect request')
86
+ expect(result).to eq('indirect request')
87
+ expect(utterance.implicatures.size).to eq(1)
88
+ end
89
+
90
+ it 'returns nil for unknown utterance' do
91
+ result = engine.generate_implicature(utterance_id: 'unknown', inferred_meaning: 'something')
92
+ expect(result).to be_nil
93
+ end
94
+ end
95
+
96
+ describe '#speaker_profile' do
97
+ it 'returns profile for a speaker with utterances' do
98
+ add_utterance(speaker: 'bob', scores: compliant_scores)
99
+ add_utterance(speaker: 'bob', scores: compliant_scores)
100
+ profile = engine.speaker_profile(speaker: 'bob')
101
+ expect(profile[:speaker]).to eq('bob')
102
+ expect(profile[:utterance_count]).to eq(2)
103
+ expect(profile[:compliance_by_maxim].keys).to match_array(%i[quality quantity relevance manner])
104
+ end
105
+
106
+ it 'returns empty profile for unknown speaker' do
107
+ profile = engine.speaker_profile(speaker: 'unknown')
108
+ expect(profile[:utterance_count]).to eq(0)
109
+ end
110
+ end
111
+
112
+ describe '#by_speech_act' do
113
+ it 'filters utterances by speech act' do
114
+ add_utterance(speech_act: :question)
115
+ add_utterance(speech_act: :assert)
116
+ add_utterance(speech_act: :question)
117
+ result = engine.by_speech_act(speech_act: :question)
118
+ expect(result.size).to eq(2)
119
+ end
120
+ end
121
+
122
+ describe '#by_speaker' do
123
+ it 'filters utterances by speaker' do
124
+ add_utterance(speaker: 'alice')
125
+ add_utterance(speaker: 'bob')
126
+ result = engine.by_speaker(speaker: 'alice')
127
+ expect(result.size).to eq(1)
128
+ end
129
+ end
130
+
131
+ describe '#most_violated_maxim' do
132
+ it 'returns nil when no utterances' do
133
+ expect(engine.most_violated_maxim).to be_nil
134
+ end
135
+
136
+ it 'returns the most commonly violated maxim' do
137
+ 3.times { add_utterance(scores: { quality: 0.1, quantity: 0.9, relevance: 0.9, manner: 0.9 }) }
138
+ add_utterance(scores: { quality: 0.9, quantity: 0.1, relevance: 0.9, manner: 0.9 })
139
+ engine.utterances.each_value { |u| engine.detect_violations(utterance_id: u.id) }
140
+ expect(engine.most_violated_maxim).to eq(:quality)
141
+ end
142
+ end
143
+
144
+ describe '#overall_cooperation' do
145
+ it 'returns 0.0 when no utterances' do
146
+ expect(engine.overall_cooperation).to eq(0.0)
147
+ end
148
+
149
+ it 'returns mean compliance across utterances' do
150
+ add_utterance(scores: { quality: 1.0, quantity: 1.0, relevance: 1.0, manner: 1.0 })
151
+ add_utterance(scores: { quality: 0.0, quantity: 0.0, relevance: 0.0, manner: 0.0 })
152
+ expect(engine.overall_cooperation).to be_within(0.01).of(0.5)
153
+ end
154
+ end
155
+
156
+ describe '#reinforce' do
157
+ it 'increases utterance confidence' do
158
+ utterance = add_utterance
159
+ original_confidence = utterance.confidence
160
+ engine.reinforce(utterance_id: utterance.id)
161
+ expect(utterance.confidence).to be > original_confidence
162
+ end
163
+ end
164
+
165
+ describe '#decay_all' do
166
+ it 'decreases confidence of all utterances' do
167
+ u1 = add_utterance
168
+ u2 = add_utterance
169
+ c1 = u1.confidence
170
+ c2 = u2.confidence
171
+ engine.decay_all
172
+ expect(u1.confidence).to be < c1
173
+ expect(u2.confidence).to be < c2
174
+ end
175
+ end
176
+
177
+ describe '#to_h' do
178
+ it 'returns stats hash with expected keys' do
179
+ add_utterance
180
+ h = engine.to_h
181
+ expect(h).to include(:utterance_count, :overall_cooperation, :most_violated_maxim,
182
+ :history_size, :speakers)
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::PragmaticInference::Helpers::Utterance do
4
+ let(:utterance) do
5
+ described_class.new(
6
+ content: 'Can you pass the salt?',
7
+ speaker: 'alice',
8
+ speech_act: :request,
9
+ maxim_scores: { quality: 0.9, quantity: 0.8, relevance: 0.9, manner: 0.95 }
10
+ )
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'assigns a UUID id' do
15
+ expect(utterance.id).to match(/\A[0-9a-f-]{36}\z/)
16
+ end
17
+
18
+ it 'sets content and speaker' do
19
+ expect(utterance.content).to eq('Can you pass the salt?')
20
+ expect(utterance.speaker).to eq('alice')
21
+ end
22
+
23
+ it 'sets speech act' do
24
+ expect(utterance.speech_act).to eq(:request)
25
+ end
26
+
27
+ it 'initializes violations and implicatures as empty arrays' do
28
+ expect(utterance.violations).to be_empty
29
+ expect(utterance.implicatures).to be_empty
30
+ end
31
+
32
+ it 'clamps confidence to valid range' do
33
+ u = described_class.new(content: 'x', speaker: 'a', speech_act: :assert, confidence: 5.0)
34
+ expect(u.confidence).to eq(1.0)
35
+ end
36
+
37
+ it 'defaults missing maxim scores to DEFAULT_CONFIDENCE' do
38
+ u = described_class.new(content: 'x', speaker: 'a', speech_act: :assert, maxim_scores: {})
39
+ expect(u.maxim_scores[:quality]).to eq(0.5)
40
+ end
41
+ end
42
+
43
+ describe '#overall_compliance' do
44
+ it 'returns mean of maxim scores' do
45
+ expected = (0.9 + 0.8 + 0.9 + 0.95) / 4.0
46
+ expect(utterance.overall_compliance).to be_within(0.001).of(expected)
47
+ end
48
+ end
49
+
50
+ describe '#violated_maxims' do
51
+ it 'returns maxims with score below 0.5' do
52
+ u = described_class.new(
53
+ content: 'test',
54
+ speaker: 'bob',
55
+ speech_act: :inform,
56
+ maxim_scores: { quality: 0.1, quantity: 0.9, relevance: 0.8, manner: 0.3 }
57
+ )
58
+ expect(u.violated_maxims).to include(:quality, :manner)
59
+ expect(u.violated_maxims).not_to include(:quantity, :relevance)
60
+ end
61
+
62
+ it 'returns empty array when all maxims compliant' do
63
+ expect(utterance.violated_maxims).to be_empty
64
+ end
65
+ end
66
+
67
+ describe '#add_implicature' do
68
+ it 'adds an inferred meaning' do
69
+ utterance.add_implicature(meaning: 'requesting action, not asking ability')
70
+ expect(utterance.implicatures.size).to eq(1)
71
+ expect(utterance.implicatures.first[:meaning]).to eq('requesting action, not asking ability')
72
+ end
73
+
74
+ it 'does not exceed MAX_IMPLICATURES' do
75
+ Legion::Extensions::Agentic::Language::PragmaticInference::Helpers::Constants::MAX_IMPLICATURES.times do |i|
76
+ utterance.add_implicature(meaning: "meaning #{i}")
77
+ end
78
+ utterance.add_implicature(meaning: 'one more')
79
+ expect(utterance.implicatures.size).to eq(
80
+ Legion::Extensions::Agentic::Language::PragmaticInference::Helpers::Constants::MAX_IMPLICATURES
81
+ )
82
+ end
83
+ end
84
+
85
+ describe '#update_confidence' do
86
+ it 'increases confidence by delta' do
87
+ original = utterance.confidence
88
+ utterance.update_confidence(0.1)
89
+ expect(utterance.confidence).to be_within(0.001).of(original + 0.1)
90
+ end
91
+
92
+ it 'clamps at ceiling' do
93
+ utterance.update_confidence(10.0)
94
+ expect(utterance.confidence).to eq(1.0)
95
+ end
96
+
97
+ it 'clamps at floor' do
98
+ utterance.update_confidence(-10.0)
99
+ expect(utterance.confidence).to eq(0.0)
100
+ end
101
+ end
102
+
103
+ describe '#to_h' do
104
+ it 'returns a hash with all expected keys' do
105
+ h = utterance.to_h
106
+ expect(h).to include(:id, :content, :speaker, :speech_act, :maxim_scores,
107
+ :violations, :implicatures, :confidence,
108
+ :overall_compliance, :violated_maxims, :created_at)
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/pragmatic_inference/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Language::PragmaticInference::Runners::PragmaticInference do
6
+ let(:client) { Legion::Extensions::Agentic::Language::PragmaticInference::Client.new }
7
+
8
+ let(:compliant_scores) { { quality: 0.9, quantity: 0.8, relevance: 0.9, manner: 0.95 } }
9
+ let(:violating_scores) { { quality: 0.05, quantity: 0.9, relevance: 0.9, manner: 0.9 } }
10
+
11
+ def analyze(speaker: 'alice', speech_act: :assert, scores: compliant_scores)
12
+ client.analyze_utterance(
13
+ content: 'test content',
14
+ speaker: speaker,
15
+ speech_act: speech_act,
16
+ maxim_scores: scores
17
+ )
18
+ end
19
+
20
+ describe '#analyze_utterance' do
21
+ it 'returns success with a valid speech act' do
22
+ result = analyze
23
+ expect(result[:success]).to be true
24
+ expect(result[:utterance_id]).to match(/\A[0-9a-f-]{36}\z/)
25
+ end
26
+
27
+ it 'returns overall_compliance' do
28
+ result = analyze(scores: compliant_scores)
29
+ expect(result[:overall_compliance]).to be_a(Float)
30
+ end
31
+
32
+ it 'rejects invalid speech act' do
33
+ result = client.analyze_utterance(
34
+ content: 'test', speaker: 'alice', speech_act: :perform, maxim_scores: {}
35
+ )
36
+ expect(result[:success]).to be false
37
+ expect(result[:error]).to eq(:invalid_speech_act)
38
+ end
39
+
40
+ it 'includes violated_maxims in result' do
41
+ result = analyze(scores: violating_scores)
42
+ expect(result[:violated_maxims]).to include(:quality)
43
+ end
44
+
45
+ it 'reports no violations for compliant utterance' do
46
+ result = analyze(scores: compliant_scores)
47
+ expect(result[:violated_maxims]).to be_empty
48
+ end
49
+ end
50
+
51
+ describe '#detect_maxim_violations' do
52
+ it 'detects violations for low-scoring utterance' do
53
+ analyzed = analyze(scores: violating_scores)
54
+ result = client.detect_maxim_violations(utterance_id: analyzed[:utterance_id])
55
+ expect(result[:success]).to be true
56
+ expect(result[:violation_count]).to be >= 1
57
+ expect(result[:violations].map { |v| v[:maxim] }).to include(:quality)
58
+ end
59
+
60
+ it 'returns zero violations for compliant utterance' do
61
+ analyzed = analyze(scores: compliant_scores)
62
+ result = client.detect_maxim_violations(utterance_id: analyzed[:utterance_id])
63
+ expect(result[:violation_count]).to eq(0)
64
+ end
65
+
66
+ it 'rejects short utterance_id' do
67
+ result = client.detect_maxim_violations(utterance_id: 'ab')
68
+ expect(result[:success]).to be false
69
+ end
70
+ end
71
+
72
+ describe '#generate_pragmatic_implicature' do
73
+ it 'adds implicature to existing utterance' do
74
+ analyzed = analyze
75
+ result = client.generate_pragmatic_implicature(
76
+ utterance_id: analyzed[:utterance_id],
77
+ inferred_meaning: 'speaker implies indirect request'
78
+ )
79
+ expect(result[:success]).to be true
80
+ expect(result[:inferred_meaning]).to eq('speaker implies indirect request')
81
+ end
82
+
83
+ it 'fails for unknown utterance' do
84
+ result = client.generate_pragmatic_implicature(
85
+ utterance_id: SecureRandom.uuid,
86
+ inferred_meaning: 'something'
87
+ )
88
+ expect(result[:success]).to be false
89
+ expect(result[:error]).to eq(:utterance_not_found)
90
+ end
91
+
92
+ it 'rejects short inferred_meaning' do
93
+ analyzed = analyze
94
+ result = client.generate_pragmatic_implicature(
95
+ utterance_id: analyzed[:utterance_id],
96
+ inferred_meaning: 'ab'
97
+ )
98
+ expect(result[:success]).to be false
99
+ expect(result[:error]).to eq(:invalid_inferred_meaning)
100
+ end
101
+
102
+ it 'rejects short utterance_id' do
103
+ result = client.generate_pragmatic_implicature(utterance_id: 'x', inferred_meaning: 'valid meaning here')
104
+ expect(result[:success]).to be false
105
+ end
106
+ end
107
+
108
+ describe '#speaker_pragmatic_profile' do
109
+ it 'returns profile for existing speaker' do
110
+ analyze(speaker: 'bob')
111
+ analyze(speaker: 'bob')
112
+ result = client.speaker_pragmatic_profile(speaker: 'bob')
113
+ expect(result[:success]).to be true
114
+ expect(result[:utterance_count]).to eq(2)
115
+ expect(result[:compliance_by_maxim]).to be_a(Hash)
116
+ end
117
+
118
+ it 'returns empty count for unknown speaker' do
119
+ result = client.speaker_pragmatic_profile(speaker: 'unknown_person')
120
+ expect(result[:success]).to be true
121
+ expect(result[:utterance_count]).to eq(0)
122
+ end
123
+
124
+ it 'rejects short speaker name' do
125
+ result = client.speaker_pragmatic_profile(speaker: 'ab')
126
+ expect(result[:success]).to be false
127
+ expect(result[:error]).to eq(:invalid_speaker)
128
+ end
129
+ end
130
+
131
+ describe '#utterances_by_speech_act' do
132
+ it 'returns utterances matching speech act' do
133
+ analyze(speech_act: :question)
134
+ analyze(speech_act: :question)
135
+ analyze(speech_act: :warn)
136
+ result = client.utterances_by_speech_act(speech_act: :question)
137
+ expect(result[:success]).to be true
138
+ expect(result[:count]).to eq(2)
139
+ end
140
+
141
+ it 'rejects invalid speech act' do
142
+ result = client.utterances_by_speech_act(speech_act: :perform)
143
+ expect(result[:success]).to be false
144
+ expect(result[:error]).to eq(:invalid_speech_act)
145
+ end
146
+ end
147
+
148
+ describe '#utterances_by_speaker' do
149
+ it 'returns utterances for given speaker' do
150
+ analyze(speaker: 'charlie')
151
+ analyze(speaker: 'charlie')
152
+ analyze(speaker: 'dave')
153
+ result = client.utterances_by_speaker(speaker: 'charlie')
154
+ expect(result[:success]).to be true
155
+ expect(result[:count]).to eq(2)
156
+ end
157
+
158
+ it 'rejects short speaker name' do
159
+ result = client.utterances_by_speaker(speaker: 'xy')
160
+ expect(result[:success]).to be false
161
+ end
162
+ end
163
+
164
+ describe '#most_violated_maxim' do
165
+ it 'returns nil maxim when no utterances' do
166
+ result = client.most_violated_maxim
167
+ expect(result[:success]).to be true
168
+ expect(result[:maxim]).to be_nil
169
+ end
170
+
171
+ it 'identifies the most violated maxim' do
172
+ 3.times do
173
+ analyzed = analyze(scores: { quality: 0.1, quantity: 0.9, relevance: 0.9, manner: 0.9 })
174
+ client.detect_maxim_violations(utterance_id: analyzed[:utterance_id])
175
+ end
176
+ analyzed = analyze(scores: { quality: 0.9, quantity: 0.1, relevance: 0.9, manner: 0.9 })
177
+ client.detect_maxim_violations(utterance_id: analyzed[:utterance_id])
178
+
179
+ result = client.most_violated_maxim
180
+ expect(result[:maxim]).to eq(:quality)
181
+ end
182
+
183
+ it 'includes a description for the violated maxim' do
184
+ analyzed = analyze(scores: violating_scores)
185
+ client.detect_maxim_violations(utterance_id: analyzed[:utterance_id])
186
+ result = client.most_violated_maxim
187
+ expect(result[:description]).to be_a(String) if result[:maxim]
188
+ end
189
+ end
190
+
191
+ describe '#overall_cooperative_compliance' do
192
+ it 'returns 0.0 when no utterances' do
193
+ result = client.overall_cooperative_compliance
194
+ expect(result[:success]).to be true
195
+ expect(result[:cooperation]).to eq(0.0)
196
+ end
197
+
198
+ it 'computes mean compliance' do
199
+ analyze(scores: { quality: 1.0, quantity: 1.0, relevance: 1.0, manner: 1.0 })
200
+ analyze(scores: { quality: 0.0, quantity: 0.0, relevance: 0.0, manner: 0.0 })
201
+ result = client.overall_cooperative_compliance
202
+ expect(result[:cooperation]).to be_within(0.01).of(0.5)
203
+ end
204
+ end
205
+
206
+ describe '#update_pragmatic_inference' do
207
+ it 'returns success with decay info' do
208
+ result = client.update_pragmatic_inference
209
+ expect(result[:success]).to be true
210
+ expect(result[:decay_rate]).to eq(Legion::Extensions::Agentic::Language::PragmaticInference::Helpers::Constants::DECAY_RATE)
211
+ end
212
+
213
+ it 'decays utterance confidence' do
214
+ analyze
215
+ before_cooperation = client.overall_cooperative_compliance[:cooperation]
216
+ client.update_pragmatic_inference
217
+ after_cooperation = client.overall_cooperative_compliance[:cooperation]
218
+ expect(after_cooperation).to be <= before_cooperation
219
+ end
220
+ end
221
+
222
+ describe '#pragmatic_inference_stats' do
223
+ it 'returns stats hash' do
224
+ analyze
225
+ result = client.pragmatic_inference_stats
226
+ expect(result[:success]).to be true
227
+ expect(result[:utterance_count]).to eq(1)
228
+ expect(result).to include(:overall_cooperation, :most_violated_maxim, :history_size, :speakers)
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ module Legion
6
+ module Logging
7
+ def self.debug(_msg); end
8
+ def self.info(_msg); end
9
+ def self.warn(_msg); end
10
+ def self.error(_msg); end
11
+ def self.fatal(_msg); end
12
+ end
13
+
14
+ module Extensions
15
+ module Core
16
+ def self.extended(_base); end
17
+ end
18
+
19
+ module Helpers
20
+ module Lex
21
+ def self.included(_base); end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ require 'legion/extensions/agentic/language'
28
+
29
+ RSpec.configure do |config|
30
+ config.example_status_persistence_file_path = '.rspec_status'
31
+ config.disable_monkey_patching!
32
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
33
+ end