lex-microsoft_teams 0.6.16 → 0.6.17
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 +11 -0
- data/lib/legion/extensions/microsoft_teams/helpers/prompt_resolver.rb +3 -1
- data/lib/legion/extensions/microsoft_teams/helpers/session_manager.rb +19 -0
- data/lib/legion/extensions/microsoft_teams/helpers/trace_retriever.rb +136 -0
- data/lib/legion/extensions/microsoft_teams/runners/bot.rb +20 -5
- data/lib/legion/extensions/microsoft_teams/version.rb +1 -1
- data/lib/legion/extensions/microsoft_teams.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 832e104a03125c669c37edf62529da10ffb5bd5b4267de12ba91c764a4e168ae
|
|
4
|
+
data.tar.gz: f6c47a1c6af1903e9bec4aec82363f4d01a439d353236144213f3c677923c14f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16bac537f53a87e92a2d37ee697eb8759739e67530e4262c18378f7af532afe5c192c3843d4bfdaafc807636b9fafea83381d36089bbaa21888ed5decf734907
|
|
7
|
+
data.tar.gz: daf6dda8b96102a644d002b7890f2faf3b343f9600bd9c8e00fa76a5db1b0ad380ab9b37addb7480a12b71f0bbbd1ac9bc00247d6d1b75dd451b19953062f93b
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
### Changed
|
|
6
6
|
- add caller identity to llm_chat calls in bot and profile_ingest runners for pipeline attribution
|
|
7
7
|
|
|
8
|
+
## [0.6.17] - 2026-03-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `Helpers::TraceRetriever` module: retrieves memory traces from the shared store at query time and formats them as LLM context (sender, teams, and chat-scoped domains; 2000-token budget; strength-ranked deduplication)
|
|
12
|
+
- `Bot#retrieve_trace_context` private method wires TraceRetriever into the handle_message flow
|
|
13
|
+
- `Bot#handle_message` now retrieves trace context before generating a response and passes it through to `generate_response` and `llm_respond`
|
|
14
|
+
- `SessionManager#get_or_create` seeds new sessions with profile traces for the owner via `trace_seed_for`
|
|
15
|
+
- `PromptResolver#resolve_prompt` accepts optional `trace_context:` keyword and appends it after preference instructions
|
|
16
|
+
- Comprehensive specs for TraceRetriever (token budget, rank/dedup, age labels, graceful degradation)
|
|
17
|
+
- Bot specs updated to verify trace context retrieval and pass-through, and nil/graceful-degradation paths
|
|
18
|
+
|
|
8
19
|
## [0.6.15] - 2026-03-23
|
|
9
20
|
|
|
10
21
|
### Added
|
|
@@ -5,7 +5,7 @@ module Legion
|
|
|
5
5
|
module MicrosoftTeams
|
|
6
6
|
module Helpers
|
|
7
7
|
module PromptResolver
|
|
8
|
-
def resolve_prompt(mode:, conversation_id:, owner_id: nil)
|
|
8
|
+
def resolve_prompt(mode:, conversation_id:, owner_id: nil, trace_context: nil)
|
|
9
9
|
settings = teams_settings
|
|
10
10
|
base = settings.dig(:bot, :system_prompt) || ''
|
|
11
11
|
|
|
@@ -18,6 +18,8 @@ module Legion
|
|
|
18
18
|
pref_instructions = preference_instructions_for(owner_id: owner_id)
|
|
19
19
|
prompt = "#{prompt}\n\n#{pref_instructions}" if pref_instructions
|
|
20
20
|
|
|
21
|
+
prompt = "#{prompt}\n\n#{trace_context}" if trace_context && !trace_context.empty?
|
|
22
|
+
|
|
21
23
|
prompt
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -35,6 +35,9 @@ module Legion
|
|
|
35
35
|
llm_config: resolve_llm_config(mode: mode, conversation_id: conversation_id, owner_id: owner_id)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
seed = trace_seed_for(owner_id: owner_id)
|
|
39
|
+
session[:trace_seed] = seed if seed
|
|
40
|
+
|
|
38
41
|
@sessions[conversation_id] = session
|
|
39
42
|
end
|
|
40
43
|
|
|
@@ -134,6 +137,22 @@ module Legion
|
|
|
134
137
|
@memory_runner ||= Object.new.extend(Legion::Extensions::Agentic::Memory::Trace::Runners::Traces)
|
|
135
138
|
end
|
|
136
139
|
|
|
140
|
+
def trace_seed_for(owner_id:)
|
|
141
|
+
return nil unless defined?(Legion::Extensions::Agentic::Memory::Trace) &&
|
|
142
|
+
Legion::Extensions::Agentic::Memory::Trace.respond_to?(:shared_store)
|
|
143
|
+
|
|
144
|
+
store = Legion::Extensions::Agentic::Memory::Trace.shared_store
|
|
145
|
+
return nil unless store
|
|
146
|
+
|
|
147
|
+
profile_traces = store.retrieve_by_domain("sender:#{owner_id}", min_strength: 0.3, limit: 5)
|
|
148
|
+
return nil if profile_traces.empty?
|
|
149
|
+
|
|
150
|
+
profile_traces.map { |t| { type: t[:trace_type], content: t[:content_payload].to_s[0, 200] } }
|
|
151
|
+
rescue StandardError => e
|
|
152
|
+
log.debug("trace_seed_for failed: #{e.message}") if defined?(log) && log.respond_to?(:debug)
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
|
|
137
156
|
def settings_val(key, default)
|
|
138
157
|
if defined?(Legion::Settings) && Legion::Settings.dig(:microsoft_teams, :bot, :session, key)
|
|
139
158
|
Legion::Settings[:microsoft_teams][:bot][:session][key]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module MicrosoftTeams
|
|
6
|
+
module Helpers
|
|
7
|
+
module TraceRetriever
|
|
8
|
+
MAX_TRACE_TOKENS = 2000
|
|
9
|
+
MAX_TRACES = 20
|
|
10
|
+
|
|
11
|
+
def retrieve_context(message:, owner_id:, chat_id: nil, channel_id: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
12
|
+
return nil unless memory_trace_available?
|
|
13
|
+
|
|
14
|
+
traces = []
|
|
15
|
+
traces.concat(retrieve_sender_traces(owner_id: owner_id))
|
|
16
|
+
traces.concat(retrieve_teams_traces)
|
|
17
|
+
traces.concat(retrieve_chat_traces(chat_id: chat_id)) if chat_id
|
|
18
|
+
|
|
19
|
+
ranked = rank_traces(traces: traces, query: message)
|
|
20
|
+
format_trace_context(traces: ranked.first(MAX_TRACES))
|
|
21
|
+
rescue StandardError => e
|
|
22
|
+
log_trace_error('retrieve_context', e)
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def retrieve_sender_traces(owner_id:)
|
|
29
|
+
return [] unless owner_id
|
|
30
|
+
|
|
31
|
+
store = shared_trace_store
|
|
32
|
+
return [] unless store
|
|
33
|
+
|
|
34
|
+
store.retrieve_by_domain("sender:#{owner_id}", min_strength: 0.1, limit: 10)
|
|
35
|
+
rescue StandardError => e
|
|
36
|
+
log_trace_error('retrieve_sender_traces', e)
|
|
37
|
+
[]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def retrieve_teams_traces
|
|
41
|
+
store = shared_trace_store
|
|
42
|
+
return [] unless store
|
|
43
|
+
|
|
44
|
+
store.retrieve_by_domain('teams', min_strength: 0.3, limit: 10)
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
log_trace_error('retrieve_teams_traces', e)
|
|
47
|
+
[]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def retrieve_chat_traces(chat_id:)
|
|
51
|
+
store = shared_trace_store
|
|
52
|
+
return [] unless store
|
|
53
|
+
|
|
54
|
+
store.retrieve_by_domain("chat:#{chat_id}", min_strength: 0.1, limit: 5)
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
log_trace_error('retrieve_chat_traces', e)
|
|
57
|
+
[]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def rank_traces(traces:, query:) # rubocop:disable Lint/UnusedMethodArgument
|
|
61
|
+
seen = Set.new
|
|
62
|
+
unique = traces.select { |t| seen.add?(t[:trace_id]) }
|
|
63
|
+
|
|
64
|
+
unique.sort_by { |t| [-(t[:strength] || 0.0), -t[:last_reinforced].to_f] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def format_trace_context(traces:)
|
|
68
|
+
return nil if traces.empty?
|
|
69
|
+
|
|
70
|
+
lines = ['## Organizational Context (from memory)']
|
|
71
|
+
token_estimate = 0
|
|
72
|
+
|
|
73
|
+
traces.each do |trace|
|
|
74
|
+
line = format_single_trace(trace)
|
|
75
|
+
next unless line
|
|
76
|
+
|
|
77
|
+
line_tokens = line.length / 4
|
|
78
|
+
break if token_estimate + line_tokens > MAX_TRACE_TOKENS
|
|
79
|
+
|
|
80
|
+
lines << line
|
|
81
|
+
token_estimate += line_tokens
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
return nil if lines.size <= 1
|
|
85
|
+
|
|
86
|
+
lines.join("\n")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def format_single_trace(trace)
|
|
90
|
+
type = trace[:trace_type] || :unknown
|
|
91
|
+
content = trace[:content_payload].to_s
|
|
92
|
+
content = "#{content[0, 200]}..." if content.length > 200
|
|
93
|
+
|
|
94
|
+
tags = (trace[:domain_tags] || []).join(', ')
|
|
95
|
+
age = trace_age_label(trace[:created_at] || trace[:last_reinforced])
|
|
96
|
+
|
|
97
|
+
"- [#{type}] #{content} (#{age}, tags: #{tags})"
|
|
98
|
+
rescue StandardError
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def trace_age_label(timestamp)
|
|
103
|
+
return 'unknown age' unless timestamp
|
|
104
|
+
|
|
105
|
+
seconds = Time.now - (timestamp.is_a?(Time) ? timestamp : Time.parse(timestamp.to_s))
|
|
106
|
+
case seconds
|
|
107
|
+
when 0..3600 then 'just now'
|
|
108
|
+
when 3600..86_400 then "#{(seconds / 3600).to_i}h ago"
|
|
109
|
+
when 86_400..604_800 then "#{(seconds / 86_400).to_i}d ago"
|
|
110
|
+
else "#{(seconds / 604_800).to_i}w ago"
|
|
111
|
+
end
|
|
112
|
+
rescue StandardError
|
|
113
|
+
'unknown age'
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def memory_trace_available?
|
|
117
|
+
defined?(Legion::Extensions::Agentic::Memory::Trace)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def shared_trace_store
|
|
121
|
+
return nil unless defined?(Legion::Extensions::Agentic::Memory::Trace) &&
|
|
122
|
+
Legion::Extensions::Agentic::Memory::Trace.respond_to?(:shared_store)
|
|
123
|
+
|
|
124
|
+
Legion::Extensions::Agentic::Memory::Trace.shared_store
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def log_trace_error(method, error)
|
|
128
|
+
return unless defined?(log)
|
|
129
|
+
|
|
130
|
+
log.debug("TraceRetriever##{method} failed: #{error.message}")
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -4,6 +4,7 @@ require 'json'
|
|
|
4
4
|
require 'legion/extensions/microsoft_teams/helpers/client'
|
|
5
5
|
require_relative '../helpers/session_manager'
|
|
6
6
|
require_relative '../helpers/subscription_registry'
|
|
7
|
+
require_relative '../helpers/trace_retriever'
|
|
7
8
|
|
|
8
9
|
module Legion
|
|
9
10
|
module Extensions
|
|
@@ -12,6 +13,7 @@ module Legion
|
|
|
12
13
|
module Bot
|
|
13
14
|
include Legion::Extensions::MicrosoftTeams::Helpers::Client
|
|
14
15
|
include Legion::Extensions::MicrosoftTeams::Helpers::PromptResolver
|
|
16
|
+
include Legion::Extensions::MicrosoftTeams::Helpers::TraceRetriever
|
|
15
17
|
|
|
16
18
|
def send_activity(service_url:, conversation_id:, activity:, **)
|
|
17
19
|
conn = bot_connection(service_url: service_url, **)
|
|
@@ -97,7 +99,8 @@ module Legion
|
|
|
97
99
|
)
|
|
98
100
|
session_manager.add_message(conversation_id: conversation_id, role: :user, content: text)
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
trace_context = retrieve_trace_context(message: text, owner_id: owner_id, chat_id: chat_id)
|
|
103
|
+
response_text = generate_response(text: text, session: session, trace_context: trace_context)
|
|
101
104
|
|
|
102
105
|
reply_result = send_reply(
|
|
103
106
|
chat_id: chat_id,
|
|
@@ -166,17 +169,20 @@ module Legion
|
|
|
166
169
|
|
|
167
170
|
private
|
|
168
171
|
|
|
169
|
-
def generate_response(text:, session:)
|
|
170
|
-
return llm_respond(text: text, session: session) if llm_available?
|
|
172
|
+
def generate_response(text:, session:, trace_context: nil)
|
|
173
|
+
return llm_respond(text: text, session: session, trace_context: trace_context) if llm_available?
|
|
171
174
|
|
|
172
175
|
"Echo: #{text}"
|
|
173
176
|
end
|
|
174
177
|
|
|
175
|
-
def llm_respond(text:, session:)
|
|
178
|
+
def llm_respond(text:, session:, trace_context: nil)
|
|
176
179
|
config = session[:llm_config] || {}
|
|
180
|
+
instructions = session[:system_prompt]
|
|
181
|
+
instructions = "#{instructions}\n\n#{trace_context}" if trace_context && !trace_context.empty?
|
|
182
|
+
|
|
177
183
|
response = llm_chat(
|
|
178
184
|
text,
|
|
179
|
-
instructions:
|
|
185
|
+
instructions: instructions,
|
|
180
186
|
model: config[:model],
|
|
181
187
|
intent: config[:intent],
|
|
182
188
|
caller: { id: session[:owner_id], extension: 'lex-microsoft_teams', mode: :bot_response }
|
|
@@ -187,6 +193,15 @@ module Legion
|
|
|
187
193
|
'I encountered an error processing your message. Please try again.'
|
|
188
194
|
end
|
|
189
195
|
|
|
196
|
+
def retrieve_trace_context(message:, owner_id:, chat_id:)
|
|
197
|
+
return nil unless respond_to?(:retrieve_context, true)
|
|
198
|
+
|
|
199
|
+
retrieve_context(message: message, owner_id: owner_id, chat_id: chat_id)
|
|
200
|
+
rescue StandardError => e
|
|
201
|
+
log.debug("retrieve_trace_context failed: #{e.message}") if defined?(log)
|
|
202
|
+
nil
|
|
203
|
+
end
|
|
204
|
+
|
|
190
205
|
def llm_available?
|
|
191
206
|
defined?(Legion::LLM) && Legion::LLM.respond_to?(:chat)
|
|
192
207
|
end
|
|
@@ -25,6 +25,7 @@ require 'legion/extensions/microsoft_teams/runners/api_ingest'
|
|
|
25
25
|
# Helpers (bot)
|
|
26
26
|
require 'legion/extensions/microsoft_teams/helpers/high_water_mark'
|
|
27
27
|
require 'legion/extensions/microsoft_teams/helpers/prompt_resolver'
|
|
28
|
+
require 'legion/extensions/microsoft_teams/helpers/trace_retriever'
|
|
28
29
|
require 'legion/extensions/microsoft_teams/helpers/session_manager'
|
|
29
30
|
require 'legion/extensions/microsoft_teams/helpers/subscription_registry'
|
|
30
31
|
require 'legion/extensions/microsoft_teams/helpers/token_cache'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-microsoft_teams
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.17
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -197,6 +197,7 @@ files:
|
|
|
197
197
|
- lib/legion/extensions/microsoft_teams/helpers/session_manager.rb
|
|
198
198
|
- lib/legion/extensions/microsoft_teams/helpers/subscription_registry.rb
|
|
199
199
|
- lib/legion/extensions/microsoft_teams/helpers/token_cache.rb
|
|
200
|
+
- lib/legion/extensions/microsoft_teams/helpers/trace_retriever.rb
|
|
200
201
|
- lib/legion/extensions/microsoft_teams/helpers/transform_definitions.rb
|
|
201
202
|
- lib/legion/extensions/microsoft_teams/hooks/auth.rb
|
|
202
203
|
- lib/legion/extensions/microsoft_teams/local_cache/extractor.rb
|