langfuse-ruby 0.1.6 → 0.1.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e15fd7f7f4e2e8b1a8018f27de35f42a3124973c8a455b50781a0d08924aa3d
4
- data.tar.gz: 91253f7addf89cef33fcf047376f8667fae8e2e9d75f096b4096d4c876d06b20
3
+ metadata.gz: 98dc87bcfe8834aa485a810f1d955216a0c17e74a6b058f42f76539350b5c542
4
+ data.tar.gz: b826b87fc097c83637969f81a0aa0ff93e520c63a73519c78bd84cee3258af16
5
5
  SHA512:
6
- metadata.gz: 62d8773683844ecc44a1d258765f0522f77e83b02cbb7b7441d892e3e6a9252348f8ccfd331e185186b46aa9fb30203146e7beffd29a1330b5ab14ff75862a47
7
- data.tar.gz: 23e33a0b7d7b8b25c07e9663cf320771a6db8160fb18302179acc8aeaaf99c1740add31c84448106df46efe22e89b2c36ce7bae6b66e600e525226854a2003d7
6
+ metadata.gz: 80b9e696b8157772731a519712087f19e751caf72009a87995c5684793a9fba1d507ab38eaedccd31ce5fac9280634d07ce7646454a7854383312891a076c525
7
+ data.tar.gz: a05b595c3cc2e437511dbafa44df1331b5d64802f2008a51797419cb1eba9691ec7edef68435f55a972dff4b699b449f57603ef1161a7e73127d22a7c346a799
@@ -9,6 +9,8 @@ jobs:
9
9
  release:
10
10
  runs-on: ubuntu-latest
11
11
  if: startsWith(github.ref, 'refs/tags/v')
12
+ permissions:
13
+ contents: write
12
14
 
13
15
  steps:
14
16
  - uses: actions/checkout@v4
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- langfuse-ruby (0.1.6)
4
+ langfuse-ruby (0.1.7)
5
5
  concurrent-ruby (~> 1.0)
6
6
  faraday (>= 1.8, < 3.0)
7
7
  faraday-multipart (~> 1.0)
data/Makefile CHANGED
@@ -26,7 +26,7 @@ lint-fix: ## Run RuboCop with auto-correct
26
26
  build: ## Build the gem
27
27
  bundle exec rake build
28
28
 
29
- release: tag build ## Release: tag + build + push to RubyGems
29
+ release: tag ## Release: tag + build + push to RubyGems
30
30
  bundle exec rake release_gem
31
31
 
32
32
  clean: ## Remove built gem files
@@ -39,7 +39,7 @@ console: ## Start an IRB console with the gem loaded
39
39
  tag: ## Create and push a version tag. Usage: make tag [VERSION=x.y.z]
40
40
  @git fetch --tags; \
41
41
  if [ -z "$(VERSION)" ]; then \
42
- LATEST=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
42
+ LATEST=$$(git tag -l 'v[0-9]*' --sort=-v:refname | head -n1); \
43
43
  if [ -z "$$LATEST" ]; then \
44
44
  NEW_TAG="v0.0.1"; \
45
45
  else \
@@ -51,7 +51,7 @@ tag: ## Create and push a version tag. Usage: make tag [VERSION=x.y.z]
51
51
  fi; \
52
52
  else \
53
53
  NEW_TAG="v$(VERSION)"; \
54
- LATEST=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
54
+ LATEST=$$(git tag -l 'v[0-9]*' --sort=-v:refname | head -n1); \
55
55
  if [ "$$LATEST" = "$$NEW_TAG" ]; then \
56
56
  echo "Tag $$NEW_TAG already exists on remote, deleting and re-pushing..."; \
57
57
  git tag -d "$$NEW_TAG" 2>/dev/null || true; \
data/README.md CHANGED
@@ -58,6 +58,36 @@ end
58
58
  client = Langfuse.new
59
59
  ```
60
60
 
61
+ ### OpenTelemetry (OTEL) Ingestion Mode
62
+
63
+ Langfuse v4 introduces a faster data model powered by OpenTelemetry. To use
64
+ it, enable the OTEL ingestion mode. This sends data via the OTLP/HTTP JSON
65
+ endpoint (`/api/public/otel/v1/traces`) with the `x-langfuse-ingestion-version: 4`
66
+ header for real-time ingestion and observation-level online evaluators.
67
+
68
+ ```ruby
69
+ # Via constructor
70
+ client = Langfuse.new(
71
+ public_key: "pk-lf-...",
72
+ secret_key: "sk-lf-...",
73
+ ingestion_mode: :otel
74
+ )
75
+
76
+ # Via global configuration
77
+ Langfuse.configure do |config|
78
+ config.public_key = "pk-lf-..."
79
+ config.secret_key = "sk-lf-..."
80
+ config.ingestion_mode = :otel
81
+ end
82
+
83
+ # Via environment variable
84
+ # LANGFUSE_INGESTION_MODE=otel
85
+ ```
86
+
87
+ All existing tracing APIs work unchanged. The SDK maps Langfuse events to
88
+ OpenTelemetry spans with the appropriate `langfuse.*` and `gen_ai.*` attributes.
89
+ No additional dependencies are required.
90
+
61
91
  ### 2. Basic Tracing
62
92
 
63
93
  ```ruby
@@ -9,10 +9,11 @@ require 'concurrent'
9
9
 
10
10
  module Langfuse
11
11
  class Client
12
- attr_reader :public_key, :secret_key, :host, :debug, :timeout, :retries, :flush_interval, :auto_flush
12
+ attr_reader :public_key, :secret_key, :host, :debug, :timeout, :retries, :flush_interval, :auto_flush,
13
+ :ingestion_mode
13
14
 
14
15
  def initialize(public_key: nil, secret_key: nil, host: nil, debug: false, timeout: 30, retries: 3,
15
- flush_interval: nil, auto_flush: nil)
16
+ flush_interval: nil, auto_flush: nil, ingestion_mode: nil)
16
17
  @public_key = public_key || ENV['LANGFUSE_PUBLIC_KEY'] || Langfuse.configuration.public_key
17
18
  @secret_key = secret_key || ENV['LANGFUSE_SECRET_KEY'] || Langfuse.configuration.secret_key
18
19
  @host = host || ENV['LANGFUSE_HOST'] || Langfuse.configuration.host
@@ -25,11 +26,14 @@ module Langfuse
25
26
  else
26
27
  auto_flush
27
28
  end
29
+ @ingestion_mode = resolve_ingestion_mode(ingestion_mode)
28
30
 
29
31
  raise AuthenticationError, 'Public key is required' unless @public_key
30
32
  raise AuthenticationError, 'Secret key is required' unless @secret_key
31
33
 
32
34
  @connection = build_connection
35
+ @otel_connection = build_otel_connection if @ingestion_mode == :otel
36
+ @otel_exporter = OtelExporter.new(connection: @otel_connection, debug: @debug) if @ingestion_mode == :otel
33
37
  @event_queue = Concurrent::Array.new
34
38
  @flush_thread = start_flush_thread if @auto_flush
35
39
  end
@@ -452,16 +456,38 @@ module Langfuse
452
456
  return
453
457
  end
454
458
 
459
+ if @ingestion_mode == :otel
460
+ send_batch_otel(valid_events)
461
+ else
462
+ send_batch_legacy(valid_events)
463
+ end
464
+ end
465
+
466
+ def send_batch_legacy(valid_events)
455
467
  batch_data = build_batch_data(valid_events)
456
468
  puts "Sending batch data: #{batch_data}" if @debug
457
469
 
458
470
  begin
459
471
  response = post('/api/public/ingestion', batch_data)
460
- puts "Flushed #{valid_events.length} events" if @debug
472
+ puts "Flushed #{valid_events.length} events (legacy)" if @debug
461
473
  response
462
474
  rescue StandardError => e
463
475
  puts "Failed to flush events: #{e.message}" if @debug
464
- # Re-queue events on failure
476
+ valid_events.each { |event| @event_queue << event }
477
+ raise
478
+ end
479
+ end
480
+
481
+ def send_batch_otel(valid_events)
482
+ puts "Sending #{valid_events.length} events via OTEL" if @debug
483
+
484
+ begin
485
+ response = @otel_exporter.export(valid_events)
486
+ handle_response(response)
487
+ puts "Flushed #{valid_events.length} events (otel)" if @debug
488
+ response
489
+ rescue StandardError => e
490
+ puts "Failed to flush OTEL events: #{e.message}" if @debug
465
491
  valid_events.each { |event| @event_queue << event }
466
492
  raise
467
493
  end
@@ -493,6 +519,15 @@ module Langfuse
493
519
  end
494
520
  end
495
521
 
522
+ def resolve_ingestion_mode(explicit_mode)
523
+ return explicit_mode.to_sym if explicit_mode
524
+
525
+ env_mode = ENV['LANGFUSE_INGESTION_MODE']
526
+ return env_mode.to_sym if env_mode && !env_mode.empty?
527
+
528
+ Langfuse.configuration.ingestion_mode || :legacy
529
+ end
530
+
496
531
  def build_connection
497
532
  Faraday.new(url: @host) do |conn|
498
533
  # 配置请求和响应处理
@@ -516,6 +551,22 @@ module Langfuse
516
551
  end
517
552
  end
518
553
 
554
+ # Build a separate Faraday connection for OTEL with the v4 ingestion header.
555
+ def build_otel_connection
556
+ Faraday.new(url: @host) do |conn|
557
+ conn.response :json, content_type: /\bjson$/
558
+
559
+ conn.headers['User-Agent'] = "langfuse-ruby/#{Langfuse::VERSION}"
560
+ conn.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{@public_key}:#{@secret_key}")}"
561
+ conn.headers['x-langfuse-ingestion-version'] = '4'
562
+ conn.headers['Content-Type'] = 'application/json'
563
+
564
+ conn.options.timeout = @timeout
565
+ conn.response :logger if @debug
566
+ conn.adapter Faraday.default_adapter
567
+ end
568
+ end
569
+
519
570
  # HTTP methods
520
571
  def get(path, params = {})
521
572
  request(:get, path, params: params)
@@ -0,0 +1,333 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'securerandom'
5
+
6
+ module Langfuse
7
+ # Converts batched Langfuse events into OTLP/HTTP JSON (ExportTraceServiceRequest)
8
+ # and sends them to the Langfuse OTEL endpoint for v4-compatible ingestion.
9
+ class OtelExporter
10
+ OTEL_ENDPOINT = '/api/public/otel/v1/traces'
11
+
12
+ # @param connection [Faraday::Connection] HTTP connection to Langfuse host
13
+ # @param debug [Boolean] whether to print debug output
14
+ def initialize(connection:, debug: false)
15
+ @connection = connection
16
+ @debug = debug
17
+ end
18
+
19
+ # Export a batch of Langfuse events as OTLP spans.
20
+ # @param events [Array<Hash>] array of event hashes from the event queue
21
+ # @return [Faraday::Response]
22
+ def export(events)
23
+ resource_spans = build_resource_spans(events)
24
+ payload = { resourceSpans: resource_spans }
25
+
26
+ puts "OTEL export payload: #{JSON.pretty_generate(payload)}" if @debug
27
+
28
+ @connection.post(OTEL_ENDPOINT) do |req|
29
+ req.headers['Content-Type'] = 'application/json'
30
+ req.body = JSON.generate(payload)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # Build the top-level resourceSpans array from events.
37
+ # Groups events by trace_id, producing one scopeSpan per trace.
38
+ def build_resource_spans(events)
39
+ grouped = group_events_by_trace(events)
40
+
41
+ scope_spans = grouped.map do |_trace_id, trace_events|
42
+ spans = trace_events.filter_map { |event| convert_event_to_span(event) }
43
+ next if spans.empty?
44
+
45
+ { scope: { name: 'langfuse-ruby', version: Langfuse::VERSION }, spans: spans }
46
+ end.compact
47
+
48
+ return [] if scope_spans.empty?
49
+
50
+ [{
51
+ resource: {
52
+ attributes: [
53
+ { key: 'service.name', value: { stringValue: 'langfuse-ruby' } },
54
+ { key: 'telemetry.sdk.name', value: { stringValue: 'langfuse-ruby' } },
55
+ { key: 'telemetry.sdk.version', value: { stringValue: Langfuse::VERSION } }
56
+ ]
57
+ },
58
+ scopeSpans: scope_spans
59
+ }]
60
+ end
61
+
62
+ # Group events by their trace ID for proper OTEL span hierarchy.
63
+ def group_events_by_trace(events)
64
+ groups = Hash.new { |h, k| h[k] = [] }
65
+
66
+ events.each do |event|
67
+ body = event[:body] || {}
68
+ trace_id = body['traceId'] || body['trace_id'] || body['id'] || 'unknown'
69
+ groups[trace_id] << event
70
+ end
71
+
72
+ groups
73
+ end
74
+
75
+ # Convert a single Langfuse event to an OTLP span hash, or nil if not convertible.
76
+ def convert_event_to_span(event)
77
+ type = event[:type]
78
+ body = event[:body] || {}
79
+
80
+ case type
81
+ when 'trace-create'
82
+ build_trace_span(body)
83
+ when 'span-create', 'span-update'
84
+ build_observation_span(body, 'span')
85
+ when 'generation-create', 'generation-update'
86
+ build_observation_span(body, 'generation')
87
+ when 'event-create'
88
+ build_event_span(body)
89
+ when 'score-create'
90
+ build_score_span(body)
91
+ end
92
+ end
93
+
94
+ # Build a root OTEL span for a Langfuse trace.
95
+ def build_trace_span(body)
96
+ trace_id = to_otel_trace_id(body['id'])
97
+ span_id = to_otel_span_id(body['id'])
98
+
99
+ attributes = []
100
+ add_attr(attributes, 'langfuse.trace.name', body['name'])
101
+ add_attr(attributes, 'langfuse.user.id', body['userId'])
102
+ add_attr(attributes, 'langfuse.session.id', body['sessionId'])
103
+ add_attr(attributes, 'langfuse.release', body['release'])
104
+ add_attr(attributes, 'langfuse.version', body['version'])
105
+ add_json_attr(attributes, 'langfuse.trace.input', body['input'])
106
+ add_json_attr(attributes, 'langfuse.trace.output', body['output'])
107
+ add_json_attr(attributes, 'langfuse.trace.metadata', body['metadata'])
108
+
109
+ tags = body['tags']
110
+ if tags.is_a?(Array) && !tags.empty?
111
+ add_array_attr(attributes, 'langfuse.trace.tags', tags)
112
+ end
113
+
114
+ {
115
+ traceId: trace_id,
116
+ spanId: span_id,
117
+ name: body['name'] || 'trace',
118
+ kind: 1, # SPAN_KIND_INTERNAL
119
+ startTimeUnixNano: to_unix_nano(body['timestamp']),
120
+ endTimeUnixNano: to_unix_nano(body['timestamp']),
121
+ attributes: attributes,
122
+ status: { code: 1 } # STATUS_CODE_OK
123
+ }
124
+ end
125
+
126
+ # Build an OTEL span for a Langfuse span or generation observation.
127
+ def build_observation_span(body, obs_type)
128
+ trace_id = to_otel_trace_id(body['traceId'])
129
+ span_id = to_otel_span_id(body['id'])
130
+
131
+ span = {
132
+ traceId: trace_id,
133
+ spanId: span_id,
134
+ name: body['name'] || obs_type,
135
+ kind: 1, # SPAN_KIND_INTERNAL
136
+ startTimeUnixNano: to_unix_nano(body['startTime']),
137
+ endTimeUnixNano: to_unix_nano(body['endTime'] || body['startTime']),
138
+ attributes: build_observation_attributes(body, obs_type),
139
+ status: { code: 1 }
140
+ }
141
+
142
+ # Set parent span ID
143
+ parent_id = body['parentObservationId']
144
+ if parent_id
145
+ span[:parentSpanId] = to_otel_span_id(parent_id)
146
+ else
147
+ # Parent is the trace root span
148
+ span[:parentSpanId] = to_otel_span_id(body['traceId'])
149
+ end
150
+
151
+ span
152
+ end
153
+
154
+ # Build an OTEL span for a Langfuse event (zero-duration span).
155
+ def build_event_span(body)
156
+ trace_id = to_otel_trace_id(body['traceId'])
157
+ span_id = to_otel_span_id(body['id'])
158
+ timestamp = to_unix_nano(body['startTime'])
159
+
160
+ attributes = []
161
+ add_attr(attributes, 'langfuse.observation.type', 'event')
162
+ add_json_attr(attributes, 'langfuse.observation.input', body['input'])
163
+ add_json_attr(attributes, 'langfuse.observation.output', body['output'])
164
+ add_json_attr(attributes, 'langfuse.observation.metadata', body['metadata'])
165
+ add_attr(attributes, 'langfuse.observation.level', body['level'])
166
+ add_attr(attributes, 'langfuse.observation.status_message', body['statusMessage'])
167
+
168
+ span = {
169
+ traceId: trace_id,
170
+ spanId: span_id,
171
+ name: body['name'] || 'event',
172
+ kind: 1,
173
+ startTimeUnixNano: timestamp,
174
+ endTimeUnixNano: timestamp,
175
+ attributes: attributes,
176
+ status: { code: 1 }
177
+ }
178
+
179
+ parent_id = body['parentObservationId']
180
+ if parent_id
181
+ span[:parentSpanId] = to_otel_span_id(parent_id)
182
+ elsif body['traceId']
183
+ span[:parentSpanId] = to_otel_span_id(body['traceId'])
184
+ end
185
+
186
+ span
187
+ end
188
+
189
+ # Build a minimal OTEL span for a score event.
190
+ def build_score_span(body)
191
+ trace_id_raw = body['traceId']
192
+ return nil unless trace_id_raw
193
+
194
+ trace_id = to_otel_trace_id(trace_id_raw)
195
+ span_id = to_otel_span_id(body['id'] || SecureRandom.uuid)
196
+ timestamp = to_unix_nano(body['timestamp'] || Time.now.utc.iso8601(3))
197
+
198
+ attributes = []
199
+ add_attr(attributes, 'langfuse.score.name', body['name'])
200
+ add_attr(attributes, 'langfuse.score.value', body['value'])
201
+ add_attr(attributes, 'langfuse.score.data_type', body['dataType'])
202
+ add_attr(attributes, 'langfuse.score.comment', body['comment'])
203
+ add_attr(attributes, 'langfuse.observation.type', 'score')
204
+
205
+ if body['observationId']
206
+ add_attr(attributes, 'langfuse.score.observation_id', body['observationId'])
207
+ end
208
+
209
+ span = {
210
+ traceId: trace_id,
211
+ spanId: span_id,
212
+ name: "score-#{body['name']}",
213
+ kind: 1,
214
+ startTimeUnixNano: timestamp,
215
+ endTimeUnixNano: timestamp,
216
+ attributes: attributes,
217
+ status: { code: 1 }
218
+ }
219
+
220
+ # Parent is either the observation or the trace
221
+ parent_raw = body['observationId'] || trace_id_raw
222
+ span[:parentSpanId] = to_otel_span_id(parent_raw) if parent_raw
223
+
224
+ span
225
+ end
226
+
227
+ # Build OTEL attributes for a span/generation observation.
228
+ def build_observation_attributes(body, obs_type)
229
+ attributes = []
230
+ effective_type = body['type'] || obs_type
231
+ add_attr(attributes, 'langfuse.observation.type', effective_type)
232
+ add_json_attr(attributes, 'langfuse.observation.input', body['input'])
233
+ add_json_attr(attributes, 'langfuse.observation.output', body['output'])
234
+ add_json_attr(attributes, 'langfuse.observation.metadata', body['metadata'])
235
+ add_attr(attributes, 'langfuse.observation.level', body['level'])
236
+ add_attr(attributes, 'langfuse.observation.status_message', body['statusMessage'])
237
+
238
+ if obs_type == 'generation'
239
+ add_generation_attributes(attributes, body)
240
+ end
241
+
242
+ attributes
243
+ end
244
+
245
+ # Add generation-specific gen_ai.* attributes.
246
+ def add_generation_attributes(attributes, body)
247
+ add_attr(attributes, 'gen_ai.request.model', body['model'])
248
+
249
+ model_params = body['modelParameters']
250
+ if model_params.is_a?(Hash)
251
+ model_params.each do |key, value|
252
+ add_attr(attributes, "gen_ai.request.#{key}", value) unless value.nil?
253
+ end
254
+ end
255
+
256
+ usage = body['usage']
257
+ if usage.is_a?(Hash)
258
+ add_attr(attributes, 'gen_ai.usage.prompt_tokens', usage['promptTokens'] || usage['prompt_tokens'])
259
+ add_attr(attributes, 'gen_ai.usage.completion_tokens', usage['completionTokens'] || usage['completion_tokens'])
260
+ total = usage['totalTokens'] || usage['total_tokens']
261
+ add_attr(attributes, 'gen_ai.usage.total_tokens', total) if total
262
+ end
263
+
264
+ add_attr(attributes, 'langfuse.observation.completion_start_time', body['completionStartTime'])
265
+ end
266
+
267
+ # Convert a UUID string to OTEL 32-char hex trace ID.
268
+ # OTEL trace IDs are 16 bytes (32 hex chars).
269
+ def to_otel_trace_id(uuid_str)
270
+ return '0' * 32 unless uuid_str
271
+
272
+ hex = uuid_str.to_s.delete('-')
273
+ hex.ljust(32, '0')[0, 32]
274
+ end
275
+
276
+ # Convert a UUID string to OTEL 16-char hex span ID.
277
+ # OTEL span IDs are 8 bytes (16 hex chars).
278
+ def to_otel_span_id(uuid_str)
279
+ return '0' * 16 unless uuid_str
280
+
281
+ hex = uuid_str.to_s.delete('-')
282
+ hex[0, 16]
283
+ end
284
+
285
+ # Convert an ISO8601 timestamp string to nanoseconds since epoch.
286
+ def to_unix_nano(timestamp_str)
287
+ return '0' unless timestamp_str
288
+
289
+ time = Time.parse(timestamp_str.to_s)
290
+ ((time.to_f * 1_000_000_000).to_i).to_s
291
+ rescue ArgumentError
292
+ '0'
293
+ end
294
+
295
+ # Add a string/numeric attribute to the attributes array.
296
+ def add_attr(attributes, key, value)
297
+ return if value.nil?
298
+
299
+ otel_value = case value
300
+ when String
301
+ { stringValue: value }
302
+ when Integer
303
+ { intValue: value.to_s }
304
+ when Float
305
+ { doubleValue: value }
306
+ when TrueClass, FalseClass
307
+ { boolValue: value }
308
+ else
309
+ { stringValue: value.to_s }
310
+ end
311
+
312
+ attributes << { key: key, value: otel_value }
313
+ end
314
+
315
+ # Add a JSON-serialized attribute (for complex objects like input/output).
316
+ def add_json_attr(attributes, key, value)
317
+ return if value.nil?
318
+ return if value.is_a?(Hash) && value.empty?
319
+ return if value.is_a?(Array) && value.empty?
320
+
321
+ json_str = value.is_a?(String) ? value : JSON.generate(value)
322
+ attributes << { key: key, value: { stringValue: json_str } }
323
+ end
324
+
325
+ # Add an array attribute for tags.
326
+ def add_array_attr(attributes, key, values)
327
+ return if values.nil? || values.empty?
328
+
329
+ array_values = values.map { |v| { stringValue: v.to_s } }
330
+ attributes << { key: key, value: { arrayValue: { values: array_values } } }
331
+ end
332
+ end
333
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Langfuse
4
- VERSION = '0.1.6'
4
+ VERSION = '0.1.7'
5
5
  end
data/lib/langfuse.rb CHANGED
@@ -12,6 +12,7 @@ require_relative 'langfuse/evaluation'
12
12
  require_relative 'langfuse/errors'
13
13
  require_relative 'langfuse/utils'
14
14
  require_relative 'langfuse/null_objects'
15
+ require_relative 'langfuse/otel_exporter'
15
16
 
16
17
  # Ruby SDK for Langfuse - Open source LLM engineering platform
17
18
  module Langfuse
@@ -157,7 +158,8 @@ module Langfuse
157
158
 
158
159
  # Configuration class for Langfuse client settings
159
160
  class Configuration
160
- attr_accessor :public_key, :secret_key, :host, :debug, :timeout, :retries, :flush_interval, :auto_flush
161
+ attr_accessor :public_key, :secret_key, :host, :debug, :timeout, :retries, :flush_interval, :auto_flush,
162
+ :ingestion_mode
161
163
 
162
164
  def initialize
163
165
  @public_key = nil
@@ -168,6 +170,7 @@ module Langfuse
168
170
  @retries = 3
169
171
  @flush_interval = 5
170
172
  @auto_flush = true
173
+ @ingestion_mode = :legacy # :legacy or :otel
171
174
  end
172
175
  end
173
176
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: langfuse-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Sun
@@ -229,6 +229,7 @@ files:
229
229
  - lib/langfuse/generation.rb
230
230
  - lib/langfuse/null_objects.rb
231
231
  - lib/langfuse/observation_types.rb
232
+ - lib/langfuse/otel_exporter.rb
232
233
  - lib/langfuse/prompt.rb
233
234
  - lib/langfuse/span.rb
234
235
  - lib/langfuse/trace.rb