legion-llm 0.5.22 → 0.5.23

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: 7e7ff9c35609294ace1f203076485aaba0a811ba0adbe7d01197ce91de7a06ea
4
- data.tar.gz: c493d891ded32632daf9b7e367fa115efc9903cc63d408c8c0799bb8bc242ca1
3
+ metadata.gz: 9bfeb8ca0bacd82ef217f76275e20d8df7a15f83a324bcf9f090232ed7374837
4
+ data.tar.gz: cb4ceb07ae6004670a9e582a1fad6d3d2bc9a87e5a123116ff1ef995e5576744
5
5
  SHA512:
6
- metadata.gz: 2cd3766d938ba6814f2a14af190441a0d9b0e011538da22744729b678313ff03045fc84ae4e1ecab7dcff9ac31a1d4d92afdff25d3b5e7ca322a0303ef54c0fa
7
- data.tar.gz: 3a03dd436e31b0d02290e23af4cadc755136c89321af205a83c0c18dac4cac9a8f320875a578ac6405dafd409a0b3bc516431d1dff34670731ab803618df36ae
6
+ metadata.gz: 0a24a288be1a94ef846aea5c47871c7f7875e99749078a77a431af854fbdbf3582cfdf1191aa570d9ae08d77f723bb653fa4b431258c77f5e050e2bd8ec367ef
7
+ data.tar.gz: '0508aced0a6e1e8f09fb2e6ae2c8121f81d2b8efe765e37b1bb7d23c5ae688dcb9dc314c0a92cb6caef004c0ee8767f3b79e961728d3f278a5ceaf99bd16d844'
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.5.23] - 2026-03-31
6
+
7
+ ### Added
8
+ - `Hooks::Reciprocity` — after_chat hook that records a `:given` social exchange event via `Social::Social::Client#record_exchange` when a caller with identity receives an LLM response; silently no-ops when social extension or identity is absent
9
+ - Partner context enrichment in `Pipeline::Steps::GaiaAdvisory` (step 7) — when the caller identity is registered as a partner in `Legion::Gaia::BondRegistry`, the advisory data is enriched with a `:partner_context` hash containing standing, compatibility, recent_sentiment, and interaction_pattern; sourced from Apollo Local `partner`-tagged entries with full graceful degradation when Apollo is unavailable
10
+
5
11
  ## [0.5.22] - 2026-03-31
6
12
 
7
13
  ### Added
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module LLM
5
+ module Hooks
6
+ # Records a :given exchange event on the social graph whenever the LLM
7
+ # responds to a caller that carries an identity. Runs as an after_chat hook.
8
+ #
9
+ # The hook is intentionally lightweight — it does not block the response
10
+ # path and silently swallows all errors so a social-layer problem never
11
+ # surfaces to the caller.
12
+ module Reciprocity
13
+ module_function
14
+
15
+ def install
16
+ Legion::LLM::Hooks.after_chat do |caller: nil, **|
17
+ record_reciprocity(caller: caller)
18
+ nil
19
+ end
20
+ end
21
+
22
+ def record_reciprocity(caller:)
23
+ identity = caller&.dig(:requested_by, :identity)
24
+ return unless identity
25
+
26
+ runner = social_runner
27
+ return unless runner
28
+
29
+ runner.record_exchange(agent_id: identity, action: :communication, direction: :given)
30
+ rescue StandardError => e
31
+ Legion::Logging.debug "[LLM::Reciprocity] hook error: #{e.message}" if defined?(Legion::Logging)
32
+ end
33
+
34
+ def social_runner
35
+ return nil unless defined?(Legion::Extensions::Agentic::Social::Social::Client)
36
+
37
+ Legion::Extensions::Agentic::Social::Social::Client.new
38
+ rescue StandardError => e
39
+ Legion::Logging.debug "[LLM::Reciprocity] social_runner error: #{e.message}" if defined?(Legion::Logging)
40
+ nil
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -6,6 +6,7 @@ require 'legion/llm/hooks/metering'
6
6
  require 'legion/llm/hooks/cost_tracking'
7
7
  require 'legion/llm/hooks/budget_guard'
8
8
  require 'legion/llm/hooks/reflection'
9
+ require 'legion/llm/hooks/reciprocity'
9
10
 
10
11
  module Legion
11
12
  module LLM
@@ -19,6 +19,8 @@ module Legion
19
19
 
20
20
  return if advisory.nil? || advisory.empty?
21
21
 
22
+ enrich_advisory_with_partner_context(advisory)
23
+
22
24
  @enrichments['gaia:advisory'] = {
23
25
  content: advisory_summary(advisory),
24
26
  data: advisory,
@@ -48,6 +50,29 @@ module Legion
48
50
  @warnings << "GAIA advisory error: #{e.message}"
49
51
  end
50
52
 
53
+ # Exposed as a public method so specs can stub it on instances.
54
+ def build_partner_context(identity)
55
+ return default_partner_context unless apollo_local_available?
56
+
57
+ entries = ::Legion::Apollo::Local.query(
58
+ text: identity,
59
+ tags: ['partner'],
60
+ limit: 5
61
+ )
62
+
63
+ results = entries.is_a?(Hash) ? (entries[:results] || []) : Array(entries)
64
+
65
+ {
66
+ standing: extract_standing(results),
67
+ compatibility: extract_compatibility(results),
68
+ recent_sentiment: extract_sentiment(results),
69
+ interaction_pattern: extract_interaction_pattern(results)
70
+ }
71
+ rescue StandardError => e
72
+ Legion::Logging.debug "[GaiaAdvisory] build_partner_context error: #{e.message}" if defined?(Legion::Logging)
73
+ nil
74
+ end
75
+
51
76
  private
52
77
 
53
78
  def advisory_summary(advisory)
@@ -57,6 +82,84 @@ module Legion
57
82
  parts << "suppress:#{advisory[:suppress]&.join(',')}" if advisory[:suppress]
58
83
  parts.empty? ? 'no enrichment' : parts.join(', ')
59
84
  end
85
+
86
+ def enrich_advisory_with_partner_context(advisory)
87
+ return unless defined?(::Legion::Gaia::BondRegistry)
88
+
89
+ identity = @request.caller&.dig(:requested_by, :identity)
90
+ return unless identity
91
+ return unless ::Legion::Gaia::BondRegistry.partner?(identity)
92
+
93
+ partner_ctx = build_partner_context(identity)
94
+ advisory[:partner_context] = partner_ctx if partner_ctx
95
+ rescue StandardError => e
96
+ Legion::Logging.debug "[GaiaAdvisory] partner context error: #{e.message}" if defined?(Legion::Logging)
97
+ end
98
+
99
+ def apollo_local_available?
100
+ defined?(::Legion::Apollo::Local) && ::Legion::Apollo::Local.started?
101
+ rescue StandardError
102
+ false
103
+ end
104
+
105
+ def default_partner_context
106
+ {
107
+ standing: :unknown,
108
+ compatibility: nil,
109
+ recent_sentiment: :neutral,
110
+ interaction_pattern: :unknown
111
+ }
112
+ end
113
+
114
+ def extract_standing(results)
115
+ entry = results.find { |r| r[:content].to_s.match?(/standing/i) }
116
+ return :unknown unless entry
117
+
118
+ content = entry[:content].to_s
119
+ if content.match?(/good|trusted|positive/i)
120
+ :good
121
+ elsif content.match?(/poor|untrusted|negative/i)
122
+ :poor
123
+ else
124
+ :neutral
125
+ end
126
+ end
127
+
128
+ def extract_compatibility(results)
129
+ entry = results.find { |r| r[:content].to_s.match?(/compat/i) }
130
+ return nil unless entry
131
+
132
+ match = entry[:content].to_s.match(/(\d+(?:\.\d+)?)/)
133
+ match ? match[1].to_f : nil
134
+ end
135
+
136
+ def extract_sentiment(results)
137
+ entry = results.find { |r| r[:content].to_s.match?(/sentiment|empathy|affect/i) }
138
+ return :neutral unless entry
139
+
140
+ content = entry[:content].to_s
141
+ if content.match?(/positive|happy|pleasant/i)
142
+ :positive
143
+ elsif content.match?(/negative|unhappy|tense/i)
144
+ :negative
145
+ else
146
+ :neutral
147
+ end
148
+ end
149
+
150
+ def extract_interaction_pattern(results)
151
+ entry = results.find { |r| r[:content].to_s.match?(/interaction|memory|trace/i) }
152
+ return :unknown unless entry
153
+
154
+ content = entry[:content].to_s
155
+ if content.match?(/frequent|regular|daily/i)
156
+ :frequent
157
+ elsif content.match?(/occasional|sometimes/i)
158
+ :occasional
159
+ else
160
+ :infrequent
161
+ end
162
+ end
60
163
  end
61
164
  end
62
165
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module LLM
5
- VERSION = '0.5.22'
5
+ VERSION = '0.5.23'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.22
4
+ version: 0.5.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -253,6 +253,7 @@ files:
253
253
  - lib/legion/llm/hooks/cost_tracking.rb
254
254
  - lib/legion/llm/hooks/metering.rb
255
255
  - lib/legion/llm/hooks/rag_guard.rb
256
+ - lib/legion/llm/hooks/reciprocity.rb
256
257
  - lib/legion/llm/hooks/reflection.rb
257
258
  - lib/legion/llm/hooks/response_guard.rb
258
259
  - lib/legion/llm/off_peak.rb