langfuse-rb 0.1.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 +7 -0
- data/CHANGELOG.md +60 -0
- data/LICENSE +21 -0
- data/README.md +106 -0
- data/lib/langfuse/api_client.rb +330 -0
- data/lib/langfuse/cache_warmer.rb +219 -0
- data/lib/langfuse/chat_prompt_client.rb +98 -0
- data/lib/langfuse/client.rb +338 -0
- data/lib/langfuse/config.rb +135 -0
- data/lib/langfuse/observations.rb +615 -0
- data/lib/langfuse/otel_attributes.rb +275 -0
- data/lib/langfuse/otel_setup.rb +123 -0
- data/lib/langfuse/prompt_cache.rb +131 -0
- data/lib/langfuse/propagation.rb +471 -0
- data/lib/langfuse/rails_cache_adapter.rb +200 -0
- data/lib/langfuse/score_client.rb +321 -0
- data/lib/langfuse/span_processor.rb +61 -0
- data/lib/langfuse/text_prompt_client.rb +67 -0
- data/lib/langfuse/types.rb +353 -0
- data/lib/langfuse/version.rb +5 -0
- data/lib/langfuse.rb +457 -0
- metadata +177 -0
data/lib/langfuse.rb
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "langfuse/version"
|
|
4
|
+
require_relative "langfuse/types"
|
|
5
|
+
|
|
6
|
+
# Langfuse Ruby SDK
|
|
7
|
+
#
|
|
8
|
+
# Official Ruby SDK for Langfuse, providing LLM tracing, observability,
|
|
9
|
+
# and prompt management capabilities.
|
|
10
|
+
#
|
|
11
|
+
# @example Global configuration (Rails initializer)
|
|
12
|
+
# Langfuse.configure do |config|
|
|
13
|
+
# config.public_key = ENV['LANGFUSE_PUBLIC_KEY']
|
|
14
|
+
# config.secret_key = ENV['LANGFUSE_SECRET_KEY']
|
|
15
|
+
# config.cache_ttl = 120
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# @example Using the global client
|
|
19
|
+
# client = Langfuse.client
|
|
20
|
+
# prompt = client.get_prompt("greeting")
|
|
21
|
+
#
|
|
22
|
+
module Langfuse
|
|
23
|
+
class Error < StandardError; end
|
|
24
|
+
class ConfigurationError < Error; end
|
|
25
|
+
class ApiError < Error; end
|
|
26
|
+
class NotFoundError < ApiError; end
|
|
27
|
+
class UnauthorizedError < ApiError; end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
require_relative "langfuse/config"
|
|
31
|
+
require_relative "langfuse/prompt_cache"
|
|
32
|
+
require_relative "langfuse/rails_cache_adapter"
|
|
33
|
+
require_relative "langfuse/cache_warmer"
|
|
34
|
+
require_relative "langfuse/api_client"
|
|
35
|
+
require_relative "langfuse/otel_setup"
|
|
36
|
+
require_relative "langfuse/otel_attributes"
|
|
37
|
+
require_relative "langfuse/propagation"
|
|
38
|
+
require_relative "langfuse/span_processor"
|
|
39
|
+
require_relative "langfuse/observations"
|
|
40
|
+
require_relative "langfuse/score_client"
|
|
41
|
+
require_relative "langfuse/text_prompt_client"
|
|
42
|
+
require_relative "langfuse/chat_prompt_client"
|
|
43
|
+
require_relative "langfuse/client"
|
|
44
|
+
|
|
45
|
+
# rubocop:disable Metrics/ModuleLength
|
|
46
|
+
module Langfuse
|
|
47
|
+
# rubocop:disable Metrics/ClassLength
|
|
48
|
+
class << self
|
|
49
|
+
attr_writer :configuration
|
|
50
|
+
|
|
51
|
+
# Returns the global configuration object
|
|
52
|
+
#
|
|
53
|
+
# @return [Config] the global configuration
|
|
54
|
+
def configuration
|
|
55
|
+
@configuration ||= Config.new
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Configure Langfuse globally
|
|
59
|
+
#
|
|
60
|
+
# @yield [Config] the configuration object
|
|
61
|
+
# @return [Config] the configured configuration
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# Langfuse.configure do |config|
|
|
65
|
+
# config.public_key = ENV['LANGFUSE_PUBLIC_KEY']
|
|
66
|
+
# config.secret_key = ENV['LANGFUSE_SECRET_KEY']
|
|
67
|
+
# end
|
|
68
|
+
def configure
|
|
69
|
+
yield(configuration)
|
|
70
|
+
|
|
71
|
+
# Auto-initialize OpenTelemetry
|
|
72
|
+
OtelSetup.setup(configuration)
|
|
73
|
+
|
|
74
|
+
configuration
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Returns the global singleton client
|
|
78
|
+
#
|
|
79
|
+
# @return [Client] the global client instance
|
|
80
|
+
def client
|
|
81
|
+
@client ||= Client.new(configuration)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Shutdown Langfuse and flush any pending traces and scores
|
|
85
|
+
#
|
|
86
|
+
# Call this when shutting down your application to ensure
|
|
87
|
+
# all traces and scores are sent to Langfuse.
|
|
88
|
+
#
|
|
89
|
+
# @param timeout [Integer] Timeout in seconds
|
|
90
|
+
# @return [void]
|
|
91
|
+
#
|
|
92
|
+
# @example In a Rails initializer or shutdown hook
|
|
93
|
+
# at_exit { Langfuse.shutdown }
|
|
94
|
+
#
|
|
95
|
+
def shutdown(timeout: 30)
|
|
96
|
+
client.shutdown if @client
|
|
97
|
+
OtelSetup.shutdown(timeout: timeout)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Force flush all pending traces
|
|
101
|
+
#
|
|
102
|
+
# @param timeout [Integer] Timeout in seconds
|
|
103
|
+
# @return [void]
|
|
104
|
+
def force_flush(timeout: 30)
|
|
105
|
+
OtelSetup.force_flush(timeout: timeout)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Propagate trace-level attributes to all spans created within this context.
|
|
109
|
+
#
|
|
110
|
+
# This method sets attributes on the currently active span AND automatically
|
|
111
|
+
# propagates them to all new child spans created within the block. This is the
|
|
112
|
+
# recommended way to set trace-level attributes like user_id, session_id, and metadata
|
|
113
|
+
# dimensions that should be consistently applied across all observations in a trace.
|
|
114
|
+
#
|
|
115
|
+
# **IMPORTANT**: Call this as early as possible within your trace/workflow. Only the
|
|
116
|
+
# currently active span and spans created after entering this context will have these
|
|
117
|
+
# attributes. Pre-existing spans will NOT be retroactively updated.
|
|
118
|
+
#
|
|
119
|
+
# @param user_id [String, nil] User identifier (≤200 characters)
|
|
120
|
+
# @param session_id [String, nil] Session identifier (≤200 characters)
|
|
121
|
+
# @param metadata [Hash<String, String>, nil] Additional metadata (all values ≤200 characters)
|
|
122
|
+
# @param version [String, nil] Version identifier (≤200 characters)
|
|
123
|
+
# @param tags [Array<String>, nil] List of tags (each ≤200 characters)
|
|
124
|
+
# @param as_baggage [Boolean] If true, propagates via OpenTelemetry baggage for cross-service propagation
|
|
125
|
+
# @yield Block within which attributes are propagated
|
|
126
|
+
# @return [Object] The result of the block
|
|
127
|
+
#
|
|
128
|
+
# @example Basic usage
|
|
129
|
+
# Langfuse.propagate_attributes(user_id: "user_123", session_id: "session_abc") do
|
|
130
|
+
# Langfuse.observe("operation") do |span|
|
|
131
|
+
# # Current span has user_id and session_id
|
|
132
|
+
# span.start_observation("child") do |child|
|
|
133
|
+
# # Child span inherits user_id and session_id
|
|
134
|
+
# end
|
|
135
|
+
# end
|
|
136
|
+
# end
|
|
137
|
+
#
|
|
138
|
+
# @example With metadata and tags
|
|
139
|
+
# Langfuse.propagate_attributes(
|
|
140
|
+
# user_id: "user_123",
|
|
141
|
+
# metadata: { environment: "production", region: "us-east" },
|
|
142
|
+
# tags: ["api", "v2"]
|
|
143
|
+
# ) do
|
|
144
|
+
# # All spans inherit these attributes
|
|
145
|
+
# end
|
|
146
|
+
#
|
|
147
|
+
# @example Cross-service propagation
|
|
148
|
+
# Langfuse.propagate_attributes(
|
|
149
|
+
# user_id: "user_123",
|
|
150
|
+
# as_baggage: true
|
|
151
|
+
# ) do
|
|
152
|
+
# # Attributes propagate via HTTP headers
|
|
153
|
+
# end
|
|
154
|
+
def propagate_attributes(user_id: nil, session_id: nil, metadata: nil, version: nil, tags: nil,
|
|
155
|
+
as_baggage: false, &)
|
|
156
|
+
Propagation.propagate_attributes(
|
|
157
|
+
user_id: user_id,
|
|
158
|
+
session_id: session_id,
|
|
159
|
+
metadata: metadata,
|
|
160
|
+
version: version,
|
|
161
|
+
tags: tags,
|
|
162
|
+
as_baggage: as_baggage,
|
|
163
|
+
&
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Create a score event and queue it for batching
|
|
168
|
+
#
|
|
169
|
+
# @param name [String] Score name (required)
|
|
170
|
+
# @param value [Numeric, Integer, String] Score value (type depends on data_type)
|
|
171
|
+
# @param trace_id [String, nil] Trace ID to associate with the score
|
|
172
|
+
# @param observation_id [String, nil] Observation ID to associate with the score
|
|
173
|
+
# @param comment [String, nil] Optional comment
|
|
174
|
+
# @param metadata [Hash, nil] Optional metadata hash
|
|
175
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
176
|
+
# @return [void]
|
|
177
|
+
# @raise [ArgumentError] if validation fails
|
|
178
|
+
#
|
|
179
|
+
# @example Numeric score
|
|
180
|
+
# Langfuse.create_score(name: "quality", value: 0.85, trace_id: "abc123")
|
|
181
|
+
#
|
|
182
|
+
# @example Boolean score
|
|
183
|
+
# Langfuse.create_score(name: "passed", value: true, trace_id: "abc123", data_type: :boolean)
|
|
184
|
+
#
|
|
185
|
+
# @example Categorical score
|
|
186
|
+
# Langfuse.create_score(name: "category", value: "high", trace_id: "abc123", data_type: :categorical)
|
|
187
|
+
# rubocop:disable Metrics/ParameterLists
|
|
188
|
+
def create_score(name:, value:, trace_id: nil, observation_id: nil, comment: nil, metadata: nil,
|
|
189
|
+
data_type: :numeric)
|
|
190
|
+
client.create_score(
|
|
191
|
+
name: name,
|
|
192
|
+
value: value,
|
|
193
|
+
trace_id: trace_id,
|
|
194
|
+
observation_id: observation_id,
|
|
195
|
+
comment: comment,
|
|
196
|
+
metadata: metadata,
|
|
197
|
+
data_type: data_type
|
|
198
|
+
)
|
|
199
|
+
end
|
|
200
|
+
# rubocop:enable Metrics/ParameterLists
|
|
201
|
+
|
|
202
|
+
# Create a score for the currently active observation (from OTel span)
|
|
203
|
+
#
|
|
204
|
+
# Extracts observation_id and trace_id from the active OpenTelemetry span.
|
|
205
|
+
#
|
|
206
|
+
# @param name [String] Score name (required)
|
|
207
|
+
# @param value [Numeric, Integer, String] Score value
|
|
208
|
+
# @param comment [String, nil] Optional comment
|
|
209
|
+
# @param metadata [Hash, nil] Optional metadata hash
|
|
210
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
211
|
+
# @return [void]
|
|
212
|
+
# @raise [ArgumentError] if no active span or validation fails
|
|
213
|
+
#
|
|
214
|
+
# @example
|
|
215
|
+
# Langfuse.observe("operation") do |obs|
|
|
216
|
+
# Langfuse.score_active_observation(name: "accuracy", value: 0.92)
|
|
217
|
+
# end
|
|
218
|
+
def score_active_observation(name:, value:, comment: nil, metadata: nil, data_type: :numeric)
|
|
219
|
+
client.score_active_observation(
|
|
220
|
+
name: name,
|
|
221
|
+
value: value,
|
|
222
|
+
comment: comment,
|
|
223
|
+
metadata: metadata,
|
|
224
|
+
data_type: data_type
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Create a score for the currently active trace (from OTel span)
|
|
229
|
+
#
|
|
230
|
+
# Extracts trace_id from the active OpenTelemetry span.
|
|
231
|
+
#
|
|
232
|
+
# @param name [String] Score name (required)
|
|
233
|
+
# @param value [Numeric, Integer, String] Score value
|
|
234
|
+
# @param comment [String, nil] Optional comment
|
|
235
|
+
# @param metadata [Hash, nil] Optional metadata hash
|
|
236
|
+
# @param data_type [Symbol] Data type (:numeric, :boolean, :categorical)
|
|
237
|
+
# @return [void]
|
|
238
|
+
# @raise [ArgumentError] if no active span or validation fails
|
|
239
|
+
#
|
|
240
|
+
# @example
|
|
241
|
+
# Langfuse.observe("operation") do |obs|
|
|
242
|
+
# Langfuse.score_active_trace(name: "overall_quality", value: 5)
|
|
243
|
+
# end
|
|
244
|
+
def score_active_trace(name:, value:, comment: nil, metadata: nil, data_type: :numeric)
|
|
245
|
+
client.score_active_trace(
|
|
246
|
+
name: name,
|
|
247
|
+
value: value,
|
|
248
|
+
comment: comment,
|
|
249
|
+
metadata: metadata,
|
|
250
|
+
data_type: data_type
|
|
251
|
+
)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Force flush all queued score events
|
|
255
|
+
#
|
|
256
|
+
# Sends all queued score events to the API immediately.
|
|
257
|
+
#
|
|
258
|
+
# @return [void]
|
|
259
|
+
#
|
|
260
|
+
# @example
|
|
261
|
+
# Langfuse.flush_scores
|
|
262
|
+
def flush_scores
|
|
263
|
+
client.flush_scores if @client
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Reset global configuration and client (useful for testing)
|
|
267
|
+
#
|
|
268
|
+
# @return [void]
|
|
269
|
+
def reset!
|
|
270
|
+
client.shutdown if @client
|
|
271
|
+
OtelSetup.shutdown(timeout: 5) if OtelSetup.initialized?
|
|
272
|
+
@configuration = nil
|
|
273
|
+
@client = nil
|
|
274
|
+
rescue StandardError
|
|
275
|
+
# Ignore shutdown errors during reset (e.g., in tests)
|
|
276
|
+
@configuration = nil
|
|
277
|
+
@client = nil
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Creates a new observation (root or child)
|
|
281
|
+
#
|
|
282
|
+
# This is the module-level factory method that creates observations of any type.
|
|
283
|
+
# It can create root observations (when parent_span_context is nil) or child
|
|
284
|
+
# observations (when parent_span_context is provided).
|
|
285
|
+
#
|
|
286
|
+
# @param name [String] Descriptive name for the observation
|
|
287
|
+
# @param attrs [Hash, Types::SpanAttributes, Types::GenerationAttributes, nil] Observation attributes
|
|
288
|
+
# @param as_type [Symbol, String] Observation type (:span, :generation, :event, etc.)
|
|
289
|
+
# @param parent_span_context [OpenTelemetry::Trace::SpanContext, nil] Parent span context for child observations
|
|
290
|
+
# @param start_time [Time, Integer, nil] Optional start time (Time object or Unix timestamp in nanoseconds)
|
|
291
|
+
# @param skip_validation [Boolean] Skip validation (for internal use). Defaults to false.
|
|
292
|
+
# @return [BaseObservation] The observation wrapper (Span, Generation, or Event)
|
|
293
|
+
#
|
|
294
|
+
# @example Create root span
|
|
295
|
+
# span = Langfuse.start_observation("root-operation", { input: {...} })
|
|
296
|
+
#
|
|
297
|
+
# @example Create child generation
|
|
298
|
+
# child = Langfuse.start_observation("llm-call", { model: "gpt-4" },
|
|
299
|
+
# as_type: :generation,
|
|
300
|
+
# parent_span_context: parent.otel_span.context)
|
|
301
|
+
def start_observation(name, attrs = {}, as_type: :span, parent_span_context: nil, start_time: nil,
|
|
302
|
+
skip_validation: false)
|
|
303
|
+
type_str = as_type.to_s
|
|
304
|
+
|
|
305
|
+
unless skip_validation || valid_observation_type?(as_type)
|
|
306
|
+
valid_types = OBSERVATION_TYPES.values.sort.join(", ")
|
|
307
|
+
raise ArgumentError, "Invalid observation type: #{type_str}. Valid types: #{valid_types}"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
otel_tracer = otel_tracer()
|
|
311
|
+
otel_span = create_otel_span(
|
|
312
|
+
name: name,
|
|
313
|
+
start_time: start_time,
|
|
314
|
+
parent_span_context: parent_span_context,
|
|
315
|
+
otel_tracer: otel_tracer
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# Serialize attributes
|
|
319
|
+
# Only set attributes if span is still recording (should always be true here, but guard for safety)
|
|
320
|
+
if otel_span.recording?
|
|
321
|
+
otel_attrs = OtelAttributes.create_observation_attributes(type_str, attrs.to_h)
|
|
322
|
+
otel_attrs.each { |key, value| otel_span.set_attribute(key, value) }
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Wrap in appropriate class
|
|
326
|
+
observation = wrap_otel_span(otel_span, type_str, otel_tracer, attributes: attrs)
|
|
327
|
+
|
|
328
|
+
# Events auto-end immediately when created
|
|
329
|
+
observation.end if type_str == OBSERVATION_TYPES[:event]
|
|
330
|
+
|
|
331
|
+
observation
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# User-facing convenience method for creating root observations
|
|
335
|
+
#
|
|
336
|
+
# @param name [String] Descriptive name for the observation
|
|
337
|
+
# @param attrs [Hash] Observation attributes (optional positional or keyword)
|
|
338
|
+
# @param as_type [Symbol, String] Observation type (:span, :generation, :event, etc.)
|
|
339
|
+
# @yield [observation] Optional block that receives the observation object
|
|
340
|
+
# @yieldparam observation [BaseObservation] The observation object
|
|
341
|
+
# @return [BaseObservation, Object] The observation (or block return value if block given)
|
|
342
|
+
#
|
|
343
|
+
# @example Block-based API (auto-ends)
|
|
344
|
+
# Langfuse.observe("operation") do |obs|
|
|
345
|
+
# result = perform_operation
|
|
346
|
+
# obs.update(output: result)
|
|
347
|
+
# end
|
|
348
|
+
#
|
|
349
|
+
# @example Stateful API (manual end)
|
|
350
|
+
# obs = Langfuse.observe("operation", input: { data: "test" })
|
|
351
|
+
# obs.update(output: { result: "success" })
|
|
352
|
+
# obs.end
|
|
353
|
+
def observe(name, attrs = {}, as_type: :span, **kwargs, &block)
|
|
354
|
+
# Merge positional attrs and keyword kwargs
|
|
355
|
+
merged_attrs = attrs.to_h.merge(kwargs)
|
|
356
|
+
observation = start_observation(name, merged_attrs, as_type: as_type)
|
|
357
|
+
|
|
358
|
+
if block
|
|
359
|
+
# Block-based API: auto-ends when block completes
|
|
360
|
+
# Set context and execute block
|
|
361
|
+
current_context = OpenTelemetry::Context.current
|
|
362
|
+
result = OpenTelemetry::Context.with_current(
|
|
363
|
+
OpenTelemetry::Trace.context_with_span(observation.otel_span, parent_context: current_context)
|
|
364
|
+
) do
|
|
365
|
+
block.call(observation)
|
|
366
|
+
end
|
|
367
|
+
# Only end if not already ended (events auto-end in start_observation)
|
|
368
|
+
observation.end unless as_type.to_s == OBSERVATION_TYPES[:event]
|
|
369
|
+
result
|
|
370
|
+
else
|
|
371
|
+
# Stateful API - return observation
|
|
372
|
+
# Events already auto-ended in start_observation
|
|
373
|
+
observation
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Registry mapping observation type strings to their wrapper classes
|
|
378
|
+
OBSERVATION_TYPE_REGISTRY = {
|
|
379
|
+
OBSERVATION_TYPES[:generation] => Generation,
|
|
380
|
+
OBSERVATION_TYPES[:embedding] => Embedding,
|
|
381
|
+
OBSERVATION_TYPES[:event] => Event,
|
|
382
|
+
OBSERVATION_TYPES[:agent] => Agent,
|
|
383
|
+
OBSERVATION_TYPES[:tool] => Tool,
|
|
384
|
+
OBSERVATION_TYPES[:chain] => Chain,
|
|
385
|
+
OBSERVATION_TYPES[:retriever] => Retriever,
|
|
386
|
+
OBSERVATION_TYPES[:evaluator] => Evaluator,
|
|
387
|
+
OBSERVATION_TYPES[:guardrail] => Guardrail,
|
|
388
|
+
OBSERVATION_TYPES[:span] => Span
|
|
389
|
+
}.freeze
|
|
390
|
+
|
|
391
|
+
private
|
|
392
|
+
|
|
393
|
+
# Validates that an observation type is valid
|
|
394
|
+
#
|
|
395
|
+
# Checks if the provided type (symbol or string) matches a valid observation type
|
|
396
|
+
# in the OBSERVATION_TYPES constant.
|
|
397
|
+
#
|
|
398
|
+
# @param type [Symbol, String, Object] The observation type to validate
|
|
399
|
+
# @return [Boolean] true if valid, false otherwise
|
|
400
|
+
#
|
|
401
|
+
# @example
|
|
402
|
+
# valid_observation_type?(:span) # => true
|
|
403
|
+
# valid_observation_type?("span") # => true
|
|
404
|
+
# valid_observation_type?(:invalid) # => false
|
|
405
|
+
# valid_observation_type?(nil) # => false
|
|
406
|
+
def valid_observation_type?(type)
|
|
407
|
+
return false unless type.respond_to?(:to_sym)
|
|
408
|
+
|
|
409
|
+
OBSERVATION_TYPES.key?(type.to_sym)
|
|
410
|
+
rescue TypeError
|
|
411
|
+
false
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Gets the OpenTelemetry tracer for Langfuse
|
|
415
|
+
#
|
|
416
|
+
# @return [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
|
|
417
|
+
def otel_tracer
|
|
418
|
+
OpenTelemetry.tracer_provider.tracer("langfuse-rb", Langfuse::VERSION)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Creates an OpenTelemetry span (root or child)
|
|
422
|
+
#
|
|
423
|
+
# @param name [String] Span name
|
|
424
|
+
# @param start_time [Time, Integer, nil] Optional start time
|
|
425
|
+
# @param parent_span_context [OpenTelemetry::Trace::SpanContext, nil] Parent span context
|
|
426
|
+
# @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
|
|
427
|
+
# @return [OpenTelemetry::SDK::Trace::Span] The created span
|
|
428
|
+
def create_otel_span(name:, otel_tracer:, start_time: nil, parent_span_context: nil)
|
|
429
|
+
if parent_span_context
|
|
430
|
+
# Create child span with parent context
|
|
431
|
+
# Create a non-recording span from the parent context to set in context
|
|
432
|
+
parent_span = OpenTelemetry::Trace.non_recording_span(parent_span_context)
|
|
433
|
+
parent_context = OpenTelemetry::Trace.context_with_span(parent_span)
|
|
434
|
+
OpenTelemetry::Context.with_current(parent_context) do
|
|
435
|
+
otel_tracer.start_span(name, start_timestamp: start_time)
|
|
436
|
+
end
|
|
437
|
+
else
|
|
438
|
+
# Create root span
|
|
439
|
+
otel_tracer.start_span(name, start_timestamp: start_time)
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# Wraps an OpenTelemetry span in the appropriate observation class
|
|
444
|
+
#
|
|
445
|
+
# @param otel_span [OpenTelemetry::SDK::Trace::Span] The OTel span
|
|
446
|
+
# @param type_str [String] Observation type string
|
|
447
|
+
# @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
|
|
448
|
+
# @param attributes [Hash, nil] Optional attributes
|
|
449
|
+
# @return [BaseObservation] Appropriate observation wrapper instance
|
|
450
|
+
def wrap_otel_span(otel_span, type_str, otel_tracer, attributes: nil)
|
|
451
|
+
observation_class = OBSERVATION_TYPE_REGISTRY[type_str] || Span
|
|
452
|
+
observation_class.new(otel_span, otel_tracer, attributes: attributes)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
# rubocop:enable Metrics/ClassLength
|
|
456
|
+
end
|
|
457
|
+
# rubocop:enable Metrics/ModuleLength
|
metadata
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: langfuse-rb
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- SimplePractice
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-retry
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: mustache
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: opentelemetry-api
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.2'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.2'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: opentelemetry-common
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.21'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.21'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: opentelemetry-exporter-otlp
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0.28'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0.28'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: opentelemetry-sdk
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.4'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.4'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: base64
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0.2'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0.2'
|
|
124
|
+
description: Official Ruby SDK for Langfuse, providing LLM tracing, observability,
|
|
125
|
+
and prompt management capabilities
|
|
126
|
+
email:
|
|
127
|
+
- open-source-langfuse-rb@simplepractice.com
|
|
128
|
+
executables: []
|
|
129
|
+
extensions: []
|
|
130
|
+
extra_rdoc_files: []
|
|
131
|
+
files:
|
|
132
|
+
- CHANGELOG.md
|
|
133
|
+
- LICENSE
|
|
134
|
+
- README.md
|
|
135
|
+
- lib/langfuse.rb
|
|
136
|
+
- lib/langfuse/api_client.rb
|
|
137
|
+
- lib/langfuse/cache_warmer.rb
|
|
138
|
+
- lib/langfuse/chat_prompt_client.rb
|
|
139
|
+
- lib/langfuse/client.rb
|
|
140
|
+
- lib/langfuse/config.rb
|
|
141
|
+
- lib/langfuse/observations.rb
|
|
142
|
+
- lib/langfuse/otel_attributes.rb
|
|
143
|
+
- lib/langfuse/otel_setup.rb
|
|
144
|
+
- lib/langfuse/prompt_cache.rb
|
|
145
|
+
- lib/langfuse/propagation.rb
|
|
146
|
+
- lib/langfuse/rails_cache_adapter.rb
|
|
147
|
+
- lib/langfuse/score_client.rb
|
|
148
|
+
- lib/langfuse/span_processor.rb
|
|
149
|
+
- lib/langfuse/text_prompt_client.rb
|
|
150
|
+
- lib/langfuse/types.rb
|
|
151
|
+
- lib/langfuse/version.rb
|
|
152
|
+
homepage: https://github.com/simplepractice/langfuse-rb
|
|
153
|
+
licenses:
|
|
154
|
+
- MIT
|
|
155
|
+
metadata:
|
|
156
|
+
homepage_uri: https://github.com/simplepractice/langfuse-rb
|
|
157
|
+
source_code_uri: https://github.com/simplepractice/langfuse-rb
|
|
158
|
+
changelog_uri: https://github.com/simplepractice/langfuse-rb/blob/main/CHANGELOG.md
|
|
159
|
+
rubygems_mfa_required: 'true'
|
|
160
|
+
rdoc_options: []
|
|
161
|
+
require_paths:
|
|
162
|
+
- lib
|
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
164
|
+
requirements:
|
|
165
|
+
- - ">="
|
|
166
|
+
- !ruby/object:Gem::Version
|
|
167
|
+
version: 3.2.0
|
|
168
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - ">="
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: '0'
|
|
173
|
+
requirements: []
|
|
174
|
+
rubygems_version: 3.7.2
|
|
175
|
+
specification_version: 4
|
|
176
|
+
summary: Ruby SDK for Langfuse - LLM observability and prompt management
|
|
177
|
+
test_files: []
|