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,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/language/language/helpers/constants'
|
|
4
|
+
require 'legion/extensions/agentic/language/language/helpers/summarizer'
|
|
5
|
+
require 'legion/extensions/agentic/language/language/helpers/lexicon'
|
|
6
|
+
require 'legion/extensions/agentic/language/language/runners/language'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Agentic
|
|
11
|
+
module Language
|
|
12
|
+
module Language
|
|
13
|
+
class Client
|
|
14
|
+
include Runners::Language
|
|
15
|
+
|
|
16
|
+
attr_reader :lexicon
|
|
17
|
+
|
|
18
|
+
def initialize(lexicon: nil, **)
|
|
19
|
+
@lexicon = lexicon || Helpers::Lexicon.new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module Language
|
|
8
|
+
module Helpers
|
|
9
|
+
module Constants
|
|
10
|
+
# Maximum traces to consider in a single summarization
|
|
11
|
+
MAX_TRACES_PER_SUMMARY = 50
|
|
12
|
+
|
|
13
|
+
# Minimum strength threshold for including a trace in a summary
|
|
14
|
+
MIN_SUMMARY_STRENGTH = 0.1
|
|
15
|
+
|
|
16
|
+
# Summary depth levels
|
|
17
|
+
DEPTHS = %i[brief standard detailed].freeze
|
|
18
|
+
|
|
19
|
+
# Trace type priority for summary ordering
|
|
20
|
+
TYPE_PRIORITY = {
|
|
21
|
+
firmware: 0,
|
|
22
|
+
identity: 1,
|
|
23
|
+
procedural: 2,
|
|
24
|
+
semantic: 3,
|
|
25
|
+
trust: 4,
|
|
26
|
+
episodic: 5,
|
|
27
|
+
sensory: 6
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
30
|
+
# Knowledge quality thresholds
|
|
31
|
+
KNOWLEDGE_RICH = 10 # traces for "rich knowledge"
|
|
32
|
+
KNOWLEDGE_MODERATE = 5 # traces for "moderate knowledge"
|
|
33
|
+
KNOWLEDGE_SPARSE = 1 # traces for "sparse knowledge"
|
|
34
|
+
|
|
35
|
+
# Wonder resolution: minimum traces to consider a domain "known"
|
|
36
|
+
RESOLUTION_THRESHOLD = 3
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module Language
|
|
8
|
+
module Helpers
|
|
9
|
+
class Lexicon
|
|
10
|
+
attr_reader :domain_summaries
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@domain_summaries = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def store_summary(domain, summary)
|
|
17
|
+
domain = domain.to_sym
|
|
18
|
+
@domain_summaries[domain] = summary.merge(cached_at: Time.now.utc)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get_summary(domain)
|
|
22
|
+
@domain_summaries[domain.to_sym]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def known_domains
|
|
26
|
+
@domain_summaries.keys
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def knowledge_map
|
|
30
|
+
@domain_summaries.transform_values do |summary|
|
|
31
|
+
{
|
|
32
|
+
knowledge_level: summary[:knowledge_level],
|
|
33
|
+
trace_count: summary[:trace_count],
|
|
34
|
+
cached_at: summary[:cached_at]
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def stale?(domain, max_age: 300)
|
|
40
|
+
summary = get_summary(domain)
|
|
41
|
+
return true unless summary
|
|
42
|
+
|
|
43
|
+
(Time.now.utc - summary[:cached_at]) > max_age
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def clear(domain = nil)
|
|
47
|
+
if domain
|
|
48
|
+
@domain_summaries.delete(domain.to_sym)
|
|
49
|
+
else
|
|
50
|
+
@domain_summaries.clear
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def size
|
|
55
|
+
@domain_summaries.size
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module Language
|
|
8
|
+
module Helpers
|
|
9
|
+
module Summarizer
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def summarize_domain(traces, domain:, depth: :standard)
|
|
13
|
+
return empty_summary(domain) if traces.empty?
|
|
14
|
+
|
|
15
|
+
grouped = group_by_type(traces)
|
|
16
|
+
knowledge_level = classify_knowledge(traces.size)
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
domain: domain,
|
|
20
|
+
knowledge_level: knowledge_level,
|
|
21
|
+
trace_count: traces.size,
|
|
22
|
+
type_breakdown: type_breakdown(grouped),
|
|
23
|
+
key_facts: extract_key_facts(grouped, depth: depth),
|
|
24
|
+
strength_stats: strength_stats(traces),
|
|
25
|
+
emotional_tone: emotional_tone(traces),
|
|
26
|
+
freshness: freshness_assessment(traces)
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def group_by_type(traces)
|
|
31
|
+
grouped = traces.group_by { |t| t[:trace_type] || :unknown }
|
|
32
|
+
grouped.sort_by { |type, _| Constants::TYPE_PRIORITY[type] || 99 }.to_h
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def type_breakdown(grouped)
|
|
36
|
+
grouped.transform_values(&:size)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def extract_key_facts(grouped, depth: :standard)
|
|
40
|
+
limit = case depth
|
|
41
|
+
when :brief then 3
|
|
42
|
+
when :detailed then 15
|
|
43
|
+
else 7
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
facts = []
|
|
47
|
+
grouped.each do |type, traces|
|
|
48
|
+
sorted = traces.sort_by { |t| -(t[:strength] || 0) }
|
|
49
|
+
count = [sorted.size, limit_per_type(type, limit)].min
|
|
50
|
+
sorted.first(count).each do |trace|
|
|
51
|
+
facts << format_fact(trace, type)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
facts.first(limit)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def format_fact(trace, type)
|
|
59
|
+
content = extract_content_text(trace[:content_payload])
|
|
60
|
+
{
|
|
61
|
+
trace_id: trace[:trace_id],
|
|
62
|
+
type: type,
|
|
63
|
+
content: content,
|
|
64
|
+
strength: (trace[:strength] || 0).round(3),
|
|
65
|
+
confidence: (trace[:confidence] || 0.5).round(3),
|
|
66
|
+
domain_tags: trace[:domain_tags] || []
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def extract_content_text(payload)
|
|
71
|
+
case payload
|
|
72
|
+
when String then payload
|
|
73
|
+
when Hash then payload[:text] || payload[:content] || payload[:summary] || payload.to_s
|
|
74
|
+
when Array then payload.first.to_s
|
|
75
|
+
else payload.to_s
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def classify_knowledge(count)
|
|
80
|
+
if count >= Constants::KNOWLEDGE_RICH then :rich
|
|
81
|
+
elsif count >= Constants::KNOWLEDGE_MODERATE then :moderate
|
|
82
|
+
elsif count >= Constants::KNOWLEDGE_SPARSE then :sparse
|
|
83
|
+
else :none
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def strength_stats(traces)
|
|
88
|
+
strengths = traces.map { |t| t[:strength] || 0 }
|
|
89
|
+
{
|
|
90
|
+
mean: (strengths.sum / strengths.size).round(3),
|
|
91
|
+
max: strengths.max.round(3),
|
|
92
|
+
min: strengths.min.round(3),
|
|
93
|
+
strong: strengths.count { |s| s >= 0.5 },
|
|
94
|
+
weak: strengths.count { |s| s < 0.3 }
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def emotional_tone(traces)
|
|
99
|
+
valences = traces.map { |t| t[:emotional_valence] || 0.0 }
|
|
100
|
+
intensities = traces.map { |t| t[:emotional_intensity] || 0.0 }
|
|
101
|
+
|
|
102
|
+
avg_valence = valences.sum / valences.size
|
|
103
|
+
avg_intensity = intensities.sum / intensities.size
|
|
104
|
+
|
|
105
|
+
{
|
|
106
|
+
avg_valence: avg_valence.round(3),
|
|
107
|
+
avg_intensity: avg_intensity.round(3),
|
|
108
|
+
tone: tone_label(avg_valence, avg_intensity)
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def tone_label(valence, intensity)
|
|
113
|
+
if intensity < 0.2 then :neutral
|
|
114
|
+
elsif valence > 0.3 then :positive
|
|
115
|
+
elsif valence < -0.3 then :negative
|
|
116
|
+
else :mixed
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def freshness_assessment(traces)
|
|
121
|
+
now = Time.now.utc
|
|
122
|
+
ages = traces.map { |t| now - (t[:last_reinforced] || t[:created_at] || now) }
|
|
123
|
+
avg_age = ages.sum / ages.size
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
avg_age_seconds: avg_age.round(1),
|
|
127
|
+
freshest: ages.min.round(1),
|
|
128
|
+
stalest: ages.max.round(1),
|
|
129
|
+
label: freshness_label(avg_age)
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def freshness_label(avg_age)
|
|
134
|
+
if avg_age < 60 then :very_fresh
|
|
135
|
+
elsif avg_age < 3600 then :fresh
|
|
136
|
+
elsif avg_age < 86_400 then :aging
|
|
137
|
+
else :stale
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def empty_summary(domain)
|
|
142
|
+
{
|
|
143
|
+
domain: domain,
|
|
144
|
+
knowledge_level: :none,
|
|
145
|
+
trace_count: 0,
|
|
146
|
+
type_breakdown: {},
|
|
147
|
+
key_facts: [],
|
|
148
|
+
strength_stats: { mean: 0.0, max: 0.0, min: 0.0, strong: 0, weak: 0 },
|
|
149
|
+
emotional_tone: { avg_valence: 0.0, avg_intensity: 0.0, tone: :neutral },
|
|
150
|
+
freshness: { avg_age_seconds: 0.0, freshest: 0.0, stalest: 0.0, label: :stale }
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def limit_per_type(type, total_limit)
|
|
155
|
+
case type
|
|
156
|
+
when :firmware, :identity then [total_limit, 3].min
|
|
157
|
+
when :procedural then [total_limit, 2].min
|
|
158
|
+
else [total_limit, 5].min
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Agentic
|
|
6
|
+
module Language
|
|
7
|
+
module Language
|
|
8
|
+
module Runners
|
|
9
|
+
module Language
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
12
|
+
|
|
13
|
+
def summarize(domain:, depth: :standard, traces: [], **)
|
|
14
|
+
traces = filter_traces(traces, domain)
|
|
15
|
+
summary = Helpers::Summarizer.summarize_domain(traces, domain: domain.to_sym, depth: depth.to_sym)
|
|
16
|
+
lexicon.store_summary(domain, summary)
|
|
17
|
+
|
|
18
|
+
Legion::Logging.debug "[language] summarize domain=#{domain} traces=#{traces.size} " \
|
|
19
|
+
"knowledge=#{summary[:knowledge_level]}"
|
|
20
|
+
summary
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def what_do_i_know(domain:, depth: :standard, traces: [], **)
|
|
24
|
+
summary = if lexicon.stale?(domain)
|
|
25
|
+
summarize(domain: domain, depth: depth, traces: traces)
|
|
26
|
+
else
|
|
27
|
+
lexicon.get_summary(domain)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
prose = generate_knowledge_prose(summary)
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
domain: domain.to_sym,
|
|
34
|
+
knowledge_level: summary[:knowledge_level],
|
|
35
|
+
prose: prose,
|
|
36
|
+
fact_count: summary[:key_facts]&.size || 0,
|
|
37
|
+
summary: summary
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def can_answer_wonder?(wonder:, traces: [], **)
|
|
42
|
+
domain = wonder.is_a?(Hash) ? wonder[:domain] : :general
|
|
43
|
+
relevant = filter_traces(traces, domain)
|
|
44
|
+
|
|
45
|
+
answerable = relevant.size >= Helpers::Constants::RESOLUTION_THRESHOLD
|
|
46
|
+
confidence = answerable ? compute_answer_confidence(relevant) : 0.0
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
answerable: answerable,
|
|
50
|
+
confidence: confidence.round(3),
|
|
51
|
+
domain: domain,
|
|
52
|
+
trace_count: relevant.size,
|
|
53
|
+
threshold: Helpers::Constants::RESOLUTION_THRESHOLD
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def knowledge_map(**)
|
|
58
|
+
{
|
|
59
|
+
domains: lexicon.knowledge_map,
|
|
60
|
+
known_domains: lexicon.known_domains,
|
|
61
|
+
total_domains: lexicon.size
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def language_stats(**)
|
|
66
|
+
{
|
|
67
|
+
cached_domains: lexicon.size,
|
|
68
|
+
known_domains: lexicon.known_domains,
|
|
69
|
+
knowledge_map: lexicon.knowledge_map
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def lexicon
|
|
76
|
+
@lexicon ||= Helpers::Lexicon.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def filter_traces(traces, domain)
|
|
80
|
+
return [] unless traces.is_a?(Array)
|
|
81
|
+
|
|
82
|
+
domain_sym = domain.to_sym
|
|
83
|
+
domain_str = domain.to_s
|
|
84
|
+
|
|
85
|
+
matching = traces.select do |t|
|
|
86
|
+
tags = t[:domain_tags] || []
|
|
87
|
+
tags.any? { |tag| tag.to_s == domain_str || tag.to_sym == domain_sym }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
matching
|
|
91
|
+
.select { |t| (t[:strength] || 0) >= Helpers::Constants::MIN_SUMMARY_STRENGTH }
|
|
92
|
+
.sort_by { |t| -(t[:strength] || 0) }
|
|
93
|
+
.first(Helpers::Constants::MAX_TRACES_PER_SUMMARY)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def generate_knowledge_prose(summary)
|
|
97
|
+
domain = summary[:domain]
|
|
98
|
+
level = summary[:knowledge_level]
|
|
99
|
+
count = summary[:trace_count]
|
|
100
|
+
facts = summary[:key_facts] || []
|
|
101
|
+
|
|
102
|
+
base = "About #{domain}: I have #{level} knowledge (#{count} traces)."
|
|
103
|
+
|
|
104
|
+
if facts.empty?
|
|
105
|
+
"#{base} No specific facts available."
|
|
106
|
+
else
|
|
107
|
+
fact_lines = facts.first(5).map { |f| "- #{truncate(f[:content], 120)}" }
|
|
108
|
+
"#{base}\nKey facts:\n#{fact_lines.join("\n")}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def compute_answer_confidence(traces)
|
|
113
|
+
return 0.0 if traces.empty?
|
|
114
|
+
|
|
115
|
+
strengths = traces.map { |t| t[:strength] || 0.0 }
|
|
116
|
+
confidences = traces.map { |t| t[:confidence] || 0.5 }
|
|
117
|
+
|
|
118
|
+
avg_strength = strengths.sum / strengths.size
|
|
119
|
+
avg_confidence = confidences.sum / confidences.size
|
|
120
|
+
|
|
121
|
+
((avg_strength * 0.5) + (avg_confidence * 0.5)).clamp(0.0, 1.0)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def truncate(text, max_length)
|
|
125
|
+
text = text.to_s
|
|
126
|
+
text.length > max_length ? "#{text[0...max_length]}..." : text
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/language/language/version'
|
|
4
|
+
require 'legion/extensions/agentic/language/language/helpers/constants'
|
|
5
|
+
require 'legion/extensions/agentic/language/language/helpers/summarizer'
|
|
6
|
+
require 'legion/extensions/agentic/language/language/helpers/lexicon'
|
|
7
|
+
require 'legion/extensions/agentic/language/language/runners/language'
|
|
8
|
+
require 'legion/extensions/agentic/language/language/client'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module Agentic
|
|
13
|
+
module Language
|
|
14
|
+
module Language
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event'
|
|
4
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative'
|
|
5
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine'
|
|
6
|
+
require 'legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module Extensions
|
|
10
|
+
module Agentic
|
|
11
|
+
module Language
|
|
12
|
+
module NarrativeReasoning
|
|
13
|
+
class Client
|
|
14
|
+
include Runners::NarrativeReasoning
|
|
15
|
+
|
|
16
|
+
def initialize(**)
|
|
17
|
+
@narrative_engine = Helpers::NarrativeEngine.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
attr_reader :narrative_engine
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
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 Narrative
|
|
12
|
+
ARC_STAGES = %i[beginning rising_action climax falling_action resolution].freeze
|
|
13
|
+
|
|
14
|
+
COHERENCE_LABELS = {
|
|
15
|
+
(0.8..) => :compelling,
|
|
16
|
+
(0.6...0.8) => :coherent,
|
|
17
|
+
(0.4...0.6) => :developing,
|
|
18
|
+
(0.2...0.4) => :fragmented,
|
|
19
|
+
(..0.2) => :incoherent
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
DEFAULT_COHERENCE = 0.5
|
|
23
|
+
COHERENCE_FLOOR = 0.0
|
|
24
|
+
COHERENCE_CEILING = 1.0
|
|
25
|
+
COHERENCE_BOOST = 0.1
|
|
26
|
+
DECAY_RATE = 0.02
|
|
27
|
+
|
|
28
|
+
attr_reader :id, :title, :domain, :events, :characters, :themes,
|
|
29
|
+
:arc_stage, :coherence, :created_at, :last_updated_at
|
|
30
|
+
|
|
31
|
+
def initialize(title:, domain: nil, id: nil)
|
|
32
|
+
@id = id || SecureRandom.uuid
|
|
33
|
+
@title = title
|
|
34
|
+
@domain = domain
|
|
35
|
+
@events = []
|
|
36
|
+
@characters = []
|
|
37
|
+
@themes = []
|
|
38
|
+
@arc_stage = ARC_STAGES.first
|
|
39
|
+
@coherence = DEFAULT_COHERENCE
|
|
40
|
+
@created_at = Time.now.utc
|
|
41
|
+
@last_updated_at = Time.now.utc
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_event(event)
|
|
45
|
+
@events << event
|
|
46
|
+
event.characters.each { |chr| @characters << chr unless @characters.include?(chr) }
|
|
47
|
+
auto_advance_arc
|
|
48
|
+
@coherence = (@coherence + COHERENCE_BOOST).clamp(COHERENCE_FLOOR, COHERENCE_CEILING)
|
|
49
|
+
@last_updated_at = Time.now.utc
|
|
50
|
+
event
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def add_theme(theme)
|
|
54
|
+
@themes << theme unless @themes.include?(theme)
|
|
55
|
+
@last_updated_at = Time.now.utc
|
|
56
|
+
theme
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def advance_arc!
|
|
60
|
+
idx = ARC_STAGES.index(@arc_stage)
|
|
61
|
+
return @arc_stage if idx.nil? || idx >= ARC_STAGES.size - 1
|
|
62
|
+
|
|
63
|
+
@arc_stage = ARC_STAGES[idx + 1]
|
|
64
|
+
@last_updated_at = Time.now.utc
|
|
65
|
+
@arc_stage
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def causal_chain
|
|
69
|
+
chain = []
|
|
70
|
+
@events.each do |event|
|
|
71
|
+
event.causes.each do |cause_id|
|
|
72
|
+
cause = @events.find { |e| e.id == cause_id }
|
|
73
|
+
chain << { cause: cause&.to_h, effect: event.to_h } if cause
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
chain
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def coherence_label
|
|
80
|
+
COHERENCE_LABELS.find { |range, _label| range.cover?(@coherence) }&.last || :incoherent
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def complete?
|
|
84
|
+
@arc_stage == :resolution
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def decay_coherence
|
|
88
|
+
@coherence = (@coherence - DECAY_RATE).clamp(COHERENCE_FLOOR, COHERENCE_CEILING)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_h
|
|
92
|
+
{
|
|
93
|
+
id: @id,
|
|
94
|
+
title: @title,
|
|
95
|
+
domain: @domain,
|
|
96
|
+
events: @events.map(&:to_h),
|
|
97
|
+
characters: @characters,
|
|
98
|
+
themes: @themes,
|
|
99
|
+
arc_stage: @arc_stage,
|
|
100
|
+
coherence: @coherence,
|
|
101
|
+
coherence_label: coherence_label,
|
|
102
|
+
complete: complete?,
|
|
103
|
+
created_at: @created_at,
|
|
104
|
+
last_updated_at: @last_updated_at
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def auto_advance_arc
|
|
111
|
+
return if complete?
|
|
112
|
+
|
|
113
|
+
events_per_stage = [(@events.size / 5.0).ceil, 1].max
|
|
114
|
+
stage_idx = [(@events.size / events_per_stage), ARC_STAGES.size - 1].min
|
|
115
|
+
@arc_stage = ARC_STAGES[stage_idx]
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|