langfuse-rb 0.5.0 → 0.6.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 +17 -1
- data/lib/langfuse/client.rb +8 -2
- data/lib/langfuse/config.rb +55 -2
- data/lib/langfuse/masking.rb +29 -0
- data/lib/langfuse/observations.rb +44 -62
- data/lib/langfuse/otel_attributes.rb +56 -11
- data/lib/langfuse/otel_setup.rb +4 -3
- data/lib/langfuse/prompt_cache.rb +1 -0
- data/lib/langfuse/propagation.rb +16 -24
- data/lib/langfuse/score_client.rb +19 -7
- data/lib/langfuse/span_processor.rb +33 -11
- data/lib/langfuse/types.rb +3 -3
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +17 -6
- 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: e320f34169f77bea79aa259867b62aa7164d77a693e864cb1d03cb86d0d188a2
|
|
4
|
+
data.tar.gz: 59a40715dbb46604f6287b46b80c34e1524f7279fed261b7795d781854df987f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d18c3cb807c759c20f72a4259ccfb0502f1800b671aadcf14ea24f88e0be227ed200677f1cbabef22e467c06f2f1ce5f1cf0a0d1a379e14fe29327fce5597ec0
|
|
7
|
+
data.tar.gz: ff132aea8911b044a43de1a9abe1e54f3a680069b3b480f2e8109953600600b80abe4b923a13d3672f22ff1fe5c4ab3aef93246f2a9946d1c4f593495228ac0f
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-03-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Tracing payload masking via `Config#mask` (#68)
|
|
14
|
+
- Cost details and usage details support on generations (#61)
|
|
15
|
+
- Client-level `environment` and `release` configuration (#52)
|
|
16
|
+
- Configurable parameters when creating scores (#48)
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Prompt cache key defaults unlabeled/unversioned fetches to production, matching JS/Python semantics (#63)
|
|
20
|
+
- Tags sent as native arrays instead of JSON strings on OTel span attributes (#66)
|
|
21
|
+
- Enforce 200-character tag length limit on traces (#67)
|
|
22
|
+
- Score API parity between `Langfuse.create_score` and `Client#create_score` (#49)
|
|
23
|
+
- Corrected misleading YARD docstrings for SWR cache config
|
|
24
|
+
|
|
10
25
|
## [0.5.0] - 2026-02-09
|
|
11
26
|
|
|
12
27
|
### Added
|
|
@@ -62,7 +77,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
62
77
|
- Migrated from legacy ingestion API to OTLP endpoint
|
|
63
78
|
- Removed `tracing_enabled` configuration flag (#2)
|
|
64
79
|
|
|
65
|
-
[Unreleased]: https://github.com/simplepractice/langfuse-rb/compare/v0.
|
|
80
|
+
[Unreleased]: https://github.com/simplepractice/langfuse-rb/compare/v0.6.0...HEAD
|
|
81
|
+
[0.6.0]: https://github.com/simplepractice/langfuse-rb/compare/v0.5.0...v0.6.0
|
|
66
82
|
[0.5.0]: https://github.com/simplepractice/langfuse-rb/compare/v0.4.0...v0.5.0
|
|
67
83
|
[0.4.0]: https://github.com/simplepractice/langfuse-rb/compare/v0.3.0...v0.4.0
|
|
68
84
|
[0.3.0]: https://github.com/simplepractice/langfuse-rb/compare/v0.2.0...v0.3.0
|
data/lib/langfuse/client.rb
CHANGED
|
@@ -285,10 +285,13 @@ module Langfuse
|
|
|
285
285
|
#
|
|
286
286
|
# @param name [String] Score name (required)
|
|
287
287
|
# @param value [Numeric, Integer, String] Score value (type depends on data_type)
|
|
288
|
+
# @param id [String, nil] Score ID
|
|
288
289
|
# @param trace_id [String, nil] Trace ID to associate with the score
|
|
290
|
+
# @param session_id [String, nil] Session ID to associate with the score
|
|
289
291
|
# @param observation_id [String, nil] Observation ID to associate with the score
|
|
290
292
|
# @param comment [String, nil] Optional comment
|
|
291
293
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
294
|
+
# @param environment [String, nil] Optional environment
|
|
292
295
|
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
293
296
|
# @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score
|
|
294
297
|
# @param config_id [String, nil] Optional score config ID
|
|
@@ -304,15 +307,18 @@ module Langfuse
|
|
|
304
307
|
# @example Categorical score
|
|
305
308
|
# client.create_score(name: "category", value: "high", trace_id: "abc123", data_type: :categorical)
|
|
306
309
|
# rubocop:disable Metrics/ParameterLists
|
|
307
|
-
def create_score(name:, value:, trace_id: nil,
|
|
308
|
-
data_type: :numeric, dataset_run_id: nil, config_id: nil)
|
|
310
|
+
def create_score(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil,
|
|
311
|
+
metadata: nil, environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil)
|
|
309
312
|
@score_client.create(
|
|
310
313
|
name: name,
|
|
311
314
|
value: value,
|
|
315
|
+
id: id,
|
|
312
316
|
trace_id: trace_id,
|
|
317
|
+
session_id: session_id,
|
|
313
318
|
observation_id: observation_id,
|
|
314
319
|
comment: comment,
|
|
315
320
|
metadata: metadata,
|
|
321
|
+
environment: environment,
|
|
316
322
|
data_type: data_type,
|
|
317
323
|
dataset_run_id: dataset_run_id,
|
|
318
324
|
config_id: config_id
|
data/lib/langfuse/config.rb
CHANGED
|
@@ -46,10 +46,10 @@ module Langfuse
|
|
|
46
46
|
# @return [Integer] Lock timeout in seconds for distributed cache stampede protection
|
|
47
47
|
attr_accessor :cache_lock_timeout
|
|
48
48
|
|
|
49
|
-
# @return [Boolean] Enable stale-while-revalidate caching (
|
|
49
|
+
# @return [Boolean] Enable stale-while-revalidate caching (requires cache_stale_ttl > 0 to activate)
|
|
50
50
|
attr_accessor :cache_stale_while_revalidate
|
|
51
51
|
|
|
52
|
-
# @return [Integer, Symbol] Stale TTL in seconds (grace period for serving stale data, default: 0
|
|
52
|
+
# @return [Integer, Symbol] Stale TTL in seconds (grace period for serving stale data, default: 0)
|
|
53
53
|
# Accepts :indefinite which is automatically normalized to 1000 years (31,536,000,000 seconds) for practical "never expire" behavior.
|
|
54
54
|
attr_accessor :cache_stale_ttl
|
|
55
55
|
|
|
@@ -68,6 +68,16 @@ module Langfuse
|
|
|
68
68
|
# @return [Symbol] ActiveJob queue name for async processing
|
|
69
69
|
attr_accessor :job_queue
|
|
70
70
|
|
|
71
|
+
# @return [String, nil] Default tracing environment applied to new traces/observations
|
|
72
|
+
attr_accessor :environment
|
|
73
|
+
|
|
74
|
+
# @return [String, nil] Default release identifier applied to new traces/observations
|
|
75
|
+
attr_accessor :release
|
|
76
|
+
|
|
77
|
+
# @return [#call, nil] Mask callable applied to input, output, and metadata before serialization.
|
|
78
|
+
# Receives `data:` keyword argument. nil disables masking.
|
|
79
|
+
attr_accessor :mask
|
|
80
|
+
|
|
71
81
|
# @return [String] Default Langfuse API base URL
|
|
72
82
|
DEFAULT_BASE_URL = "https://cloud.langfuse.com"
|
|
73
83
|
|
|
@@ -107,11 +117,26 @@ module Langfuse
|
|
|
107
117
|
# @return [Integer] Number of seconds representing indefinite cache duration (~1000 years)
|
|
108
118
|
INDEFINITE_SECONDS = 1000 * 365 * 24 * 60 * 60
|
|
109
119
|
|
|
120
|
+
# @return [Array<String>] Common CI environment variables that contain a release SHA
|
|
121
|
+
COMMON_RELEASE_ENV_KEYS = %w[
|
|
122
|
+
RENDER_GIT_COMMIT
|
|
123
|
+
CI_COMMIT_SHA
|
|
124
|
+
CIRCLE_SHA1
|
|
125
|
+
SOURCE_VERSION
|
|
126
|
+
TRAVIS_COMMIT
|
|
127
|
+
GIT_COMMIT
|
|
128
|
+
GITHUB_SHA
|
|
129
|
+
BITBUCKET_COMMIT
|
|
130
|
+
BUILD_SOURCEVERSION
|
|
131
|
+
DRONE_COMMIT_SHA
|
|
132
|
+
].freeze
|
|
133
|
+
|
|
110
134
|
# Initialize a new Config object
|
|
111
135
|
#
|
|
112
136
|
# @yield [config] Optional block for configuration
|
|
113
137
|
# @yieldparam config [Config] The config instance
|
|
114
138
|
# @return [Config] a new Config instance
|
|
139
|
+
# rubocop:disable Metrics/AbcSize
|
|
115
140
|
def initialize
|
|
116
141
|
@public_key = ENV.fetch("LANGFUSE_PUBLIC_KEY", nil)
|
|
117
142
|
@secret_key = ENV.fetch("LANGFUSE_SECRET_KEY", nil)
|
|
@@ -128,10 +153,14 @@ module Langfuse
|
|
|
128
153
|
@batch_size = DEFAULT_BATCH_SIZE
|
|
129
154
|
@flush_interval = DEFAULT_FLUSH_INTERVAL
|
|
130
155
|
@job_queue = DEFAULT_JOB_QUEUE
|
|
156
|
+
@environment = env_value("LANGFUSE_TRACING_ENVIRONMENT")
|
|
157
|
+
@release = env_value("LANGFUSE_RELEASE") || detect_release_from_ci_env
|
|
158
|
+
@mask = nil
|
|
131
159
|
@logger = default_logger
|
|
132
160
|
|
|
133
161
|
yield(self) if block_given?
|
|
134
162
|
end
|
|
163
|
+
# rubocop:enable Metrics/AbcSize
|
|
135
164
|
|
|
136
165
|
# Validate the configuration
|
|
137
166
|
#
|
|
@@ -154,6 +183,8 @@ module Langfuse
|
|
|
154
183
|
validate_swr_config!
|
|
155
184
|
|
|
156
185
|
validate_cache_backend!
|
|
186
|
+
|
|
187
|
+
validate_mask!
|
|
157
188
|
end
|
|
158
189
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
159
190
|
|
|
@@ -223,5 +254,27 @@ module Langfuse
|
|
|
223
254
|
|
|
224
255
|
raise ConfigurationError, "cache_refresh_threads must be positive"
|
|
225
256
|
end
|
|
257
|
+
|
|
258
|
+
def validate_mask!
|
|
259
|
+
return if mask.nil? || mask.respond_to?(:call)
|
|
260
|
+
|
|
261
|
+
raise ConfigurationError, "mask must respond to #call"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def detect_release_from_ci_env
|
|
265
|
+
COMMON_RELEASE_ENV_KEYS.each do |key|
|
|
266
|
+
value = env_value(key)
|
|
267
|
+
return value if value
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
nil
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def env_value(key)
|
|
274
|
+
value = ENV.fetch(key, nil)
|
|
275
|
+
return nil if value.nil? || value.empty?
|
|
276
|
+
|
|
277
|
+
value
|
|
278
|
+
end
|
|
226
279
|
end
|
|
227
280
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Langfuse
|
|
4
|
+
# Central masking chokepoint for tracing payload fields.
|
|
5
|
+
#
|
|
6
|
+
# Applies a user-provided mask callable to input, output, and metadata
|
|
7
|
+
# before serialization. Fail-closed: if the mask raises, the entire
|
|
8
|
+
# field is replaced with {FALLBACK}.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
module Masking
|
|
12
|
+
# Replacement string used when the mask callable raises
|
|
13
|
+
FALLBACK = "<fully masked due to failed mask function>"
|
|
14
|
+
|
|
15
|
+
# Apply the mask callable to a data value.
|
|
16
|
+
#
|
|
17
|
+
# @param data [Object, nil] The value to mask (input, output, or metadata)
|
|
18
|
+
# @param mask [#call, nil] Callable receiving `data:` keyword; nil disables masking
|
|
19
|
+
# @return [Object] Masked data, original data (when mask is nil/data is nil), or {FALLBACK}
|
|
20
|
+
def self.apply(data, mask:)
|
|
21
|
+
return data if mask.nil? || data.nil?
|
|
22
|
+
|
|
23
|
+
mask.call(data: data)
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
Langfuse.configuration.logger.warn("Langfuse: Mask function failed: #{e.message}")
|
|
26
|
+
FALLBACK
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -111,13 +111,14 @@ module Langfuse
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
# Updates trace-level attributes (user_id, session_id, tags, etc.) for the entire trace.
|
|
114
|
+
# Tags exceeding 200 characters are silently dropped with a warning log.
|
|
114
115
|
#
|
|
115
116
|
# @param attrs [Hash, Types::TraceAttributes] Trace attributes to set
|
|
116
117
|
# @return [self]
|
|
117
118
|
def update_trace(attrs)
|
|
118
119
|
return self unless @otel_span.recording?
|
|
119
120
|
|
|
120
|
-
otel_attrs = OtelAttributes.create_trace_attributes(attrs.to_h)
|
|
121
|
+
otel_attrs = OtelAttributes.create_trace_attributes(attrs.to_h, mask: Langfuse.configuration.mask)
|
|
121
122
|
otel_attrs.each { |key, value| @otel_span.set_attribute(key, value) }
|
|
122
123
|
self
|
|
123
124
|
end
|
|
@@ -196,9 +197,10 @@ module Langfuse
|
|
|
196
197
|
# @param level [String] Log level (debug, default, warning, error)
|
|
197
198
|
# @return [void]
|
|
198
199
|
def event(name:, input: nil, level: "default")
|
|
200
|
+
masked_input = Masking.apply(input, mask: Langfuse.configuration.mask)
|
|
199
201
|
attributes = {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
OtelAttributes::OBSERVATION_INPUT => masked_input&.to_json,
|
|
203
|
+
OtelAttributes::OBSERVATION_LEVEL => level
|
|
202
204
|
}.compact
|
|
203
205
|
|
|
204
206
|
@otel_span.add_event(name, attributes: attributes)
|
|
@@ -242,7 +244,7 @@ module Langfuse
|
|
|
242
244
|
attrs_hash = attrs.to_h.merge(kwargs)
|
|
243
245
|
|
|
244
246
|
# Use @type instance variable set during initialization
|
|
245
|
-
otel_attrs = OtelAttributes.create_observation_attributes(type, attrs_hash)
|
|
247
|
+
otel_attrs = OtelAttributes.create_observation_attributes(type, attrs_hash, mask: Langfuse.configuration.mask)
|
|
246
248
|
otel_attrs.each { |key, value| @otel_span.set_attribute(key, value) }
|
|
247
249
|
end
|
|
248
250
|
|
|
@@ -291,6 +293,35 @@ module Langfuse
|
|
|
291
293
|
end
|
|
292
294
|
end
|
|
293
295
|
|
|
296
|
+
# Shared setters for observation types that interact with a model (Generation, Embedding).
|
|
297
|
+
# @api private
|
|
298
|
+
module ModelSetters
|
|
299
|
+
# @param value [Hash] Usage hash with token counts
|
|
300
|
+
# @return [void]
|
|
301
|
+
# @deprecated Use #usage_details= instead.
|
|
302
|
+
def usage=(value)
|
|
303
|
+
self.usage_details = value
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# @param value [Hash] Usage details hash (preserves key shape as provided)
|
|
307
|
+
# @return [void]
|
|
308
|
+
def usage_details=(value)
|
|
309
|
+
update_observation_attributes(usage_details: value)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# @param value [String] Model name (e.g., "gpt-4", "claude-3-opus")
|
|
313
|
+
# @return [void]
|
|
314
|
+
def model=(value)
|
|
315
|
+
update_observation_attributes(model: value)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# @param value [Hash] Model parameters (temperature, max_tokens, etc.)
|
|
319
|
+
# @return [void]
|
|
320
|
+
def model_parameters=(value)
|
|
321
|
+
update_observation_attributes(model_parameters: value)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
294
325
|
# Observation for LLM calls. Provides methods to set output, usage, and other LLM-specific metadata.
|
|
295
326
|
#
|
|
296
327
|
# @example Block-based API
|
|
@@ -299,7 +330,7 @@ module Langfuse
|
|
|
299
330
|
# gen.input = [{ role: "user", content: "Hello" }]
|
|
300
331
|
# response = call_llm(gen.input)
|
|
301
332
|
# gen.output = response
|
|
302
|
-
# gen.
|
|
333
|
+
# gen.usage_details = { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 }
|
|
303
334
|
# end
|
|
304
335
|
#
|
|
305
336
|
# @example Stateful API
|
|
@@ -315,6 +346,8 @@ module Langfuse
|
|
|
315
346
|
# gen.end
|
|
316
347
|
#
|
|
317
348
|
class Generation < BaseObservation
|
|
349
|
+
include ModelSetters
|
|
350
|
+
|
|
318
351
|
# @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span
|
|
319
352
|
# @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
|
|
320
353
|
# @param attributes [Hash, Types::GenerationAttributes, nil] Optional initial attributes
|
|
@@ -329,45 +362,10 @@ module Langfuse
|
|
|
329
362
|
self
|
|
330
363
|
end
|
|
331
364
|
|
|
332
|
-
# @param value [Hash]
|
|
365
|
+
# @param value [Hash] Cost details hash (prefer :input, :output, :total for aggregate Langfuse cost metrics)
|
|
333
366
|
# @return [void]
|
|
334
|
-
def
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
# Convert to Langfuse API format (camelCase keys)
|
|
338
|
-
usage_hash = {
|
|
339
|
-
promptTokens: value[:prompt_tokens] || value["prompt_tokens"],
|
|
340
|
-
completionTokens: value[:completion_tokens] || value["completion_tokens"],
|
|
341
|
-
totalTokens: value[:total_tokens] || value["total_tokens"]
|
|
342
|
-
}.compact
|
|
343
|
-
|
|
344
|
-
usage_json = usage_hash.to_json
|
|
345
|
-
@otel_span.set_attribute("langfuse.observation.usage", usage_json)
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
# @param value [String] Model name (e.g., "gpt-4", "claude-3-opus")
|
|
349
|
-
# @return [void]
|
|
350
|
-
def model=(value)
|
|
351
|
-
return unless @otel_span.recording?
|
|
352
|
-
|
|
353
|
-
@otel_span.set_attribute("langfuse.observation.model", value.to_s)
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
# @param value [Hash] Model parameters (temperature, max_tokens, etc.)
|
|
357
|
-
# @return [void]
|
|
358
|
-
def model_parameters=(value)
|
|
359
|
-
return unless @otel_span.recording?
|
|
360
|
-
|
|
361
|
-
# Convert to Langfuse API format (camelCase keys)
|
|
362
|
-
params_hash = {}
|
|
363
|
-
value.each do |k, v|
|
|
364
|
-
key_str = k.to_s
|
|
365
|
-
# Convert snake_case to camelCase
|
|
366
|
-
camel_key = key_str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
|
|
367
|
-
params_hash[camel_key] = v
|
|
368
|
-
end
|
|
369
|
-
params_json = params_hash.to_json
|
|
370
|
-
@otel_span.set_attribute("langfuse.observation.modelParameters", params_json)
|
|
367
|
+
def cost_details=(value)
|
|
368
|
+
update_observation_attributes(cost_details: value)
|
|
371
369
|
end
|
|
372
370
|
end
|
|
373
371
|
|
|
@@ -630,7 +628,7 @@ module Langfuse
|
|
|
630
628
|
# vectors = embedding_service.generate(embedding.input, model: embedding.model)
|
|
631
629
|
# embedding.update(
|
|
632
630
|
# output: vectors,
|
|
633
|
-
#
|
|
631
|
+
# usage_details: { prompt_tokens: 20, total_tokens: 20 }
|
|
634
632
|
# )
|
|
635
633
|
# end
|
|
636
634
|
#
|
|
@@ -647,6 +645,8 @@ module Langfuse
|
|
|
647
645
|
# embedding.end
|
|
648
646
|
#
|
|
649
647
|
class Embedding < BaseObservation
|
|
648
|
+
include ModelSetters
|
|
649
|
+
|
|
650
650
|
# @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span
|
|
651
651
|
# @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
|
|
652
652
|
# @param attributes [Hash, Types::EmbeddingAttributes, nil] Optional initial attributes
|
|
@@ -660,23 +660,5 @@ module Langfuse
|
|
|
660
660
|
update_observation_attributes(attrs)
|
|
661
661
|
self
|
|
662
662
|
end
|
|
663
|
-
|
|
664
|
-
# @param value [Hash] Usage hash with token counts (:prompt_tokens, :total_tokens)
|
|
665
|
-
# @return [void]
|
|
666
|
-
def usage=(value)
|
|
667
|
-
update_observation_attributes(usage_details: value)
|
|
668
|
-
end
|
|
669
|
-
|
|
670
|
-
# @param value [String] Model name (e.g., "text-embedding-ada-002")
|
|
671
|
-
# @return [void]
|
|
672
|
-
def model=(value)
|
|
673
|
-
update_observation_attributes(model: value)
|
|
674
|
-
end
|
|
675
|
-
|
|
676
|
-
# @param value [Hash] Model parameters (temperature, max_tokens, etc.)
|
|
677
|
-
# @return [void]
|
|
678
|
-
def model_parameters=(value)
|
|
679
|
-
update_observation_attributes(model_parameters: value)
|
|
680
|
-
end
|
|
681
663
|
end
|
|
682
664
|
end
|
|
@@ -59,12 +59,16 @@ module Langfuse
|
|
|
59
59
|
RELEASE = "langfuse.release"
|
|
60
60
|
ENVIRONMENT = "langfuse.environment"
|
|
61
61
|
|
|
62
|
+
# Validation limits
|
|
63
|
+
MAX_TAG_LENGTH = 200
|
|
64
|
+
|
|
62
65
|
# Creates OpenTelemetry attributes from Langfuse trace attributes
|
|
63
66
|
#
|
|
64
67
|
# Converts user-friendly trace attributes into the internal OpenTelemetry
|
|
65
68
|
# attribute format required by the span processor.
|
|
66
69
|
#
|
|
67
70
|
# @param attrs [Types::TraceAttributes, Hash] Trace attributes object or hash
|
|
71
|
+
# @param mask [#call, nil] Mask callable applied to input, output, and metadata
|
|
68
72
|
# @return [Hash] OpenTelemetry attributes hash with non-nil values
|
|
69
73
|
#
|
|
70
74
|
# @example
|
|
@@ -77,23 +81,25 @@ module Langfuse
|
|
|
77
81
|
# )
|
|
78
82
|
# otel_attrs = Langfuse::OtelAttributes.create_trace_attributes(attrs)
|
|
79
83
|
#
|
|
80
|
-
def self.create_trace_attributes(attrs)
|
|
84
|
+
def self.create_trace_attributes(attrs, mask: nil)
|
|
81
85
|
# Convert to hash if it's a TraceAttributes object
|
|
82
86
|
attrs = attrs.to_h
|
|
83
87
|
get_value = ->(key) { get_hash_value(attrs, key) }
|
|
84
88
|
|
|
89
|
+
input, output, metadata = mask_payload_fields(get_value, mask: mask)
|
|
90
|
+
|
|
85
91
|
attributes = {
|
|
86
92
|
TRACE_NAME => get_value.call(:name),
|
|
87
93
|
TRACE_USER_ID => get_value.call(:user_id),
|
|
88
94
|
TRACE_SESSION_ID => get_value.call(:session_id),
|
|
89
95
|
VERSION => get_value.call(:version),
|
|
90
96
|
RELEASE => get_value.call(:release),
|
|
91
|
-
TRACE_INPUT => serialize(
|
|
92
|
-
TRACE_OUTPUT => serialize(
|
|
93
|
-
TRACE_TAGS =>
|
|
97
|
+
TRACE_INPUT => serialize(input),
|
|
98
|
+
TRACE_OUTPUT => serialize(output),
|
|
99
|
+
TRACE_TAGS => normalize_tags(get_value.call(:tags)),
|
|
94
100
|
ENVIRONMENT => get_value.call(:environment),
|
|
95
101
|
TRACE_PUBLIC => get_value.call(:public),
|
|
96
|
-
**flatten_metadata(
|
|
102
|
+
**flatten_metadata(metadata, TRACE_METADATA)
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
# Remove nil values
|
|
@@ -107,6 +113,7 @@ module Langfuse
|
|
|
107
113
|
#
|
|
108
114
|
# @param type [String] Observation type (e.g., "generation", "span", "event")
|
|
109
115
|
# @param attrs [Types::SpanAttributes, Types::GenerationAttributes, Hash] Observation attributes
|
|
116
|
+
# @param mask [#call, nil] Mask callable applied to input, output, and metadata
|
|
110
117
|
# @return [Hash] OpenTelemetry attributes hash with non-nil values
|
|
111
118
|
#
|
|
112
119
|
# @example
|
|
@@ -117,11 +124,11 @@ module Langfuse
|
|
|
117
124
|
# )
|
|
118
125
|
# otel_attrs = Langfuse::OtelAttributes.create_observation_attributes("generation", attrs)
|
|
119
126
|
#
|
|
120
|
-
def self.create_observation_attributes(type, attrs)
|
|
127
|
+
def self.create_observation_attributes(type, attrs, mask: nil)
|
|
121
128
|
attrs = attrs.to_h
|
|
122
129
|
get_value = ->(key) { get_hash_value(attrs, key) }
|
|
123
130
|
|
|
124
|
-
otel_attributes = build_observation_base_attributes(type, get_value)
|
|
131
|
+
otel_attributes = build_observation_base_attributes(type, get_value, mask: mask)
|
|
125
132
|
add_prompt_attributes(otel_attributes, get_value.call(:prompt))
|
|
126
133
|
|
|
127
134
|
# Remove nil values
|
|
@@ -155,6 +162,27 @@ module Langfuse
|
|
|
155
162
|
end
|
|
156
163
|
end
|
|
157
164
|
|
|
165
|
+
# Filters tags to String-only elements within 200-char limit, returns nil if empty or nil
|
|
166
|
+
#
|
|
167
|
+
# @param tags [Array, nil] Raw tags array (each tag must be ≤200 characters; oversized tags are dropped with a warning)
|
|
168
|
+
# @return [Array<String>, nil] Filtered tags or nil
|
|
169
|
+
# @api private
|
|
170
|
+
def self.normalize_tags(tags)
|
|
171
|
+
return nil if tags.nil?
|
|
172
|
+
|
|
173
|
+
logger = Langfuse.configuration.logger
|
|
174
|
+
filtered = tags.select do |t|
|
|
175
|
+
next false unless t.is_a?(String)
|
|
176
|
+
|
|
177
|
+
if t.length > MAX_TAG_LENGTH
|
|
178
|
+
logger.warn("Langfuse: Tag exceeds #{MAX_TAG_LENGTH} characters (#{t.length} chars). Dropping.")
|
|
179
|
+
next false
|
|
180
|
+
end
|
|
181
|
+
true
|
|
182
|
+
end
|
|
183
|
+
filtered.empty? ? nil : filtered
|
|
184
|
+
end
|
|
185
|
+
|
|
158
186
|
# Flattens and serializes metadata into OpenTelemetry attribute format
|
|
159
187
|
#
|
|
160
188
|
# Converts nested metadata objects into dot-notation attribute keys.
|
|
@@ -210,6 +238,20 @@ module Langfuse
|
|
|
210
238
|
end
|
|
211
239
|
end
|
|
212
240
|
|
|
241
|
+
# Applies masking to the three payload fields (input, output, metadata)
|
|
242
|
+
#
|
|
243
|
+
# @param get_value [Proc] Lambda to get values from attributes hash
|
|
244
|
+
# @param mask [#call, nil] Mask callable
|
|
245
|
+
# @return [Array(Object, Object, Object)] Masked [input, output, metadata]
|
|
246
|
+
# @api private
|
|
247
|
+
def self.mask_payload_fields(get_value, mask:)
|
|
248
|
+
[
|
|
249
|
+
Masking.apply(get_value.call(:input), mask: mask),
|
|
250
|
+
Masking.apply(get_value.call(:output), mask: mask),
|
|
251
|
+
Masking.apply(get_value.call(:metadata), mask: mask)
|
|
252
|
+
]
|
|
253
|
+
end
|
|
254
|
+
|
|
213
255
|
# Gets a value from a hash supporting both symbol and string keys
|
|
214
256
|
# Handles false values correctly (doesn't treat false as nil)
|
|
215
257
|
#
|
|
@@ -228,23 +270,26 @@ module Langfuse
|
|
|
228
270
|
#
|
|
229
271
|
# @param type [String] Observation type
|
|
230
272
|
# @param get_value [Proc] Lambda to get values from attributes hash
|
|
273
|
+
# @param mask [#call, nil] Mask callable applied to input, output, and metadata
|
|
231
274
|
# @return [Hash] Base observation attributes
|
|
232
275
|
# @api private
|
|
233
|
-
def self.build_observation_base_attributes(type, get_value)
|
|
276
|
+
def self.build_observation_base_attributes(type, get_value, mask: nil)
|
|
277
|
+
input, output, metadata = mask_payload_fields(get_value, mask: mask)
|
|
278
|
+
|
|
234
279
|
{
|
|
235
280
|
OBSERVATION_TYPE => type,
|
|
236
281
|
OBSERVATION_LEVEL => get_value.call(:level),
|
|
237
282
|
OBSERVATION_STATUS_MESSAGE => get_value.call(:status_message),
|
|
238
283
|
VERSION => get_value.call(:version),
|
|
239
|
-
OBSERVATION_INPUT => serialize(
|
|
240
|
-
OBSERVATION_OUTPUT => serialize(
|
|
284
|
+
OBSERVATION_INPUT => serialize(input),
|
|
285
|
+
OBSERVATION_OUTPUT => serialize(output),
|
|
241
286
|
OBSERVATION_MODEL => get_value.call(:model),
|
|
242
287
|
OBSERVATION_USAGE_DETAILS => serialize(get_value.call(:usage_details)),
|
|
243
288
|
OBSERVATION_COST_DETAILS => serialize(get_value.call(:cost_details)),
|
|
244
289
|
OBSERVATION_COMPLETION_START_TIME => serialize(get_value.call(:completion_start_time)),
|
|
245
290
|
OBSERVATION_MODEL_PARAMETERS => serialize(get_value.call(:model_parameters)),
|
|
246
291
|
ENVIRONMENT => get_value.call(:environment),
|
|
247
|
-
**flatten_metadata(
|
|
292
|
+
**flatten_metadata(metadata, OBSERVATION_METADATA)
|
|
248
293
|
}
|
|
249
294
|
end
|
|
250
295
|
|
data/lib/langfuse/otel_setup.rb
CHANGED
|
@@ -56,9 +56,10 @@ module Langfuse
|
|
|
56
56
|
@tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new
|
|
57
57
|
@tracer_provider.add_span_processor(processor)
|
|
58
58
|
|
|
59
|
-
# Add span processor for propagated attributes
|
|
60
|
-
# This must be added AFTER the BatchSpanProcessor
|
|
61
|
-
|
|
59
|
+
# Add span processor for propagated attributes and env/release defaults
|
|
60
|
+
# This must be added AFTER the BatchSpanProcessor so it runs before export and can
|
|
61
|
+
# apply all attributes (propagated IDs, environment, release) to the spans being sent
|
|
62
|
+
span_processor = SpanProcessor.new(config: config)
|
|
62
63
|
@tracer_provider.add_span_processor(span_processor)
|
|
63
64
|
|
|
64
65
|
# Set as global tracer provider
|
data/lib/langfuse/propagation.rb
CHANGED
|
@@ -166,15 +166,12 @@ module Langfuse
|
|
|
166
166
|
span_key = _get_propagated_span_key(key)
|
|
167
167
|
|
|
168
168
|
if key == "metadata" && value.is_a?(Hash)
|
|
169
|
-
# Handle metadata - flatten into individual attributes
|
|
170
169
|
value.each do |k, v|
|
|
171
170
|
metadata_key = "#{OtelAttributes::TRACE_METADATA}.#{k}"
|
|
172
171
|
propagated_attributes[metadata_key] = v.to_s
|
|
173
172
|
end
|
|
174
173
|
elsif key == "tags" && value.is_a?(Array)
|
|
175
|
-
|
|
176
|
-
serialized_tags = OtelAttributes.serialize(value)
|
|
177
|
-
propagated_attributes[span_key] = serialized_tags if serialized_tags
|
|
174
|
+
propagated_attributes[span_key] = value unless value.empty?
|
|
178
175
|
else
|
|
179
176
|
propagated_attributes[span_key] = value.to_s
|
|
180
177
|
end
|
|
@@ -209,7 +206,7 @@ module Langfuse
|
|
|
209
206
|
def self._merge_tags(context, context_key, new_tags)
|
|
210
207
|
existing = context.value(context_key) || []
|
|
211
208
|
existing = existing.to_a if existing.respond_to?(:to_a)
|
|
212
|
-
(existing + new_tags).uniq
|
|
209
|
+
(existing + new_tags).uniq.freeze
|
|
213
210
|
end
|
|
214
211
|
|
|
215
212
|
# Set a propagated attribute in context and on current span
|
|
@@ -229,36 +226,31 @@ module Langfuse
|
|
|
229
226
|
baggage_key = _get_propagated_baggage_key(key)
|
|
230
227
|
|
|
231
228
|
# Merge metadata/tags with existing context values
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
229
|
+
merged = if key == "metadata" && value.is_a?(Hash)
|
|
230
|
+
_merge_metadata(context, context_key, value)
|
|
231
|
+
elsif key == "tags" && value.is_a?(Array)
|
|
232
|
+
_merge_tags(context, context_key, value)
|
|
233
|
+
else
|
|
234
|
+
value
|
|
235
|
+
end
|
|
239
236
|
|
|
240
|
-
|
|
241
|
-
context = context.set_value(context_key, value)
|
|
237
|
+
context = context.set_value(context_key, merged)
|
|
242
238
|
|
|
243
239
|
# Set on current span (if recording)
|
|
244
240
|
if span&.recording?
|
|
245
|
-
if key == "metadata" &&
|
|
246
|
-
|
|
247
|
-
value.each do |k, v|
|
|
241
|
+
if key == "metadata" && merged.is_a?(Hash)
|
|
242
|
+
merged.each do |k, v|
|
|
248
243
|
metadata_key = "#{OtelAttributes::TRACE_METADATA}.#{k}"
|
|
249
244
|
span.set_attribute(metadata_key, v.to_s)
|
|
250
245
|
end
|
|
251
|
-
elsif key == "tags" &&
|
|
252
|
-
|
|
253
|
-
serialized_tags = OtelAttributes.serialize(value)
|
|
254
|
-
span.set_attribute(span_key, serialized_tags) if serialized_tags
|
|
246
|
+
elsif key == "tags" && merged.is_a?(Array)
|
|
247
|
+
span.set_attribute(span_key, merged) unless merged.empty?
|
|
255
248
|
else
|
|
256
|
-
span.set_attribute(span_key,
|
|
249
|
+
span.set_attribute(span_key, merged.to_s)
|
|
257
250
|
end
|
|
258
251
|
end
|
|
259
252
|
|
|
260
253
|
# Set in baggage (if requested and available)
|
|
261
|
-
# Note: Baggage support requires opentelemetry-baggage gem
|
|
262
254
|
if as_baggage
|
|
263
255
|
unless baggage_available?
|
|
264
256
|
Langfuse.configuration.logger.warn(
|
|
@@ -270,7 +262,7 @@ module Langfuse
|
|
|
270
262
|
context = _set_baggage_attribute(
|
|
271
263
|
context: context,
|
|
272
264
|
key: key,
|
|
273
|
-
value:
|
|
265
|
+
value: merged,
|
|
274
266
|
baggage_key: baggage_key
|
|
275
267
|
)
|
|
276
268
|
end
|
|
@@ -51,10 +51,13 @@ module Langfuse
|
|
|
51
51
|
#
|
|
52
52
|
# @param name [String] Score name (required)
|
|
53
53
|
# @param value [Numeric, Integer, String] Score value (type depends on data_type)
|
|
54
|
+
# @param id [String, nil] Score ID
|
|
54
55
|
# @param trace_id [String, nil] Trace ID to associate with the score
|
|
56
|
+
# @param session_id [String, nil] Session ID to associate with the score
|
|
55
57
|
# @param observation_id [String, nil] Observation ID to associate with the score
|
|
56
58
|
# @param comment [String, nil] Optional comment
|
|
57
59
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
60
|
+
# @param environment [String, nil] Optional environment
|
|
58
61
|
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
59
62
|
# @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score
|
|
60
63
|
# @param config_id [String, nil] Optional score config ID
|
|
@@ -70,19 +73,23 @@ module Langfuse
|
|
|
70
73
|
# @example Categorical score
|
|
71
74
|
# create(name: "category", value: "high", trace_id: "abc123", data_type: :categorical)
|
|
72
75
|
# rubocop:disable Metrics/ParameterLists
|
|
73
|
-
def create(name:, value:, trace_id: nil,
|
|
74
|
-
data_type: :numeric, dataset_run_id: nil, config_id: nil)
|
|
76
|
+
def create(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil,
|
|
77
|
+
metadata: nil, environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil)
|
|
75
78
|
validate_name(name)
|
|
79
|
+
# Keep identifier policy server-side to preserve cross-SDK parity and avoid blocking valid future payloads.
|
|
76
80
|
normalized_value = normalize_value(value, data_type)
|
|
77
81
|
data_type_str = Types::SCORE_DATA_TYPES[data_type] || raise(ArgumentError, "Invalid data_type: #{data_type}")
|
|
78
82
|
|
|
79
83
|
event = build_score_event(
|
|
80
84
|
name: name,
|
|
81
85
|
value: normalized_value,
|
|
86
|
+
id: id,
|
|
82
87
|
trace_id: trace_id,
|
|
88
|
+
session_id: session_id,
|
|
83
89
|
observation_id: observation_id,
|
|
84
90
|
comment: comment,
|
|
85
91
|
metadata: metadata,
|
|
92
|
+
environment: environment,
|
|
86
93
|
data_type: data_type_str,
|
|
87
94
|
dataset_run_id: dataset_run_id,
|
|
88
95
|
config_id: config_id
|
|
@@ -204,25 +211,30 @@ module Langfuse
|
|
|
204
211
|
#
|
|
205
212
|
# @param name [String] Score name
|
|
206
213
|
# @param value [Object] Normalized score value
|
|
214
|
+
# @param id [String, nil] Score ID
|
|
207
215
|
# @param trace_id [String, nil] Trace ID
|
|
216
|
+
# @param session_id [String, nil] Session ID
|
|
208
217
|
# @param observation_id [String, nil] Observation ID
|
|
209
218
|
# @param comment [String, nil] Comment
|
|
210
219
|
# @param metadata [Hash, nil] Metadata
|
|
220
|
+
# @param environment [String, nil] Environment
|
|
211
221
|
# @param data_type [String] Data type string (NUMERIC, BOOLEAN, CATEGORICAL)
|
|
212
222
|
# @return [Hash] Event hash
|
|
213
|
-
# rubocop:disable Metrics/ParameterLists
|
|
214
|
-
def build_score_event(name:, value:, trace_id:, observation_id:, comment:, metadata:,
|
|
215
|
-
dataset_run_id: nil, config_id: nil)
|
|
223
|
+
# rubocop:disable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
224
|
+
def build_score_event(name:, value:, id:, trace_id:, session_id:, observation_id:, comment:, metadata:,
|
|
225
|
+
environment:, data_type:, dataset_run_id: nil, config_id: nil)
|
|
216
226
|
body = {
|
|
217
|
-
id: SecureRandom.uuid,
|
|
227
|
+
id: id || SecureRandom.uuid,
|
|
218
228
|
name: name,
|
|
219
229
|
value: value,
|
|
220
230
|
dataType: data_type
|
|
221
231
|
}
|
|
222
232
|
body[:traceId] = trace_id if trace_id
|
|
233
|
+
body[:sessionId] = session_id if session_id
|
|
223
234
|
body[:observationId] = observation_id if observation_id
|
|
224
235
|
body[:comment] = comment if comment
|
|
225
236
|
body[:metadata] = metadata if metadata
|
|
237
|
+
body[:environment] = environment if environment
|
|
226
238
|
body[:datasetRunId] = dataset_run_id if dataset_run_id
|
|
227
239
|
body[:configId] = config_id if config_id
|
|
228
240
|
|
|
@@ -233,7 +245,7 @@ module Langfuse
|
|
|
233
245
|
body: body
|
|
234
246
|
}
|
|
235
247
|
end
|
|
236
|
-
# rubocop:enable Metrics/ParameterLists
|
|
248
|
+
# rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
237
249
|
|
|
238
250
|
# Normalize and validate score value based on data type
|
|
239
251
|
#
|
|
@@ -3,14 +3,21 @@
|
|
|
3
3
|
require "opentelemetry/sdk"
|
|
4
4
|
|
|
5
5
|
module Langfuse
|
|
6
|
-
# Span processor that
|
|
6
|
+
# Span processor that applies default and propagated trace attributes on new spans.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# On span start, this processor first applies configured trace defaults
|
|
9
|
+
# (environment/release), then overlays attributes propagated in OpenTelemetry
|
|
10
|
+
# context (user/session/metadata/tags/version). This ensures consistent
|
|
11
|
+
# trace dimensions while still honoring per-request propagation.
|
|
11
12
|
#
|
|
12
13
|
# @api private
|
|
13
14
|
class SpanProcessor < OpenTelemetry::SDK::Trace::SpanProcessor
|
|
15
|
+
# @param config [Langfuse::Config, nil] SDK configuration used to build trace defaults
|
|
16
|
+
def initialize(config: Langfuse.configuration)
|
|
17
|
+
@default_trace_attributes = build_default_trace_attributes(config).freeze
|
|
18
|
+
super()
|
|
19
|
+
end
|
|
20
|
+
|
|
14
21
|
# Called when a span starts
|
|
15
22
|
#
|
|
16
23
|
# @param span [OpenTelemetry::SDK::Trace::Span] The span that started
|
|
@@ -19,13 +26,8 @@ module Langfuse
|
|
|
19
26
|
def on_start(span, parent_context)
|
|
20
27
|
return unless span.recording?
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# Set attributes on span
|
|
26
|
-
propagated_attrs.each do |key, value|
|
|
27
|
-
span.set_attribute(key, value)
|
|
28
|
-
end
|
|
29
|
+
apply_attributes(span, @default_trace_attributes)
|
|
30
|
+
apply_attributes(span, propagated_attributes(parent_context))
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
# Called when a span ends
|
|
@@ -57,5 +59,25 @@ module Langfuse
|
|
|
57
59
|
_ = timeout # Suppress unused argument warning
|
|
58
60
|
0
|
|
59
61
|
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def build_default_trace_attributes(config)
|
|
66
|
+
return {} unless config
|
|
67
|
+
|
|
68
|
+
OtelAttributes.create_trace_attributes(
|
|
69
|
+
{ environment: config.environment, release: config.release }
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def propagated_attributes(parent_context)
|
|
74
|
+
return {} unless parent_context
|
|
75
|
+
|
|
76
|
+
Propagation.get_propagated_attributes_from_context(parent_context)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def apply_attributes(span, attributes)
|
|
80
|
+
attributes.each { |key, value| span.set_attribute(key, value) }
|
|
81
|
+
end
|
|
60
82
|
end
|
|
61
83
|
end
|
data/lib/langfuse/types.rb
CHANGED
|
@@ -173,7 +173,7 @@ module Langfuse
|
|
|
173
173
|
# @return [Hash, nil] Token usage and other model-specific usage metrics
|
|
174
174
|
attr_accessor :usage_details
|
|
175
175
|
|
|
176
|
-
# @return [Hash, nil] Cost breakdown for the generation (
|
|
176
|
+
# @return [Hash, nil] Cost breakdown for the generation (prefer :input, :output, :total)
|
|
177
177
|
attr_accessor :cost_details
|
|
178
178
|
|
|
179
179
|
# @return [Hash, nil] Information about the prompt used from Langfuse prompt management
|
|
@@ -186,7 +186,7 @@ module Langfuse
|
|
|
186
186
|
# @param model [String, nil] Model name
|
|
187
187
|
# @param model_parameters [Hash, nil] Model parameters
|
|
188
188
|
# @param usage_details [Hash, nil] Usage metrics
|
|
189
|
-
# @param cost_details [Hash, nil] Cost breakdown
|
|
189
|
+
# @param cost_details [Hash, nil] Cost breakdown (prefer :input, :output, :total)
|
|
190
190
|
# @param prompt [Hash, nil] Prompt information with :name, :version, :is_fallback keys
|
|
191
191
|
# @param kwargs [Hash] Additional keyword arguments passed to SpanAttributes
|
|
192
192
|
# rubocop:disable Metrics/ParameterLists
|
|
@@ -287,7 +287,7 @@ module Langfuse
|
|
|
287
287
|
# @param input [Object, nil] Input data
|
|
288
288
|
# @param output [Object, nil] Output data
|
|
289
289
|
# @param metadata [Hash, nil] Additional metadata
|
|
290
|
-
# @param tags [Array<String>, nil] Tags array
|
|
290
|
+
# @param tags [Array<String>, nil] Tags array (each tag must be ≤200 characters; oversized tags are dropped)
|
|
291
291
|
# @param public [Boolean, nil] Public visibility flag
|
|
292
292
|
# @param environment [String, nil] Environment identifier
|
|
293
293
|
# rubocop:disable Metrics/ParameterLists
|
data/lib/langfuse/version.rb
CHANGED
data/lib/langfuse.rb
CHANGED
|
@@ -45,6 +45,7 @@ require_relative "langfuse/rails_cache_adapter"
|
|
|
45
45
|
require_relative "langfuse/cache_warmer"
|
|
46
46
|
require_relative "langfuse/api_client"
|
|
47
47
|
require_relative "langfuse/otel_setup"
|
|
48
|
+
require_relative "langfuse/masking"
|
|
48
49
|
require_relative "langfuse/otel_attributes"
|
|
49
50
|
require_relative "langfuse/propagation"
|
|
50
51
|
require_relative "langfuse/span_processor"
|
|
@@ -190,11 +191,16 @@ module Langfuse
|
|
|
190
191
|
#
|
|
191
192
|
# @param name [String] Score name (required)
|
|
192
193
|
# @param value [Numeric, Integer, String] Score value (type depends on data_type)
|
|
194
|
+
# @param id [String, nil] Score ID
|
|
193
195
|
# @param trace_id [String, nil] Trace ID to associate with the score
|
|
196
|
+
# @param session_id [String, nil] Session ID to associate with the score
|
|
194
197
|
# @param observation_id [String, nil] Observation ID to associate with the score
|
|
195
198
|
# @param comment [String, nil] Optional comment
|
|
196
199
|
# @param metadata [Hash, nil] Optional metadata hash
|
|
200
|
+
# @param environment [String, nil] Optional environment
|
|
197
201
|
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
202
|
+
# @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score
|
|
203
|
+
# @param config_id [String, nil] Optional score config ID
|
|
198
204
|
# @return [void]
|
|
199
205
|
# @raise [ArgumentError] if validation fails
|
|
200
206
|
#
|
|
@@ -207,16 +213,21 @@ module Langfuse
|
|
|
207
213
|
# @example Categorical score
|
|
208
214
|
# Langfuse.create_score(name: "category", value: "high", trace_id: "abc123", data_type: :categorical)
|
|
209
215
|
# rubocop:disable Metrics/ParameterLists
|
|
210
|
-
def create_score(name:, value:, trace_id: nil,
|
|
211
|
-
data_type: :numeric)
|
|
216
|
+
def create_score(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil,
|
|
217
|
+
metadata: nil, environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil)
|
|
212
218
|
client.create_score(
|
|
213
219
|
name: name,
|
|
214
220
|
value: value,
|
|
221
|
+
id: id,
|
|
215
222
|
trace_id: trace_id,
|
|
223
|
+
session_id: session_id,
|
|
216
224
|
observation_id: observation_id,
|
|
217
225
|
comment: comment,
|
|
218
226
|
metadata: metadata,
|
|
219
|
-
|
|
227
|
+
environment: environment,
|
|
228
|
+
data_type: data_type,
|
|
229
|
+
dataset_run_id: dataset_run_id,
|
|
230
|
+
config_id: config_id
|
|
220
231
|
)
|
|
221
232
|
end
|
|
222
233
|
# rubocop:enable Metrics/ParameterLists
|
|
@@ -341,12 +352,12 @@ module Langfuse
|
|
|
341
352
|
# Serialize attributes
|
|
342
353
|
# Only set attributes if span is still recording (should always be true here, but guard for safety)
|
|
343
354
|
if otel_span.recording?
|
|
344
|
-
otel_attrs = OtelAttributes.create_observation_attributes(type_str, attrs.to_h)
|
|
355
|
+
otel_attrs = OtelAttributes.create_observation_attributes(type_str, attrs.to_h, mask: configuration.mask)
|
|
345
356
|
otel_attrs.each { |key, value| otel_span.set_attribute(key, value) }
|
|
346
357
|
end
|
|
347
358
|
|
|
348
|
-
# Wrap in appropriate class
|
|
349
|
-
observation = wrap_otel_span(otel_span, type_str, otel_tracer
|
|
359
|
+
# Wrap in appropriate class (attributes already set on span above — pass nil to avoid double-masking)
|
|
360
|
+
observation = wrap_otel_span(otel_span, type_str, otel_tracer)
|
|
350
361
|
|
|
351
362
|
# Events auto-end immediately when created
|
|
352
363
|
observation.end if type_str == OBSERVATION_TYPES[:event]
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: langfuse-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SimplePractice
|
|
@@ -171,6 +171,7 @@ files:
|
|
|
171
171
|
- lib/langfuse/experiment_result.rb
|
|
172
172
|
- lib/langfuse/experiment_runner.rb
|
|
173
173
|
- lib/langfuse/item_result.rb
|
|
174
|
+
- lib/langfuse/masking.rb
|
|
174
175
|
- lib/langfuse/observations.rb
|
|
175
176
|
- lib/langfuse/otel_attributes.rb
|
|
176
177
|
- lib/langfuse/otel_setup.rb
|