lex-agentic-language 0.1.10 → 0.1.12
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 +4 -4
- data/CHANGELOG.md +12 -0
- data/lib/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending.rb +2 -0
- data/lib/legion/extensions/agentic/language/grammar/runners/cognitive_grammar.rb +4 -0
- data/lib/legion/extensions/agentic/language/inner_speech/helpers/inner_voice.rb +10 -2
- data/lib/legion/extensions/agentic/language/inner_speech/helpers/speech_stream.rb +1 -1
- data/lib/legion/extensions/agentic/language/inner_speech/helpers/utterance.rb +10 -4
- data/lib/legion/extensions/agentic/language/narrator/helpers/llm_enhancer.rb +27 -4
- data/lib/legion/extensions/agentic/language/narrator/runners/narrator.rb +1 -1
- data/lib/legion/extensions/agentic/language/version.rb +1 -1
- data/spec/legion/extensions/agentic/language/inner_speech/runners/inner_speech_spec.rb +1 -1
- data/spec/legion/extensions/agentic/language/narrator/helpers/llm_enhancer_spec.rb +15 -14
- data/spec/legion/extensions/agentic/language/narrator/runners/narrator_spec.rb +9 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0628d5ec05971b87c4538c364688f7bab530bdeebac624544f03a8e31b358c40'
|
|
4
|
+
data.tar.gz: a4f69abf5079a18fa17aa600ea42026097049309ad781700dfebe6f10b642719
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 500c995f74f282853e088ad87056f4a4fe444628d20a4aadd7536e9e42aadb01be1186e94cdecec3969a20b645649d07fbfbd22c3dfbdbd7f94724abc4e2e9b4
|
|
7
|
+
data.tar.gz: b083eab9bf653d1ed7f6117517d6ac8e5749d4a0b91e1d82340d356b7f815e25c23f5884c39aed549cee962550cf291bde1faf3becc82a746e5d99e42191ff6d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.12] - 2026-06-01
|
|
4
|
+
### Fixed
|
|
5
|
+
- `SpeechStream#append` used `:"utt_#{@counter}"` (interpolated Symbol) causing unbounded Symbol allocation — changed to String ID
|
|
6
|
+
- `Utterance#resolve_mode` and `#resolve_voice` called `to_sym` on arbitrary user input — replaced with safe string-based lookup returning only pre-existing constant Symbols
|
|
7
|
+
- `InnerVoice#switch_voice` called `to_sym` on user input — added `resolve_voice` helper with same safe pattern
|
|
8
|
+
- `CognitiveGrammar` runner called `.to_h` on nil when engine rejects invalid input — added nil guards returning structured error responses
|
|
9
|
+
|
|
10
|
+
## [0.1.11] - 2026-05-07
|
|
11
|
+
### Fixed
|
|
12
|
+
- Narrator LLM enhancement now sends native chat message payloads with system/user roles while preserving legacy session fallback for older test doubles.
|
|
13
|
+
- LLM narration state maps reflection `unacted_count` into pending adaptations.
|
|
14
|
+
|
|
3
15
|
## [0.1.10] - 2026-04-27
|
|
4
16
|
### Fixed
|
|
5
17
|
- Narrator LLM enhancement now skips empty idle narration and rate-limits provider failure warnings without warning-level backtrace floods. Fixes #7
|
data/lib/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending.rb
CHANGED
|
@@ -40,6 +40,8 @@ module Legion
|
|
|
40
40
|
def create_blend(space_a_id:, space_b_id:, blend_type: :double_scope, **)
|
|
41
41
|
log.debug "[conceptual_blending] blend: a=#{space_a_id} b=#{space_b_id} type=#{blend_type}"
|
|
42
42
|
blend = engine.blend(space_a_id: space_a_id, space_b_id: space_b_id, blend_type: blend_type)
|
|
43
|
+
return { success: false, reason: :invalid_blend_type } unless blend
|
|
44
|
+
|
|
43
45
|
{ success: true, blend: blend.to_h }
|
|
44
46
|
rescue ArgumentError => e
|
|
45
47
|
log.debug "[conceptual_blending] blend failed: #{e.message}"
|
|
@@ -17,6 +17,8 @@ module Legion
|
|
|
17
17
|
expression_type: expression_type,
|
|
18
18
|
domain: domain
|
|
19
19
|
)
|
|
20
|
+
return { created: false, reason: :invalid_expression_type } unless construction
|
|
21
|
+
|
|
20
22
|
log.debug "[cognitive_grammar] created construction form=#{form} type=#{expression_type} domain=#{domain}"
|
|
21
23
|
construction.to_h
|
|
22
24
|
end
|
|
@@ -34,6 +36,8 @@ module Legion
|
|
|
34
36
|
dynamicity: dynamicity,
|
|
35
37
|
construction_id: construction_id
|
|
36
38
|
)
|
|
39
|
+
return { created: false, reason: :invalid_parameters } unless construal
|
|
40
|
+
|
|
37
41
|
log.debug "[cognitive_grammar] created construal scene=#{scene} figure=#{figure}"
|
|
38
42
|
construal.to_h
|
|
39
43
|
end
|
|
@@ -32,8 +32,8 @@ module Legion
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def switch_voice(voice:)
|
|
35
|
-
sym = voice
|
|
36
|
-
return nil unless
|
|
35
|
+
sym = resolve_voice(voice)
|
|
36
|
+
return nil unless sym
|
|
37
37
|
|
|
38
38
|
old_voice = @active_voice
|
|
39
39
|
@active_voice = sym
|
|
@@ -128,6 +128,14 @@ module Legion
|
|
|
128
128
|
|
|
129
129
|
private
|
|
130
130
|
|
|
131
|
+
def resolve_voice(voice)
|
|
132
|
+
return voice if voice.is_a?(Symbol) && VOICE_TYPES.include?(voice)
|
|
133
|
+
|
|
134
|
+
voice_str = voice.to_s
|
|
135
|
+
index = VOICE_TYPES.map(&:to_s).index(voice_str)
|
|
136
|
+
index ? VOICE_TYPES[index] : nil
|
|
137
|
+
end
|
|
138
|
+
|
|
131
139
|
def record_event(type, **details)
|
|
132
140
|
@history << { type: type, at: Time.now.utc }.merge(details)
|
|
133
141
|
@history.shift while @history.size > MAX_HISTORY
|
|
@@ -84,13 +84,19 @@ module Legion
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def resolve_mode(mode)
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
return mode if mode.is_a?(Symbol) && SPEECH_MODES.include?(mode)
|
|
88
|
+
|
|
89
|
+
mode_str = mode.to_s
|
|
90
|
+
index = SPEECH_MODES.map(&:to_s).index(mode_str)
|
|
91
|
+
index ? SPEECH_MODES[index] : :narrating
|
|
89
92
|
end
|
|
90
93
|
|
|
91
94
|
def resolve_voice(voice)
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
return voice if voice.is_a?(Symbol) && VOICE_TYPES.include?(voice)
|
|
96
|
+
|
|
97
|
+
voice_str = voice.to_s
|
|
98
|
+
index = VOICE_TYPES.map(&:to_s).index(voice_str)
|
|
99
|
+
index ? VOICE_TYPES[index] : :rational
|
|
94
100
|
end
|
|
95
101
|
end
|
|
96
102
|
end
|
|
@@ -51,16 +51,39 @@ module Legion
|
|
|
51
51
|
caller: { extension: 'lex-agentic-language', mode: :narrator }
|
|
52
52
|
)
|
|
53
53
|
content = response&.message&.dig(:content)
|
|
54
|
-
::Struct.new(:content).new(content) if content
|
|
55
54
|
else
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
response = Legion::LLM.chat( # rubocop:disable Legion/HelperMigration/DirectLlm
|
|
56
|
+
message: [
|
|
57
|
+
{ role: 'system', content: SYSTEM_PROMPT },
|
|
58
|
+
{ role: 'user', content: prompt }
|
|
59
|
+
],
|
|
60
|
+
caller: { extension: 'lex-agentic-language', mode: :narrator }
|
|
61
|
+
)
|
|
62
|
+
content = extract_response_content(response, prompt)
|
|
59
63
|
end
|
|
64
|
+
::Struct.new(:content).new(content) if content
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
private_class_method :llm_ask
|
|
63
68
|
|
|
69
|
+
def extract_response_content(response, prompt = nil)
|
|
70
|
+
return response.strip if response.is_a?(String)
|
|
71
|
+
return response.content if response.respond_to?(:content)
|
|
72
|
+
|
|
73
|
+
if response.respond_to?(:ask)
|
|
74
|
+
response.with_instructions(SYSTEM_PROMPT) if response.respond_to?(:with_instructions)
|
|
75
|
+
asked = response.ask(prompt)
|
|
76
|
+
return extract_response_content(asked)
|
|
77
|
+
end
|
|
78
|
+
return nil unless response.is_a?(Hash)
|
|
79
|
+
|
|
80
|
+
response[:content] || response['content'] ||
|
|
81
|
+
response.dig(:message, :content) || response.dig('message', 'content') ||
|
|
82
|
+
response[:response] || response['response']
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private_class_method :extract_response_content
|
|
86
|
+
|
|
64
87
|
def pipeline_available?
|
|
65
88
|
!!(defined?(Legion::LLM::Pipeline::GaiaCaller) &&
|
|
66
89
|
Legion::LLM.respond_to?(:pipeline_enabled?) &&
|
|
@@ -11,7 +11,7 @@ RSpec.describe Legion::Extensions::Agentic::Language::InnerSpeech::Runners::Inne
|
|
|
11
11
|
it 'creates an utterance' do
|
|
12
12
|
result = host.inner_speak(content: 'testing', mode: :planning)
|
|
13
13
|
expect(result[:success]).to be true
|
|
14
|
-
expect(result[:utterance_id]).to be_a(
|
|
14
|
+
expect(result[:utterance_id]).to be_a(String)
|
|
15
15
|
expect(result[:mode]).to eq(:planning)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -49,27 +49,32 @@ RSpec.describe Legion::Extensions::Agentic::Language::Narrator::Helpers::LlmEnha
|
|
|
49
49
|
|
|
50
50
|
context 'when LLM returns a response' do
|
|
51
51
|
it 'returns the response content as a string' do
|
|
52
|
-
response_double = double('response', content: 'I feel alert and curious about what lies ahead.')
|
|
53
|
-
chat_double = double('chat')
|
|
54
|
-
allow(chat_double).to receive(:with_instructions)
|
|
55
|
-
allow(chat_double).to receive(:ask).and_return(response_double)
|
|
56
52
|
llm_double = double('Legion::LLM', started?: true)
|
|
57
|
-
allow(llm_double).to receive(:chat).and_return(
|
|
53
|
+
allow(llm_double).to receive(:chat).and_return(content: 'I feel alert and curious about what lies ahead.')
|
|
58
54
|
stub_const('Legion::LLM', llm_double)
|
|
59
55
|
|
|
60
56
|
result = described_class.narrate(sections_data: sections_data)
|
|
61
57
|
expect(result).to be_a(String)
|
|
62
58
|
expect(result).to eq('I feel alert and curious about what lies ahead.')
|
|
63
59
|
end
|
|
60
|
+
|
|
61
|
+
it 'sends system instructions and user prompt in the native chat message payload' do
|
|
62
|
+
llm_double = double('Legion::LLM', started?: true)
|
|
63
|
+
allow(llm_double).to receive(:chat).and_return(content: 'native narrative')
|
|
64
|
+
stub_const('Legion::LLM', llm_double)
|
|
65
|
+
|
|
66
|
+
described_class.narrate(sections_data: sections_data)
|
|
67
|
+
|
|
68
|
+
expect(llm_double).to have_received(:chat)
|
|
69
|
+
.with(hash_including(message: array_including(hash_including(role: 'system'),
|
|
70
|
+
hash_including(role: 'user'))))
|
|
71
|
+
end
|
|
64
72
|
end
|
|
65
73
|
|
|
66
74
|
context 'when LLM returns nil response' do
|
|
67
75
|
it 'returns nil' do
|
|
68
|
-
chat_double = double('chat')
|
|
69
|
-
allow(chat_double).to receive(:with_instructions)
|
|
70
|
-
allow(chat_double).to receive(:ask).and_return(nil)
|
|
71
76
|
llm_double = double('Legion::LLM', started?: true)
|
|
72
|
-
allow(llm_double).to receive(:chat).and_return(
|
|
77
|
+
allow(llm_double).to receive(:chat).and_return(nil)
|
|
73
78
|
stub_const('Legion::LLM', llm_double)
|
|
74
79
|
|
|
75
80
|
result = described_class.narrate(sections_data: sections_data)
|
|
@@ -104,12 +109,8 @@ RSpec.describe Legion::Extensions::Agentic::Language::Narrator::Helpers::LlmEnha
|
|
|
104
109
|
|
|
105
110
|
context 'with empty sections_data' do
|
|
106
111
|
it 'does not raise and returns a string when LLM responds' do
|
|
107
|
-
response_double = double('response', content: 'Everything seems quiet.')
|
|
108
|
-
chat_double = double('chat')
|
|
109
|
-
allow(chat_double).to receive(:with_instructions)
|
|
110
|
-
allow(chat_double).to receive(:ask).and_return(response_double)
|
|
111
112
|
llm_double = double('Legion::LLM', started?: true)
|
|
112
|
-
allow(llm_double).to receive(:chat).and_return(
|
|
113
|
+
allow(llm_double).to receive(:chat).and_return(content: 'Everything seems quiet.')
|
|
113
114
|
stub_const('Legion::LLM', llm_double)
|
|
114
115
|
|
|
115
116
|
result = described_class.narrate(sections_data: {})
|
|
@@ -33,6 +33,15 @@ RSpec.describe Legion::Extensions::Agentic::Language::Narrator::Runners::Narrato
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
describe 'reflection data for LLM narration' do
|
|
37
|
+
it 'maps reflection unacted_count to pending_adaptations' do
|
|
38
|
+
data = client.send(:llm_reflection_data, reflection: { health: 0.7, unacted_count: 3 })
|
|
39
|
+
|
|
40
|
+
expect(data[:health]).to eq(0.7)
|
|
41
|
+
expect(data[:pending_adaptations]).to eq(3)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
36
45
|
describe '#recent_entries' do
|
|
37
46
|
before do
|
|
38
47
|
5.times { |i| client.narrate(tick_results: { emotional_evaluation: { valence: i * 0.2 } }, cognitive_state: {}) }
|