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
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Langfuse
|
|
4
|
+
# Observation type constants
|
|
5
|
+
OBSERVATION_TYPES = {
|
|
6
|
+
span: "span",
|
|
7
|
+
generation: "generation",
|
|
8
|
+
embedding: "embedding",
|
|
9
|
+
event: "event",
|
|
10
|
+
agent: "agent",
|
|
11
|
+
tool: "tool",
|
|
12
|
+
chain: "chain",
|
|
13
|
+
retriever: "retriever",
|
|
14
|
+
evaluator: "evaluator",
|
|
15
|
+
guardrail: "guardrail"
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
# Base class for all Langfuse observation wrappers.
|
|
19
|
+
#
|
|
20
|
+
# Provides unified functionality for spans, generations, events, and specialized observation types.
|
|
21
|
+
# Wraps OpenTelemetry spans with Langfuse-specific functionality. Uses unified `start_observation()`
|
|
22
|
+
# method with `as_type` parameter, aligning with langfuse-js architecture.
|
|
23
|
+
#
|
|
24
|
+
# @example Block-based API (auto-ends)
|
|
25
|
+
# Langfuse.observe("parent-operation", input: { query: "test" }) do |span|
|
|
26
|
+
# # Child span
|
|
27
|
+
# span.start_observation("data-processing", input: { step: "fetch" }) do |child|
|
|
28
|
+
# result = fetch_data
|
|
29
|
+
# child.update(output: result)
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# # Child generation (LLM call)
|
|
33
|
+
# span.start_observation("llm-call", { model: "gpt-4", input: [{ role: "user", content: "Hello" }] }, as_type: :generation) do |gen|
|
|
34
|
+
# response = call_llm
|
|
35
|
+
# gen.update(output: response, usage_details: { prompt_tokens: 100, completion_tokens: 50 })
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# @example Stateful API (manual end)
|
|
40
|
+
# span = Langfuse.start_observation("parent-operation", { input: { query: "test" } })
|
|
41
|
+
#
|
|
42
|
+
# # Child span
|
|
43
|
+
# child_span = span.start_observation("data-validation", { input: { data: result } })
|
|
44
|
+
# validate_data
|
|
45
|
+
# child_span.update(output: { valid: true })
|
|
46
|
+
# child_span.end
|
|
47
|
+
#
|
|
48
|
+
# # Child generation (LLM call)
|
|
49
|
+
# gen = span.start_observation("llm-summary", {
|
|
50
|
+
# model: "gpt-3.5-turbo",
|
|
51
|
+
# input: [{ role: "user", content: "Summarize" }]
|
|
52
|
+
# }, as_type: :generation)
|
|
53
|
+
# summary = call_llm
|
|
54
|
+
# gen.update(output: summary, usage_details: { prompt_tokens: 50, completion_tokens: 25 })
|
|
55
|
+
# gen.end
|
|
56
|
+
#
|
|
57
|
+
# span.end
|
|
58
|
+
#
|
|
59
|
+
# @abstract Subclass and pass type: to super to create concrete observation types
|
|
60
|
+
class BaseObservation
|
|
61
|
+
attr_reader :otel_span, :otel_tracer, :type
|
|
62
|
+
|
|
63
|
+
# @param otel_span [OpenTelemetry::SDK::Trace::Span] The underlying OTel span
|
|
64
|
+
# @param otel_tracer [OpenTelemetry::SDK::Trace::Tracer] The OTel tracer
|
|
65
|
+
# @param attributes [Hash, Types::SpanAttributes, Types::GenerationAttributes, nil] Optional initial attributes
|
|
66
|
+
# @param type [String] Observation type (e.g., "span", "generation", "event")
|
|
67
|
+
def initialize(otel_span, otel_tracer, attributes: nil, type: nil)
|
|
68
|
+
@otel_span = otel_span
|
|
69
|
+
@otel_tracer = otel_tracer
|
|
70
|
+
@type = type || raise(ArgumentError, "type must be provided")
|
|
71
|
+
|
|
72
|
+
# Set initial attributes if provided
|
|
73
|
+
return unless attributes
|
|
74
|
+
|
|
75
|
+
update_observation_attributes(attributes.to_h)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [String] Hex-encoded span ID (16 hex characters)
|
|
79
|
+
def id
|
|
80
|
+
@otel_span.context.span_id.unpack1("H*")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [String] Hex-encoded trace ID (32 hex characters)
|
|
84
|
+
def trace_id
|
|
85
|
+
@otel_span.context.trace_id.unpack1("H*")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @return [String] URL to view this trace in Langfuse UI
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# span = Langfuse.observe("operation") do |obs|
|
|
92
|
+
# puts "View trace: #{obs.trace_url}"
|
|
93
|
+
# end
|
|
94
|
+
def trace_url
|
|
95
|
+
Langfuse.client.trace_url(trace_id)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @param end_time [Time, Integer, nil] Optional end time (Time object or Unix timestamp in nanoseconds)
|
|
99
|
+
def end(end_time: nil)
|
|
100
|
+
@otel_span.finish(end_timestamp: end_time)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Updates trace-level attributes (user_id, session_id, tags, etc.) for the entire trace.
|
|
104
|
+
#
|
|
105
|
+
# @param attrs [Hash, Types::TraceAttributes] Trace attributes to set
|
|
106
|
+
# @return [self]
|
|
107
|
+
def update_trace(attrs)
|
|
108
|
+
return self unless @otel_span.recording?
|
|
109
|
+
|
|
110
|
+
otel_attrs = OtelAttributes.create_trace_attributes(attrs.to_h)
|
|
111
|
+
otel_attrs.each { |key, value| @otel_span.set_attribute(key, value) }
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Creates a child observation within this observation's context.
|
|
116
|
+
#
|
|
117
|
+
# Supports block-based (auto-ends) and stateful (manual end) APIs. Events auto-end when created without a block.
|
|
118
|
+
#
|
|
119
|
+
# @param name [String] Descriptive name for the child observation
|
|
120
|
+
# @param attrs [Hash, Types::SpanAttributes, Types::GenerationAttributes, nil] Observation attributes
|
|
121
|
+
# @param as_type [Symbol, String] Observation type (:span, :generation, :event, etc.). Defaults to `:span`.
|
|
122
|
+
# @yield [observation] Optional block that receives the observation object
|
|
123
|
+
# @return [BaseObservation, Object] The child observation (or block return value if block given)
|
|
124
|
+
def start_observation(name, attrs = {}, as_type: :span, &block)
|
|
125
|
+
# Call module-level factory with parent context
|
|
126
|
+
# Skip validation to allow unknown types to fall back to Span
|
|
127
|
+
child = Langfuse.start_observation(
|
|
128
|
+
name,
|
|
129
|
+
attrs,
|
|
130
|
+
as_type: as_type,
|
|
131
|
+
parent_span_context: @otel_span.context,
|
|
132
|
+
skip_validation: true
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if block
|
|
136
|
+
# Block-based API: auto-ends when block completes
|
|
137
|
+
# Set context and execute block
|
|
138
|
+
current_context = OpenTelemetry::Context.current
|
|
139
|
+
result = OpenTelemetry::Context.with_current(
|
|
140
|
+
OpenTelemetry::Trace.context_with_span(child.otel_span, parent_context: current_context)
|
|
141
|
+
) do
|
|
142
|
+
block.call(child)
|
|
143
|
+
end
|
|
144
|
+
# Only end if not already ended (events auto-end in start_observation)
|
|
145
|
+
child.end unless as_type.to_s == OBSERVATION_TYPES[:event]
|
|
146
|
+
result
|
|
147
|
+
else
|
|
148
|
+
# Stateful API - return observation
|
|
149
|
+
# Events already auto-ended in start_observation
|
|
150
|
+
child
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Sets observation-level input attributes.
|
|
155
|
+
#
|
|
156
|
+
# @param value [Object] Input value (will be JSON-encoded)
|
|
157
|
+
def input=(value)
|
|
158
|
+
update_observation_attributes(input: value)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Sets observation-level output attributes.
|
|
162
|
+
#
|
|
163
|
+
# @param value [Object] Output value (will be JSON-encoded)
|
|
164
|
+
def output=(value)
|
|
165
|
+
update_observation_attributes(output: value)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @param value [Hash] Metadata hash (expanded into individual langfuse.observation.metadata.* attributes)
|
|
169
|
+
def metadata=(value)
|
|
170
|
+
update_observation_attributes(metadata: value)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @param value [String] Level (DEBUG, DEFAULT, WARNING, ERROR)
|
|
174
|
+
def level=(value)
|
|
175
|
+
update_observation_attributes(level: value)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @param name [String] Event name
|
|
179
|
+
# @param input [Object, nil] Optional event data
|
|
180
|
+
# @param level [String] Log level (debug, default, warning, error)
|
|
181
|
+
#
|
|
182
|
+
def event(name:, input: nil, level: "default")
|
|
183
|
+
attributes = {
|
|
184
|
+
"langfuse.observation.input" => input&.to_json,
|
|
185
|
+
"langfuse.observation.level" => level
|
|
186
|
+
}.compact
|
|
187
|
+
|
|
188
|
+
@otel_span.add_event(name, attributes: attributes)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @return [OpenTelemetry::SDK::Trace::Span]
|
|
192
|
+
def current_span
|
|
193
|
+
@otel_span
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Protected method used by subclasses' public `update` methods.
|
|
197
|
+
#
|
|
198
|
+
# @param attrs [Hash, Types::SpanAttributes, Types::GenerationAttributes] Attributes to update
|
|
199
|
+
# @api private
|
|
200
|
+
protected
|
|
201
|
+
|
|
202
|
+
def update_observation_attributes(attrs = {}, **kwargs)
|
|
203
|
+
# Don't set attributes on ended spans
|
|
204
|
+
return unless @otel_span.recording?
|
|
205
|
+
|
|
206
|
+
# Merge keyword arguments into attrs hash
|
|
207
|
+
attrs_hash = if kwargs.any?
|
|
208
|
+
attrs.to_h.merge(kwargs)
|
|
209
|
+
else
|
|
210
|
+
attrs.to_h
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Use @type instance variable set during initialization
|
|
214
|
+
otel_attrs = OtelAttributes.create_observation_attributes(type, attrs_hash)
|
|
215
|
+
otel_attrs.each { |key, value| @otel_span.set_attribute(key, value) }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Converts a prompt object to hash format for OtelAttributes.
|
|
219
|
+
#
|
|
220
|
+
# @param prompt [Object, Hash, nil] Prompt object or hash
|
|
221
|
+
# @return [Hash, Object, nil] Hash with name and version, or original prompt
|
|
222
|
+
# @api protected
|
|
223
|
+
def normalize_prompt(prompt)
|
|
224
|
+
case prompt
|
|
225
|
+
in obj if obj.respond_to?(:name) && obj.respond_to?(:version)
|
|
226
|
+
{ name: obj.name, version: obj.version }
|
|
227
|
+
else
|
|
228
|
+
prompt
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# General-purpose observation for tracking operations, functions, or logical units of work.
|
|
234
|
+
#
|
|
235
|
+
# @example Block-based API
|
|
236
|
+
# Langfuse.observe("data-processing", input: { query: "test" }) do |span|
|
|
237
|
+
# result = process_data
|
|
238
|
+
# span.update(output: result, metadata: { duration_ms: 150 })
|
|
239
|
+
# end
|
|
240
|
+
#
|
|
241
|
+
# @example Stateful API
|
|
242
|
+
# span = Langfuse.start_observation("data-processing", input: { query: "test" })
|
|
243
|
+
# result = process_data
|
|
244
|
+
# span.update(output: result)
|
|
245
|
+
# span.end
|
|
246
|
+
#
|
|
247
|
+
class Span < BaseObservation
|
|
248
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
249
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:span])
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# @param attrs [Hash, Types::SpanAttributes] Span attributes to set
|
|
253
|
+
# @return [self]
|
|
254
|
+
def update(attrs)
|
|
255
|
+
update_observation_attributes(attrs)
|
|
256
|
+
self
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Observation for LLM calls. Provides methods to set output, usage, and other LLM-specific metadata.
|
|
261
|
+
#
|
|
262
|
+
# @example Block-based API
|
|
263
|
+
# Langfuse.observe("chat-completion", as_type: :generation) do |gen|
|
|
264
|
+
# gen.model = "gpt-4"
|
|
265
|
+
# gen.input = [{ role: "user", content: "Hello" }]
|
|
266
|
+
# response = call_llm(gen.input)
|
|
267
|
+
# gen.output = response
|
|
268
|
+
# gen.usage = { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 }
|
|
269
|
+
# end
|
|
270
|
+
#
|
|
271
|
+
# @example Stateful API
|
|
272
|
+
# gen = Langfuse.start_observation("chat-completion", {
|
|
273
|
+
# model: "gpt-3.5-turbo",
|
|
274
|
+
# input: [{ role: "user", content: "Summarize this" }]
|
|
275
|
+
# }, as_type: :generation)
|
|
276
|
+
# response = call_llm(gen.input)
|
|
277
|
+
# gen.update(
|
|
278
|
+
# output: response,
|
|
279
|
+
# usage_details: { prompt_tokens: 50, completion_tokens: 25, total_tokens: 75 }
|
|
280
|
+
# )
|
|
281
|
+
# gen.end
|
|
282
|
+
#
|
|
283
|
+
class Generation < BaseObservation
|
|
284
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
285
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:generation])
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# @param attrs [Hash, Types::GenerationAttributes] Generation attributes to set
|
|
289
|
+
# @return [self]
|
|
290
|
+
def update(attrs)
|
|
291
|
+
update_observation_attributes(attrs)
|
|
292
|
+
self
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# @param value [Hash] Usage hash with token counts (:prompt_tokens, :completion_tokens, :total_tokens)
|
|
296
|
+
def usage=(value)
|
|
297
|
+
return unless @otel_span.recording?
|
|
298
|
+
|
|
299
|
+
# Convert to Langfuse API format (camelCase keys)
|
|
300
|
+
usage_hash = {
|
|
301
|
+
promptTokens: value[:prompt_tokens] || value["prompt_tokens"],
|
|
302
|
+
completionTokens: value[:completion_tokens] || value["completion_tokens"],
|
|
303
|
+
totalTokens: value[:total_tokens] || value["total_tokens"]
|
|
304
|
+
}.compact
|
|
305
|
+
|
|
306
|
+
usage_json = usage_hash.to_json
|
|
307
|
+
@otel_span.set_attribute("langfuse.observation.usage", usage_json)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# @param value [String] Model name (e.g., "gpt-4", "claude-3-opus")
|
|
311
|
+
def model=(value)
|
|
312
|
+
return unless @otel_span.recording?
|
|
313
|
+
|
|
314
|
+
@otel_span.set_attribute("langfuse.observation.model", value.to_s)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# @param value [Hash] Model parameters (temperature, max_tokens, etc.)
|
|
318
|
+
def model_parameters=(value)
|
|
319
|
+
return unless @otel_span.recording?
|
|
320
|
+
|
|
321
|
+
# Convert to Langfuse API format (camelCase keys)
|
|
322
|
+
params_hash = {}
|
|
323
|
+
value.each do |k, v|
|
|
324
|
+
key_str = k.to_s
|
|
325
|
+
# Convert snake_case to camelCase
|
|
326
|
+
camel_key = key_str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
|
|
327
|
+
params_hash[camel_key] = v
|
|
328
|
+
end
|
|
329
|
+
params_json = params_hash.to_json
|
|
330
|
+
@otel_span.set_attribute("langfuse.observation.modelParameters", params_json)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Point-in-time occurrence. Automatically ended when created without a block.
|
|
335
|
+
#
|
|
336
|
+
# @example Creating an event
|
|
337
|
+
# Langfuse.observe("user-action", as_type: :event) do |event|
|
|
338
|
+
# event.update(input: { action: "button_click", button_id: "submit" })
|
|
339
|
+
# end
|
|
340
|
+
#
|
|
341
|
+
# @example Event without block (auto-ends)
|
|
342
|
+
# event = Langfuse.start_observation("error-occurred", {
|
|
343
|
+
# input: { error: "Connection timeout" },
|
|
344
|
+
# level: "error"
|
|
345
|
+
# }, as_type: :event)
|
|
346
|
+
# # Event is automatically ended
|
|
347
|
+
#
|
|
348
|
+
class Event < BaseObservation
|
|
349
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
350
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:event])
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# @param attrs [Hash, Types::SpanAttributes] Event attributes to set
|
|
354
|
+
# @return [self]
|
|
355
|
+
def update(attrs)
|
|
356
|
+
update_observation_attributes(attrs)
|
|
357
|
+
self
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Observation for tracking agent-based workflows that make decisions and use tools.
|
|
362
|
+
#
|
|
363
|
+
# @example Block-based API
|
|
364
|
+
# Langfuse.observe("agent-workflow", as_type: :agent) do |agent|
|
|
365
|
+
# agent.input = { task: "Find weather for NYC" }
|
|
366
|
+
# # Agent makes decisions and uses tools
|
|
367
|
+
# agent.start_observation("tool-call", { tool_name: "weather_api" }, as_type: :tool) do |tool|
|
|
368
|
+
# weather = fetch_weather("NYC")
|
|
369
|
+
# tool.update(output: weather)
|
|
370
|
+
# end
|
|
371
|
+
# agent.update(output: { result: "Sunny, 72°F" })
|
|
372
|
+
# end
|
|
373
|
+
#
|
|
374
|
+
# @example Stateful API
|
|
375
|
+
# agent = Langfuse.start_observation("agent-workflow", {
|
|
376
|
+
# input: { task: "Research topic" }
|
|
377
|
+
# }, as_type: :agent)
|
|
378
|
+
# # Agent logic here
|
|
379
|
+
# agent.update(output: { result: "Research complete" })
|
|
380
|
+
# agent.end
|
|
381
|
+
#
|
|
382
|
+
class Agent < BaseObservation
|
|
383
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
384
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:agent])
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# @param attrs [Hash, Types::AgentAttributes] Agent attributes to set
|
|
388
|
+
# @return [self]
|
|
389
|
+
def update(attrs)
|
|
390
|
+
update_observation_attributes(attrs)
|
|
391
|
+
self
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Observation for tracking individual tool calls and external API interactions.
|
|
396
|
+
#
|
|
397
|
+
# @example Block-based API
|
|
398
|
+
# Langfuse.observe("api-call", as_type: :tool) do |tool|
|
|
399
|
+
# tool.input = { endpoint: "/users", method: "GET" }
|
|
400
|
+
# response = http_client.get("/users")
|
|
401
|
+
# tool.update(output: response.body, metadata: { status_code: response.status })
|
|
402
|
+
# end
|
|
403
|
+
#
|
|
404
|
+
# @example Stateful API
|
|
405
|
+
# tool = Langfuse.start_observation("database-query", {
|
|
406
|
+
# input: { query: "SELECT * FROM users" }
|
|
407
|
+
# }, as_type: :tool)
|
|
408
|
+
# results = db.execute(tool.input[:query])
|
|
409
|
+
# tool.update(output: results)
|
|
410
|
+
# tool.end
|
|
411
|
+
#
|
|
412
|
+
class Tool < BaseObservation
|
|
413
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
414
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:tool])
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# @param attrs [Hash, Types::ToolAttributes] Tool attributes to set
|
|
418
|
+
# @return [self]
|
|
419
|
+
def update(attrs)
|
|
420
|
+
update_observation_attributes(attrs)
|
|
421
|
+
self
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Observation for tracking structured multi-step workflows and process chains.
|
|
426
|
+
#
|
|
427
|
+
# @example Block-based API
|
|
428
|
+
# Langfuse.observe("rag-pipeline", as_type: :chain) do |chain|
|
|
429
|
+
# chain.input = { query: "What is Ruby?" }
|
|
430
|
+
# # Step 1: Retrieve documents
|
|
431
|
+
# chain.start_observation("retrieve", { query: chain.input[:query] }, as_type: :retriever) do |ret|
|
|
432
|
+
# docs = vector_db.search(chain.input[:query])
|
|
433
|
+
# ret.update(output: docs)
|
|
434
|
+
# end
|
|
435
|
+
# # Step 2: Generate response
|
|
436
|
+
# chain.start_observation("generate", { model: "gpt-4" }, as_type: :generation) do |gen|
|
|
437
|
+
# response = llm.generate(docs)
|
|
438
|
+
# gen.update(output: response)
|
|
439
|
+
# end
|
|
440
|
+
# chain.update(output: { answer: "Ruby is a programming language..." })
|
|
441
|
+
# end
|
|
442
|
+
#
|
|
443
|
+
# @example Stateful API
|
|
444
|
+
# chain = Langfuse.start_observation("multi-step-process", {
|
|
445
|
+
# input: { data: "input_data" }
|
|
446
|
+
# }, as_type: :chain)
|
|
447
|
+
# # Chain steps here
|
|
448
|
+
# chain.update(output: { result: "processed_data" })
|
|
449
|
+
# chain.end
|
|
450
|
+
#
|
|
451
|
+
class Chain < BaseObservation
|
|
452
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
453
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:chain])
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# @param attrs [Hash, Types::ChainAttributes] Chain attributes to set
|
|
457
|
+
# @return [self]
|
|
458
|
+
def update(attrs)
|
|
459
|
+
update_observation_attributes(attrs)
|
|
460
|
+
self
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Observation for tracking document retrieval and search operations.
|
|
465
|
+
#
|
|
466
|
+
# @example Block-based API
|
|
467
|
+
# Langfuse.observe("document-search", as_type: :retriever) do |retriever|
|
|
468
|
+
# retriever.input = { query: "Ruby programming", top_k: 5 }
|
|
469
|
+
# documents = vector_db.search(retriever.input[:query], limit: retriever.input[:top_k])
|
|
470
|
+
# retriever.update(
|
|
471
|
+
# output: documents,
|
|
472
|
+
# metadata: { num_results: documents.length, search_time_ms: 45 }
|
|
473
|
+
# )
|
|
474
|
+
# end
|
|
475
|
+
#
|
|
476
|
+
# @example Stateful API
|
|
477
|
+
# retriever = Langfuse.start_observation("semantic-search", {
|
|
478
|
+
# input: { query: "machine learning", top_k: 10 }
|
|
479
|
+
# }, as_type: :retriever)
|
|
480
|
+
# results = search_index.query(retriever.input[:query])
|
|
481
|
+
# retriever.update(output: results)
|
|
482
|
+
# retriever.end
|
|
483
|
+
#
|
|
484
|
+
class Retriever < BaseObservation
|
|
485
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
486
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:retriever])
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# @param attrs [Hash, Types::RetrieverAttributes] Retriever attributes to set
|
|
490
|
+
# @return [self]
|
|
491
|
+
def update(attrs)
|
|
492
|
+
update_observation_attributes(attrs)
|
|
493
|
+
self
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Observation for tracking quality assessment and evaluation operations.
|
|
498
|
+
#
|
|
499
|
+
# @example Block-based API
|
|
500
|
+
# Langfuse.observe("quality-check", as_type: :evaluator) do |evaluator|
|
|
501
|
+
# evaluator.input = { response: "Ruby is a language", expected: "Ruby is a programming language" }
|
|
502
|
+
# score = calculate_similarity(evaluator.input[:response], evaluator.input[:expected])
|
|
503
|
+
# evaluator.update(
|
|
504
|
+
# output: { score: score, passed: score > 0.8 },
|
|
505
|
+
# metadata: { metric: "similarity" }
|
|
506
|
+
# )
|
|
507
|
+
# end
|
|
508
|
+
#
|
|
509
|
+
# @example Stateful API
|
|
510
|
+
# evaluator = Langfuse.start_observation("response-evaluation", {
|
|
511
|
+
# input: { response: llm_output, criteria: "accuracy" }
|
|
512
|
+
# }, as_type: :evaluator)
|
|
513
|
+
# evaluation_result = evaluate_response(evaluator.input[:response], evaluator.input[:criteria])
|
|
514
|
+
# evaluator.update(output: evaluation_result)
|
|
515
|
+
# evaluator.end
|
|
516
|
+
#
|
|
517
|
+
class Evaluator < BaseObservation
|
|
518
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
519
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:evaluator])
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# @param attrs [Hash, Types::EvaluatorAttributes] Evaluator attributes to set
|
|
523
|
+
# @return [self]
|
|
524
|
+
def update(attrs)
|
|
525
|
+
update_observation_attributes(attrs)
|
|
526
|
+
self
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# Observation for tracking safety checks and compliance enforcement.
|
|
531
|
+
#
|
|
532
|
+
# @example Block-based API
|
|
533
|
+
# Langfuse.observe("content-moderation", as_type: :guardrail) do |guardrail|
|
|
534
|
+
# guardrail.input = { content: user_input }
|
|
535
|
+
# result = moderation_service.check(guardrail.input[:content])
|
|
536
|
+
# guardrail.update(
|
|
537
|
+
# output: { passed: result.safe, reason: result.reason },
|
|
538
|
+
# metadata: { check_type: "toxicity" }
|
|
539
|
+
# )
|
|
540
|
+
# end
|
|
541
|
+
#
|
|
542
|
+
# @example Stateful API
|
|
543
|
+
# guardrail = Langfuse.start_observation("safety-check", {
|
|
544
|
+
# input: { prompt: user_prompt }
|
|
545
|
+
# }, as_type: :guardrail)
|
|
546
|
+
# safety_result = safety_service.validate(guardrail.input[:prompt])
|
|
547
|
+
# guardrail.update(output: { safe: safety_result.safe, violations: safety_result.violations })
|
|
548
|
+
# guardrail.end
|
|
549
|
+
#
|
|
550
|
+
class Guardrail < BaseObservation
|
|
551
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
552
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:guardrail])
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# @param attrs [Hash, Types::GuardrailAttributes] Guardrail attributes to set
|
|
556
|
+
# @return [self]
|
|
557
|
+
def update(attrs)
|
|
558
|
+
update_observation_attributes(attrs)
|
|
559
|
+
self
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# Observation for tracking embedding generation calls and vector operations.
|
|
564
|
+
#
|
|
565
|
+
# @example Block-based API
|
|
566
|
+
# Langfuse.observe("generate-embeddings", as_type: :embedding) do |embedding|
|
|
567
|
+
# embedding.model = "text-embedding-ada-002"
|
|
568
|
+
# embedding.input = ["Ruby is a language", "Python is a language"]
|
|
569
|
+
# vectors = embedding_service.generate(embedding.input, model: embedding.model)
|
|
570
|
+
# embedding.update(
|
|
571
|
+
# output: vectors,
|
|
572
|
+
# usage: { prompt_tokens: 20, total_tokens: 20 }
|
|
573
|
+
# )
|
|
574
|
+
# end
|
|
575
|
+
#
|
|
576
|
+
# @example Stateful API
|
|
577
|
+
# embedding = Langfuse.start_observation("vectorize", {
|
|
578
|
+
# model: "text-embedding-ada-002",
|
|
579
|
+
# input: "Convert this text to vector"
|
|
580
|
+
# }, as_type: :embedding)
|
|
581
|
+
# vector = embedding_api.create(embedding.input, model: embedding.model)
|
|
582
|
+
# embedding.update(
|
|
583
|
+
# output: vector,
|
|
584
|
+
# usage_details: { prompt_tokens: 10, total_tokens: 10 }
|
|
585
|
+
# )
|
|
586
|
+
# embedding.end
|
|
587
|
+
#
|
|
588
|
+
class Embedding < BaseObservation
|
|
589
|
+
def initialize(otel_span, otel_tracer, attributes: nil)
|
|
590
|
+
super(otel_span, otel_tracer, attributes: attributes, type: OBSERVATION_TYPES[:embedding])
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# @param attrs [Hash, Types::EmbeddingAttributes] Embedding attributes to set
|
|
594
|
+
# @return [self]
|
|
595
|
+
def update(attrs)
|
|
596
|
+
update_observation_attributes(attrs)
|
|
597
|
+
self
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
# @param value [Hash] Usage hash with token counts (:prompt_tokens, :total_tokens)
|
|
601
|
+
def usage=(value)
|
|
602
|
+
update_observation_attributes(usage_details: value)
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# @param value [String] Model name (e.g., "text-embedding-ada-002")
|
|
606
|
+
def model=(value)
|
|
607
|
+
update_observation_attributes(model: value)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# @param value [Hash] Model parameters (temperature, max_tokens, etc.)
|
|
611
|
+
def model_parameters=(value)
|
|
612
|
+
update_observation_attributes(model_parameters: value)
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
end
|