langfuse-rb 0.7.0 → 0.9.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 +28 -1
- data/README.md +32 -74
- data/lib/langfuse/api_client.rb +72 -0
- data/lib/langfuse/chat_prompt_client.rb +135 -20
- data/lib/langfuse/client.rb +94 -20
- data/lib/langfuse/config.rb +59 -6
- data/lib/langfuse/otel_attributes.rb +12 -4
- data/lib/langfuse/otel_setup.rb +140 -80
- data/lib/langfuse/prompt_renderer.rb +18 -0
- data/lib/langfuse/sampling.rb +20 -0
- data/lib/langfuse/score_client.rb +43 -18
- data/lib/langfuse/span_filter.rb +81 -0
- data/lib/langfuse/span_processor.rb +37 -36
- data/lib/langfuse/text_prompt_client.rb +21 -3
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +74 -5
- metadata +6 -6
|
@@ -3,22 +3,26 @@
|
|
|
3
3
|
require "opentelemetry/sdk"
|
|
4
4
|
|
|
5
5
|
module Langfuse
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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.
|
|
6
|
+
# Batch span processor that owns Langfuse's enrichment and export filtering.
|
|
12
7
|
#
|
|
13
8
|
# @api private
|
|
14
|
-
class SpanProcessor < OpenTelemetry::SDK::Trace::
|
|
15
|
-
# @param config [Langfuse::Config
|
|
16
|
-
|
|
9
|
+
class SpanProcessor < OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor
|
|
10
|
+
# @param config [Langfuse::Config] SDK configuration used for defaults and filtering
|
|
11
|
+
# @param exporter [#export, #force_flush, #shutdown] Span exporter used by the batch processor
|
|
12
|
+
def initialize(config:, exporter:)
|
|
13
|
+
@logger = config.logger
|
|
17
14
|
@default_trace_attributes = build_default_trace_attributes(config).freeze
|
|
18
|
-
|
|
15
|
+
@should_export_span = config.should_export_span || Langfuse.method(:default_export_span?)
|
|
16
|
+
|
|
17
|
+
super(
|
|
18
|
+
exporter,
|
|
19
|
+
max_queue_size: config.batch_size * 2,
|
|
20
|
+
schedule_delay: schedule_delay_for(config),
|
|
21
|
+
max_export_batch_size: config.batch_size
|
|
22
|
+
)
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
#
|
|
25
|
+
# Apply Langfuse trace defaults and propagated attributes before a span records work.
|
|
22
26
|
#
|
|
23
27
|
# @param span [OpenTelemetry::SDK::Trace::Span] The span that started
|
|
24
28
|
# @param parent_context [OpenTelemetry::Context] The parent context
|
|
@@ -30,41 +34,28 @@ module Langfuse
|
|
|
30
34
|
apply_attributes(span, propagated_attributes(parent_context))
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
#
|
|
37
|
+
# Drop spans when the export filter rejects them or raises.
|
|
34
38
|
#
|
|
35
39
|
# @param span [OpenTelemetry::SDK::Trace::Span] The span that ended
|
|
36
40
|
# @return [void]
|
|
37
41
|
def on_finish(span)
|
|
38
|
-
|
|
39
|
-
end
|
|
42
|
+
return unless should_export_span?(span)
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
#
|
|
43
|
-
# @param timeout [Integer, nil] Timeout in seconds (unused for this processor)
|
|
44
|
-
# @return [Integer] Always returns 0 (no timeout needed for no-op)
|
|
45
|
-
def shutdown(timeout: nil)
|
|
46
|
-
# No-op - nothing to clean up
|
|
47
|
-
# Return 0 to match OpenTelemetry SDK expectation (it finds max timeout from processors)
|
|
48
|
-
_ = timeout # Suppress unused argument warning
|
|
49
|
-
0
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Force flush (no-op for this processor)
|
|
53
|
-
#
|
|
54
|
-
# @param timeout [Integer, nil] Timeout in seconds (unused for this processor)
|
|
55
|
-
# @return [Integer] Always returns 0 (no timeout needed for no-op)
|
|
56
|
-
def force_flush(timeout: nil)
|
|
57
|
-
# No-op - nothing to flush
|
|
58
|
-
# Return 0 to match OpenTelemetry SDK expectation (it finds max timeout from processors)
|
|
59
|
-
_ = timeout # Suppress unused argument warning
|
|
60
|
-
0
|
|
44
|
+
super
|
|
61
45
|
end
|
|
62
46
|
|
|
63
47
|
private
|
|
64
48
|
|
|
65
|
-
|
|
66
|
-
|
|
49
|
+
# Sync mode relies on explicit `force_flush` calls, so keep the background flush
|
|
50
|
+
# interval long enough that it rarely fires on its own.
|
|
51
|
+
SYNC_SCHEDULE_DELAY_MS = 60_000
|
|
52
|
+
private_constant :SYNC_SCHEDULE_DELAY_MS
|
|
53
|
+
|
|
54
|
+
def schedule_delay_for(config)
|
|
55
|
+
config.tracing_async ? config.flush_interval * 1000 : SYNC_SCHEDULE_DELAY_MS
|
|
56
|
+
end
|
|
67
57
|
|
|
58
|
+
def build_default_trace_attributes(config)
|
|
68
59
|
OtelAttributes.create_trace_attributes(
|
|
69
60
|
{ environment: config.environment, release: config.release }
|
|
70
61
|
)
|
|
@@ -79,5 +70,15 @@ module Langfuse
|
|
|
79
70
|
def apply_attributes(span, attributes)
|
|
80
71
|
attributes.each { |key, value| span.set_attribute(key, value) }
|
|
81
72
|
end
|
|
73
|
+
|
|
74
|
+
def should_export_span?(span)
|
|
75
|
+
@should_export_span.call(span)
|
|
76
|
+
rescue StandardError => e
|
|
77
|
+
@logger.error(
|
|
78
|
+
"Langfuse tracing dropped span '#{span.name}' because should_export_span raised: " \
|
|
79
|
+
"#{e.class}: #{e.message}"
|
|
80
|
+
)
|
|
81
|
+
false
|
|
82
|
+
end
|
|
82
83
|
end
|
|
83
84
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "prompt_renderer"
|
|
4
4
|
|
|
5
5
|
module Langfuse
|
|
6
6
|
# Text prompt client for compiling text prompts with variable substitution
|
|
@@ -38,11 +38,21 @@ module Langfuse
|
|
|
38
38
|
# @return [String] Raw prompt template string
|
|
39
39
|
attr_reader :prompt
|
|
40
40
|
|
|
41
|
+
# @return [String, nil] Optional commit message for this prompt version
|
|
42
|
+
attr_reader :commit_message
|
|
43
|
+
|
|
44
|
+
# @return [Hash, nil] Optional dependency resolution graph for composed prompts
|
|
45
|
+
attr_reader :resolution_graph
|
|
46
|
+
|
|
47
|
+
# @return [Boolean] Whether this client uses caller-provided fallback content
|
|
48
|
+
attr_reader :is_fallback
|
|
49
|
+
|
|
41
50
|
# Initialize a new text prompt client
|
|
42
51
|
#
|
|
43
52
|
# @param prompt_data [Hash] The prompt data from the API
|
|
53
|
+
# @param is_fallback [Boolean] Whether this client wraps caller-provided fallback content
|
|
44
54
|
# @raise [ArgumentError] if prompt data is invalid
|
|
45
|
-
def initialize(prompt_data)
|
|
55
|
+
def initialize(prompt_data, is_fallback: false)
|
|
46
56
|
validate_prompt_data!(prompt_data)
|
|
47
57
|
|
|
48
58
|
@name = prompt_data["name"]
|
|
@@ -51,6 +61,14 @@ module Langfuse
|
|
|
51
61
|
@labels = prompt_data["labels"] || []
|
|
52
62
|
@tags = prompt_data["tags"] || []
|
|
53
63
|
@config = prompt_data["config"] || {}
|
|
64
|
+
@commit_message = prompt_data["commitMessage"]
|
|
65
|
+
@resolution_graph = prompt_data["resolutionGraph"]
|
|
66
|
+
@is_fallback = is_fallback
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [String] Prompt type ("text")
|
|
70
|
+
def type
|
|
71
|
+
"text"
|
|
54
72
|
end
|
|
55
73
|
|
|
56
74
|
# Compile the prompt with variable substitution
|
|
@@ -64,7 +82,7 @@ module Langfuse
|
|
|
64
82
|
def compile(**kwargs)
|
|
65
83
|
return prompt if kwargs.empty?
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
PromptRenderer.render(prompt, kwargs)
|
|
68
86
|
end
|
|
69
87
|
|
|
70
88
|
private
|
data/lib/langfuse/version.rb
CHANGED
data/lib/langfuse.rb
CHANGED
|
@@ -44,6 +44,8 @@ require_relative "langfuse/prompt_cache"
|
|
|
44
44
|
require_relative "langfuse/rails_cache_adapter"
|
|
45
45
|
require_relative "langfuse/cache_warmer"
|
|
46
46
|
require_relative "langfuse/api_client"
|
|
47
|
+
require_relative "langfuse/span_filter"
|
|
48
|
+
require_relative "langfuse/sampling"
|
|
47
49
|
require_relative "langfuse/otel_setup"
|
|
48
50
|
require_relative "langfuse/masking"
|
|
49
51
|
require_relative "langfuse/otel_attributes"
|
|
@@ -52,6 +54,7 @@ require_relative "langfuse/span_processor"
|
|
|
52
54
|
require_relative "langfuse/observations"
|
|
53
55
|
require_relative "langfuse/trace_id"
|
|
54
56
|
require_relative "langfuse/score_client"
|
|
57
|
+
require_relative "langfuse/prompt_renderer"
|
|
55
58
|
require_relative "langfuse/text_prompt_client"
|
|
56
59
|
require_relative "langfuse/chat_prompt_client"
|
|
57
60
|
require_relative "langfuse/timestamp_parser"
|
|
@@ -91,10 +94,6 @@ module Langfuse
|
|
|
91
94
|
# end
|
|
92
95
|
def configure
|
|
93
96
|
yield(configuration)
|
|
94
|
-
|
|
95
|
-
# Auto-initialize OpenTelemetry
|
|
96
|
-
OtelSetup.setup(configuration)
|
|
97
|
-
|
|
98
97
|
configuration
|
|
99
98
|
end
|
|
100
99
|
|
|
@@ -105,6 +104,28 @@ module Langfuse
|
|
|
105
104
|
@client ||= Client.new(configuration)
|
|
106
105
|
end
|
|
107
106
|
|
|
107
|
+
# Return Langfuse's internal tracer provider for explicit global OpenTelemetry installation.
|
|
108
|
+
#
|
|
109
|
+
# @return [OpenTelemetry::SDK::Trace::TracerProvider]
|
|
110
|
+
# @raise [ConfigurationError] if tracing is not fully configured
|
|
111
|
+
#
|
|
112
|
+
# @example
|
|
113
|
+
# Langfuse.configure do |config|
|
|
114
|
+
# config.public_key = ENV["LANGFUSE_PUBLIC_KEY"]
|
|
115
|
+
# config.secret_key = ENV["LANGFUSE_SECRET_KEY"]
|
|
116
|
+
# end
|
|
117
|
+
#
|
|
118
|
+
# OpenTelemetry.tracer_provider = Langfuse.tracer_provider
|
|
119
|
+
def tracer_provider
|
|
120
|
+
unless tracing_config_ready?
|
|
121
|
+
raise ConfigurationError,
|
|
122
|
+
"Langfuse tracing is disabled until public_key, secret_key, and base_url are configured."
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
OtelSetup.setup(configuration) unless OtelSetup.initialized?
|
|
126
|
+
OtelSetup.tracer_provider
|
|
127
|
+
end
|
|
128
|
+
|
|
108
129
|
# Shutdown Langfuse and flush any pending traces and scores
|
|
109
130
|
#
|
|
110
131
|
# Call this when shutting down your application to ensure
|
|
@@ -323,10 +344,14 @@ module Langfuse
|
|
|
323
344
|
OtelSetup.shutdown(timeout: 5) if OtelSetup.initialized?
|
|
324
345
|
@configuration = nil
|
|
325
346
|
@client = nil
|
|
347
|
+
@noop_tracer = nil
|
|
348
|
+
@tracing_disabled_warning_emitted = false
|
|
326
349
|
rescue StandardError
|
|
327
350
|
# Ignore shutdown errors during reset (e.g., in tests)
|
|
328
351
|
@configuration = nil
|
|
329
352
|
@client = nil
|
|
353
|
+
@noop_tracer = nil
|
|
354
|
+
@tracing_disabled_warning_emitted = false
|
|
330
355
|
end
|
|
331
356
|
|
|
332
357
|
# Creates a new observation (root or child)
|
|
@@ -478,7 +503,10 @@ module Langfuse
|
|
|
478
503
|
#
|
|
479
504
|
# @return [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
|
|
480
505
|
def otel_tracer
|
|
481
|
-
|
|
506
|
+
return tracer_provider.tracer(LANGFUSE_TRACER_NAME, Langfuse::VERSION) if setup_tracing_if_ready
|
|
507
|
+
|
|
508
|
+
warn_tracing_disabled_once
|
|
509
|
+
noop_tracer
|
|
482
510
|
end
|
|
483
511
|
|
|
484
512
|
# Creates an OpenTelemetry span (root or child)
|
|
@@ -514,6 +542,47 @@ module Langfuse
|
|
|
514
542
|
observation_class = OBSERVATION_TYPE_REGISTRY[type_str] || Span
|
|
515
543
|
observation_class.new(otel_span, otel_tracer, attributes: attributes)
|
|
516
544
|
end
|
|
545
|
+
|
|
546
|
+
# rubocop:disable Naming/PredicateMethod
|
|
547
|
+
def setup_tracing_if_ready
|
|
548
|
+
return true if OtelSetup.initialized?
|
|
549
|
+
return false unless tracing_config_ready?
|
|
550
|
+
|
|
551
|
+
OtelSetup.setup(configuration)
|
|
552
|
+
true
|
|
553
|
+
end
|
|
554
|
+
# rubocop:enable Naming/PredicateMethod
|
|
555
|
+
|
|
556
|
+
def tracing_config_ready?
|
|
557
|
+
configured?(configuration.public_key) &&
|
|
558
|
+
configured?(configuration.secret_key) &&
|
|
559
|
+
configured?(configuration.base_url)
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def configured?(value)
|
|
563
|
+
!value.nil? && !value.empty?
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def warn_tracing_disabled_once
|
|
567
|
+
return if @tracing_disabled_warning_emitted
|
|
568
|
+
|
|
569
|
+
tracing_warning_mutex.synchronize do
|
|
570
|
+
return if @tracing_disabled_warning_emitted
|
|
571
|
+
|
|
572
|
+
configuration.logger.warn(
|
|
573
|
+
"Langfuse tracing is disabled until public_key, secret_key, and base_url are configured."
|
|
574
|
+
)
|
|
575
|
+
@tracing_disabled_warning_emitted = true
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
def tracing_warning_mutex
|
|
580
|
+
@tracing_warning_mutex ||= Mutex.new
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def noop_tracer
|
|
584
|
+
@noop_tracer ||= OpenTelemetry::Trace::TracerProvider.new.tracer(LANGFUSE_TRACER_NAME, Langfuse::VERSION)
|
|
585
|
+
end
|
|
517
586
|
end
|
|
518
587
|
# rubocop:enable Metrics/ClassLength
|
|
519
588
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: langfuse-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SimplePractice
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: faraday
|
|
@@ -177,9 +176,12 @@ files:
|
|
|
177
176
|
- lib/langfuse/otel_attributes.rb
|
|
178
177
|
- lib/langfuse/otel_setup.rb
|
|
179
178
|
- lib/langfuse/prompt_cache.rb
|
|
179
|
+
- lib/langfuse/prompt_renderer.rb
|
|
180
180
|
- lib/langfuse/propagation.rb
|
|
181
181
|
- lib/langfuse/rails_cache_adapter.rb
|
|
182
|
+
- lib/langfuse/sampling.rb
|
|
182
183
|
- lib/langfuse/score_client.rb
|
|
184
|
+
- lib/langfuse/span_filter.rb
|
|
183
185
|
- lib/langfuse/span_processor.rb
|
|
184
186
|
- lib/langfuse/stale_while_revalidate.rb
|
|
185
187
|
- lib/langfuse/text_prompt_client.rb
|
|
@@ -196,7 +198,6 @@ metadata:
|
|
|
196
198
|
source_code_uri: https://github.com/simplepractice/langfuse-rb
|
|
197
199
|
changelog_uri: https://github.com/simplepractice/langfuse-rb/blob/main/CHANGELOG.md
|
|
198
200
|
rubygems_mfa_required: 'true'
|
|
199
|
-
post_install_message:
|
|
200
201
|
rdoc_options: []
|
|
201
202
|
require_paths:
|
|
202
203
|
- lib
|
|
@@ -211,8 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
211
212
|
- !ruby/object:Gem::Version
|
|
212
213
|
version: '0'
|
|
213
214
|
requirements: []
|
|
214
|
-
rubygems_version:
|
|
215
|
-
signing_key:
|
|
215
|
+
rubygems_version: 4.0.8
|
|
216
216
|
specification_version: 4
|
|
217
217
|
summary: Ruby SDK for Langfuse - LLM observability and prompt management
|
|
218
218
|
test_files: []
|