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,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module PragmaticInference
8
+ module Helpers
9
+ class PragmaticEngine
10
+ attr_reader :utterances, :history
11
+
12
+ def initialize
13
+ @utterances = {}
14
+ @history = []
15
+ end
16
+
17
+ def analyze_utterance(content:, speaker:, speech_act:, literal_meaning: nil,
18
+ domain: nil, maxim_scores: {})
19
+ trim_utterances if @utterances.size >= Constants::MAX_UTTERANCES
20
+
21
+ utterance = Utterance.new(
22
+ content: content,
23
+ speaker: speaker,
24
+ speech_act: speech_act,
25
+ literal_meaning: literal_meaning,
26
+ domain: domain,
27
+ maxim_scores: maxim_scores
28
+ )
29
+
30
+ @utterances[utterance.id] = utterance
31
+ record_history(utterance.id, :analyzed)
32
+ utterance
33
+ end
34
+
35
+ def detect_violations(utterance_id:)
36
+ utterance = @utterances[utterance_id]
37
+ return [] unless utterance
38
+
39
+ violations = []
40
+ utterance.maxim_scores.each do |maxim, score|
41
+ next if score >= 0.5
42
+
43
+ violation_type = classify_violation(score)
44
+ violation = { maxim: maxim, violation_type: violation_type, score: score }
45
+ violations << violation
46
+ utterance.violations << violation
47
+ end
48
+
49
+ violations
50
+ end
51
+
52
+ def generate_implicature(utterance_id:, inferred_meaning:)
53
+ utterance = @utterances[utterance_id]
54
+ return nil unless utterance
55
+
56
+ utterance.add_implicature(meaning: inferred_meaning)
57
+ record_history(utterance_id, :implicature_added)
58
+ inferred_meaning
59
+ end
60
+
61
+ def speaker_profile(speaker:)
62
+ speaker_utterances = by_speaker(speaker: speaker)
63
+ return { speaker: speaker, utterance_count: 0, compliance_by_maxim: {} } if speaker_utterances.empty?
64
+
65
+ compliance_by_maxim = Constants::MAXIMS.to_h do |maxim|
66
+ scores = speaker_utterances.map { |u| u.maxim_scores[maxim] }
67
+ mean = scores.sum / scores.size.to_f
68
+ [maxim, mean.round(3)]
69
+ end
70
+
71
+ {
72
+ speaker: speaker,
73
+ utterance_count: speaker_utterances.size,
74
+ compliance_by_maxim: compliance_by_maxim,
75
+ overall_compliance: (compliance_by_maxim.values.sum / compliance_by_maxim.size.to_f).round(3)
76
+ }
77
+ end
78
+
79
+ def by_speech_act(speech_act:)
80
+ @utterances.values.select { |u| u.speech_act == speech_act }
81
+ end
82
+
83
+ def by_speaker(speaker:)
84
+ @utterances.values.select { |u| u.speaker == speaker }
85
+ end
86
+
87
+ def most_violated_maxim
88
+ return nil if @utterances.empty?
89
+
90
+ violation_counts = Hash.new(0)
91
+ @utterances.each_value do |utterance|
92
+ utterance.violated_maxims.each { |maxim| violation_counts[maxim] += 1 }
93
+ end
94
+
95
+ return nil if violation_counts.empty?
96
+
97
+ violation_counts.max_by { |_maxim, count| count }&.first
98
+ end
99
+
100
+ def overall_cooperation
101
+ return 0.0 if @utterances.empty?
102
+
103
+ total = @utterances.values.sum(&:overall_compliance)
104
+ total / @utterances.size.to_f
105
+ end
106
+
107
+ def reinforce(utterance_id:)
108
+ utterance = @utterances[utterance_id]
109
+ return unless utterance
110
+
111
+ utterance.update_confidence(Constants::REINFORCEMENT_RATE)
112
+ end
113
+
114
+ def decay_all
115
+ @utterances.each_value do |utterance|
116
+ utterance.update_confidence(-Constants::DECAY_RATE)
117
+ end
118
+ end
119
+
120
+ def count
121
+ @utterances.size
122
+ end
123
+
124
+ def to_h
125
+ {
126
+ utterance_count: @utterances.size,
127
+ overall_cooperation: overall_cooperation.round(3),
128
+ most_violated_maxim: most_violated_maxim,
129
+ history_size: @history.size,
130
+ speakers: @utterances.values.map(&:speaker).uniq.size
131
+ }
132
+ end
133
+
134
+ private
135
+
136
+ def classify_violation(score)
137
+ if score < 0.1
138
+ :violating
139
+ elsif score < 0.3
140
+ :flouting
141
+ elsif score < 0.5
142
+ :opting_out
143
+ else
144
+ :none
145
+ end
146
+ end
147
+
148
+ def record_history(utterance_id, event)
149
+ @history << { utterance_id: utterance_id, event: event, at: Time.now.utc }
150
+ @history.shift while @history.size > Constants::MAX_HISTORY
151
+ end
152
+
153
+ def trim_utterances
154
+ overflow = @utterances.size - Constants::MAX_UTTERANCES + 1
155
+ keys_to_remove = @utterances.keys.first(overflow)
156
+ keys_to_remove.each { |k| @utterances.delete(k) }
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Agentic
8
+ module Language
9
+ module PragmaticInference
10
+ module Helpers
11
+ class Utterance
12
+ attr_reader :id, :content, :speaker, :speech_act, :literal_meaning,
13
+ :domain, :maxim_scores, :violations, :implicatures,
14
+ :confidence, :created_at
15
+
16
+ def initialize(content:, speaker:, speech_act:, literal_meaning: nil,
17
+ domain: nil, maxim_scores: {}, confidence: Constants::DEFAULT_CONFIDENCE)
18
+ @id = SecureRandom.uuid
19
+ @content = content
20
+ @speaker = speaker
21
+ @speech_act = speech_act
22
+ @literal_meaning = literal_meaning
23
+ @domain = domain
24
+ @maxim_scores = build_maxim_scores(maxim_scores)
25
+ @violations = []
26
+ @implicatures = []
27
+ @confidence = confidence.clamp(Constants::CONFIDENCE_FLOOR, Constants::CONFIDENCE_CEILING)
28
+ @created_at = Time.now.utc
29
+ end
30
+
31
+ def overall_compliance
32
+ return 0.0 if @maxim_scores.empty?
33
+
34
+ @maxim_scores.values.sum / @maxim_scores.size.to_f
35
+ end
36
+
37
+ def violated_maxims
38
+ @maxim_scores.select { |_maxim, score| score < 0.5 }.keys
39
+ end
40
+
41
+ def add_implicature(meaning:)
42
+ return if @implicatures.size >= Constants::MAX_IMPLICATURES
43
+
44
+ @implicatures << { meaning: meaning, added_at: Time.now.utc }
45
+ end
46
+
47
+ def update_confidence(delta)
48
+ @confidence = (@confidence + delta).clamp(Constants::CONFIDENCE_FLOOR, Constants::CONFIDENCE_CEILING)
49
+ end
50
+
51
+ def to_h
52
+ {
53
+ id: @id,
54
+ content: @content,
55
+ speaker: @speaker,
56
+ speech_act: @speech_act,
57
+ literal_meaning: @literal_meaning,
58
+ domain: @domain,
59
+ maxim_scores: @maxim_scores,
60
+ violations: @violations,
61
+ implicatures: @implicatures,
62
+ confidence: @confidence,
63
+ overall_compliance: overall_compliance,
64
+ violated_maxims: violated_maxims,
65
+ created_at: @created_at
66
+ }
67
+ end
68
+
69
+ private
70
+
71
+ def build_maxim_scores(scores)
72
+ Constants::MAXIMS.to_h do |maxim|
73
+ [maxim, (scores[maxim] || Constants::DEFAULT_CONFIDENCE).clamp(
74
+ Constants::CONFIDENCE_FLOOR, Constants::CONFIDENCE_CEILING
75
+ )]
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module PragmaticInference
8
+ module Runners
9
+ module PragmaticInference
10
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
11
+ Legion::Extensions::Helpers.const_defined?(:Lex)
12
+
13
+ def analyze_utterance(content:, speaker:, speech_act:, literal_meaning: nil,
14
+ domain: nil, maxim_scores: {}, **)
15
+ return { success: false, error: :invalid_speech_act } unless Helpers::Constants.valid_speech_act?(speech_act)
16
+
17
+ utterance = engine.analyze_utterance(
18
+ content: content,
19
+ speaker: speaker,
20
+ speech_act: speech_act,
21
+ literal_meaning: literal_meaning,
22
+ domain: domain,
23
+ maxim_scores: maxim_scores
24
+ )
25
+
26
+ Legion::Logging.debug "[pragmatic_inference] analyzed utterance id=#{utterance.id[0..7]} " \
27
+ "speaker=#{speaker} speech_act=#{speech_act} " \
28
+ "compliance=#{utterance.overall_compliance.round(2)}"
29
+
30
+ { success: true, utterance_id: utterance.id, overall_compliance: utterance.overall_compliance,
31
+ violated_maxims: utterance.violated_maxims }
32
+ end
33
+
34
+ def detect_maxim_violations(utterance_id:, **)
35
+ return { success: false, error: :invalid_utterance_id } if utterance_id.to_s.length < 3
36
+
37
+ violations = engine.detect_violations(utterance_id: utterance_id)
38
+
39
+ Legion::Logging.debug "[pragmatic_inference] violations detected id=#{utterance_id[0..7]} count=#{violations.size}"
40
+
41
+ { success: true, utterance_id: utterance_id, violations: violations, violation_count: violations.size }
42
+ end
43
+
44
+ def generate_pragmatic_implicature(utterance_id:, inferred_meaning:, **)
45
+ return { success: false, error: :invalid_utterance_id } if utterance_id.to_s.length < 3
46
+ return { success: false, error: :invalid_inferred_meaning } if inferred_meaning.to_s.length < 3
47
+
48
+ result = engine.generate_implicature(utterance_id: utterance_id, inferred_meaning: inferred_meaning)
49
+
50
+ if result
51
+ Legion::Logging.debug "[pragmatic_inference] implicature added id=#{utterance_id[0..7]}"
52
+ { success: true, utterance_id: utterance_id, inferred_meaning: result }
53
+ else
54
+ Legion::Logging.debug "[pragmatic_inference] implicature failed: utterance not found id=#{utterance_id[0..7]}"
55
+ { success: false, error: :utterance_not_found }
56
+ end
57
+ end
58
+
59
+ def speaker_pragmatic_profile(speaker:, **)
60
+ return { success: false, error: :invalid_speaker } if speaker.to_s.length < 3
61
+
62
+ profile = engine.speaker_profile(speaker: speaker)
63
+
64
+ Legion::Logging.debug "[pragmatic_inference] speaker profile speaker=#{speaker} " \
65
+ "utterances=#{profile[:utterance_count]}"
66
+
67
+ { success: true, **profile }
68
+ end
69
+
70
+ def utterances_by_speech_act(speech_act:, **)
71
+ return { success: false, error: :invalid_speech_act } unless Helpers::Constants.valid_speech_act?(speech_act)
72
+
73
+ utterances = engine.by_speech_act(speech_act: speech_act)
74
+
75
+ Legion::Logging.debug "[pragmatic_inference] by_speech_act act=#{speech_act} count=#{utterances.size}"
76
+
77
+ { success: true, speech_act: speech_act, utterances: utterances.map(&:to_h), count: utterances.size }
78
+ end
79
+
80
+ def utterances_by_speaker(speaker:, **)
81
+ return { success: false, error: :invalid_speaker } if speaker.to_s.length < 3
82
+
83
+ utterances = engine.by_speaker(speaker: speaker)
84
+
85
+ Legion::Logging.debug "[pragmatic_inference] by_speaker speaker=#{speaker} count=#{utterances.size}"
86
+
87
+ { success: true, speaker: speaker, utterances: utterances.map(&:to_h), count: utterances.size }
88
+ end
89
+
90
+ def most_violated_maxim(**)
91
+ maxim = engine.most_violated_maxim
92
+
93
+ Legion::Logging.debug "[pragmatic_inference] most violated maxim=#{maxim.inspect}"
94
+
95
+ { success: true, maxim: maxim,
96
+ description: maxim ? Helpers::Constants::MAXIM_DESCRIPTIONS[maxim] : nil }
97
+ end
98
+
99
+ def overall_cooperative_compliance(**)
100
+ cooperation = engine.overall_cooperation
101
+ total = engine.count
102
+
103
+ Legion::Logging.debug "[pragmatic_inference] cooperation=#{cooperation.round(2)} total=#{total}"
104
+
105
+ { success: true, cooperation: cooperation, utterance_count: total }
106
+ end
107
+
108
+ def update_pragmatic_inference(**)
109
+ engine.decay_all
110
+
111
+ Legion::Logging.debug "[pragmatic_inference] decay applied to #{engine.count} utterances"
112
+
113
+ { success: true, decayed_count: engine.count, decay_rate: Helpers::Constants::DECAY_RATE }
114
+ end
115
+
116
+ def pragmatic_inference_stats(**)
117
+ stats = engine.to_h
118
+
119
+ Legion::Logging.debug "[pragmatic_inference] stats: utterances=#{stats[:utterance_count]} " \
120
+ "cooperation=#{stats[:overall_cooperation]}"
121
+
122
+ { success: true, **stats }
123
+ end
124
+
125
+ private
126
+
127
+ def engine
128
+ @engine ||= Helpers::PragmaticEngine.new
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ module PragmaticInference
8
+ VERSION = '0.1.0'
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/pragmatic_inference/version'
4
+ require 'legion/extensions/agentic/language/pragmatic_inference/helpers/constants'
5
+ require 'legion/extensions/agentic/language/pragmatic_inference/helpers/utterance'
6
+ require 'legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine'
7
+ require 'legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference'
8
+
9
+ module Legion
10
+ module Extensions
11
+ module Agentic
12
+ module Language
13
+ module PragmaticInference
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Agentic
6
+ module Language
7
+ VERSION = '0.1.0'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'language/version'
4
+ require_relative 'language/grammar'
5
+ require_relative 'language/conceptual_blending'
6
+ require_relative 'language/conceptual_metaphor'
7
+ require_relative 'language/language'
8
+ require_relative 'language/inner_speech'
9
+ require_relative 'language/narrator'
10
+ require_relative 'language/narrative_reasoning'
11
+ require_relative 'language/frame_semantics'
12
+ require_relative 'language/pragmatic_inference'
13
+
14
+ module Legion
15
+ module Extensions
16
+ module Agentic
17
+ module Language
18
+ extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
19
+
20
+ def remote_invocable?
21
+ false
22
+ end
23
+
24
+ # Sub-modules are required here as extensions are consolidated.
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/conceptual_blending/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualBlending::Client do
6
+ let(:client) { described_class.new }
7
+
8
+ describe '#initialize' do
9
+ it 'creates a client with a default engine' do
10
+ expect(client).to respond_to(:create_mental_space)
11
+ end
12
+
13
+ it 'accepts an injected engine' do
14
+ engine = Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::BlendingEngine.new
15
+ injected_client = described_class.new(engine: engine)
16
+ expect(injected_client).to respond_to(:conceptual_blending_stats)
17
+ end
18
+ end
19
+
20
+ it 'responds to all runner methods' do
21
+ expect(client).to respond_to(:create_mental_space)
22
+ expect(client).to respond_to(:add_space_element)
23
+ expect(client).to respond_to(:add_space_relation)
24
+ expect(client).to respond_to(:create_blend)
25
+ expect(client).to respond_to(:elaborate_blend)
26
+ expect(client).to respond_to(:compress_blend)
27
+ expect(client).to respond_to(:best_blends)
28
+ expect(client).to respond_to(:blend_quality)
29
+ expect(client).to respond_to(:update_conceptual_blending)
30
+ expect(client).to respond_to(:conceptual_blending_stats)
31
+ end
32
+
33
+ it 'round-trips a full blending workflow' do
34
+ # Create two spaces
35
+ bio_result = client.create_mental_space(name: 'biology', domain: 'science')
36
+ comp_result = client.create_mental_space(name: 'computing', domain: 'technology')
37
+
38
+ bio_id = bio_result[:space][:id]
39
+ comp_id = comp_result[:space][:id]
40
+
41
+ # Populate spaces
42
+ client.add_space_element(space_id: bio_id, name: 'virus', properties: { spreads: true })
43
+ client.add_space_element(space_id: comp_id, name: 'software', properties: { executes: true })
44
+ client.add_space_relation(space_id: bio_id, from: 'virus', to: 'host', type: :infects)
45
+ client.add_space_relation(space_id: comp_id, from: 'software', to: 'system', type: :corrupts)
46
+
47
+ # Blend
48
+ blend_result = client.create_blend(space_a_id: bio_id, space_b_id: comp_id)
49
+ expect(blend_result[:success]).to be true
50
+ blend_id = blend_result[:blend][:id]
51
+
52
+ # Elaborate
53
+ elab_result = client.elaborate_blend(blend_id: blend_id, emergent_property: 'computer_virus')
54
+ expect(elab_result[:success]).to be true
55
+
56
+ # Quality
57
+ quality_result = client.blend_quality(blend_id: blend_id)
58
+ expect(quality_result[:quality_label]).to be_a(Symbol)
59
+
60
+ # Stats reflect both spaces
61
+ stats = client.conceptual_blending_stats
62
+ expect(stats[:spaces_count]).to eq(2)
63
+ expect(stats[:blends_count]).to eq(1)
64
+ end
65
+
66
+ it 'maintains isolated state per client instance' do
67
+ client_a = described_class.new
68
+ client_b = described_class.new
69
+
70
+ client_a.create_mental_space(name: 'space_a', domain: 'domain_a')
71
+
72
+ stats_a = client_a.conceptual_blending_stats
73
+ stats_b = client_b.conceptual_blending_stats
74
+
75
+ expect(stats_a[:spaces_count]).to eq(1)
76
+ expect(stats_b[:spaces_count]).to eq(0)
77
+ end
78
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::Blend do
4
+ let(:input_ids) { [SecureRandom.uuid, SecureRandom.uuid] }
5
+ let(:generic_space) { { shared_relation_types: [:infects], mapped_elements: [] } }
6
+ let(:blended_elements) { { merged_elements: %w[virus software], emergent_properties: [] } }
7
+
8
+ subject(:blend) do
9
+ described_class.new(
10
+ input_space_ids: input_ids,
11
+ generic_space: generic_space,
12
+ blended_elements: blended_elements,
13
+ blend_type: :double_scope
14
+ )
15
+ end
16
+
17
+ describe '#initialize' do
18
+ it 'assigns a uuid id' do
19
+ expect(blend.id).to match(/\A[0-9a-f-]{36}\z/)
20
+ end
21
+
22
+ it 'sets input_space_ids' do
23
+ expect(blend.input_space_ids).to eq(input_ids)
24
+ end
25
+
26
+ it 'sets blend_type' do
27
+ expect(blend.blend_type).to eq(:double_scope)
28
+ end
29
+
30
+ it 'defaults strength to DEFAULT_STRENGTH' do
31
+ expect(blend.strength).to eq(Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::Constants::DEFAULT_STRENGTH)
32
+ end
33
+
34
+ it 'starts with use_count 0' do
35
+ expect(blend.use_count).to eq(0)
36
+ end
37
+ end
38
+
39
+ describe '#use!' do
40
+ it 'increments use_count' do
41
+ blend.use!
42
+ expect(blend.use_count).to eq(1)
43
+ end
44
+
45
+ it 'updates last_used_at' do
46
+ original = blend.last_used_at
47
+ sleep(0.01)
48
+ blend.use!
49
+ expect(blend.last_used_at).to be >= original
50
+ end
51
+
52
+ it 'returns self' do
53
+ expect(blend.use!).to eq(blend)
54
+ end
55
+ end
56
+
57
+ describe '#elaborate' do
58
+ it 'appends to emergent_properties' do
59
+ blend.elaborate(emergent_property: 'software_spreads_like_virus')
60
+ expect(blend.blended_elements[:emergent_properties]).to include('software_spreads_like_virus')
61
+ end
62
+
63
+ it 'boosts strength by ELABORATION_BOOST' do
64
+ original = blend.strength
65
+ blend.elaborate(emergent_property: 'new_prop')
66
+ expected = (original + Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::Constants::ELABORATION_BOOST)
67
+ .clamp(0.0, 1.0)
68
+ expect(blend.strength).to be_within(0.001).of(expected)
69
+ end
70
+
71
+ it 'does not exceed strength ceiling' do
72
+ 10.times { blend.elaborate(emergent_property: 'prop') }
73
+ expect(blend.strength).to be <= 1.0
74
+ end
75
+
76
+ it 'returns self' do
77
+ expect(blend.elaborate(emergent_property: 'x')).to eq(blend)
78
+ end
79
+ end
80
+
81
+ describe '#compress' do
82
+ it 'reduces strength by COMPRESSION_PENALTY' do
83
+ original = blend.strength
84
+ blend.compress(removed_element: 'virus')
85
+ expected = (original - Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::Constants::COMPRESSION_PENALTY)
86
+ .clamp(0.0, 1.0)
87
+ expect(blend.strength).to be_within(0.001).of(expected)
88
+ end
89
+
90
+ it 'does not go below strength floor' do
91
+ 20.times { blend.compress(removed_element: 'x') }
92
+ expect(blend.strength).to be >= 0.0
93
+ end
94
+
95
+ it 'returns self' do
96
+ expect(blend.compress(removed_element: 'x')).to eq(blend)
97
+ end
98
+ end
99
+
100
+ describe '#quality_score' do
101
+ it 'returns a float between 0 and 1' do
102
+ expect(blend.quality_score).to be_between(0.0, 1.0)
103
+ end
104
+
105
+ it 'increases with more emergent properties' do
106
+ base_score = blend.quality_score
107
+ 5.times { |idx| blend.elaborate(emergent_property: "prop_#{idx}") }
108
+ expect(blend.quality_score).to be > base_score
109
+ end
110
+
111
+ it 'increases with more use' do
112
+ base_score = blend.quality_score
113
+ 10.times { blend.use! }
114
+ expect(blend.quality_score).to be >= base_score
115
+ end
116
+ end
117
+
118
+ describe '#quality_label' do
119
+ it 'returns a symbol from QUALITY_LABELS' do
120
+ labels = Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::Constants::QUALITY_LABELS.values
121
+ expect(labels).to include(blend.quality_label)
122
+ end
123
+ end
124
+
125
+ describe '#stale?' do
126
+ it 'returns false for a freshly created blend' do
127
+ expect(blend.stale?).to be false
128
+ end
129
+ end
130
+
131
+ describe '#to_h' do
132
+ it 'includes all key fields' do
133
+ result = blend.to_h
134
+ expect(result).to include(
135
+ :id, :input_space_ids, :generic_space, :blended_elements,
136
+ :blend_type, :strength, :use_count, :quality_score, :quality_label,
137
+ :stale, :created_at, :last_used_at
138
+ )
139
+ end
140
+ end
141
+ end