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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +13 -0
- data/lex-agentic-language.gemspec +30 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending/client.rb +25 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/blend.rb +91 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/blending_engine.rb +171 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/constants.rb +35 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/mental_space.rb +51 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending.rb +106 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending.rb +20 -0
- data/lib/legion/extensions/agentic/language/conceptual_metaphor/client.rb +19 -0
- data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/constants.rb +49 -0
- data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor.rb +109 -0
- data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_engine.rb +154 -0
- data/lib/legion/extensions/agentic/language/conceptual_metaphor/runners/conceptual_metaphor.rb +107 -0
- data/lib/legion/extensions/agentic/language/conceptual_metaphor/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/conceptual_metaphor.rb +19 -0
- data/lib/legion/extensions/agentic/language/frame_semantics/helpers/client.rb +23 -0
- data/lib/legion/extensions/agentic/language/frame_semantics/helpers/constants.rb +32 -0
- data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame.rb +109 -0
- data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame_engine.rb +139 -0
- data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame_instance.rb +51 -0
- data/lib/legion/extensions/agentic/language/frame_semantics/runners/frame_semantics.rb +108 -0
- data/lib/legion/extensions/agentic/language/frame_semantics/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/frame_semantics.rb +20 -0
- data/lib/legion/extensions/agentic/language/grammar/client.rb +29 -0
- data/lib/legion/extensions/agentic/language/grammar/helpers/constants.rb +38 -0
- data/lib/legion/extensions/agentic/language/grammar/helpers/construal.rb +68 -0
- data/lib/legion/extensions/agentic/language/grammar/helpers/construction.rb +68 -0
- data/lib/legion/extensions/agentic/language/grammar/helpers/grammar_engine.rb +119 -0
- data/lib/legion/extensions/agentic/language/grammar/runners/cognitive_grammar.rb +100 -0
- data/lib/legion/extensions/agentic/language/grammar/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/grammar.rb +20 -0
- data/lib/legion/extensions/agentic/language/inner_speech/client.rb +19 -0
- data/lib/legion/extensions/agentic/language/inner_speech/helpers/constants.rb +53 -0
- data/lib/legion/extensions/agentic/language/inner_speech/helpers/inner_voice.rb +141 -0
- data/lib/legion/extensions/agentic/language/inner_speech/helpers/speech_stream.rb +128 -0
- data/lib/legion/extensions/agentic/language/inner_speech/helpers/utterance.rb +90 -0
- data/lib/legion/extensions/agentic/language/inner_speech/runners/inner_speech.rb +87 -0
- data/lib/legion/extensions/agentic/language/inner_speech/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/inner_speech.rb +20 -0
- data/lib/legion/extensions/agentic/language/language/client.rb +26 -0
- data/lib/legion/extensions/agentic/language/language/helpers/constants.rb +43 -0
- data/lib/legion/extensions/agentic/language/language/helpers/lexicon.rb +63 -0
- data/lib/legion/extensions/agentic/language/language/helpers/summarizer.rb +167 -0
- data/lib/legion/extensions/agentic/language/language/runners/language.rb +134 -0
- data/lib/legion/extensions/agentic/language/language/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/language.rb +19 -0
- data/lib/legion/extensions/agentic/language/narrative_reasoning/client.rb +28 -0
- data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative.rb +123 -0
- data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine.rb +122 -0
- data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event.rb +41 -0
- data/lib/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning.rb +122 -0
- data/lib/legion/extensions/agentic/language/narrative_reasoning/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/narrative_reasoning.rb +18 -0
- data/lib/legion/extensions/agentic/language/narrator/client.rb +27 -0
- data/lib/legion/extensions/agentic/language/narrator/helpers/constants.rb +69 -0
- data/lib/legion/extensions/agentic/language/narrator/helpers/journal.rb +68 -0
- data/lib/legion/extensions/agentic/language/narrator/helpers/llm_enhancer.rb +105 -0
- data/lib/legion/extensions/agentic/language/narrator/helpers/prose.rb +122 -0
- data/lib/legion/extensions/agentic/language/narrator/helpers/synthesizer.rb +138 -0
- data/lib/legion/extensions/agentic/language/narrator/runners/narrator.rb +196 -0
- data/lib/legion/extensions/agentic/language/narrator/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/narrator.rb +21 -0
- data/lib/legion/extensions/agentic/language/pragmatic_inference/client.rb +28 -0
- data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/constants.rb +52 -0
- data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine.rb +164 -0
- data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/utterance.rb +84 -0
- data/lib/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference.rb +136 -0
- data/lib/legion/extensions/agentic/language/pragmatic_inference/version.rb +13 -0
- data/lib/legion/extensions/agentic/language/pragmatic_inference.rb +18 -0
- data/lib/legion/extensions/agentic/language/version.rb +11 -0
- data/lib/legion/extensions/agentic/language.rb +28 -0
- data/spec/legion/extensions/agentic/language/conceptual_blending/client_spec.rb +78 -0
- data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/blend_spec.rb +141 -0
- data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/blending_engine_spec.rb +211 -0
- data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/mental_space_spec.rb +85 -0
- data/spec/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending_spec.rb +162 -0
- data/spec/legion/extensions/agentic/language/conceptual_metaphor/client_spec.rb +29 -0
- data/spec/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_engine_spec.rb +166 -0
- data/spec/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_spec.rb +133 -0
- data/spec/legion/extensions/agentic/language/conceptual_metaphor/runners/conceptual_metaphor_spec.rb +133 -0
- data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_engine_spec.rb +227 -0
- data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_instance_spec.rb +83 -0
- data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_spec.rb +213 -0
- data/spec/legion/extensions/agentic/language/frame_semantics/runners/frame_semantics_spec.rb +155 -0
- data/spec/legion/extensions/agentic/language/grammar/client_spec.rb +121 -0
- data/spec/legion/extensions/agentic/language/grammar/cognitive_grammar_spec.rb +18 -0
- data/spec/legion/extensions/agentic/language/grammar/helpers/constants_spec.rb +67 -0
- data/spec/legion/extensions/agentic/language/grammar/helpers/construal_spec.rb +124 -0
- data/spec/legion/extensions/agentic/language/grammar/helpers/construction_spec.rb +155 -0
- data/spec/legion/extensions/agentic/language/grammar/helpers/grammar_engine_spec.rb +206 -0
- data/spec/legion/extensions/agentic/language/grammar/runners/cognitive_grammar_spec.rb +189 -0
- data/spec/legion/extensions/agentic/language/inner_speech/client_spec.rb +39 -0
- data/spec/legion/extensions/agentic/language/inner_speech/helpers/inner_voice_spec.rb +185 -0
- data/spec/legion/extensions/agentic/language/inner_speech/helpers/speech_stream_spec.rb +158 -0
- data/spec/legion/extensions/agentic/language/inner_speech/helpers/utterance_spec.rb +121 -0
- data/spec/legion/extensions/agentic/language/inner_speech/runners/inner_speech_spec.rb +102 -0
- data/spec/legion/extensions/agentic/language/language/client_spec.rb +20 -0
- data/spec/legion/extensions/agentic/language/language/helpers/constants_spec.rb +31 -0
- data/spec/legion/extensions/agentic/language/language/helpers/lexicon_spec.rb +116 -0
- data/spec/legion/extensions/agentic/language/language/helpers/summarizer_spec.rb +224 -0
- data/spec/legion/extensions/agentic/language/language/runners/language_spec.rb +169 -0
- data/spec/legion/extensions/agentic/language/narrative_reasoning/client_spec.rb +19 -0
- data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine_spec.rb +182 -0
- data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event_spec.rb +61 -0
- data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_spec.rb +168 -0
- data/spec/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning_spec.rb +174 -0
- data/spec/legion/extensions/agentic/language/narrator/client_spec.rb +24 -0
- data/spec/legion/extensions/agentic/language/narrator/helpers/journal_spec.rb +95 -0
- data/spec/legion/extensions/agentic/language/narrator/helpers/llm_enhancer_spec.rb +107 -0
- data/spec/legion/extensions/agentic/language/narrator/helpers/prose_spec.rb +134 -0
- data/spec/legion/extensions/agentic/language/narrator/helpers/synthesizer_spec.rb +89 -0
- data/spec/legion/extensions/agentic/language/narrator/runners/narrator_llm_spec.rb +74 -0
- data/spec/legion/extensions/agentic/language/narrator/runners/narrator_spec.rb +126 -0
- data/spec/legion/extensions/agentic/language/pragmatic_inference/client_spec.rb +19 -0
- data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/constants_spec.rb +73 -0
- data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine_spec.rb +185 -0
- data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/utterance_spec.rb +111 -0
- data/spec/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference_spec.rb +231 -0
- data/spec/spec_helper.rb +33 -0
- 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
|
data/lib/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference.rb
ADDED
|
@@ -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,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,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
|