langfuse-rb 0.12.2 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6fba08995eafb09635b4825f3295730326f9cbcaacbe6914311fbcd9bb54c2d
4
- data.tar.gz: e10b412642da2ee0a0dad027f20b72052b80d872b000091a310c2aeb505cb5b3
3
+ metadata.gz: 48ce9deac30bb5cbbb9df10203bf6a7ea930fc43fa14d29170bdf41f672c9213
4
+ data.tar.gz: 7fec96c181ce22befedbafefd937ab37114677abe82adf1fc71fa5903b0b5b02
5
5
  SHA512:
6
- metadata.gz: f1a0901369bba61664e00c6ab145b9227d7cf51e3e5a6c1371aa138c113c99d7a6a6e328166468900d51b9e6edeb4971855426fbd13e9c2b98a518fac2eb8380
7
- data.tar.gz: 0f0a2895156d25673254a80256553f01d00724036dc2fd487ba8a2fededb424fb0adf54a45bb5547c17f20ee54d2be78d850e8cabe211256564b9d512121a665
6
+ metadata.gz: 4acb19d60809504ec0e9f037de4633d43758fe8ce894d4924d0fead4f6e5a37a792d77d6c3cc432776713dd2124a9ee33748bf5d811aacf1b5a731f5e92b9445
7
+ data.tar.gz: f639e18b59bd80cea44f99b0db5458a833f05c42eef252dba7018b7537b752966995656b47b322c7aadcfc5e152e95f20612693c897ac8d0015c17fdfc2feacd
data/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.4] - 2026-10-12
11
+
12
+ ### Fixed
13
+ - Mark explicit-ID observations as roots for legacy trace input/output (#129).
14
+ - Create parentless roots for explicit `trace_id:` values and apply root sampling (#128).
15
+
16
+ ## [0.12.3] - 2026-09-15
17
+
18
+ ### Changed
19
+ - Allow JSON 3.x and require Faraday 2.14.4 or newer for compatible response parsing (#123).
20
+
10
21
  ## [0.12.2] - 2026-09-04
11
22
 
12
23
  ### Fixed
@@ -183,7 +194,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
183
194
  - Migrated from legacy ingestion API to OTLP endpoint
184
195
  - Removed `tracing_enabled` configuration flag (#2)
185
196
 
186
- [Unreleased]: https://github.com/simplepractice/langfuse-rb/compare/v0.12.2...HEAD
197
+ [Unreleased]: https://github.com/simplepractice/langfuse-rb/compare/v0.12.4...HEAD
198
+ [0.12.4]: https://github.com/simplepractice/langfuse-rb/compare/v0.12.3...v0.12.4
199
+ [0.12.3]: https://github.com/simplepractice/langfuse-rb/compare/v0.12.2...v0.12.3
187
200
  [0.12.2]: https://github.com/simplepractice/langfuse-rb/compare/v0.12.1...v0.12.2
188
201
  [0.12.1]: https://github.com/simplepractice/langfuse-rb/compare/v0.12.0...v0.12.1
189
202
  [0.12.0]: https://github.com/simplepractice/langfuse-rb/compare/v0.11.0...v0.12.0
@@ -53,6 +53,7 @@ module Langfuse
53
53
  OBSERVATION_PROMPT_NAME = "langfuse.observation.prompt.name"
54
54
  OBSERVATION_PROMPT_VERSION = "langfuse.observation.prompt.version"
55
55
  OBSERVATION_COMPLETION_START_TIME = "langfuse.observation.completion_start_time"
56
+ AS_ROOT = "langfuse.internal.as_root"
56
57
  IS_APP_ROOT = "langfuse.internal.is_app_root"
57
58
 
58
59
  # Experiment attributes
@@ -6,6 +6,7 @@ require "base64"
6
6
  require_relative "masking_exporter"
7
7
  require_relative "trace_export_guard"
8
8
  require_relative "resilient_metrics_reporter"
9
+ require_relative "root_span_id_generator"
9
10
 
10
11
  module Langfuse
11
12
  # OpenTelemetry initialization and setup for Langfuse tracing.
@@ -122,7 +123,7 @@ module Langfuse
122
123
  )
123
124
 
124
125
  OpenTelemetry::SDK::Trace::TracerProvider
125
- .new(sampler: build_sampler(config.sample_rate))
126
+ .new(sampler: build_sampler(config.sample_rate), id_generator: RootSpanIdGenerator.new)
126
127
  .tap { |provider| provider.add_span_processor(processor) }
127
128
  end
128
129
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Langfuse
4
+ # Supplies deterministic IDs only during explicit root creation on one provider.
5
+ #
6
+ # @api private
7
+ class RootSpanIdGenerator
8
+ def initialize
9
+ @trace_id_key = OpenTelemetry::Context.create_key("langfuse-root-trace-id")
10
+ end
11
+
12
+ def generate_trace_id
13
+ OpenTelemetry::Context.value(@trace_id_key) || OpenTelemetry::Trace.generate_trace_id
14
+ end
15
+
16
+ def generate_span_id
17
+ OpenTelemetry::Trace.generate_span_id
18
+ end
19
+
20
+ # A trace ID identifies a trace, not a missing parent. Preserve propagated
21
+ # attributes while clearing the ambient span and its internal root claim.
22
+ # The provider-local key and Context scope isolate concurrent fibers/threads
23
+ # and restore the caller's context even when span creation raises.
24
+ #
25
+ # @param trace_id [String] Validated binary W3C trace ID
26
+ # @yieldparam context [OpenTelemetry::Context] Parentless root context
27
+ # @return [Object] The block result
28
+ # @api private
29
+ def with_trace_id(trace_id)
30
+ context = OpenTelemetry::Trace.context_with_span(OpenTelemetry::Trace::Span::INVALID)
31
+ if Propagation.baggage_available?
32
+ context = OpenTelemetry::Baggage.remove_value(Propagation::LANGFUSE_TRACE_ID_BAGGAGE_KEY, context: context)
33
+ end
34
+ context = context.set_value(@trace_id_key, trace_id)
35
+ OpenTelemetry::Context.with_current(context) { yield context }
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langfuse
4
- VERSION = "0.12.2"
4
+ VERSION = "0.12.4"
5
5
  end
data/lib/langfuse.rb CHANGED
@@ -463,14 +463,15 @@ module Langfuse
463
463
  # Creates a new observation (root or child)
464
464
  #
465
465
  # This is the module-level factory method that creates observations of any type.
466
- # It can create root observations (when parent_span_context is nil) or child
467
- # observations (when parent_span_context is provided).
466
+ # An explicit trace_id creates a parentless root. Otherwise it inherits the
467
+ # active span, or the supplied parent_span_context for child observations.
468
468
  #
469
469
  # @param name [String] Descriptive name for the observation
470
470
  # @param attrs [Hash, Types::SpanAttributes, Types::GenerationAttributes, nil] Observation attributes
471
471
  # @param as_type [Symbol, String] Observation type (:span, :generation, :event, etc.)
472
- # @param trace_id [String, nil] Optional 32-char lowercase hex trace ID to attach the observation to.
472
+ # @param trace_id [String, nil] Optional 32-char lowercase hex trace ID for a new parentless root.
473
473
  # Mutually exclusive with `parent_span_context`. Use {Langfuse.create_trace_id} to generate one.
474
+ # Also marks the observation as a root for legacy trace summaries.
474
475
  # @param parent_span_context [OpenTelemetry::Trace::SpanContext, nil] Parent span context for child observations
475
476
  # @param start_time [Time, Integer, nil] Optional start time (Time object or Unix timestamp in nanoseconds)
476
477
  # @param skip_validation [Boolean] Skip validation (for internal use). Defaults to false.
@@ -496,13 +497,17 @@ module Langfuse
496
497
  type_str = as_type.to_s
497
498
  validate_observation_type!(as_type, type_str) unless skip_validation
498
499
 
499
- otel_tracer = otel_tracer()
500
+ otel_tracer, id_generator = otel_tracer_and_id_generator
500
501
  otel_span = create_otel_span(
501
502
  name: name,
502
503
  start_time: start_time,
503
504
  parent_span_context: parent_span_context,
504
- otel_tracer: otel_tracer
505
+ otel_tracer: otel_tracer,
506
+ trace_id: trace_id && parent_span_context.trace_id,
507
+ id_generator: id_generator
505
508
  )
509
+ # Match Python's explicit trace-context root mark for legacy trace input/output.
510
+ otel_span.set_attribute(OtelAttributes::AS_ROOT, true) if trace_id
506
511
  apply_observation_attributes(otel_span, type_str, attrs)
507
512
 
508
513
  observation = wrap_otel_span(otel_span, type_str, otel_tracer)
@@ -517,7 +522,7 @@ module Langfuse
517
522
  # @param name [String] Descriptive name for the observation
518
523
  # @param attrs [Hash] Observation attributes (optional positional or keyword)
519
524
  # @param as_type [Symbol, String] Observation type (:span, :generation, :event, etc.)
520
- # @param trace_id [String, nil] Optional 32-char lowercase hex trace ID to attach the observation to.
525
+ # @param trace_id [String, nil] Optional 32-char lowercase hex trace ID for a new parentless root.
521
526
  # Use {Langfuse.create_trace_id} to generate one. Forwarded to {.start_observation}.
522
527
  # @param kwargs [Hash] Additional keyword arguments merged into observation attributes (e.g., input:, output:, metadata:)
523
528
  # @yield [observation] Optional block that receives the observation object
@@ -605,13 +610,19 @@ module Langfuse
605
610
  false
606
611
  end
607
612
 
608
- # Gets the OpenTelemetry tracer for Langfuse
613
+ # Gets the OpenTelemetry tracer and its root ID generator for Langfuse
609
614
  #
610
- # @return [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
611
- def otel_tracer
612
- return tracer_provider.tracer(LANGFUSE_TRACER_NAME, Langfuse::VERSION) if ensure_tracing_started
615
+ # Capture the ID generator from the same provider as the returned tracer,
616
+ # so concurrent shutdown or reconfiguration cannot switch its root context key.
617
+ # @return [Array] The OTel tracer and optional root ID generator
618
+ # @api private
619
+ def otel_tracer_and_id_generator
620
+ return [noop_tracer, nil] unless ensure_tracing_started
613
621
 
614
- noop_tracer
622
+ provider = tracer_provider
623
+ id_generator = provider.id_generator if provider.respond_to?(:id_generator)
624
+ id_generator = nil unless id_generator.is_a?(RootSpanIdGenerator)
625
+ [provider.tracer(LANGFUSE_TRACER_NAME, Langfuse::VERSION), id_generator]
615
626
  end
616
627
 
617
628
  # Creates an OpenTelemetry span (root or child)
@@ -620,8 +631,20 @@ module Langfuse
620
631
  # @param start_time [Time, Integer, nil] Optional start time
621
632
  # @param parent_span_context [OpenTelemetry::Trace::SpanContext, nil] Parent span context
622
633
  # @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
634
+ # @param trace_id [String, nil] Validated binary trace ID for an explicit root
635
+ # @param id_generator [RootSpanIdGenerator, nil] ID generator captured with the tracer
623
636
  # @return [OpenTelemetry::SDK::Trace::Span] The created span
624
- def create_otel_span(name:, otel_tracer:, start_time: nil, parent_span_context: nil)
637
+ # @api private
638
+ def create_otel_span(name:, otel_tracer:, start_time: nil, parent_span_context: nil, trace_id: nil,
639
+ id_generator: nil)
640
+ if trace_id && id_generator
641
+ # Start an actual root through the internal provider's ID generator, so
642
+ # native root sampling sees an invalid parent rather than a sampled placeholder.
643
+ return id_generator.with_trace_id(trace_id) do |context|
644
+ otel_tracer.start_span(name, with_parent: context, start_timestamp: start_time)
645
+ end
646
+ end
647
+
625
648
  if parent_span_context
626
649
  # Create child span with parent context
627
650
  # Create a non-recording span from the parent context to set in context
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: langfuse-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.2
4
+ version: 0.12.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - SimplePractice
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-05 00:00:00.000000000 Z
11
+ date: 2026-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -16,7 +16,7 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 2.14.3
19
+ version: 2.14.4
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '3'
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 2.14.3
29
+ version: 2.14.4
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '3'
@@ -68,22 +68,22 @@ dependencies:
68
68
  name: json
69
69
  requirement: !ruby/object:Gem::Requirement
70
70
  requirements:
71
- - - "~>"
72
- - !ruby/object:Gem::Version
73
- version: '2.19'
74
71
  - - ">="
75
72
  - !ruby/object:Gem::Version
76
73
  version: 2.19.9
74
+ - - "<"
75
+ - !ruby/object:Gem::Version
76
+ version: '4'
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
- - - "~>"
82
- - !ruby/object:Gem::Version
83
- version: '2.19'
84
81
  - - ">="
85
82
  - !ruby/object:Gem::Version
86
83
  version: 2.19.9
84
+ - - "<"
85
+ - !ruby/object:Gem::Version
86
+ version: '4'
87
87
  - !ruby/object:Gem::Dependency
88
88
  name: concurrent-ruby
89
89
  requirement: !ruby/object:Gem::Requirement
@@ -222,6 +222,7 @@ files:
222
222
  - lib/langfuse/rails_cache_adapter.rb
223
223
  - lib/langfuse/read_api.rb
224
224
  - lib/langfuse/resilient_metrics_reporter.rb
225
+ - lib/langfuse/root_span_id_generator.rb
225
226
  - lib/langfuse/sampling.rb
226
227
  - lib/langfuse/score_client.rb
227
228
  - lib/langfuse/score_value.rb