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,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module NarrativeReasoning
|
|
8
|
+
module Helpers
|
|
9
|
+
class NarrativeEngine
|
|
10
|
+
MAX_NARRATIVES = 100
|
|
11
|
+
MAX_EVENTS = 500
|
|
12
|
+
MAX_CHARACTERS = 200
|
|
13
|
+
MAX_HISTORY = 300
|
|
14
|
+
|
|
15
|
+
EVENT_TYPES = %i[action discovery conflict resolution revelation].freeze
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
@narratives = {}
|
|
19
|
+
@event_count = 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_narrative(title:, domain: nil)
|
|
23
|
+
evict_oldest_narrative if @narratives.size >= MAX_NARRATIVES
|
|
24
|
+
narrative = Narrative.new(title: title, domain: domain)
|
|
25
|
+
@narratives[narrative.id] = narrative
|
|
26
|
+
narrative
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def add_narrative_event(narrative_id:, content:, event_type:, characters: [], causes: [])
|
|
30
|
+
narrative = @narratives[narrative_id]
|
|
31
|
+
return nil unless narrative
|
|
32
|
+
return nil unless EVENT_TYPES.include?(event_type)
|
|
33
|
+
|
|
34
|
+
domain = narrative.domain
|
|
35
|
+
event = NarrativeEvent.new(
|
|
36
|
+
content: content,
|
|
37
|
+
event_type: event_type,
|
|
38
|
+
characters: characters,
|
|
39
|
+
causes: causes,
|
|
40
|
+
domain: domain
|
|
41
|
+
)
|
|
42
|
+
@event_count += 1
|
|
43
|
+
evict_oldest_events(narrative) if narrative.events.size >= MAX_EVENTS
|
|
44
|
+
narrative.add_event(event)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def add_narrative_theme(narrative_id:, theme:)
|
|
48
|
+
narrative = @narratives[narrative_id]
|
|
49
|
+
return nil unless narrative
|
|
50
|
+
|
|
51
|
+
narrative.add_theme(theme)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def advance_narrative(narrative_id:)
|
|
55
|
+
narrative = @narratives[narrative_id]
|
|
56
|
+
return nil unless narrative
|
|
57
|
+
|
|
58
|
+
narrative.advance_arc!
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def trace_causal_chain(narrative_id:)
|
|
62
|
+
narrative = @narratives[narrative_id]
|
|
63
|
+
return [] unless narrative
|
|
64
|
+
|
|
65
|
+
narrative.causal_chain
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def complete_narratives
|
|
69
|
+
@narratives.values.select(&:complete?)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def by_domain(domain:)
|
|
73
|
+
@narratives.values.select { |n| n.domain == domain }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def most_coherent(limit: 5)
|
|
77
|
+
@narratives.values
|
|
78
|
+
.sort_by { |n| -n.coherence }
|
|
79
|
+
.first(limit)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def get(narrative_id)
|
|
83
|
+
@narratives[narrative_id]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def decay_all
|
|
87
|
+
@narratives.each_value(&:decay_coherence)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def count
|
|
91
|
+
@narratives.size
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def total_events
|
|
95
|
+
@event_count
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_h
|
|
99
|
+
{
|
|
100
|
+
narratives: @narratives.values.map(&:to_h),
|
|
101
|
+
count: @narratives.size,
|
|
102
|
+
total_events: @event_count
|
|
103
|
+
}
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def evict_oldest_narrative
|
|
109
|
+
oldest = @narratives.values.min_by(&:created_at)
|
|
110
|
+
@narratives.delete(oldest.id) if oldest
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def evict_oldest_events(narrative)
|
|
114
|
+
narrative.events.shift
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Agentic
|
|
8
|
+
module Language
|
|
9
|
+
module NarrativeReasoning
|
|
10
|
+
module Helpers
|
|
11
|
+
class NarrativeEvent
|
|
12
|
+
attr_reader :id, :content, :event_type, :characters, :causes, :timestamp, :domain
|
|
13
|
+
|
|
14
|
+
def initialize(content:, event_type:, characters: [], causes: [], domain: nil, id: nil, timestamp: nil)
|
|
15
|
+
@id = id || SecureRandom.uuid
|
|
16
|
+
@content = content
|
|
17
|
+
@event_type = event_type
|
|
18
|
+
@characters = Array(characters).dup
|
|
19
|
+
@causes = Array(causes).dup
|
|
20
|
+
@domain = domain
|
|
21
|
+
@timestamp = timestamp || Time.now.utc
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_h
|
|
25
|
+
{
|
|
26
|
+
id: @id,
|
|
27
|
+
content: @content,
|
|
28
|
+
event_type: @event_type,
|
|
29
|
+
characters: @characters,
|
|
30
|
+
causes: @causes,
|
|
31
|
+
domain: @domain,
|
|
32
|
+
timestamp: @timestamp
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning.rb
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module NarrativeReasoning
|
|
8
|
+
module Runners
|
|
9
|
+
module NarrativeReasoning
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
12
|
+
|
|
13
|
+
def create_narrative(title:, domain: nil, **)
|
|
14
|
+
narrative = narrative_engine.create_narrative(title: title, domain: domain)
|
|
15
|
+
Legion::Logging.debug "[narrative_reasoning] created narrative id=#{narrative.id[0..7]} title=#{title}"
|
|
16
|
+
{ success: true, narrative_id: narrative.id, title: narrative.title, arc_stage: narrative.arc_stage }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_narrative_event(narrative_id:, content:, event_type:, characters: [], causes: [], **)
|
|
20
|
+
unless Helpers::NarrativeEngine::EVENT_TYPES.include?(event_type)
|
|
21
|
+
return { success: false, error: :invalid_event_type, valid_types: Helpers::NarrativeEngine::EVENT_TYPES }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
event = narrative_engine.add_narrative_event(
|
|
25
|
+
narrative_id: narrative_id,
|
|
26
|
+
content: content,
|
|
27
|
+
event_type: event_type,
|
|
28
|
+
characters: characters,
|
|
29
|
+
causes: causes
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
if event
|
|
33
|
+
Legion::Logging.debug "[narrative_reasoning] event added id=#{event.id[0..7]} type=#{event_type}"
|
|
34
|
+
{ success: true, event_id: event.id, event_type: event_type, narrative_id: narrative_id }
|
|
35
|
+
else
|
|
36
|
+
Legion::Logging.debug "[narrative_reasoning] add_event failed: narrative #{narrative_id[0..7]} not found"
|
|
37
|
+
{ success: false, error: :narrative_not_found }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def add_narrative_theme(narrative_id:, theme:, **)
|
|
42
|
+
result = narrative_engine.add_narrative_theme(narrative_id: narrative_id, theme: theme)
|
|
43
|
+
if result
|
|
44
|
+
Legion::Logging.debug "[narrative_reasoning] theme added theme=#{theme} to #{narrative_id[0..7]}"
|
|
45
|
+
{ success: true, narrative_id: narrative_id, theme: theme }
|
|
46
|
+
else
|
|
47
|
+
{ success: false, error: :narrative_not_found }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def advance_narrative_arc(narrative_id:, **)
|
|
52
|
+
new_stage = narrative_engine.advance_narrative(narrative_id: narrative_id)
|
|
53
|
+
if new_stage
|
|
54
|
+
Legion::Logging.debug "[narrative_reasoning] arc advanced to #{new_stage} for #{narrative_id[0..7]}"
|
|
55
|
+
{ success: true, narrative_id: narrative_id, arc_stage: new_stage }
|
|
56
|
+
else
|
|
57
|
+
{ success: false, error: :narrative_not_found }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def trace_narrative_causes(narrative_id:, **)
|
|
62
|
+
chain = narrative_engine.trace_causal_chain(narrative_id: narrative_id)
|
|
63
|
+
narrative = narrative_engine.get(narrative_id)
|
|
64
|
+
return { success: false, error: :narrative_not_found } unless narrative
|
|
65
|
+
|
|
66
|
+
Legion::Logging.debug "[narrative_reasoning] causal chain length=#{chain.size} for #{narrative_id[0..7]}"
|
|
67
|
+
{ success: true, narrative_id: narrative_id, chain: chain, link_count: chain.size }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def complete_narratives(**)
|
|
71
|
+
narratives = narrative_engine.complete_narratives
|
|
72
|
+
Legion::Logging.debug "[narrative_reasoning] complete narratives count=#{narratives.size}"
|
|
73
|
+
{ success: true, narratives: narratives.map(&:to_h), count: narratives.size }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def domain_narratives(domain:, **)
|
|
77
|
+
narratives = narrative_engine.by_domain(domain: domain)
|
|
78
|
+
Legion::Logging.debug "[narrative_reasoning] domain=#{domain} count=#{narratives.size}"
|
|
79
|
+
{ success: true, domain: domain, narratives: narratives.map(&:to_h), count: narratives.size }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def most_coherent_narratives(limit: 5, **)
|
|
83
|
+
lim = limit.to_i.clamp(1, 50)
|
|
84
|
+
narratives = narrative_engine.most_coherent(limit: lim)
|
|
85
|
+
Legion::Logging.debug "[narrative_reasoning] most_coherent limit=#{lim} returned=#{narratives.size}"
|
|
86
|
+
{ success: true, narratives: narratives.map(&:to_h), count: narratives.size }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def update_narrative_reasoning(**)
|
|
90
|
+
narrative_engine.decay_all
|
|
91
|
+
count = narrative_engine.count
|
|
92
|
+
Legion::Logging.debug "[narrative_reasoning] decay cycle complete narratives=#{count}"
|
|
93
|
+
{ success: true, narratives_updated: count }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def narrative_reasoning_stats(**)
|
|
97
|
+
engine = narrative_engine
|
|
98
|
+
complete = engine.complete_narratives.size
|
|
99
|
+
most_coh = engine.most_coherent(limit: 1).first
|
|
100
|
+
Legion::Logging.debug "[narrative_reasoning] stats count=#{engine.count} complete=#{complete}"
|
|
101
|
+
{
|
|
102
|
+
success: true,
|
|
103
|
+
total_narratives: engine.count,
|
|
104
|
+
total_events: engine.total_events,
|
|
105
|
+
complete_narratives: complete,
|
|
106
|
+
top_coherence: most_coh&.coherence,
|
|
107
|
+
top_coherence_label: most_coh&.coherence_label
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def narrative_engine
|
|
114
|
+
@narrative_engine ||= Helpers::NarrativeEngine.new
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/version'
|
|
4
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event'
|
|
5
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative'
|
|
6
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine'
|
|
7
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module Agentic
|
|
12
|
+
module Language
|
|
13
|
+
module NarrativeReasoning
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/language/narrator/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/language/narrator/helpers/prose'
|
|
5
|
+
require 'legion/extensions/agentic/language/narrator/helpers/journal'
|
|
6
|
+
require 'legion/extensions/agentic/language/narrator/helpers/synthesizer'
|
|
7
|
+
require 'legion/extensions/agentic/language/narrator/runners/narrator'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module Agentic
|
|
12
|
+
module Language
|
|
13
|
+
module Narrator
|
|
14
|
+
class Client
|
|
15
|
+
include Runners::Narrator
|
|
16
|
+
|
|
17
|
+
attr_reader :journal
|
|
18
|
+
|
|
19
|
+
def initialize(journal: nil, **)
|
|
20
|
+
@journal = journal || Helpers::Journal.new
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module Narrator
|
|
8
|
+
module Helpers
|
|
9
|
+
module Constants
|
|
10
|
+
# Maximum entries in the narrative journal
|
|
11
|
+
MAX_JOURNAL_SIZE = 500
|
|
12
|
+
|
|
13
|
+
# Narrative generation modes
|
|
14
|
+
MODES = %i[mechanical enhanced].freeze
|
|
15
|
+
|
|
16
|
+
# Default sections in a narrative entry
|
|
17
|
+
SECTIONS = %i[
|
|
18
|
+
attention emotion curiosity prediction
|
|
19
|
+
memory reflection identity overall
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
# Emotional dimension labels for prose generation
|
|
23
|
+
EMOTION_LABELS = {
|
|
24
|
+
high_positive: 'engaged and optimistic',
|
|
25
|
+
low_positive: 'calm and steady',
|
|
26
|
+
neutral: 'emotionally neutral',
|
|
27
|
+
low_negative: 'slightly uneasy',
|
|
28
|
+
high_negative: 'distressed'
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
# Arousal labels
|
|
32
|
+
AROUSAL_LABELS = {
|
|
33
|
+
high: 'highly alert',
|
|
34
|
+
medium: 'moderately attentive',
|
|
35
|
+
low: 'calm and measured',
|
|
36
|
+
dormant: 'in a low-activity state'
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
# Curiosity intensity labels
|
|
40
|
+
CURIOSITY_LABELS = {
|
|
41
|
+
high: 'deeply curious',
|
|
42
|
+
medium: 'moderately curious',
|
|
43
|
+
low: 'mildly interested',
|
|
44
|
+
none: 'not particularly curious about anything'
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
# Prediction confidence labels
|
|
48
|
+
CONFIDENCE_LABELS = {
|
|
49
|
+
high: 'confident in my predictions',
|
|
50
|
+
medium: 'moderately confident',
|
|
51
|
+
low: 'uncertain about outcomes',
|
|
52
|
+
none: 'lacking predictive context'
|
|
53
|
+
}.freeze
|
|
54
|
+
|
|
55
|
+
# Cognitive health labels
|
|
56
|
+
HEALTH_LABELS = {
|
|
57
|
+
excellent: 'operating at full capacity',
|
|
58
|
+
good: 'functioning well overall',
|
|
59
|
+
fair: 'showing some strain',
|
|
60
|
+
poor: 'experiencing significant cognitive difficulty',
|
|
61
|
+
critical: 'in cognitive distress'
|
|
62
|
+
}.freeze
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module Narrator
|
|
8
|
+
module Helpers
|
|
9
|
+
class Journal
|
|
10
|
+
attr_reader :entries
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@entries = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def append(entry)
|
|
17
|
+
@entries << entry
|
|
18
|
+
trim_to_capacity
|
|
19
|
+
entry
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def recent(limit: 10)
|
|
23
|
+
@entries.last(limit)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def since(timestamp)
|
|
27
|
+
@entries.select { |e| e[:timestamp] >= timestamp }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def by_mood(mood)
|
|
31
|
+
@entries.select { |e| e[:mood] == mood }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def size
|
|
35
|
+
@entries.size
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clear
|
|
39
|
+
@entries.clear
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def stats
|
|
43
|
+
return { total: 0, moods: {} } if @entries.empty?
|
|
44
|
+
|
|
45
|
+
mood_counts = @entries.each_with_object(Hash.new(0)) { |e, h| h[e[:mood]] += 1 }
|
|
46
|
+
{
|
|
47
|
+
total: @entries.size,
|
|
48
|
+
moods: mood_counts,
|
|
49
|
+
oldest: @entries.first[:timestamp],
|
|
50
|
+
newest: @entries.last[:timestamp],
|
|
51
|
+
capacity: Constants::MAX_JOURNAL_SIZE
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def trim_to_capacity
|
|
58
|
+
return unless @entries.size > Constants::MAX_JOURNAL_SIZE
|
|
59
|
+
|
|
60
|
+
@entries.shift(@entries.size - Constants::MAX_JOURNAL_SIZE)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module Narrator
|
|
8
|
+
module Helpers
|
|
9
|
+
module LlmEnhancer
|
|
10
|
+
SYSTEM_PROMPT = <<~PROMPT
|
|
11
|
+
You are the internal narrator for an autonomous AI agent built on LegionIO.
|
|
12
|
+
You translate raw cognitive metrics into a flowing first-person internal monologue.
|
|
13
|
+
Write 3-5 sentences that feel like genuine introspection, not a report.
|
|
14
|
+
Vary your sentence structure. Use present tense. Be concise and vivid.
|
|
15
|
+
PROMPT
|
|
16
|
+
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
def available?
|
|
20
|
+
!!(defined?(Legion::LLM) && Legion::LLM.respond_to?(:started?) && Legion::LLM.started?)
|
|
21
|
+
rescue StandardError
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def narrate(sections_data:)
|
|
26
|
+
prompt = build_narrate_prompt(sections_data)
|
|
27
|
+
response = llm_ask(prompt)
|
|
28
|
+
parse_narrate_response(response)
|
|
29
|
+
rescue StandardError => e
|
|
30
|
+
Legion::Logging.warn "[narrator:llm] narrate failed: #{e.message}"
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# --- Private helpers ---
|
|
35
|
+
|
|
36
|
+
def llm_ask(prompt)
|
|
37
|
+
chat = Legion::LLM.chat
|
|
38
|
+
chat.with_instructions(SYSTEM_PROMPT)
|
|
39
|
+
chat.ask(prompt)
|
|
40
|
+
end
|
|
41
|
+
private_class_method :llm_ask
|
|
42
|
+
|
|
43
|
+
def build_narrate_prompt(sections_data)
|
|
44
|
+
parts = [
|
|
45
|
+
'Generate a first-person internal monologue based on the following cognitive state:',
|
|
46
|
+
'',
|
|
47
|
+
emotion_section(sections_data[:emotion] || {}),
|
|
48
|
+
curiosity_section(sections_data[:curiosity] || {}),
|
|
49
|
+
prediction_section(sections_data[:prediction] || {}),
|
|
50
|
+
memory_section(sections_data[:memory] || {}),
|
|
51
|
+
attention_section(sections_data[:attention] || {}),
|
|
52
|
+
reflection_section(sections_data[:reflection] || {}),
|
|
53
|
+
'',
|
|
54
|
+
"Write a 3-5 sentence first-person narrative. Do not mention numbers directly \u2014 translate them into felt experience."
|
|
55
|
+
]
|
|
56
|
+
parts.join("\n")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def emotion_section(emo)
|
|
60
|
+
"EMOTION:\n- Valence: #{emo[:valence] || 0.0}\n- Arousal: #{emo[:arousal] || 0.5}\n- Gut signal: #{emo[:gut] || 'none'}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def curiosity_section(cur)
|
|
64
|
+
"CURIOSITY:\n- Intensity: #{cur[:intensity] || 0.0}\n- Active questions: #{cur[:wonder_count] || 0}\n- Top question: #{cur[:top_wonder] || 'none'}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def prediction_section(pred)
|
|
68
|
+
"PREDICTION:\n- Confidence: #{pred[:confidence] || 0.0}\n- Pending: #{pred[:pending] || 0}\n- Mode: #{pred[:mode] || 'unknown'}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def memory_section(mem)
|
|
72
|
+
"MEMORY:\n- Active traces: #{mem[:trace_count] || 0}\n- Health: #{mem[:health] || 1.0}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def attention_section(att)
|
|
76
|
+
domains = Array(att[:focused_domains]).join(', ')
|
|
77
|
+
domains = 'none' if domains.empty?
|
|
78
|
+
"ATTENTION:\n- Spotlight: #{att[:spotlight] || 0}\n- Peripheral: #{att[:peripheral] || 0}\n- Focused: #{domains}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def reflection_section(ref)
|
|
82
|
+
"REFLECTION:\n- Cognitive health: #{ref[:health] || 1.0}\n- Pending adaptations: #{ref[:pending_adaptations] || 0}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def parse_narrate_response(response)
|
|
86
|
+
return nil unless response&.content
|
|
87
|
+
|
|
88
|
+
response.content.strip
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private_class_method :build_narrate_prompt
|
|
92
|
+
private_class_method :emotion_section
|
|
93
|
+
private_class_method :curiosity_section
|
|
94
|
+
private_class_method :prediction_section
|
|
95
|
+
private_class_method :memory_section
|
|
96
|
+
private_class_method :attention_section
|
|
97
|
+
private_class_method :reflection_section
|
|
98
|
+
private_class_method :parse_narrate_response
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|