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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e44972f87bca2dccf617db64c48e8e3b99d2174bb45f1d21cd71a72a2489a59
4
- data.tar.gz: 1e9fb62235879f874ef6c2ca07b6f58dc104fcc5fcb3d64591dcdf5d6477484f
3
+ metadata.gz: '0628d5ec05971b87c4538c364688f7bab530bdeebac624544f03a8e31b358c40'
4
+ data.tar.gz: a4f69abf5079a18fa17aa600ea42026097049309ad781700dfebe6f10b642719
5
5
  SHA512:
6
- metadata.gz: aed85b23f9a856e2a26ac20bfdacd601c0ad09c8394f38bedb8195d28852fab393ee170b29d176109c83d6ab7cc426883270621c0bc629a7cab6244f94d38cc5
7
- data.tar.gz: e57dbce2e2254462c9940b57b7dcac4b349e8edb3dad723e4c4d3c06bcc0dc6882cf6a4e52dfdb75bed12fdde5bd755f18c6e447c242fbd5d3fe4dcc42b5917b
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
@@ -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.to_sym
36
- return nil unless VOICE_TYPES.include?(sym)
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
@@ -21,7 +21,7 @@ module Legion
21
21
 
22
22
  @counter += 1
23
23
  utterance = Utterance.new(
24
- id: :"utt_#{@counter}",
24
+ id: "utt_#{@counter}",
25
25
  content: content,
26
26
  mode: mode,
27
27
  **
@@ -84,13 +84,19 @@ module Legion
84
84
  end
85
85
 
86
86
  def resolve_mode(mode)
87
- sym = mode.to_sym
88
- SPEECH_MODES.include?(sym) ? sym : :narrating
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
- sym = voice.to_sym
93
- VOICE_TYPES.include?(sym) ? sym : :rational
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
- chat = Legion::LLM.chat # rubocop:disable Legion/HelperMigration/DirectLlm
57
- chat.with_instructions(SYSTEM_PROMPT)
58
- chat.ask(prompt)
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?) &&
@@ -187,7 +187,7 @@ module Legion
187
187
  r = cognitive_state[:reflection] || {}
188
188
  {
189
189
  health: r[:health] || 1.0,
190
- pending_adaptations: r[:pending_adaptations] || 0
190
+ pending_adaptations: r[:pending_adaptations] || r[:unacted_count] || 0
191
191
  }
192
192
  end
193
193
 
@@ -4,7 +4,7 @@ module Legion
4
4
  module Extensions
5
5
  module Agentic
6
6
  module Language
7
- VERSION = '0.1.10'
7
+ VERSION = '0.1.12'
8
8
  end
9
9
  end
10
10
  end
@@ -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(Symbol)
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(chat_double)
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(chat_double)
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(chat_double)
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: {}) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-agentic-language
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity