langfuse-rb 0.10.1 → 0.11.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 +38 -1
- data/lib/langfuse/api_client.rb +75 -16
- data/lib/langfuse/app_root_tracking.rb +164 -0
- data/lib/langfuse/chat_prompt_client.rb +18 -1
- data/lib/langfuse/client.rb +114 -32
- data/lib/langfuse/config.rb +306 -63
- data/lib/langfuse/evaluation.rb +8 -4
- data/lib/langfuse/exit_hook.rb +77 -0
- data/lib/langfuse/fork_safety.rb +71 -0
- data/lib/langfuse/masking_exporter.rb +98 -0
- data/lib/langfuse/observations.rb +2 -1
- data/lib/langfuse/otel_attributes.rb +1 -0
- data/lib/langfuse/otel_setup.rb +32 -44
- data/lib/langfuse/otel_span_batch.rb +113 -0
- data/lib/langfuse/otel_span_masking.rb +89 -0
- data/lib/langfuse/otel_span_patch_applier.rb +97 -0
- data/lib/langfuse/pending_score_queue.rb +62 -0
- data/lib/langfuse/prompt_cache.rb +8 -0
- data/lib/langfuse/prompt_variables.rb +54 -0
- data/lib/langfuse/propagation.rb +101 -34
- data/lib/langfuse/rails_cache_adapter.rb +17 -1
- data/lib/langfuse/read_api.rb +242 -0
- data/lib/langfuse/resilient_metrics_reporter.rb +60 -0
- data/lib/langfuse/score_client.rb +201 -80
- data/lib/langfuse/score_value.rb +58 -0
- data/lib/langfuse/span_processor.rb +53 -4
- data/lib/langfuse/stale_while_revalidate.rb +3 -4
- data/lib/langfuse/text_prompt_client.rb +13 -1
- data/lib/langfuse/trace_export_guard.rb +47 -0
- data/lib/langfuse/traced_execution.rb +18 -12
- data/lib/langfuse/types.rb +15 -1
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +156 -44
- metadata +19 -3
|
@@ -12,32 +12,38 @@ module Langfuse
|
|
|
12
12
|
# Execute a task proc within a traced observe block.
|
|
13
13
|
#
|
|
14
14
|
# @param trace_name [String] name for the observe span
|
|
15
|
-
# @param input [Object] input set on the
|
|
16
|
-
# @param metadata [Hash] metadata set on the trace
|
|
15
|
+
# @param input [Object] input set on the root observation
|
|
16
|
+
# @param metadata [Hash] metadata set on the root observation and trace
|
|
17
17
|
# @param task [Proc] the callable to execute — receives the span
|
|
18
18
|
# @yield [span, trace_id] optional pre-task hook (e.g., dataset run linking)
|
|
19
19
|
# @return [Array<(Object, String, String, StandardError | nil)>] output, trace_id, observation_id, error
|
|
20
20
|
def self.call(trace_name:, input:, task:, metadata: {})
|
|
21
|
-
output = nil
|
|
22
21
|
trace_id = nil
|
|
23
22
|
observation_id = nil
|
|
23
|
+
output = nil
|
|
24
24
|
task_error = nil
|
|
25
25
|
|
|
26
|
-
Langfuse.observe(trace_name) do |span|
|
|
26
|
+
Langfuse.observe(trace_name, input: input, metadata: metadata) do |span|
|
|
27
27
|
trace_id = span.trace_id
|
|
28
28
|
observation_id = span.id
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
output = task.call(span)
|
|
33
|
-
span.update(output: output)
|
|
34
|
-
rescue StandardError => e
|
|
35
|
-
span.update(output: "Error: #{e.message}", level: "ERROR")
|
|
36
|
-
task_error = e
|
|
29
|
+
Langfuse.propagate_attributes(trace_name: trace_name, metadata: metadata) do
|
|
30
|
+
yield(span, trace_id) if block_given?
|
|
31
|
+
output, task_error = execute_task(span, task)
|
|
37
32
|
end
|
|
38
33
|
end
|
|
39
34
|
|
|
40
35
|
[output, trace_id, observation_id, task_error]
|
|
41
36
|
end
|
|
37
|
+
|
|
38
|
+
# @api private
|
|
39
|
+
def self.execute_task(span, task)
|
|
40
|
+
output = task.call(span)
|
|
41
|
+
span.update(output: output)
|
|
42
|
+
[output, nil]
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
span.update(output: "Error: #{e.message}", level: "ERROR")
|
|
45
|
+
[nil, e]
|
|
46
|
+
end
|
|
47
|
+
private_class_method :execute_task
|
|
42
48
|
end
|
|
43
49
|
end
|
data/lib/langfuse/types.rb
CHANGED
|
@@ -56,12 +56,17 @@ module Langfuse
|
|
|
56
56
|
# - `numeric`: Numeric values (Integer, Float, BigDecimal)
|
|
57
57
|
# - `boolean`: Boolean values (true/false or 0/1)
|
|
58
58
|
# - `categorical`: String values for categorical scores
|
|
59
|
+
# - `text`: Free-form String values (1 to 500 characters)
|
|
60
|
+
# - `correction`: Corrected output String attached to a trace or observation
|
|
61
|
+
# (conventionally named "output")
|
|
59
62
|
#
|
|
60
63
|
# @return [Hash<Symbol, String>] Hash mapping symbol keys to API string values
|
|
61
64
|
SCORE_DATA_TYPES = {
|
|
62
65
|
numeric: "NUMERIC",
|
|
63
66
|
boolean: "BOOLEAN",
|
|
64
|
-
categorical: "CATEGORICAL"
|
|
67
|
+
categorical: "CATEGORICAL",
|
|
68
|
+
text: "TEXT",
|
|
69
|
+
correction: "CORRECTION"
|
|
65
70
|
}.freeze
|
|
66
71
|
|
|
67
72
|
# Valid score data type symbols
|
|
@@ -69,6 +74,15 @@ module Langfuse
|
|
|
69
74
|
# @return [Array<Symbol>] Array of valid data type symbols
|
|
70
75
|
VALID_SCORE_DATA_TYPES = SCORE_DATA_TYPES.keys.freeze
|
|
71
76
|
|
|
77
|
+
# Score data types accepted by experiment evaluations
|
|
78
|
+
#
|
|
79
|
+
# Experiment evaluations represent metrics, so this set is intentionally
|
|
80
|
+
# narrower than {SCORE_DATA_TYPES}: text notes and corrected outputs are
|
|
81
|
+
# general scores, not experiment metrics (matching langfuse-python).
|
|
82
|
+
#
|
|
83
|
+
# @return [Array<Symbol>] Array of experiment-safe data type symbols
|
|
84
|
+
EXPERIMENT_SCORE_DATA_TYPES = %i[numeric boolean categorical].freeze
|
|
85
|
+
|
|
72
86
|
# Attributes for Langfuse span observations
|
|
73
87
|
#
|
|
74
88
|
# Spans are used to track operations, functions, or logical units of work.
|
data/lib/langfuse/version.rb
CHANGED
data/lib/langfuse.rb
CHANGED
|
@@ -29,6 +29,23 @@ module Langfuse
|
|
|
29
29
|
# Raised when a Langfuse API request fails
|
|
30
30
|
class ApiError < Error; end
|
|
31
31
|
|
|
32
|
+
# Describes whether a failed ingestion batch is safe to retry.
|
|
33
|
+
#
|
|
34
|
+
# @api private
|
|
35
|
+
class BatchDeliveryError < ApiError
|
|
36
|
+
# @param message [String] Delivery failure description
|
|
37
|
+
# @param retryable [Boolean] Whether the complete batch can be retried
|
|
38
|
+
def initialize(message, retryable:)
|
|
39
|
+
@retryable = retryable
|
|
40
|
+
super(message)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [Boolean] whether the complete batch can be retried
|
|
44
|
+
def retryable?
|
|
45
|
+
@retryable
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
32
49
|
# Raised when a requested resource is not found (HTTP 404)
|
|
33
50
|
class NotFoundError < ApiError; end
|
|
34
51
|
|
|
@@ -40,6 +57,8 @@ module Langfuse
|
|
|
40
57
|
end
|
|
41
58
|
|
|
42
59
|
require_relative "langfuse/config"
|
|
60
|
+
require_relative "langfuse/fork_safety"
|
|
61
|
+
require_relative "langfuse/exit_hook"
|
|
43
62
|
require_relative "langfuse/cache_constants"
|
|
44
63
|
require_relative "langfuse/prompt_cache"
|
|
45
64
|
require_relative "langfuse/prompt_fetch_result"
|
|
@@ -50,15 +69,20 @@ require_relative "langfuse/prompt_cache_events"
|
|
|
50
69
|
require_relative "langfuse/api_client"
|
|
51
70
|
require_relative "langfuse/span_filter"
|
|
52
71
|
require_relative "langfuse/sampling"
|
|
72
|
+
require_relative "langfuse/otel_span_masking"
|
|
53
73
|
require_relative "langfuse/otel_setup"
|
|
54
74
|
require_relative "langfuse/masking"
|
|
75
|
+
require_relative "langfuse/masking_exporter"
|
|
55
76
|
require_relative "langfuse/otel_attributes"
|
|
56
77
|
require_relative "langfuse/propagation"
|
|
78
|
+
require_relative "langfuse/app_root_tracking"
|
|
57
79
|
require_relative "langfuse/span_processor"
|
|
58
80
|
require_relative "langfuse/observations"
|
|
59
81
|
require_relative "langfuse/trace_id"
|
|
82
|
+
require_relative "langfuse/score_value"
|
|
60
83
|
require_relative "langfuse/score_client"
|
|
61
84
|
require_relative "langfuse/prompt_renderer"
|
|
85
|
+
require_relative "langfuse/prompt_variables"
|
|
62
86
|
require_relative "langfuse/text_prompt_client"
|
|
63
87
|
require_relative "langfuse/chat_prompt_client"
|
|
64
88
|
require_relative "langfuse/timestamp_parser"
|
|
@@ -76,14 +100,21 @@ require_relative "langfuse/client"
|
|
|
76
100
|
module Langfuse
|
|
77
101
|
# rubocop:disable Metrics/ClassLength
|
|
78
102
|
class << self
|
|
103
|
+
# Set the global configuration object and start its process-exit lifecycle.
|
|
104
|
+
#
|
|
79
105
|
# @param configuration [Config] the global configuration object
|
|
80
|
-
|
|
106
|
+
# @return [Config] the assigned configuration
|
|
107
|
+
def configuration=(configuration)
|
|
108
|
+
reset!
|
|
109
|
+
ExitHook.enable
|
|
110
|
+
@configuration = configuration
|
|
111
|
+
end
|
|
81
112
|
|
|
82
113
|
# Returns the global configuration object
|
|
83
114
|
#
|
|
84
115
|
# @return [Config] the global configuration
|
|
85
116
|
def configuration
|
|
86
|
-
@configuration ||= Config.new
|
|
117
|
+
@configuration ||= Config.new.tap { ExitHook.enable }
|
|
87
118
|
end
|
|
88
119
|
|
|
89
120
|
# Configure Langfuse globally
|
|
@@ -108,10 +139,27 @@ module Langfuse
|
|
|
108
139
|
@client ||= Client.new(configuration)
|
|
109
140
|
end
|
|
110
141
|
|
|
142
|
+
# Check whether the local configuration can construct a client.
|
|
143
|
+
#
|
|
144
|
+
# This method does not access the network or validate credentials.
|
|
145
|
+
#
|
|
146
|
+
# @return [Boolean] true when the local configuration is valid
|
|
147
|
+
def configured?
|
|
148
|
+
config = configuration
|
|
149
|
+
return config.valid? if config.telemetry_enabled?
|
|
150
|
+
|
|
151
|
+
config.validate_telemetry_disabled!
|
|
152
|
+
true
|
|
153
|
+
rescue ConfigurationError
|
|
154
|
+
# Reading `configuration` builds it from the environment, which can fail
|
|
155
|
+
# on its own before there is anything to validate.
|
|
156
|
+
false
|
|
157
|
+
end
|
|
158
|
+
|
|
111
159
|
# Return Langfuse's internal tracer provider for explicit global OpenTelemetry installation.
|
|
112
160
|
#
|
|
113
|
-
# @return [OpenTelemetry::SDK::Trace::TracerProvider]
|
|
114
|
-
# @raise [ConfigurationError] if tracing is
|
|
161
|
+
# @return [OpenTelemetry::SDK::Trace::TracerProvider, OpenTelemetry::Trace::TracerProvider]
|
|
162
|
+
# @raise [ConfigurationError] if tracing configuration is invalid
|
|
115
163
|
#
|
|
116
164
|
# @example
|
|
117
165
|
# Langfuse.configure do |config|
|
|
@@ -121,27 +169,27 @@ module Langfuse
|
|
|
121
169
|
#
|
|
122
170
|
# OpenTelemetry.tracer_provider = Langfuse.tracer_provider
|
|
123
171
|
def tracer_provider
|
|
124
|
-
unless
|
|
125
|
-
raise ConfigurationError,
|
|
126
|
-
"Langfuse tracing is disabled until public_key, secret_key, and base_url are configured."
|
|
127
|
-
end
|
|
172
|
+
return noop_tracer_provider unless configuration.trace_export_enabled?
|
|
128
173
|
|
|
129
174
|
OtelSetup.setup(configuration) unless OtelSetup.initialized?
|
|
130
175
|
OtelSetup.tracer_provider
|
|
176
|
+
rescue ConfigurationError => e
|
|
177
|
+
raise ConfigurationError, tracing_disabled_message(e.message)
|
|
131
178
|
end
|
|
132
179
|
|
|
133
180
|
# Shutdown Langfuse and flush any pending traces and scores
|
|
134
181
|
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
182
|
+
# Normal process exit calls this automatically. Use it directly when the
|
|
183
|
+
# application needs an earlier, explicit shutdown boundary.
|
|
137
184
|
#
|
|
138
185
|
# @param timeout [Integer] Timeout in seconds
|
|
139
186
|
# @return [void]
|
|
140
187
|
#
|
|
141
|
-
# @example
|
|
142
|
-
#
|
|
188
|
+
# @example Explicit early shutdown
|
|
189
|
+
# Langfuse.shutdown
|
|
143
190
|
#
|
|
144
191
|
def shutdown(timeout: 30)
|
|
192
|
+
ExitHook.disable
|
|
145
193
|
client.shutdown if @client
|
|
146
194
|
OtelSetup.shutdown(timeout: timeout)
|
|
147
195
|
end
|
|
@@ -170,9 +218,13 @@ module Langfuse
|
|
|
170
218
|
# @param metadata [Hash<String, String>, nil] Additional metadata (all values ≤200 characters)
|
|
171
219
|
# @param version [String, nil] Version identifier (≤200 characters)
|
|
172
220
|
# @param tags [Array<String>, nil] List of tags (each ≤200 characters)
|
|
221
|
+
# @param trace_name [String, nil] Trace name (≤200 characters)
|
|
222
|
+
# @param release [String, nil] Release identifier (≤200 characters)
|
|
223
|
+
# @param environment [String, nil] Lowercase environment identifier (≤40 characters)
|
|
173
224
|
# @param as_baggage [Boolean] If true, propagates via OpenTelemetry baggage for cross-service propagation
|
|
174
225
|
# @yield Block within which attributes are propagated
|
|
175
226
|
# @return [Object] The result of the block
|
|
227
|
+
# @raise [ArgumentError] if no block is given
|
|
176
228
|
#
|
|
177
229
|
# @example Basic usage
|
|
178
230
|
# Langfuse.propagate_attributes(user_id: "user_123", session_id: "session_abc") do
|
|
@@ -200,31 +252,36 @@ module Langfuse
|
|
|
200
252
|
# ) do
|
|
201
253
|
# # Attributes propagate via HTTP headers
|
|
202
254
|
# end
|
|
255
|
+
# rubocop:disable Metrics/ParameterLists
|
|
203
256
|
def propagate_attributes(user_id: nil, session_id: nil, metadata: nil, version: nil, tags: nil,
|
|
204
|
-
as_baggage: false, &)
|
|
257
|
+
trace_name: nil, release: nil, environment: nil, as_baggage: false, &)
|
|
205
258
|
Propagation.propagate_attributes(
|
|
206
259
|
user_id: user_id,
|
|
207
260
|
session_id: session_id,
|
|
208
261
|
metadata: metadata,
|
|
209
262
|
version: version,
|
|
210
263
|
tags: tags,
|
|
264
|
+
trace_name: trace_name,
|
|
265
|
+
release: release,
|
|
266
|
+
environment: environment,
|
|
211
267
|
as_baggage: as_baggage,
|
|
212
268
|
&
|
|
213
269
|
)
|
|
214
270
|
end
|
|
271
|
+
# rubocop:enable Metrics/ParameterLists
|
|
215
272
|
|
|
216
273
|
# Create a score event and queue it for batching
|
|
217
274
|
#
|
|
218
275
|
# @param name [String] Score name (required)
|
|
219
276
|
# @param value [Numeric, Integer, String] Score value (type depends on data_type)
|
|
220
|
-
# @param id [String, nil] Score ID
|
|
277
|
+
# @param id [String, nil] Score ID; use a stable value as an idempotency key
|
|
221
278
|
# @param trace_id [String, nil] Trace ID to associate with the score
|
|
222
279
|
# @param session_id [String, nil] Session ID to associate with the score
|
|
223
280
|
# @param observation_id [String, nil] Observation ID to associate with the score
|
|
224
281
|
# @param comment [String, nil] Optional comment
|
|
225
282
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
226
|
-
# @param environment [String, nil] Optional environment
|
|
227
|
-
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
283
|
+
# @param environment [String, nil] Optional per-score environment override
|
|
284
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical, :text, :correction)
|
|
228
285
|
# @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score
|
|
229
286
|
# @param config_id [String, nil] Optional score config ID
|
|
230
287
|
# @return [void]
|
|
@@ -258,6 +315,47 @@ module Langfuse
|
|
|
258
315
|
end
|
|
259
316
|
# rubocop:enable Metrics/ParameterLists
|
|
260
317
|
|
|
318
|
+
# Create a score immediately through the Scores API. See {ScoreClient#create!}.
|
|
319
|
+
#
|
|
320
|
+
# @param name [String] Score name (required)
|
|
321
|
+
# @param value [Numeric, Integer, String] Score value (type depends on data_type)
|
|
322
|
+
# @param id [String, nil] Score ID; use a stable value as an idempotency key
|
|
323
|
+
# @param trace_id [String, nil] Trace ID to associate with the score
|
|
324
|
+
# @param session_id [String, nil] Session ID to associate with the score
|
|
325
|
+
# @param observation_id [String, nil] Observation ID to associate with the score
|
|
326
|
+
# @param comment [String, nil] Optional comment
|
|
327
|
+
# @param metadata [Hash, nil] Optional metadata hash
|
|
328
|
+
# @param environment [String, nil] Optional per-score environment override
|
|
329
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical, :text, :correction)
|
|
330
|
+
# @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score
|
|
331
|
+
# @param config_id [String, nil] Optional score config ID
|
|
332
|
+
# @return [String, nil] ID of the created score, or nil when telemetry is disabled
|
|
333
|
+
# @raise [ArgumentError] if validation fails
|
|
334
|
+
# @raise [UnauthorizedError] if authentication fails
|
|
335
|
+
# @raise [ApiError] if the API request fails
|
|
336
|
+
#
|
|
337
|
+
# @example Create a score with an idempotency key
|
|
338
|
+
# Langfuse.create_score!(id: "feedback-abc123", name: "quality", value: 0.85, trace_id: "abc123")
|
|
339
|
+
# rubocop:disable Metrics/ParameterLists
|
|
340
|
+
def create_score!(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil,
|
|
341
|
+
metadata: nil, environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil)
|
|
342
|
+
client.create_score!(
|
|
343
|
+
name: name,
|
|
344
|
+
value: value,
|
|
345
|
+
id: id,
|
|
346
|
+
trace_id: trace_id,
|
|
347
|
+
session_id: session_id,
|
|
348
|
+
observation_id: observation_id,
|
|
349
|
+
comment: comment,
|
|
350
|
+
metadata: metadata,
|
|
351
|
+
environment: environment,
|
|
352
|
+
data_type: data_type,
|
|
353
|
+
dataset_run_id: dataset_run_id,
|
|
354
|
+
config_id: config_id
|
|
355
|
+
)
|
|
356
|
+
end
|
|
357
|
+
# rubocop:enable Metrics/ParameterLists
|
|
358
|
+
|
|
261
359
|
# Create a score for the currently active observation (from OTel span)
|
|
262
360
|
#
|
|
263
361
|
# Extracts observation_id and trace_id from the active OpenTelemetry span.
|
|
@@ -266,7 +364,7 @@ module Langfuse
|
|
|
266
364
|
# @param value [Numeric, Integer, String] Score value
|
|
267
365
|
# @param comment [String, nil] Optional comment
|
|
268
366
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
269
|
-
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
367
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical, :text, :correction)
|
|
270
368
|
# @return [void]
|
|
271
369
|
# @raise [ArgumentError] if no active span or validation fails
|
|
272
370
|
#
|
|
@@ -292,7 +390,7 @@ module Langfuse
|
|
|
292
390
|
# @param value [Numeric, Integer, String] Score value
|
|
293
391
|
# @param comment [String, nil] Optional comment
|
|
294
392
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
295
|
-
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
393
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical, :text, :correction)
|
|
296
394
|
# @return [void]
|
|
297
395
|
# @raise [ArgumentError] if no active span or validation fails
|
|
298
396
|
#
|
|
@@ -344,18 +442,21 @@ module Langfuse
|
|
|
344
442
|
#
|
|
345
443
|
# @return [void]
|
|
346
444
|
def reset!
|
|
445
|
+
ExitHook.disable
|
|
347
446
|
client.shutdown if @client
|
|
348
447
|
OtelSetup.shutdown(timeout: 5) if OtelSetup.initialized?
|
|
349
448
|
@configuration = nil
|
|
350
449
|
@client = nil
|
|
351
450
|
@noop_tracer = nil
|
|
352
|
-
@
|
|
451
|
+
@noop_tracer_provider = nil
|
|
452
|
+
@emitted_warnings = nil
|
|
353
453
|
rescue StandardError
|
|
354
454
|
# Ignore shutdown errors during reset (e.g., in tests)
|
|
355
455
|
@configuration = nil
|
|
356
456
|
@client = nil
|
|
357
457
|
@noop_tracer = nil
|
|
358
|
-
@
|
|
458
|
+
@noop_tracer_provider = nil
|
|
459
|
+
@emitted_warnings = nil
|
|
359
460
|
end
|
|
360
461
|
|
|
361
462
|
# Creates a new observation (root or child)
|
|
@@ -507,9 +608,8 @@ module Langfuse
|
|
|
507
608
|
#
|
|
508
609
|
# @return [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
|
|
509
610
|
def otel_tracer
|
|
510
|
-
return tracer_provider.tracer(LANGFUSE_TRACER_NAME, Langfuse::VERSION) if
|
|
611
|
+
return tracer_provider.tracer(LANGFUSE_TRACER_NAME, Langfuse::VERSION) if ensure_tracing_started
|
|
511
612
|
|
|
512
|
-
warn_tracing_disabled_once
|
|
513
613
|
noop_tracer
|
|
514
614
|
end
|
|
515
615
|
|
|
@@ -547,45 +647,57 @@ module Langfuse
|
|
|
547
647
|
observation_class.new(otel_span, otel_tracer, attributes: attributes)
|
|
548
648
|
end
|
|
549
649
|
|
|
550
|
-
|
|
551
|
-
|
|
650
|
+
def ensure_tracing_started
|
|
651
|
+
return false unless configuration.trace_export_enabled?
|
|
552
652
|
return true if OtelSetup.initialized?
|
|
553
|
-
return false unless tracing_config_ready?
|
|
554
653
|
|
|
555
654
|
OtelSetup.setup(configuration)
|
|
556
655
|
true
|
|
656
|
+
rescue ConfigurationError => e
|
|
657
|
+
warn_once(:tracing, tracing_disabled_message(e.message))
|
|
658
|
+
false
|
|
557
659
|
end
|
|
558
|
-
# rubocop:enable Naming/PredicateMethod
|
|
559
660
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
661
|
+
# A dropped span is otherwise silent, so warn the first time it happens
|
|
662
|
+
# without flooding logs on every subsequent call.
|
|
663
|
+
def warn_once(key, message)
|
|
664
|
+
warning_mutex.synchronize do
|
|
665
|
+
return if emitted_warnings[key]
|
|
666
|
+
|
|
667
|
+
warning_logger.warn(message)
|
|
668
|
+
emitted_warnings[key] = true
|
|
669
|
+
end
|
|
564
670
|
end
|
|
565
671
|
|
|
566
|
-
def
|
|
567
|
-
|
|
672
|
+
def emitted_warnings
|
|
673
|
+
@emitted_warnings ||= {}
|
|
568
674
|
end
|
|
569
675
|
|
|
570
|
-
|
|
571
|
-
|
|
676
|
+
# The warning fires precisely when configuration is known-bad, which
|
|
677
|
+
# includes the case where building it raises. Reading `configuration` here
|
|
678
|
+
# would re-raise and escape the rescue that called us, so use the memoized
|
|
679
|
+
# object only if it already exists.
|
|
680
|
+
def warning_logger
|
|
681
|
+
configured_logger = @configuration&.logger
|
|
682
|
+
return configured_logger if configured_logger.respond_to?(:warn)
|
|
572
683
|
|
|
573
|
-
|
|
574
|
-
|
|
684
|
+
@warning_logger ||= Logger.new($stdout, level: Logger::WARN)
|
|
685
|
+
end
|
|
575
686
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
)
|
|
579
|
-
@tracing_disabled_warning_emitted = true
|
|
580
|
-
end
|
|
687
|
+
def tracing_disabled_message(detail)
|
|
688
|
+
"Langfuse tracing is disabled: #{detail}"
|
|
581
689
|
end
|
|
582
690
|
|
|
583
|
-
def
|
|
584
|
-
@
|
|
691
|
+
def warning_mutex
|
|
692
|
+
@warning_mutex ||= Mutex.new
|
|
585
693
|
end
|
|
586
694
|
|
|
587
695
|
def noop_tracer
|
|
588
|
-
@noop_tracer ||=
|
|
696
|
+
@noop_tracer ||= noop_tracer_provider.tracer(LANGFUSE_TRACER_NAME, Langfuse::VERSION)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
def noop_tracer_provider
|
|
700
|
+
@noop_tracer_provider ||= OpenTelemetry::Trace::TracerProvider.new
|
|
589
701
|
end
|
|
590
702
|
end
|
|
591
703
|
# rubocop:enable Metrics/ClassLength
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: langfuse-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SimplePractice
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-08-19 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: faraday
|
|
@@ -160,6 +161,7 @@ files:
|
|
|
160
161
|
- README.md
|
|
161
162
|
- lib/langfuse.rb
|
|
162
163
|
- lib/langfuse/api_client.rb
|
|
164
|
+
- lib/langfuse/app_root_tracking.rb
|
|
163
165
|
- lib/langfuse/cache_constants.rb
|
|
164
166
|
- lib/langfuse/cache_warmer.rb
|
|
165
167
|
- lib/langfuse/chat_prompt_client.rb
|
|
@@ -168,28 +170,40 @@ files:
|
|
|
168
170
|
- lib/langfuse/dataset_client.rb
|
|
169
171
|
- lib/langfuse/dataset_item_client.rb
|
|
170
172
|
- lib/langfuse/evaluation.rb
|
|
173
|
+
- lib/langfuse/exit_hook.rb
|
|
171
174
|
- lib/langfuse/experiment_item.rb
|
|
172
175
|
- lib/langfuse/experiment_result.rb
|
|
173
176
|
- lib/langfuse/experiment_runner.rb
|
|
177
|
+
- lib/langfuse/fork_safety.rb
|
|
174
178
|
- lib/langfuse/item_result.rb
|
|
175
179
|
- lib/langfuse/masking.rb
|
|
180
|
+
- lib/langfuse/masking_exporter.rb
|
|
176
181
|
- lib/langfuse/observations.rb
|
|
177
182
|
- lib/langfuse/otel_attributes.rb
|
|
178
183
|
- lib/langfuse/otel_setup.rb
|
|
184
|
+
- lib/langfuse/otel_span_batch.rb
|
|
185
|
+
- lib/langfuse/otel_span_masking.rb
|
|
186
|
+
- lib/langfuse/otel_span_patch_applier.rb
|
|
187
|
+
- lib/langfuse/pending_score_queue.rb
|
|
179
188
|
- lib/langfuse/prompt_cache.rb
|
|
180
189
|
- lib/langfuse/prompt_cache_coordinator.rb
|
|
181
190
|
- lib/langfuse/prompt_cache_events.rb
|
|
182
191
|
- lib/langfuse/prompt_fetch_result.rb
|
|
183
192
|
- lib/langfuse/prompt_renderer.rb
|
|
193
|
+
- lib/langfuse/prompt_variables.rb
|
|
184
194
|
- lib/langfuse/propagation.rb
|
|
185
195
|
- lib/langfuse/rails_cache_adapter.rb
|
|
196
|
+
- lib/langfuse/read_api.rb
|
|
197
|
+
- lib/langfuse/resilient_metrics_reporter.rb
|
|
186
198
|
- lib/langfuse/sampling.rb
|
|
187
199
|
- lib/langfuse/score_client.rb
|
|
200
|
+
- lib/langfuse/score_value.rb
|
|
188
201
|
- lib/langfuse/span_filter.rb
|
|
189
202
|
- lib/langfuse/span_processor.rb
|
|
190
203
|
- lib/langfuse/stale_while_revalidate.rb
|
|
191
204
|
- lib/langfuse/text_prompt_client.rb
|
|
192
205
|
- lib/langfuse/timestamp_parser.rb
|
|
206
|
+
- lib/langfuse/trace_export_guard.rb
|
|
193
207
|
- lib/langfuse/trace_id.rb
|
|
194
208
|
- lib/langfuse/traced_execution.rb
|
|
195
209
|
- lib/langfuse/types.rb
|
|
@@ -202,6 +216,7 @@ metadata:
|
|
|
202
216
|
source_code_uri: https://github.com/simplepractice/langfuse-rb
|
|
203
217
|
changelog_uri: https://github.com/simplepractice/langfuse-rb/blob/main/CHANGELOG.md
|
|
204
218
|
rubygems_mfa_required: 'true'
|
|
219
|
+
post_install_message:
|
|
205
220
|
rdoc_options: []
|
|
206
221
|
require_paths:
|
|
207
222
|
- lib
|
|
@@ -216,7 +231,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
216
231
|
- !ruby/object:Gem::Version
|
|
217
232
|
version: '0'
|
|
218
233
|
requirements: []
|
|
219
|
-
rubygems_version: 4.
|
|
234
|
+
rubygems_version: 3.4.1
|
|
235
|
+
signing_key:
|
|
220
236
|
specification_version: 4
|
|
221
237
|
summary: Ruby SDK for Langfuse - LLM observability and prompt management
|
|
222
238
|
test_files: []
|