langfuse-ruby 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +17 -11
- data/CHANGELOG.md +37 -2
- data/Gemfile.lock +3 -3
- data/README.md +116 -21
- data/docs/TYPE_VALIDATION_TROUBLESHOOTING.md +202 -0
- data/examples/auto_flush_control.rb +205 -0
- data/examples/basic_tracing.rb +0 -12
- data/examples/connection_config_demo.rb +15 -1
- data/examples/event_usage.rb +145 -0
- data/langfuse-ruby.gemspec +2 -2
- data/lib/langfuse/client.rb +187 -53
- data/lib/langfuse/event.rb +63 -0
- data/lib/langfuse/generation.rb +19 -40
- data/lib/langfuse/span.rb +17 -0
- data/lib/langfuse/trace.rb +36 -18
- data/lib/langfuse/utils.rb +23 -1
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +4 -1
- data/test_offline.rb +122 -104
- metadata +26 -10
data/lib/langfuse/client.rb
CHANGED
@@ -6,22 +6,29 @@ require 'concurrent'
|
|
6
6
|
|
7
7
|
module Langfuse
|
8
8
|
class Client
|
9
|
-
attr_reader :public_key, :secret_key, :host, :debug, :timeout, :retries
|
9
|
+
attr_reader :public_key, :secret_key, :host, :debug, :timeout, :retries, :flush_interval, :auto_flush
|
10
10
|
|
11
|
-
def initialize(public_key: nil, secret_key: nil, host: nil, debug: false, timeout: 30, retries: 3
|
11
|
+
def initialize(public_key: nil, secret_key: nil, host: nil, debug: false, timeout: 30, retries: 3,
|
12
|
+
flush_interval: nil, auto_flush: nil)
|
12
13
|
@public_key = public_key || ENV['LANGFUSE_PUBLIC_KEY'] || Langfuse.configuration.public_key
|
13
14
|
@secret_key = secret_key || ENV['LANGFUSE_SECRET_KEY'] || Langfuse.configuration.secret_key
|
14
15
|
@host = host || ENV['LANGFUSE_HOST'] || Langfuse.configuration.host
|
15
16
|
@debug = debug || Langfuse.configuration.debug
|
16
17
|
@timeout = timeout || Langfuse.configuration.timeout
|
17
18
|
@retries = retries || Langfuse.configuration.retries
|
19
|
+
@flush_interval = flush_interval || ENV['LANGFUSE_FLUSH_INTERVAL']&.to_i || Langfuse.configuration.flush_interval
|
20
|
+
@auto_flush = if auto_flush.nil?
|
21
|
+
ENV['LANGFUSE_AUTO_FLUSH'] == 'false' ? false : Langfuse.configuration.auto_flush
|
22
|
+
else
|
23
|
+
auto_flush
|
24
|
+
end
|
18
25
|
|
19
26
|
raise AuthenticationError, 'Public key is required' unless @public_key
|
20
27
|
raise AuthenticationError, 'Secret key is required' unless @secret_key
|
21
28
|
|
22
29
|
@connection = build_connection
|
23
30
|
@event_queue = Concurrent::Array.new
|
24
|
-
@flush_thread = start_flush_thread
|
31
|
+
@flush_thread = start_flush_thread if @auto_flush
|
25
32
|
end
|
26
33
|
|
27
34
|
# Trace operations
|
@@ -91,6 +98,25 @@ module Langfuse
|
|
91
98
|
)
|
92
99
|
end
|
93
100
|
|
101
|
+
# Event operations
|
102
|
+
def event(trace_id:, name:, start_time: nil, input: nil, output: nil, metadata: nil,
|
103
|
+
level: nil, status_message: nil, parent_observation_id: nil, version: nil, **kwargs)
|
104
|
+
Event.new(
|
105
|
+
client: self,
|
106
|
+
trace_id: trace_id,
|
107
|
+
name: name,
|
108
|
+
start_time: start_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
|
+
**kwargs
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
94
120
|
# Prompt operations
|
95
121
|
def get_prompt(name, version: nil, label: nil, cache_ttl_seconds: 60)
|
96
122
|
cache_key = "prompt:#{name}:#{version}:#{label}"
|
@@ -158,29 +184,22 @@ module Langfuse
|
|
158
184
|
enqueue_event('score-create', data)
|
159
185
|
end
|
160
186
|
|
161
|
-
# HTTP methods
|
162
|
-
def get(path, params = {})
|
163
|
-
request(:get, path, params: params)
|
164
|
-
end
|
165
|
-
|
166
|
-
def post(path, data = {})
|
167
|
-
request(:post, path, json: data)
|
168
|
-
end
|
169
|
-
|
170
|
-
def put(path, data = {})
|
171
|
-
request(:put, path, json: data)
|
172
|
-
end
|
173
|
-
|
174
|
-
def delete(path, params = {})
|
175
|
-
request(:delete, path, params: params)
|
176
|
-
end
|
177
|
-
|
178
|
-
def patch(path, data = {})
|
179
|
-
request(:patch, path, json: data)
|
180
|
-
end
|
181
|
-
|
182
187
|
# Event queue management
|
183
188
|
def enqueue_event(type, body)
|
189
|
+
# 验证事件类型是否有效
|
190
|
+
valid_types = %w[
|
191
|
+
trace-create trace-update
|
192
|
+
generation-create generation-update
|
193
|
+
span-create span-update
|
194
|
+
event-create
|
195
|
+
score-create
|
196
|
+
]
|
197
|
+
|
198
|
+
unless valid_types.include?(type)
|
199
|
+
puts "Warning: Invalid event type '#{type}'. Skipping event." if @debug
|
200
|
+
return
|
201
|
+
end
|
202
|
+
|
184
203
|
event = {
|
185
204
|
id: Utils.generate_id,
|
186
205
|
type: type,
|
@@ -188,7 +207,32 @@ module Langfuse
|
|
188
207
|
body: Utils.deep_stringify_keys(body)
|
189
208
|
}
|
190
209
|
|
191
|
-
|
210
|
+
if type == 'trace-update'
|
211
|
+
# 查找对应的 trace-create 事件并更新
|
212
|
+
trace_id = body['id'] || body[:id]
|
213
|
+
if trace_id
|
214
|
+
existing_event_index = @event_queue.find_index do |existing_event|
|
215
|
+
existing_event[:type] == 'trace-create' &&
|
216
|
+
(existing_event[:body]['id'] == trace_id || existing_event[:body][:id] == trace_id)
|
217
|
+
end
|
218
|
+
|
219
|
+
if existing_event_index
|
220
|
+
# 更新现有的 trace-create 事件
|
221
|
+
@event_queue[existing_event_index][:body].merge!(event[:body])
|
222
|
+
@event_queue[existing_event_index][:timestamp] = event[:timestamp]
|
223
|
+
puts "Updated existing trace-create event for trace_id: #{trace_id}" if @debug
|
224
|
+
else
|
225
|
+
# 如果没找到对应的 trace-create 事件,将 trace-update 转换为 trace-create
|
226
|
+
event[:type] = 'trace-create'
|
227
|
+
@event_queue << event
|
228
|
+
puts "Converted trace-update to trace-create for trace_id: #{trace_id}" if @debug
|
229
|
+
end
|
230
|
+
elsif @debug
|
231
|
+
puts 'Warning: trace-update event missing trace_id, skipping'
|
232
|
+
end
|
233
|
+
else
|
234
|
+
@event_queue << event
|
235
|
+
end
|
192
236
|
puts "Enqueued event: #{type}" if @debug
|
193
237
|
end
|
194
238
|
|
@@ -198,32 +242,99 @@ module Langfuse
|
|
198
242
|
events = @event_queue.shift(@event_queue.length)
|
199
243
|
return if events.empty?
|
200
244
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
245
|
+
send_batch(events)
|
246
|
+
end
|
247
|
+
|
248
|
+
def shutdown
|
249
|
+
@flush_thread&.kill if @auto_flush
|
250
|
+
flush unless @event_queue.empty?
|
251
|
+
end
|
252
|
+
|
253
|
+
private
|
254
|
+
|
255
|
+
def debug_event_data(events)
|
256
|
+
return unless @debug
|
257
|
+
|
258
|
+
puts "\n=== Event Data Debug Information ==="
|
259
|
+
events.each_with_index do |event, index|
|
260
|
+
puts "Event #{index + 1}:"
|
261
|
+
puts " ID: #{event[:id]}"
|
262
|
+
puts " Type: #{event[:type]}"
|
263
|
+
puts " Timestamp: #{event[:timestamp]}"
|
264
|
+
puts " Body keys: #{event[:body]&.keys || 'nil'}"
|
265
|
+
|
266
|
+
# 检查常见的问题
|
267
|
+
puts ' ⚠️ WARNING: Empty or nil type!' if event[:type].nil? || event[:type].to_s.empty?
|
268
|
+
|
269
|
+
puts ' ⚠️ WARNING: Empty body!' if event[:body].nil?
|
270
|
+
|
271
|
+
puts ' ---'
|
272
|
+
end
|
273
|
+
puts "=== End Debug Information ===\n"
|
274
|
+
end
|
275
|
+
|
276
|
+
def send_batch(events)
|
277
|
+
# 调试事件数据
|
278
|
+
debug_event_data(events)
|
279
|
+
|
280
|
+
# 验证事件数据
|
281
|
+
valid_events = events.select do |event|
|
282
|
+
if event[:type].nil? || event[:type].to_s.empty?
|
283
|
+
puts "Warning: Event with empty type detected, skipping: #{event[:id]}" if @debug
|
284
|
+
false
|
285
|
+
elsif event[:body].nil?
|
286
|
+
puts "Warning: Event with empty body detected, skipping: #{event[:id]}" if @debug
|
287
|
+
false
|
288
|
+
else
|
289
|
+
true
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
if valid_events.empty?
|
294
|
+
puts 'No valid events to send' if @debug
|
295
|
+
return
|
296
|
+
end
|
297
|
+
|
298
|
+
batch_data = build_batch_data(valid_events)
|
299
|
+
puts "Sending batch data: #{batch_data}" if @debug
|
209
300
|
|
210
301
|
begin
|
211
302
|
response = post('/api/public/ingestion', batch_data)
|
212
|
-
puts "Flushed #{
|
303
|
+
puts "Flushed #{valid_events.length} events" if @debug
|
304
|
+
response
|
213
305
|
rescue StandardError => e
|
214
306
|
puts "Failed to flush events: #{e.message}" if @debug
|
215
307
|
# Re-queue events on failure
|
216
|
-
|
308
|
+
valid_events.each { |event| @event_queue << event }
|
217
309
|
raise
|
218
310
|
end
|
219
311
|
end
|
220
312
|
|
221
|
-
def
|
222
|
-
|
223
|
-
|
313
|
+
def build_batch_data(events)
|
314
|
+
{
|
315
|
+
batch: events,
|
316
|
+
metadata: Utils.deep_camelize_keys({
|
317
|
+
batch_size: events.length,
|
318
|
+
sdk_name: 'langfuse-ruby',
|
319
|
+
sdk_version: Langfuse::VERSION
|
320
|
+
})
|
321
|
+
}
|
224
322
|
end
|
225
323
|
|
226
|
-
|
324
|
+
def start_flush_thread
|
325
|
+
return unless @auto_flush
|
326
|
+
|
327
|
+
Thread.new do
|
328
|
+
loop do
|
329
|
+
sleep(@flush_interval) # Configurable flush interval
|
330
|
+
begin
|
331
|
+
flush unless @event_queue.empty?
|
332
|
+
rescue StandardError => e
|
333
|
+
puts "Error in flush thread: #{e.message}" if @debug
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
227
338
|
|
228
339
|
def build_connection
|
229
340
|
Faraday.new(url: @host) do |conn|
|
@@ -248,6 +359,27 @@ module Langfuse
|
|
248
359
|
end
|
249
360
|
end
|
250
361
|
|
362
|
+
# HTTP methods
|
363
|
+
def get(path, params = {})
|
364
|
+
request(:get, path, params: params)
|
365
|
+
end
|
366
|
+
|
367
|
+
def post(path, data = {})
|
368
|
+
request(:post, path, json: data)
|
369
|
+
end
|
370
|
+
|
371
|
+
def put(path, data = {})
|
372
|
+
request(:put, path, json: data)
|
373
|
+
end
|
374
|
+
|
375
|
+
def delete(path, params = {})
|
376
|
+
request(:delete, path, params: params)
|
377
|
+
end
|
378
|
+
|
379
|
+
def patch(path, data = {})
|
380
|
+
request(:patch, path, json: data)
|
381
|
+
end
|
382
|
+
|
251
383
|
def request(method, path, params: {}, json: nil)
|
252
384
|
retries_left = @retries
|
253
385
|
|
@@ -293,25 +425,27 @@ module Langfuse
|
|
293
425
|
when 429
|
294
426
|
raise RateLimitError, "Rate limit exceeded: #{response.body}"
|
295
427
|
when 400..499
|
296
|
-
|
428
|
+
# 为 400 错误提供更详细的错误信息
|
429
|
+
error_details = ''
|
430
|
+
if response.body.is_a?(Hash) && response.body['error']
|
431
|
+
error_details = "\nError details: #{response.body['error']}"
|
432
|
+
elsif response.body.is_a?(String)
|
433
|
+
error_details = "\nError details: #{response.body}"
|
434
|
+
end
|
435
|
+
|
436
|
+
# 特别处理类型验证错误
|
437
|
+
unless response.body.to_s.include?('invalid_union') || response.body.to_s.include?('discriminator')
|
438
|
+
raise ValidationError, "Client error (#{response.status}): #{response.body}#{error_details}"
|
439
|
+
end
|
440
|
+
|
441
|
+
raise ValidationError,
|
442
|
+
"Event type validation failed (#{response.status}): The event type or structure is invalid. Please check the event format.#{error_details}"
|
443
|
+
|
297
444
|
when 500..599
|
298
445
|
raise APIError, "Server error (#{response.status}): #{response.body}"
|
299
446
|
else
|
300
447
|
raise APIError, "Unexpected response (#{response.status}): #{response.body}"
|
301
448
|
end
|
302
449
|
end
|
303
|
-
|
304
|
-
def start_flush_thread
|
305
|
-
Thread.new do
|
306
|
-
loop do
|
307
|
-
sleep(5) # Flush every 5 seconds
|
308
|
-
begin
|
309
|
-
flush unless @event_queue.empty?
|
310
|
-
rescue StandardError => e
|
311
|
-
puts "Error in flush thread: #{e.message}" if @debug
|
312
|
-
end
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
450
|
end
|
317
451
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Langfuse
|
2
|
+
class Event
|
3
|
+
attr_reader :id, :trace_id, :name, :start_time, :input, :output, :metadata,
|
4
|
+
:level, :status_message, :parent_observation_id, :version, :client
|
5
|
+
|
6
|
+
def initialize(client:, trace_id:, name:, id: nil, start_time: nil, input: nil,
|
7
|
+
output: nil, metadata: nil, level: nil, status_message: nil,
|
8
|
+
parent_observation_id: nil, version: nil, **kwargs)
|
9
|
+
@client = client
|
10
|
+
@id = id || Utils.generate_id
|
11
|
+
@trace_id = trace_id
|
12
|
+
@name = name
|
13
|
+
@start_time = start_time || Utils.current_timestamp
|
14
|
+
@input = input
|
15
|
+
@output = output
|
16
|
+
@metadata = metadata || {}
|
17
|
+
@level = level
|
18
|
+
@status_message = status_message
|
19
|
+
@parent_observation_id = parent_observation_id
|
20
|
+
@version = version
|
21
|
+
@kwargs = kwargs
|
22
|
+
|
23
|
+
# Create the event
|
24
|
+
create_event
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_dict
|
28
|
+
{
|
29
|
+
id: @id,
|
30
|
+
trace_id: @trace_id,
|
31
|
+
name: @name,
|
32
|
+
start_time: @start_time,
|
33
|
+
input: @input,
|
34
|
+
output: @output,
|
35
|
+
metadata: @metadata,
|
36
|
+
level: @level,
|
37
|
+
status_message: @status_message,
|
38
|
+
parent_observation_id: @parent_observation_id,
|
39
|
+
version: @version
|
40
|
+
}.merge(@kwargs).compact
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def create_event
|
46
|
+
data = {
|
47
|
+
id: @id,
|
48
|
+
trace_id: @trace_id,
|
49
|
+
name: @name,
|
50
|
+
start_time: @start_time,
|
51
|
+
input: @input,
|
52
|
+
output: @output,
|
53
|
+
metadata: @metadata,
|
54
|
+
level: @level,
|
55
|
+
status_message: @status_message,
|
56
|
+
parent_observation_id: @parent_observation_id,
|
57
|
+
version: @version
|
58
|
+
}.merge(@kwargs).compact
|
59
|
+
|
60
|
+
@client.enqueue_event('event-create', data)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/langfuse/generation.rb
CHANGED
@@ -103,6 +103,23 @@ module Langfuse
|
|
103
103
|
)
|
104
104
|
end
|
105
105
|
|
106
|
+
def event(name:, start_time: nil, input: nil, output: nil, metadata: nil,
|
107
|
+
level: nil, status_message: nil, version: nil, **kwargs)
|
108
|
+
@client.event(
|
109
|
+
trace_id: @trace_id,
|
110
|
+
name: name,
|
111
|
+
start_time: start_time,
|
112
|
+
input: input,
|
113
|
+
output: output,
|
114
|
+
metadata: metadata,
|
115
|
+
level: level,
|
116
|
+
status_message: status_message,
|
117
|
+
parent_observation_id: @id,
|
118
|
+
version: version,
|
119
|
+
**kwargs
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
106
123
|
def score(name:, value:, data_type: nil, comment: nil, **kwargs)
|
107
124
|
@client.score(
|
108
125
|
observation_id: @id,
|
@@ -142,49 +159,11 @@ module Langfuse
|
|
142
159
|
private
|
143
160
|
|
144
161
|
def create_generation
|
145
|
-
|
146
|
-
id: @id,
|
147
|
-
trace_id: @trace_id,
|
148
|
-
name: @name,
|
149
|
-
start_time: @start_time,
|
150
|
-
end_time: @end_time,
|
151
|
-
completion_start_time: @completion_start_time,
|
152
|
-
model: @model,
|
153
|
-
model_parameters: @model_parameters,
|
154
|
-
input: @input,
|
155
|
-
output: @output,
|
156
|
-
usage: @usage,
|
157
|
-
metadata: @metadata,
|
158
|
-
level: @level,
|
159
|
-
status_message: @status_message,
|
160
|
-
parent_observation_id: @parent_observation_id,
|
161
|
-
version: @version
|
162
|
-
}.merge(@kwargs).compact
|
163
|
-
|
164
|
-
@client.enqueue_event('generation-create', data)
|
162
|
+
@client.enqueue_event('generation-create', to_dict)
|
165
163
|
end
|
166
164
|
|
167
165
|
def update_generation
|
168
|
-
|
169
|
-
id: @id,
|
170
|
-
trace_id: @trace_id,
|
171
|
-
name: @name,
|
172
|
-
start_time: @start_time,
|
173
|
-
end_time: @end_time,
|
174
|
-
completion_start_time: @completion_start_time,
|
175
|
-
model: @model,
|
176
|
-
model_parameters: @model_parameters,
|
177
|
-
input: @input,
|
178
|
-
output: @output,
|
179
|
-
usage: @usage,
|
180
|
-
metadata: @metadata,
|
181
|
-
level: @level,
|
182
|
-
status_message: @status_message,
|
183
|
-
parent_observation_id: @parent_observation_id,
|
184
|
-
version: @version
|
185
|
-
}.merge(@kwargs).compact
|
186
|
-
|
187
|
-
@client.enqueue_event('generation-update', data)
|
166
|
+
@client.enqueue_event('generation-update', to_dict)
|
188
167
|
end
|
189
168
|
end
|
190
169
|
end
|
data/lib/langfuse/span.rb
CHANGED
@@ -91,6 +91,23 @@ module Langfuse
|
|
91
91
|
)
|
92
92
|
end
|
93
93
|
|
94
|
+
def event(name:, start_time: nil, input: nil, output: nil, metadata: nil,
|
95
|
+
level: nil, status_message: nil, version: nil, **kwargs)
|
96
|
+
@client.event(
|
97
|
+
trace_id: @trace_id,
|
98
|
+
name: name,
|
99
|
+
start_time: start_time,
|
100
|
+
input: input,
|
101
|
+
output: output,
|
102
|
+
metadata: metadata,
|
103
|
+
level: level,
|
104
|
+
status_message: status_message,
|
105
|
+
parent_observation_id: @id,
|
106
|
+
version: version,
|
107
|
+
**kwargs
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
94
111
|
def score(name:, value:, data_type: nil, comment: nil, **kwargs)
|
95
112
|
@client.score(
|
96
113
|
observation_id: @id,
|
data/lib/langfuse/trace.rb
CHANGED
@@ -24,23 +24,6 @@ module Langfuse
|
|
24
24
|
create_trace
|
25
25
|
end
|
26
26
|
|
27
|
-
def update(name: nil, user_id: nil, session_id: nil, version: nil, release: nil,
|
28
|
-
input: nil, output: nil, metadata: nil, tags: nil, **kwargs)
|
29
|
-
@name = name if name
|
30
|
-
@user_id = user_id if user_id
|
31
|
-
@session_id = session_id if session_id
|
32
|
-
@version = version if version
|
33
|
-
@release = release if release
|
34
|
-
@input = input if input
|
35
|
-
@output = output if output
|
36
|
-
@metadata.merge!(metadata) if metadata
|
37
|
-
@tags.concat(tags) if tags
|
38
|
-
@kwargs.merge!(kwargs)
|
39
|
-
|
40
|
-
update_trace
|
41
|
-
self
|
42
|
-
end
|
43
|
-
|
44
27
|
def span(name: nil, start_time: nil, end_time: nil, input: nil, output: nil,
|
45
28
|
metadata: nil, level: nil, status_message: nil, parent_observation_id: nil,
|
46
29
|
version: nil, **kwargs)
|
@@ -84,6 +67,23 @@ module Langfuse
|
|
84
67
|
)
|
85
68
|
end
|
86
69
|
|
70
|
+
def event(name:, start_time: nil, input: nil, output: nil, metadata: nil,
|
71
|
+
level: nil, status_message: nil, parent_observation_id: nil, version: nil, **kwargs)
|
72
|
+
@client.event(
|
73
|
+
trace_id: @id,
|
74
|
+
name: name,
|
75
|
+
start_time: start_time,
|
76
|
+
input: input,
|
77
|
+
output: output,
|
78
|
+
metadata: metadata,
|
79
|
+
level: level,
|
80
|
+
status_message: status_message,
|
81
|
+
parent_observation_id: parent_observation_id,
|
82
|
+
version: version,
|
83
|
+
**kwargs
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
87
|
def score(name:, value:, data_type: nil, comment: nil, **kwargs)
|
88
88
|
@client.score(
|
89
89
|
trace_id: @id,
|
@@ -95,6 +95,23 @@ module Langfuse
|
|
95
95
|
)
|
96
96
|
end
|
97
97
|
|
98
|
+
def update(name: nil, user_id: nil, session_id: nil, version: nil,
|
99
|
+
release: nil, input: nil, output: nil, metadata: nil, tags: nil, **kwargs)
|
100
|
+
# 更新实例变量
|
101
|
+
@name = name if name
|
102
|
+
@user_id = user_id if user_id
|
103
|
+
@session_id = session_id if session_id
|
104
|
+
@version = version if version
|
105
|
+
@release = release if release
|
106
|
+
@input = input if input
|
107
|
+
@output = output if output
|
108
|
+
@metadata = metadata if metadata
|
109
|
+
@tags = tags if tags
|
110
|
+
@kwargs.merge!(kwargs) if kwargs.any?
|
111
|
+
# 触发 trace-update 事件
|
112
|
+
update_trace
|
113
|
+
end
|
114
|
+
|
98
115
|
def get_url
|
99
116
|
"#{@client.host}/trace/#{@id}"
|
100
117
|
end
|
@@ -146,7 +163,8 @@ module Langfuse
|
|
146
163
|
input: @input,
|
147
164
|
output: @output,
|
148
165
|
metadata: @metadata,
|
149
|
-
tags: @tags
|
166
|
+
tags: @tags,
|
167
|
+
timestamp: @timestamp
|
150
168
|
}.merge(@kwargs).compact
|
151
169
|
|
152
170
|
@client.enqueue_event('trace-update', data)
|
data/lib/langfuse/utils.rb
CHANGED
@@ -26,12 +26,34 @@ module Langfuse
|
|
26
26
|
return hash unless hash.is_a?(Hash)
|
27
27
|
|
28
28
|
hash.each_with_object({}) do |(key, value), result|
|
29
|
-
new_key = key.to_s
|
29
|
+
new_key = camelize_key(key.to_s)
|
30
30
|
new_value = value.is_a?(Hash) ? deep_stringify_keys(value) : value
|
31
31
|
result[new_key] = new_value
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
# 将哈希的键名转换为小驼峰格式
|
36
|
+
def deep_camelize_keys(hash)
|
37
|
+
return hash unless hash.is_a?(Hash)
|
38
|
+
|
39
|
+
hash.each_with_object({}) do |(key, value), result|
|
40
|
+
new_key = camelize_key(key.to_s)
|
41
|
+
new_value = value.is_a?(Hash) ? deep_camelize_keys(value) : value
|
42
|
+
result[new_key] = new_value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# 将蛇形命名转换为小驼峰命名
|
49
|
+
def camelize_key(key)
|
50
|
+
return key if key.empty? || !key.include?('_')
|
51
|
+
|
52
|
+
key.split('_').map.with_index do |part, index|
|
53
|
+
index.zero? ? part.downcase : part.capitalize
|
54
|
+
end.join
|
55
|
+
end
|
56
|
+
|
35
57
|
def validate_required_fields(data, required_fields)
|
36
58
|
missing_fields = required_fields.select { |field| data[field].nil? || data[field].to_s.empty? }
|
37
59
|
raise ValidationError, "Missing required fields: #{missing_fields.join(', ')}" unless missing_fields.empty?
|
data/lib/langfuse/version.rb
CHANGED
data/lib/langfuse.rb
CHANGED
@@ -5,6 +5,7 @@ require_relative 'langfuse/client'
|
|
5
5
|
require_relative 'langfuse/trace'
|
6
6
|
require_relative 'langfuse/span'
|
7
7
|
require_relative 'langfuse/generation'
|
8
|
+
require_relative 'langfuse/event'
|
8
9
|
require_relative 'langfuse/prompt'
|
9
10
|
require_relative 'langfuse/evaluation'
|
10
11
|
require_relative 'langfuse/errors'
|
@@ -30,7 +31,7 @@ module Langfuse
|
|
30
31
|
|
31
32
|
# Configuration class for Langfuse client settings
|
32
33
|
class Configuration
|
33
|
-
attr_accessor :public_key, :secret_key, :host, :debug, :timeout, :retries
|
34
|
+
attr_accessor :public_key, :secret_key, :host, :debug, :timeout, :retries, :flush_interval, :auto_flush
|
34
35
|
|
35
36
|
def initialize
|
36
37
|
@public_key = nil
|
@@ -39,6 +40,8 @@ module Langfuse
|
|
39
40
|
@debug = false
|
40
41
|
@timeout = 30
|
41
42
|
@retries = 3
|
43
|
+
@flush_interval = 5
|
44
|
+
@auto_flush = true
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|