langfuse-ruby 0.1.3 → 0.1.5
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/.github/workflows/ci.yml +53 -0
- data/CHANGELOG.md +45 -4
- data/Gemfile.lock +7 -3
- data/README.md +11 -2
- data/docs/URL_ENCODING_FIX.md +164 -0
- data/examples/basic_tracing.rb +1 -1
- data/examples/url_encoding_demo.rb +57 -0
- data/langfuse-ruby.gemspec +7 -4
- data/lib/langfuse/client.rb +184 -4
- data/lib/langfuse/event.rb +21 -5
- data/lib/langfuse/generation.rb +155 -5
- data/lib/langfuse/observation_types.rb +61 -0
- data/lib/langfuse/span.rb +162 -7
- data/lib/langfuse/trace.rb +185 -1
- data/lib/langfuse/utils.rb +5 -0
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +1 -0
- data/test_offline.rb +33 -7
- metadata +39 -9
data/lib/langfuse/client.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'faraday'
|
|
2
2
|
require 'faraday/net_http'
|
|
3
|
+
require 'faraday/multipart'
|
|
3
4
|
require 'json'
|
|
4
5
|
require 'base64'
|
|
5
6
|
require 'concurrent'
|
|
@@ -54,7 +55,7 @@ module Langfuse
|
|
|
54
55
|
# Span operations
|
|
55
56
|
def span(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
56
57
|
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
57
|
-
version: nil, **kwargs)
|
|
58
|
+
version: nil, as_type: nil, **kwargs)
|
|
58
59
|
Span.new(
|
|
59
60
|
client: self,
|
|
60
61
|
trace_id: trace_id,
|
|
@@ -68,6 +69,159 @@ module Langfuse
|
|
|
68
69
|
status_message: status_message,
|
|
69
70
|
parent_observation_id: parent_observation_id,
|
|
70
71
|
version: version,
|
|
72
|
+
as_type: as_type,
|
|
73
|
+
**kwargs
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Convenience methods for enhanced observation types
|
|
78
|
+
|
|
79
|
+
# Create an agent observation (wrapper around span with as_type: 'agent')
|
|
80
|
+
def agent(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
81
|
+
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
82
|
+
version: nil, **kwargs)
|
|
83
|
+
span(
|
|
84
|
+
trace_id: trace_id,
|
|
85
|
+
name: name,
|
|
86
|
+
start_time: start_time,
|
|
87
|
+
end_time: end_time,
|
|
88
|
+
input: input,
|
|
89
|
+
output: output,
|
|
90
|
+
metadata: metadata,
|
|
91
|
+
level: level,
|
|
92
|
+
status_message: status_message,
|
|
93
|
+
parent_observation_id: parent_observation_id,
|
|
94
|
+
version: version,
|
|
95
|
+
as_type: ObservationType::AGENT,
|
|
96
|
+
**kwargs
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Create a tool observation (wrapper around span with as_type: 'tool')
|
|
101
|
+
def tool(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
102
|
+
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
103
|
+
version: nil, **kwargs)
|
|
104
|
+
span(
|
|
105
|
+
trace_id: trace_id,
|
|
106
|
+
name: name,
|
|
107
|
+
start_time: start_time,
|
|
108
|
+
end_time: end_time,
|
|
109
|
+
input: input,
|
|
110
|
+
output: output,
|
|
111
|
+
metadata: metadata,
|
|
112
|
+
level: level,
|
|
113
|
+
status_message: status_message,
|
|
114
|
+
parent_observation_id: parent_observation_id,
|
|
115
|
+
version: version,
|
|
116
|
+
as_type: ObservationType::TOOL,
|
|
117
|
+
**kwargs
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Create a chain observation (wrapper around span with as_type: 'chain')
|
|
122
|
+
def chain(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
123
|
+
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
124
|
+
version: nil, **kwargs)
|
|
125
|
+
span(
|
|
126
|
+
trace_id: trace_id,
|
|
127
|
+
name: name,
|
|
128
|
+
start_time: start_time,
|
|
129
|
+
end_time: end_time,
|
|
130
|
+
input: input,
|
|
131
|
+
output: output,
|
|
132
|
+
metadata: metadata,
|
|
133
|
+
level: level,
|
|
134
|
+
status_message: status_message,
|
|
135
|
+
parent_observation_id: parent_observation_id,
|
|
136
|
+
version: version,
|
|
137
|
+
as_type: ObservationType::CHAIN,
|
|
138
|
+
**kwargs
|
|
139
|
+
)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Create a retriever observation (wrapper around span with as_type: 'retriever')
|
|
143
|
+
def retriever(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
144
|
+
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
145
|
+
version: nil, **kwargs)
|
|
146
|
+
span(
|
|
147
|
+
trace_id: trace_id,
|
|
148
|
+
name: name,
|
|
149
|
+
start_time: start_time,
|
|
150
|
+
end_time: end_time,
|
|
151
|
+
input: input,
|
|
152
|
+
output: output,
|
|
153
|
+
metadata: metadata,
|
|
154
|
+
level: level,
|
|
155
|
+
status_message: status_message,
|
|
156
|
+
parent_observation_id: parent_observation_id,
|
|
157
|
+
version: version,
|
|
158
|
+
as_type: ObservationType::RETRIEVER,
|
|
159
|
+
**kwargs
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Create an embedding observation (wrapper around span with as_type: 'embedding')
|
|
164
|
+
def embedding(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
165
|
+
model: nil, usage: nil, metadata: nil, level: nil, status_message: nil,
|
|
166
|
+
parent_observation_id: nil, version: nil, **kwargs)
|
|
167
|
+
merged_metadata = (metadata || {}).merge(
|
|
168
|
+
{ model: model, usage: usage }.compact
|
|
169
|
+
)
|
|
170
|
+
span(
|
|
171
|
+
trace_id: trace_id,
|
|
172
|
+
name: name,
|
|
173
|
+
start_time: start_time,
|
|
174
|
+
end_time: end_time,
|
|
175
|
+
input: input,
|
|
176
|
+
output: output,
|
|
177
|
+
metadata: merged_metadata.empty? ? nil : merged_metadata,
|
|
178
|
+
level: level,
|
|
179
|
+
status_message: status_message,
|
|
180
|
+
parent_observation_id: parent_observation_id,
|
|
181
|
+
version: version,
|
|
182
|
+
as_type: ObservationType::EMBEDDING,
|
|
183
|
+
**kwargs
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Create an evaluator observation (wrapper around span with as_type: 'evaluator')
|
|
188
|
+
def evaluator_obs(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
189
|
+
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
190
|
+
version: nil, **kwargs)
|
|
191
|
+
span(
|
|
192
|
+
trace_id: trace_id,
|
|
193
|
+
name: name,
|
|
194
|
+
start_time: start_time,
|
|
195
|
+
end_time: end_time,
|
|
196
|
+
input: input,
|
|
197
|
+
output: output,
|
|
198
|
+
metadata: metadata,
|
|
199
|
+
level: level,
|
|
200
|
+
status_message: status_message,
|
|
201
|
+
parent_observation_id: parent_observation_id,
|
|
202
|
+
version: version,
|
|
203
|
+
as_type: ObservationType::EVALUATOR,
|
|
204
|
+
**kwargs
|
|
205
|
+
)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Create a guardrail observation (wrapper around span with as_type: 'guardrail')
|
|
209
|
+
def guardrail(trace_id:, name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
210
|
+
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
|
211
|
+
version: nil, **kwargs)
|
|
212
|
+
span(
|
|
213
|
+
trace_id: trace_id,
|
|
214
|
+
name: name,
|
|
215
|
+
start_time: start_time,
|
|
216
|
+
end_time: end_time,
|
|
217
|
+
input: input,
|
|
218
|
+
output: output,
|
|
219
|
+
metadata: metadata,
|
|
220
|
+
level: level,
|
|
221
|
+
status_message: status_message,
|
|
222
|
+
parent_observation_id: parent_observation_id,
|
|
223
|
+
version: version,
|
|
224
|
+
as_type: ObservationType::GUARDRAIL,
|
|
71
225
|
**kwargs
|
|
72
226
|
)
|
|
73
227
|
end
|
|
@@ -125,7 +279,8 @@ module Langfuse
|
|
|
125
279
|
return cached_prompt[:prompt]
|
|
126
280
|
end
|
|
127
281
|
|
|
128
|
-
|
|
282
|
+
encoded_name = Utils.url_encode(name)
|
|
283
|
+
path = "/api/public/v2/prompts/#{encoded_name}"
|
|
129
284
|
params = {}
|
|
130
285
|
params[:version] = version if version
|
|
131
286
|
params[:label] = label if label
|
|
@@ -188,7 +343,7 @@ module Langfuse
|
|
|
188
343
|
def enqueue_event(type, body)
|
|
189
344
|
# 验证事件类型是否有效
|
|
190
345
|
valid_types = %w[
|
|
191
|
-
trace-create
|
|
346
|
+
trace-create trace-update
|
|
192
347
|
generation-create generation-update
|
|
193
348
|
span-create span-update
|
|
194
349
|
event-create
|
|
@@ -207,7 +362,32 @@ module Langfuse
|
|
|
207
362
|
body: Utils.deep_stringify_keys(body)
|
|
208
363
|
}
|
|
209
364
|
|
|
210
|
-
|
|
365
|
+
if type == 'trace-update'
|
|
366
|
+
# 查找对应的 trace-create 事件并更新
|
|
367
|
+
trace_id = body['id'] || body[:id]
|
|
368
|
+
if trace_id
|
|
369
|
+
existing_event_index = @event_queue.find_index do |existing_event|
|
|
370
|
+
existing_event[:type] == 'trace-create' &&
|
|
371
|
+
(existing_event[:body]['id'] == trace_id || existing_event[:body][:id] == trace_id)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
if existing_event_index
|
|
375
|
+
# 更新现有的 trace-create 事件
|
|
376
|
+
@event_queue[existing_event_index][:body].merge!(event[:body])
|
|
377
|
+
@event_queue[existing_event_index][:timestamp] = event[:timestamp]
|
|
378
|
+
puts "Updated existing trace-create event for trace_id: #{trace_id}" if @debug
|
|
379
|
+
else
|
|
380
|
+
# 如果没找到对应的 trace-create 事件,将 trace-update 转换为 trace-create
|
|
381
|
+
event[:type] = 'trace-create'
|
|
382
|
+
@event_queue << event
|
|
383
|
+
puts "Converted trace-update to trace-create for trace_id: #{trace_id}" if @debug
|
|
384
|
+
end
|
|
385
|
+
elsif @debug
|
|
386
|
+
puts 'Warning: trace-update event missing trace_id, skipping'
|
|
387
|
+
end
|
|
388
|
+
else
|
|
389
|
+
@event_queue << event
|
|
390
|
+
end
|
|
211
391
|
puts "Enqueued event: #{type}" if @debug
|
|
212
392
|
end
|
|
213
393
|
|
data/lib/langfuse/event.rb
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
module Langfuse
|
|
2
2
|
class Event
|
|
3
3
|
attr_reader :id, :trace_id, :name, :start_time, :input, :output, :metadata,
|
|
4
|
-
:level, :status_message, :parent_observation_id, :version, :client
|
|
4
|
+
:level, :status_message, :parent_observation_id, :version, :as_type, :client
|
|
5
5
|
|
|
6
6
|
def initialize(client:, trace_id:, name:, id: nil, start_time: nil, input: nil,
|
|
7
7
|
output: nil, metadata: nil, level: nil, status_message: nil,
|
|
8
|
-
parent_observation_id: nil, version: nil, **kwargs)
|
|
8
|
+
parent_observation_id: nil, version: nil, as_type: nil, **kwargs)
|
|
9
9
|
@client = client
|
|
10
10
|
@id = id || Utils.generate_id
|
|
11
11
|
@trace_id = trace_id
|
|
@@ -18,6 +18,7 @@ module Langfuse
|
|
|
18
18
|
@status_message = status_message
|
|
19
19
|
@parent_observation_id = parent_observation_id
|
|
20
20
|
@version = version
|
|
21
|
+
@as_type = validate_as_type(as_type)
|
|
21
22
|
@kwargs = kwargs
|
|
22
23
|
|
|
23
24
|
# Create the event
|
|
@@ -25,7 +26,7 @@ module Langfuse
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def to_dict
|
|
28
|
-
{
|
|
29
|
+
data = {
|
|
29
30
|
id: @id,
|
|
30
31
|
trace_id: @trace_id,
|
|
31
32
|
name: @name,
|
|
@@ -37,11 +38,24 @@ module Langfuse
|
|
|
37
38
|
status_message: @status_message,
|
|
38
39
|
parent_observation_id: @parent_observation_id,
|
|
39
40
|
version: @version
|
|
40
|
-
}
|
|
41
|
+
}
|
|
42
|
+
data[:type] = @as_type if @as_type
|
|
43
|
+
data.merge(@kwargs).compact
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
private
|
|
44
47
|
|
|
48
|
+
def validate_as_type(type)
|
|
49
|
+
return nil if type.nil?
|
|
50
|
+
|
|
51
|
+
type_str = type.to_s
|
|
52
|
+
unless ObservationType.valid?(type_str)
|
|
53
|
+
raise ValidationError, "Invalid observation type: #{type}. Valid types are: #{ObservationType::ALL.join(', ')}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
type_str
|
|
57
|
+
end
|
|
58
|
+
|
|
45
59
|
def create_event
|
|
46
60
|
data = {
|
|
47
61
|
id: @id,
|
|
@@ -55,7 +69,9 @@ module Langfuse
|
|
|
55
69
|
status_message: @status_message,
|
|
56
70
|
parent_observation_id: @parent_observation_id,
|
|
57
71
|
version: @version
|
|
58
|
-
}
|
|
72
|
+
}
|
|
73
|
+
data[:type] = @as_type if @as_type
|
|
74
|
+
data = data.merge(@kwargs).compact
|
|
59
75
|
|
|
60
76
|
@client.enqueue_event('event-create', data)
|
|
61
77
|
end
|
data/lib/langfuse/generation.rb
CHANGED
|
@@ -2,12 +2,12 @@ module Langfuse
|
|
|
2
2
|
class Generation
|
|
3
3
|
attr_reader :id, :trace_id, :name, :start_time, :end_time, :completion_start_time,
|
|
4
4
|
:model, :model_parameters, :input, :output, :usage, :metadata, :level,
|
|
5
|
-
:status_message, :parent_observation_id, :version, :client
|
|
5
|
+
:status_message, :parent_observation_id, :version, :as_type, :client
|
|
6
6
|
|
|
7
7
|
def initialize(client:, trace_id:, id: nil, name: nil, start_time: nil, end_time: nil,
|
|
8
8
|
completion_start_time: nil, model: nil, model_parameters: nil, input: nil,
|
|
9
9
|
output: nil, usage: nil, metadata: nil, level: nil, status_message: nil,
|
|
10
|
-
parent_observation_id: nil, version: nil, **kwargs)
|
|
10
|
+
parent_observation_id: nil, version: nil, as_type: nil, **kwargs)
|
|
11
11
|
@client = client
|
|
12
12
|
@id = id || Utils.generate_id
|
|
13
13
|
@trace_id = trace_id
|
|
@@ -25,6 +25,7 @@ module Langfuse
|
|
|
25
25
|
@status_message = status_message
|
|
26
26
|
@parent_observation_id = parent_observation_id
|
|
27
27
|
@version = version
|
|
28
|
+
@as_type = validate_as_type(as_type)
|
|
28
29
|
@kwargs = kwargs
|
|
29
30
|
|
|
30
31
|
# Create the generation
|
|
@@ -62,8 +63,9 @@ module Langfuse
|
|
|
62
63
|
self
|
|
63
64
|
end
|
|
64
65
|
|
|
66
|
+
# Create a child span
|
|
65
67
|
def span(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
66
|
-
metadata: nil, level: nil, status_message: nil, version: nil, **kwargs)
|
|
68
|
+
metadata: nil, level: nil, status_message: nil, version: nil, as_type: nil, **kwargs)
|
|
67
69
|
@client.span(
|
|
68
70
|
trace_id: @trace_id,
|
|
69
71
|
name: name,
|
|
@@ -76,10 +78,12 @@ module Langfuse
|
|
|
76
78
|
status_message: status_message,
|
|
77
79
|
parent_observation_id: @id,
|
|
78
80
|
version: version,
|
|
81
|
+
as_type: as_type,
|
|
79
82
|
**kwargs
|
|
80
83
|
)
|
|
81
84
|
end
|
|
82
85
|
|
|
86
|
+
# Create a child generation
|
|
83
87
|
def generation(name: nil, start_time: nil, end_time: nil, completion_start_time: nil,
|
|
84
88
|
model: nil, model_parameters: nil, input: nil, output: nil, usage: nil,
|
|
85
89
|
metadata: nil, level: nil, status_message: nil, version: nil, **kwargs)
|
|
@@ -103,6 +107,7 @@ module Langfuse
|
|
|
103
107
|
)
|
|
104
108
|
end
|
|
105
109
|
|
|
110
|
+
# Create a child event
|
|
106
111
|
def event(name:, start_time: nil, input: nil, output: nil, metadata: nil,
|
|
107
112
|
level: nil, status_message: nil, version: nil, **kwargs)
|
|
108
113
|
@client.event(
|
|
@@ -120,6 +125,138 @@ module Langfuse
|
|
|
120
125
|
)
|
|
121
126
|
end
|
|
122
127
|
|
|
128
|
+
# Convenience methods for enhanced observation types
|
|
129
|
+
|
|
130
|
+
# Create a child agent observation
|
|
131
|
+
def agent(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
132
|
+
metadata: nil, level: nil, status_message: nil, version: nil, **kwargs)
|
|
133
|
+
span(
|
|
134
|
+
name: name,
|
|
135
|
+
start_time: start_time,
|
|
136
|
+
end_time: end_time,
|
|
137
|
+
input: input,
|
|
138
|
+
output: output,
|
|
139
|
+
metadata: metadata,
|
|
140
|
+
level: level,
|
|
141
|
+
status_message: status_message,
|
|
142
|
+
version: version,
|
|
143
|
+
as_type: ObservationType::AGENT,
|
|
144
|
+
**kwargs
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Create a child tool observation
|
|
149
|
+
def tool(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
150
|
+
metadata: nil, level: nil, status_message: nil, version: nil, **kwargs)
|
|
151
|
+
span(
|
|
152
|
+
name: name,
|
|
153
|
+
start_time: start_time,
|
|
154
|
+
end_time: end_time,
|
|
155
|
+
input: input,
|
|
156
|
+
output: output,
|
|
157
|
+
metadata: metadata,
|
|
158
|
+
level: level,
|
|
159
|
+
status_message: status_message,
|
|
160
|
+
version: version,
|
|
161
|
+
as_type: ObservationType::TOOL,
|
|
162
|
+
**kwargs
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Create a child chain observation
|
|
167
|
+
def chain(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
168
|
+
metadata: nil, level: nil, status_message: nil, version: nil, **kwargs)
|
|
169
|
+
span(
|
|
170
|
+
name: name,
|
|
171
|
+
start_time: start_time,
|
|
172
|
+
end_time: end_time,
|
|
173
|
+
input: input,
|
|
174
|
+
output: output,
|
|
175
|
+
metadata: metadata,
|
|
176
|
+
level: level,
|
|
177
|
+
status_message: status_message,
|
|
178
|
+
version: version,
|
|
179
|
+
as_type: ObservationType::CHAIN,
|
|
180
|
+
**kwargs
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Create a child retriever observation
|
|
185
|
+
def retriever(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
186
|
+
metadata: nil, level: nil, status_message: nil, version: nil, **kwargs)
|
|
187
|
+
span(
|
|
188
|
+
name: name,
|
|
189
|
+
start_time: start_time,
|
|
190
|
+
end_time: end_time,
|
|
191
|
+
input: input,
|
|
192
|
+
output: output,
|
|
193
|
+
metadata: metadata,
|
|
194
|
+
level: level,
|
|
195
|
+
status_message: status_message,
|
|
196
|
+
version: version,
|
|
197
|
+
as_type: ObservationType::RETRIEVER,
|
|
198
|
+
**kwargs
|
|
199
|
+
)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Create a child embedding observation
|
|
203
|
+
def embedding(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
204
|
+
model: nil, usage: nil, metadata: nil, level: nil, status_message: nil,
|
|
205
|
+
version: nil, **kwargs)
|
|
206
|
+
merged_metadata = (metadata || {}).merge(
|
|
207
|
+
{ model: model, usage: usage }.compact
|
|
208
|
+
)
|
|
209
|
+
span(
|
|
210
|
+
name: name,
|
|
211
|
+
start_time: start_time,
|
|
212
|
+
end_time: end_time,
|
|
213
|
+
input: input,
|
|
214
|
+
output: output,
|
|
215
|
+
metadata: merged_metadata.empty? ? nil : merged_metadata,
|
|
216
|
+
level: level,
|
|
217
|
+
status_message: status_message,
|
|
218
|
+
version: version,
|
|
219
|
+
as_type: ObservationType::EMBEDDING,
|
|
220
|
+
**kwargs
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Create a child evaluator observation
|
|
225
|
+
def evaluator(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
226
|
+
metadata: nil, level: nil, status_message: nil, version: nil, **kwargs)
|
|
227
|
+
span(
|
|
228
|
+
name: name,
|
|
229
|
+
start_time: start_time,
|
|
230
|
+
end_time: end_time,
|
|
231
|
+
input: input,
|
|
232
|
+
output: output,
|
|
233
|
+
metadata: metadata,
|
|
234
|
+
level: level,
|
|
235
|
+
status_message: status_message,
|
|
236
|
+
version: version,
|
|
237
|
+
as_type: ObservationType::EVALUATOR,
|
|
238
|
+
**kwargs
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Create a child guardrail observation
|
|
243
|
+
def guardrail(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
|
244
|
+
metadata: nil, level: nil, status_message: nil, version: nil, **kwargs)
|
|
245
|
+
span(
|
|
246
|
+
name: name,
|
|
247
|
+
start_time: start_time,
|
|
248
|
+
end_time: end_time,
|
|
249
|
+
input: input,
|
|
250
|
+
output: output,
|
|
251
|
+
metadata: metadata,
|
|
252
|
+
level: level,
|
|
253
|
+
status_message: status_message,
|
|
254
|
+
version: version,
|
|
255
|
+
as_type: ObservationType::GUARDRAIL,
|
|
256
|
+
**kwargs
|
|
257
|
+
)
|
|
258
|
+
end
|
|
259
|
+
|
|
123
260
|
def score(name:, value:, data_type: nil, comment: nil, **kwargs)
|
|
124
261
|
@client.score(
|
|
125
262
|
observation_id: @id,
|
|
@@ -136,7 +273,7 @@ module Langfuse
|
|
|
136
273
|
end
|
|
137
274
|
|
|
138
275
|
def to_dict
|
|
139
|
-
{
|
|
276
|
+
data = {
|
|
140
277
|
id: @id,
|
|
141
278
|
trace_id: @trace_id,
|
|
142
279
|
name: @name,
|
|
@@ -153,11 +290,24 @@ module Langfuse
|
|
|
153
290
|
status_message: @status_message,
|
|
154
291
|
parent_observation_id: @parent_observation_id,
|
|
155
292
|
version: @version
|
|
156
|
-
}
|
|
293
|
+
}
|
|
294
|
+
data[:type] = @as_type if @as_type
|
|
295
|
+
data.merge(@kwargs).compact
|
|
157
296
|
end
|
|
158
297
|
|
|
159
298
|
private
|
|
160
299
|
|
|
300
|
+
def validate_as_type(type)
|
|
301
|
+
return nil if type.nil?
|
|
302
|
+
|
|
303
|
+
type_str = type.to_s
|
|
304
|
+
unless ObservationType.valid?(type_str)
|
|
305
|
+
raise ValidationError, "Invalid observation type: #{type}. Valid types are: #{ObservationType::ALL.join(', ')}"
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
type_str
|
|
309
|
+
end
|
|
310
|
+
|
|
161
311
|
def create_generation
|
|
162
312
|
@client.enqueue_event('generation-create', to_dict)
|
|
163
313
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Langfuse
|
|
4
|
+
# Valid observation types for Langfuse
|
|
5
|
+
# These types provide semantic meaning to observations in traces
|
|
6
|
+
module ObservationType
|
|
7
|
+
# Core observation types (existing)
|
|
8
|
+
SPAN = 'span'
|
|
9
|
+
GENERATION = 'generation'
|
|
10
|
+
EVENT = 'event'
|
|
11
|
+
|
|
12
|
+
# Enhanced observation types (new in 2025)
|
|
13
|
+
AGENT = 'agent' # Agent workflows and reasoning
|
|
14
|
+
TOOL = 'tool' # Tool/function calls
|
|
15
|
+
CHAIN = 'chain' # Chain operations (e.g., retrieval chains)
|
|
16
|
+
RETRIEVER = 'retriever' # Data retrieval (vector stores, databases)
|
|
17
|
+
EMBEDDING = 'embedding' # Embedding generation
|
|
18
|
+
EVALUATOR = 'evaluator' # Evaluation/scoring functions
|
|
19
|
+
GUARDRAIL = 'guardrail' # Safety filters and content moderation
|
|
20
|
+
|
|
21
|
+
# All valid observation types
|
|
22
|
+
ALL = [
|
|
23
|
+
SPAN,
|
|
24
|
+
GENERATION,
|
|
25
|
+
EVENT,
|
|
26
|
+
AGENT,
|
|
27
|
+
TOOL,
|
|
28
|
+
CHAIN,
|
|
29
|
+
RETRIEVER,
|
|
30
|
+
EMBEDDING,
|
|
31
|
+
EVALUATOR,
|
|
32
|
+
GUARDRAIL
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
# Types that are aliases for span (use span-create/span-update events)
|
|
36
|
+
SPAN_BASED = [
|
|
37
|
+
SPAN,
|
|
38
|
+
AGENT,
|
|
39
|
+
TOOL,
|
|
40
|
+
CHAIN,
|
|
41
|
+
RETRIEVER,
|
|
42
|
+
EMBEDDING,
|
|
43
|
+
EVALUATOR,
|
|
44
|
+
GUARDRAIL
|
|
45
|
+
].freeze
|
|
46
|
+
|
|
47
|
+
# Validate if a type is valid
|
|
48
|
+
def self.valid?(type)
|
|
49
|
+
return true if type.nil? # nil is valid (defaults to base type)
|
|
50
|
+
|
|
51
|
+
ALL.include?(type.to_s)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if type uses span events
|
|
55
|
+
def self.span_based?(type)
|
|
56
|
+
return true if type.nil?
|
|
57
|
+
|
|
58
|
+
SPAN_BASED.include?(type.to_s)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|