strongmind-platform-sdk 3.28.0 → 3.29.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +5 -5
- data/lib/platform_sdk/observability/langfuse/notification_subscriber.rb +127 -0
- data/lib/platform_sdk/observability/langfuse/ruby_llm_adapter.rb +118 -0
- data/lib/platform_sdk/observability/langfuse/spec_support.rb +6 -0
- data/lib/platform_sdk/observability/langfuse/trace_summarizable.rb +43 -0
- data/lib/platform_sdk/observability/langfuse.rb +25 -10
- data/lib/platform_sdk/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 799fe97b77fb105775c8b846e6916380be6845209a5157633d832186ae81bd25
|
|
4
|
+
data.tar.gz: ee0bbab328e6e27bcf72a5e634535f9ed0ab91bb57976b94a9c1fd24fac268f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7080031758db9d9bd0894dcc2b8054df0377f1bc33a6da0ba987f7be9888c343cb0e2feb83d9d811c95114e1891ff4a62c1cde7d445333b35a5bcbf6a2b4007a
|
|
7
|
+
data.tar.gz: c8e8bb3e06b1b6808cbc02c50664921dd6850520050cf5e0191a4bc01b3e2e4357bb8d0fa4c4edd14a95db1134d351a5ca3fbd1105243eb0cfd317ac85955f80
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [3.29.0] - 2026-05-12
|
|
4
|
+
|
|
5
|
+
- Add `PlatformSdk::Observability::Langfuse::NotificationSubscriber` — subscribes to the `llm_call.platform_sdk` `ActiveSupport::Notifications` event and forwards to `Recorder.record_generation`. Apps can instrument LLM calls without coupling to a host concern; adding a second observability backend later is one more `subscribe` call.
|
|
6
|
+
- Auto-installed by `Langfuse.configure` (opt out with `autosubscribe: false`).
|
|
7
|
+
- Payload contract: `name`, `model`, `input`, `output`, `usage: { input_tokens:, output_tokens: }`, `prompt: { name:, version: }`, `provider`, `error:` (optional Exception — when set, recorded as a failure observation).
|
|
8
|
+
- Add `PlatformSdk::Observability::Langfuse::TraceSummarizable` mixin — template method for jobs to record a meaningful summary on the trace's Output column via `record_langfuse_trace_output` + `langfuse_trace_output_attributes`.
|
|
9
|
+
|
|
3
10
|
## [3.28.0] - 2026-05-05
|
|
4
11
|
|
|
5
12
|
- Add `PlatformSdk::Observability::Langfuse` module providing OpenTelemetry-based tracing exported to Langfuse Cloud via OTLP.
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
strongmind-platform-sdk (3.
|
|
4
|
+
strongmind-platform-sdk (3.29.0)
|
|
5
5
|
asset_sync
|
|
6
6
|
aws-sdk-cloudwatch
|
|
7
7
|
aws-sdk-secretsmanager (~> 1.66)
|
|
@@ -154,7 +154,7 @@ GEM
|
|
|
154
154
|
ffi (1.17.0)
|
|
155
155
|
ffi (1.17.0-x86_64-darwin)
|
|
156
156
|
ffi (1.17.0-x86_64-linux-gnu)
|
|
157
|
-
fog-aws (3.33.
|
|
157
|
+
fog-aws (3.33.2)
|
|
158
158
|
base64 (>= 0.2, < 0.4)
|
|
159
159
|
fog-core (~> 2.6)
|
|
160
160
|
fog-json (~> 1.1)
|
|
@@ -164,7 +164,7 @@ GEM
|
|
|
164
164
|
excon (~> 1.0)
|
|
165
165
|
formatador (>= 0.2, < 2.0)
|
|
166
166
|
mime-types
|
|
167
|
-
fog-json (1.
|
|
167
|
+
fog-json (1.3.0)
|
|
168
168
|
fog-core
|
|
169
169
|
multi_json (~> 1.10)
|
|
170
170
|
fog-xml (0.1.5)
|
|
@@ -213,7 +213,7 @@ GEM
|
|
|
213
213
|
mime-types (3.7.0)
|
|
214
214
|
logger
|
|
215
215
|
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
|
216
|
-
mime-types-data (3.2026.
|
|
216
|
+
mime-types-data (3.2026.0414)
|
|
217
217
|
mini_mime (1.1.5)
|
|
218
218
|
mini_portile2 (2.8.7)
|
|
219
219
|
minitest (5.24.1)
|
|
@@ -221,7 +221,7 @@ GEM
|
|
|
221
221
|
mutex_m (0.2.0)
|
|
222
222
|
net-http (0.4.1)
|
|
223
223
|
uri
|
|
224
|
-
net-imap (0.6.
|
|
224
|
+
net-imap (0.6.4)
|
|
225
225
|
date
|
|
226
226
|
net-protocol
|
|
227
227
|
net-pop (0.1.2)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/notifications'
|
|
4
|
+
|
|
5
|
+
module PlatformSdk
|
|
6
|
+
module Observability
|
|
7
|
+
module Langfuse
|
|
8
|
+
# Subscribes to `llm_call.platform_sdk` ActiveSupport::Notifications events
|
|
9
|
+
# and forwards them to `Recorder.record_generation`.
|
|
10
|
+
#
|
|
11
|
+
# Apps instrument their LLM calls with the convention:
|
|
12
|
+
#
|
|
13
|
+
# ActiveSupport::Notifications.instrument(
|
|
14
|
+
# 'llm_call.platform_sdk',
|
|
15
|
+
# name: 'chat_response',
|
|
16
|
+
# model: 'claude-3-5-sonnet',
|
|
17
|
+
# input: prompt_messages,
|
|
18
|
+
# output: response_text,
|
|
19
|
+
# usage: { input_tokens: 120, output_tokens: 84 }
|
|
20
|
+
# ) { call_the_llm }
|
|
21
|
+
#
|
|
22
|
+
# Inside the block, the payload Hash can be mutated to enrich it with
|
|
23
|
+
# values only known after the call completes. (`Instrumenter#instrument`
|
|
24
|
+
# passes the same Hash to the block and reads it again after `yield`;
|
|
25
|
+
# this isn't documented in the Rails Guide but is relied on across
|
|
26
|
+
# the ecosystem.)
|
|
27
|
+
#
|
|
28
|
+
# Two error-passing paths:
|
|
29
|
+
#
|
|
30
|
+
# 1. **Direct `instrument`-with-a-block callers** that let the block
|
|
31
|
+
# raise: Rails populates `:exception_object` automatically and this
|
|
32
|
+
# subscriber records a failure observation from it.
|
|
33
|
+
#
|
|
34
|
+
# 2. **Caller-rescues-and-rethrows** patterns (e.g. `RubyLLMAdapter.fire`,
|
|
35
|
+
# course-builder's `LlmErrorHandling#with_llm_error_handling`): set
|
|
36
|
+
# `payload[:error] = exception` before firing. Rails only auto-sets
|
|
37
|
+
# `:exception_object` for the block form, so adapter-style callers
|
|
38
|
+
# that `instrument` without a block need to populate `:error`.
|
|
39
|
+
#
|
|
40
|
+
# `format_output` prefers `:error` when both are set so the
|
|
41
|
+
# caller-provided value wins.
|
|
42
|
+
#
|
|
43
|
+
# Idempotent: `install!` is safe to call repeatedly — only the first
|
|
44
|
+
# call registers a subscriber.
|
|
45
|
+
module NotificationSubscriber
|
|
46
|
+
EVENT_NAME = LLM_CALL_EVENT
|
|
47
|
+
DEFAULT_NAME = 'llm_call'
|
|
48
|
+
|
|
49
|
+
@installed = false
|
|
50
|
+
@subscriber = nil
|
|
51
|
+
@mutex = Mutex.new
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
def install!
|
|
55
|
+
@mutex.synchronize do
|
|
56
|
+
return if @installed
|
|
57
|
+
|
|
58
|
+
@subscriber = ActiveSupport::Notifications.subscribe(EVENT_NAME) do |_name, _start, _finish, _id, payload|
|
|
59
|
+
handle_event(payload)
|
|
60
|
+
end
|
|
61
|
+
@installed = true
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def reset!
|
|
66
|
+
@mutex.synchronize do
|
|
67
|
+
ActiveSupport::Notifications.unsubscribe(@subscriber) if @subscriber
|
|
68
|
+
@subscriber = nil
|
|
69
|
+
@installed = false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def installed?
|
|
74
|
+
@installed
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def handle_event(payload)
|
|
80
|
+
payload ||= {}
|
|
81
|
+
Langfuse.record_generation(**build_kwargs(payload))
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
OpenTelemetry.handle_error(
|
|
84
|
+
message: "Langfuse notification subscriber failed: #{e.class}: #{e.message[0, 200]}"
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def build_kwargs(payload)
|
|
89
|
+
warn_if_misshaped_prompt(payload[:prompt])
|
|
90
|
+
prompt = payload[:prompt].is_a?(Hash) ? payload[:prompt] : {}
|
|
91
|
+
{
|
|
92
|
+
name: payload[:name] || DEFAULT_NAME, model: payload[:model],
|
|
93
|
+
input: payload[:input], output: format_output(payload),
|
|
94
|
+
usage: payload[:usage],
|
|
95
|
+
prompt_name: prompt[:name], prompt_version: prompt[:version],
|
|
96
|
+
provider: payload[:provider]
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Surfaces a non-Hash `:prompt` value through OpenTelemetry so
|
|
101
|
+
# callers see the misconfiguration in development instead of
|
|
102
|
+
# silently losing prompt tracing. A plain string like
|
|
103
|
+
# `prompt: 'my_prompt_name'` (easy to confuse with `name:`) would
|
|
104
|
+
# otherwise fall back to `{}` with no feedback.
|
|
105
|
+
def warn_if_misshaped_prompt(raw_prompt)
|
|
106
|
+
return if raw_prompt.nil? || raw_prompt.is_a?(Hash)
|
|
107
|
+
|
|
108
|
+
OpenTelemetry.handle_error(
|
|
109
|
+
message: 'Langfuse NotificationSubscriber: :prompt must be a Hash with :name and :version ' \
|
|
110
|
+
"(got #{raw_prompt.class}); prompt info will not be recorded"
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# `:error` (caller-set) takes precedence over `:exception_object`
|
|
115
|
+
# (Rails-set when an instrument block raises) so a caller that
|
|
116
|
+
# rescues and re-fires has the final say. Cap the formatted
|
|
117
|
+
# message so a multi-MB upstream error body can't be forwarded
|
|
118
|
+
# verbatim to the telemetry sink.
|
|
119
|
+
def format_output(payload)
|
|
120
|
+
error = payload[:error] || payload[:exception_object]
|
|
121
|
+
error ? "[#{error.class}] #{error.message.to_s[0, 500]}" : payload[:output]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/notifications'
|
|
4
|
+
|
|
5
|
+
module PlatformSdk
|
|
6
|
+
module Observability
|
|
7
|
+
module Langfuse
|
|
8
|
+
# Fires `llm_call.platform_sdk` ActiveSupport::Notifications events with
|
|
9
|
+
# a payload extracted from a RubyConversations / RubyLLM-shaped chat.
|
|
10
|
+
#
|
|
11
|
+
# Apps that use the RubyLLM (or RubyConversations) gem call this from
|
|
12
|
+
# any LLM call site to get cost, token, model, input, and output
|
|
13
|
+
# captured in Langfuse without having to know the OTel attribute keys:
|
|
14
|
+
#
|
|
15
|
+
# PlatformSdk::Observability::Langfuse::RubyLLMAdapter.fire(
|
|
16
|
+
# conversation: conversation_manager,
|
|
17
|
+
# context: 'chat_response'
|
|
18
|
+
# )
|
|
19
|
+
#
|
|
20
|
+
# The adapter is RubyLLM-aware but does not require the RubyLLM gem at
|
|
21
|
+
# load time — `RubyLLM::Content` references are guarded by `defined?`,
|
|
22
|
+
# so apps that don't use RubyLLM can load the SDK without paying for
|
|
23
|
+
# this code path.
|
|
24
|
+
#
|
|
25
|
+
# Dedup: nested or repeated calls that share the same `chat.messages.last`
|
|
26
|
+
# object are skipped, so a single LLM response can't double-fire when
|
|
27
|
+
# multiple layers of error-handling each wrap the same yield. The
|
|
28
|
+
# dedup state is registered via `Langfuse.track_thread_local`, so
|
|
29
|
+
# `Traceable`-wrapped Sidekiq jobs get it cleared in their ensure
|
|
30
|
+
# block. Callers outside `Traceable` (Rails controllers, long-lived
|
|
31
|
+
# Puma threads) should invoke
|
|
32
|
+
# `Langfuse.clear_tracked_thread_locals!` at their request/operation
|
|
33
|
+
# boundary to avoid dedup state leaking between unrelated calls.
|
|
34
|
+
module RubyLLMAdapter
|
|
35
|
+
MAX_PROMPT_MESSAGES = 30
|
|
36
|
+
THREAD_LOCAL_KEY = :platform_sdk_langfuse_last_message_id
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# Fire an `llm_call.platform_sdk` notification for a single LLM call.
|
|
40
|
+
# Accepts either a RubyConversations conversation (preferred — the
|
|
41
|
+
# adapter reads `conversation.chat` and `conversation.model_identifier`)
|
|
42
|
+
# or a raw RubyLLM::Chat. Returns nil — never raises.
|
|
43
|
+
def fire(context:, conversation: nil, chat: nil, error: nil)
|
|
44
|
+
payload = build_payload(context:, conversation:, chat:, error:)
|
|
45
|
+
return unless payload
|
|
46
|
+
|
|
47
|
+
ActiveSupport::Notifications.instrument(LLM_CALL_EVENT, payload)
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
OpenTelemetry.handle_error(
|
|
50
|
+
message: "RubyLLMAdapter.fire failed: #{e.class}: #{e.message[0, 200]}"
|
|
51
|
+
)
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Build the notification payload from a RubyLLM-shaped conversation
|
|
58
|
+
# or chat. Returns nil when there's nothing to record (e.g. empty
|
|
59
|
+
# chat, dup of the last recorded response, or Langfuse disabled).
|
|
60
|
+
# Internal — exercise via `fire` and span assertions; the Hash
|
|
61
|
+
# shape is not a stable public contract.
|
|
62
|
+
def build_payload(context:, conversation: nil, chat: nil, error: nil)
|
|
63
|
+
return nil unless Langfuse.enabled?
|
|
64
|
+
|
|
65
|
+
chat ||= conversation&.chat
|
|
66
|
+
last = chat&.messages&.last
|
|
67
|
+
return nil if last.nil? && error.nil?
|
|
68
|
+
return nil if duplicate_llm_response?(last)
|
|
69
|
+
|
|
70
|
+
track_llm_response(last) if last
|
|
71
|
+
|
|
72
|
+
{
|
|
73
|
+
name: context || 'llm_call',
|
|
74
|
+
model: last&.model_id || conversation&.model_identifier,
|
|
75
|
+
input: chat ? serialize_input(chat) : nil,
|
|
76
|
+
output: last&.content,
|
|
77
|
+
usage: { input_tokens: last&.input_tokens, output_tokens: last&.output_tokens },
|
|
78
|
+
error:
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def duplicate_llm_response?(last)
|
|
83
|
+
return false if last.nil?
|
|
84
|
+
|
|
85
|
+
Thread.current[THREAD_LOCAL_KEY] == last.object_id
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def track_llm_response(last)
|
|
89
|
+
Langfuse.track_thread_local(THREAD_LOCAL_KEY)
|
|
90
|
+
Thread.current[THREAD_LOCAL_KEY] = last.object_id
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def serialize_input(chat)
|
|
94
|
+
messages = chat&.messages
|
|
95
|
+
return nil if messages.nil? || messages.empty?
|
|
96
|
+
|
|
97
|
+
# Strip the assistant's response (last message) so input represents what was sent TO the LLM.
|
|
98
|
+
# Cap to the most-recent N messages to bound allocation on long tool-heavy conversations.
|
|
99
|
+
# `last(n)` returns a fresh array, so we can pop in place — one allocation instead of two.
|
|
100
|
+
prompt_messages = messages.last(MAX_PROMPT_MESSAGES + 1).tap(&:pop)
|
|
101
|
+
prompt_messages.map { |m| { role: m.role, content: extract_message_content(m) } }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def extract_message_content(message)
|
|
105
|
+
content = message.content
|
|
106
|
+
return content.to_s unless ruby_llm_content?(content)
|
|
107
|
+
|
|
108
|
+
content.text.to_s
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def ruby_llm_content?(content)
|
|
112
|
+
defined?(::RubyLLM::Content) && content.is_a?(::RubyLLM::Content)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -37,7 +37,13 @@ module PlatformSdk
|
|
|
37
37
|
)
|
|
38
38
|
end
|
|
39
39
|
config.before(:each) do
|
|
40
|
+
# Drain the BatchSpanProcessor before clearing — otherwise spans
|
|
41
|
+
# emitted by the previous example sit in the processor's queue
|
|
42
|
+
# and flush into the next example's view, producing
|
|
43
|
+
# order-dependent assertion failures.
|
|
44
|
+
PlatformSdk::Observability::Langfuse.configuration&.tracer_provider&.force_flush(timeout: 5)
|
|
40
45
|
PlatformSdk::Observability::Langfuse::SpecSupport.exporter&.clear
|
|
46
|
+
PlatformSdk::Observability::Langfuse.clear_tracked_thread_locals!
|
|
41
47
|
end
|
|
42
48
|
config.include TestHelpers
|
|
43
49
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PlatformSdk
|
|
4
|
+
module Observability
|
|
5
|
+
module Langfuse
|
|
6
|
+
# Mix into Sidekiq jobs (or anything running under a `Traceable` span)
|
|
7
|
+
# to record a meaningful summary of the work in the trace's Output
|
|
8
|
+
# column in Langfuse.
|
|
9
|
+
#
|
|
10
|
+
# Including classes implement `#langfuse_trace_output_attributes`
|
|
11
|
+
# returning a Hash; call `record_langfuse_trace_output` once the
|
|
12
|
+
# relevant instance state is settled (typically at the end of perform).
|
|
13
|
+
#
|
|
14
|
+
# class GenerateThingJob
|
|
15
|
+
# include Sidekiq::Job
|
|
16
|
+
# include PlatformSdk::Observability::Langfuse::Traceable
|
|
17
|
+
# include PlatformSdk::Observability::Langfuse::TraceSummarizable
|
|
18
|
+
#
|
|
19
|
+
# def perform(args)
|
|
20
|
+
# @thing = build_thing(args)
|
|
21
|
+
# record_langfuse_trace_output
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# private
|
|
25
|
+
#
|
|
26
|
+
# def langfuse_trace_output_attributes
|
|
27
|
+
# { thing_id: @thing&.id, status: @thing&.status }
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
module TraceSummarizable
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def record_langfuse_trace_output
|
|
34
|
+
PlatformSdk::Observability::Langfuse.set_trace_output(langfuse_trace_output_attributes)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def langfuse_trace_output_attributes
|
|
38
|
+
raise NotImplementedError, "#{self.class} must implement #langfuse_trace_output_attributes"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Constants declared up-front so the files we require below can reference
|
|
4
|
+
# them at load time without a circular dependency.
|
|
5
|
+
module PlatformSdk
|
|
6
|
+
module Observability
|
|
7
|
+
module Langfuse
|
|
8
|
+
# ActiveSupport::Notifications event name for LLM calls. Owned by the
|
|
9
|
+
# `Langfuse` namespace (rather than `NotificationSubscriber`) so the
|
|
10
|
+
# adapter can emit without requiring the subscriber to be loaded.
|
|
11
|
+
LLM_CALL_EVENT = 'llm_call.platform_sdk'
|
|
12
|
+
|
|
13
|
+
OWNED_THREAD_LOCALS_KEY = :platform_sdk_langfuse_owned_thread_locals
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
3
18
|
require 'platform_sdk/observability/langfuse/coercions'
|
|
4
19
|
require 'platform_sdk/observability/langfuse/configuration'
|
|
5
20
|
require 'platform_sdk/observability/langfuse/null_span_exporter'
|
|
6
21
|
require 'platform_sdk/observability/langfuse/recorder'
|
|
22
|
+
require 'platform_sdk/observability/langfuse/trace_summarizable'
|
|
7
23
|
require 'platform_sdk/observability/langfuse/traceable'
|
|
8
24
|
require 'platform_sdk/observability/langfuse/sidekiq_lifecycle'
|
|
25
|
+
require 'platform_sdk/observability/langfuse/notification_subscriber'
|
|
26
|
+
require 'platform_sdk/observability/langfuse/ruby_llm_adapter'
|
|
9
27
|
|
|
10
28
|
module PlatformSdk
|
|
11
29
|
module Observability
|
|
@@ -13,20 +31,16 @@ module PlatformSdk
|
|
|
13
31
|
class Error < StandardError; end
|
|
14
32
|
class ConfigurationError < Error; end
|
|
15
33
|
|
|
16
|
-
OWNED_THREAD_LOCALS_KEY = :platform_sdk_langfuse_owned_thread_locals
|
|
17
|
-
|
|
18
34
|
class << self
|
|
19
35
|
attr_reader :configuration
|
|
20
36
|
|
|
21
|
-
def configure(app_name:, environment: nil, prompt_label: nil, exporter: nil)
|
|
37
|
+
def configure(app_name:, environment: nil, prompt_label: nil, exporter: nil, autosubscribe: true)
|
|
22
38
|
@configuration&.force_flush_and_shutdown
|
|
23
|
-
@configuration = Configuration.new(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
SidekiqLifecycle.install! if @configuration.enabled?
|
|
39
|
+
@configuration = Configuration.new(app_name:, environment:, prompt_label:, exporter:)
|
|
40
|
+
if @configuration.enabled?
|
|
41
|
+
SidekiqLifecycle.install!
|
|
42
|
+
NotificationSubscriber.install! if autosubscribe
|
|
43
|
+
end
|
|
30
44
|
@configuration
|
|
31
45
|
end
|
|
32
46
|
|
|
@@ -86,6 +100,7 @@ module PlatformSdk
|
|
|
86
100
|
@configuration&.force_flush_and_shutdown
|
|
87
101
|
@configuration = nil
|
|
88
102
|
SidekiqLifecycle.reset!
|
|
103
|
+
NotificationSubscriber.reset!
|
|
89
104
|
end
|
|
90
105
|
|
|
91
106
|
# Register thread-local keys that should be cleared at the next
|
data/lib/platform_sdk/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: strongmind-platform-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.29.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Platform Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -318,10 +318,13 @@ files:
|
|
|
318
318
|
- lib/platform_sdk/observability/langfuse.rb
|
|
319
319
|
- lib/platform_sdk/observability/langfuse/coercions.rb
|
|
320
320
|
- lib/platform_sdk/observability/langfuse/configuration.rb
|
|
321
|
+
- lib/platform_sdk/observability/langfuse/notification_subscriber.rb
|
|
321
322
|
- lib/platform_sdk/observability/langfuse/null_span_exporter.rb
|
|
322
323
|
- lib/platform_sdk/observability/langfuse/recorder.rb
|
|
324
|
+
- lib/platform_sdk/observability/langfuse/ruby_llm_adapter.rb
|
|
323
325
|
- lib/platform_sdk/observability/langfuse/sidekiq_lifecycle.rb
|
|
324
326
|
- lib/platform_sdk/observability/langfuse/spec_support.rb
|
|
327
|
+
- lib/platform_sdk/observability/langfuse/trace_summarizable.rb
|
|
325
328
|
- lib/platform_sdk/observability/langfuse/traceable.rb
|
|
326
329
|
- lib/platform_sdk/one_roster.rb
|
|
327
330
|
- lib/platform_sdk/one_roster/client.rb
|