langfuse-ruby 0.1.7 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98dc87bcfe8834aa485a810f1d955216a0c17e74a6b058f42f76539350b5c542
4
- data.tar.gz: b826b87fc097c83637969f81a0aa0ff93e520c63a73519c78bd84cee3258af16
3
+ metadata.gz: 7b2c65ac52311951b2658204cbf3f76afff090465ef9e500718f59f194dbcea9
4
+ data.tar.gz: db397fdd6ca24db06b67c92eed232663d7845504d9dc0e7759098ebdbe88e620
5
5
  SHA512:
6
- metadata.gz: 80b9e696b8157772731a519712087f19e751caf72009a87995c5684793a9fba1d507ab38eaedccd31ce5fac9280634d07ce7646454a7854383312891a076c525
7
- data.tar.gz: a05b595c3cc2e437511dbafa44df1331b5d64802f2008a51797419cb1eba9691ec7edef68435f55a972dff4b699b449f57603ef1161a7e73127d22a7c346a799
6
+ metadata.gz: a1ba2f150b0a6d16ccc99d26144455d6fcb66d95a7e9d644bb50d12de91c95fa7751b6b3145f507a74da1b367eacaf3d43a91073a249815695f7260fc2c66d56
7
+ data.tar.gz: 6d27a0bd423f14637597acfc243eaf4c74e3e533eb87ceab62e46fffc8249bd3043866ec6d5a0450b8eb7bc82b29c4e553fdfe3ae9c4c3d468d5fa241b0a3c68
@@ -7,15 +7,30 @@ on:
7
7
  branches: [ master ]
8
8
 
9
9
  jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: '4.0'
20
+ bundler-cache: true
21
+
22
+ - name: Run RuboCop
23
+ run: bundle exec rubocop
24
+
10
25
  test:
11
26
  runs-on: ubuntu-latest
12
27
 
13
28
  strategy:
14
29
  matrix:
15
- ruby-version: ['3.1', '3.2', '3.3', '3.4']
30
+ ruby-version: ['3.1', '3.2', '3.3', '3.4', '4.0']
16
31
 
17
32
  steps:
18
- - uses: actions/checkout@v4
33
+ - uses: actions/checkout@v6
19
34
 
20
35
  - name: Set up Ruby ${{ matrix.ruby-version }}
21
36
  uses: ruby/setup-ruby@v1
@@ -29,25 +44,21 @@ jobs:
29
44
  - name: Run offline tests
30
45
  run: bundle exec ruby scripts/test_offline.rb
31
46
 
32
- - name: Run RuboCop
33
- run: bundle exec rubocop
34
- continue-on-error: true
35
-
36
47
  build:
37
48
  runs-on: ubuntu-latest
38
- needs: test
49
+ needs: [lint, test]
39
50
 
40
51
  steps:
41
- - uses: actions/checkout@v4
52
+ - uses: actions/checkout@v6
42
53
 
43
54
  - name: Set up Ruby
44
55
  uses: ruby/setup-ruby@v1
45
56
  with:
46
- ruby-version: '3.4'
57
+ ruby-version: '4.0'
47
58
  bundler-cache: true
48
59
 
49
60
  - name: Build gem
50
61
  run: gem build langfuse-ruby.gemspec
51
62
 
52
63
  - name: Verify gem can be installed
53
- run: gem install langfuse-ruby-*.gem
64
+ run: gem install langfuse-ruby-*.gem
@@ -13,12 +13,12 @@ jobs:
13
13
  contents: write
14
14
 
15
15
  steps:
16
- - uses: actions/checkout@v4
16
+ - uses: actions/checkout@v6
17
17
 
18
18
  - name: Set up Ruby
19
19
  uses: ruby/setup-ruby@v1
20
20
  with:
21
- ruby-version: '3.4'
21
+ ruby-version: '4.0'
22
22
  bundler-cache: true
23
23
 
24
24
  - name: Run tests
data/.mise.toml ADDED
@@ -0,0 +1,8 @@
1
+ # mise project toolchain config
2
+ # https://mise.jdx.dev/configuration.html
3
+ #
4
+ # This project defaults to the latest stable Ruby release.
5
+ # Run `mise install` after cloning to install it, then `bundle install`.
6
+
7
+ [tools]
8
+ ruby = "latest"
data/.rubocop.yml CHANGED
@@ -47,7 +47,7 @@ Metrics/CyclomaticComplexity:
47
47
  Max: 20
48
48
 
49
49
  Metrics/PerceivedComplexity:
50
- Max: 20
50
+ Max: 25
51
51
 
52
52
  Metrics/ParameterLists:
53
53
  Enabled: false
@@ -63,4 +63,4 @@ Lint/UnusedMethodArgument:
63
63
  - 'lib/langfuse/evaluation.rb'
64
64
 
65
65
  Metrics/ClassLength:
66
- Max: 600
66
+ Max: 800
data/CHANGELOG.md CHANGED
@@ -7,17 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-07-18
11
+
10
12
  ### Added
11
- - **Simplified API**: New class-level convenience methods for easier usage
12
- - `Langfuse.trace(name, **options, &block)` - Block-based tracing with automatic flush
13
- - `Langfuse.get_prompt(name, variables:, retries:)` - Get and compile prompts with retry support
14
- - `Langfuse.client` - Thread-safe singleton client access
15
- - `Langfuse.flush` / `Langfuse.shutdown` / `Langfuse.reset!` - Client lifecycle management
16
- - **Graceful Degradation**: Null object pattern for fault tolerance
17
- - `NullTrace`, `NullGeneration`, `NullSpan`, `NullEvent` - No-op objects when Langfuse is unavailable
18
- - **Retry Support**: `get_prompt` now supports configurable retries with exponential backoff (default: 2 retries)
19
- - New example file `examples/simplified_usage.rb` demonstrating the simplified API
20
- - Comprehensive test coverage for convenience methods (28 new tests)
13
+ - **Environment support**: `environment` config / `LANGFUSE_TRACING_ENVIRONMENT` env var, injected into trace, observation and score bodies
14
+ - **Sampling**: `sample_rate` config / `LANGFUSE_SAMPLE_RATE` env var, deterministic trace-based sampling (all events of a trace share the same decision)
15
+ - **Masking**: `mask` callable applied to `input`/`output`/`metadata` before sending, for PII redaction
16
+ - **flush_at threshold**: flush as soon as the queue reaches `flush_at` events (default 15, env `LANGFUSE_FLUSH_AT`), via a condition-variable wake-up on the flush thread
17
+ - **Batch chunking**: ingestion batches are split to respect the 3.5 MB API limit; oversized single events are dropped with a warning
18
+ - **207 partial-success handling**: per-event errors from the ingestion API are logged via the structured logger
19
+ - **Score full fields**: `session_id`, `dataset_run_id`, `metadata`, `config_id`, `queue_id`, `id`, `environment`, and string values for CATEGORICAL/CORRECTION scores; `create_score` alias
20
+ - **Generation usage_details / cost_details**: new v4 usage model (arbitrary keys such as cache tokens) alongside legacy `usage`
21
+ - **Generation prompt linking**: `prompt:` accepts a `Langfuse::Prompt` or `{ name:, version: }` hash, emitted as `promptName`/`promptVersion`
22
+ - **Trace public field**: `public:` flag for shareable traces
23
+ - **LANGFUSE_BASE_URL** env var alias (new SDK standard) alongside `LANGFUSE_HOST`
24
+ - **Structured Logger**: replaces `puts` with `Logger`; level controlled by `debug` / `LANGFUSE_DEBUG`
25
+ - **at_exit shutdown hook**: pending events are flushed on process exit (configurable via `shutdown_on_exit`)
26
+ - **W3C hex IDs in OTel mode**: native 32-char trace IDs and 16-char span IDs for OTel ingestion
27
+ - **OTel exporter attributes**: `langfuse.environment`, `langfuse.trace.public`, `langfuse.internal.as_root`, `langfuse.observation.usage_details`, `langfuse.observation.cost_details`, `langfuse.observation.prompt.name`, `langfuse.observation.prompt.version`
28
+ - **Simplified API**: Class-level convenience methods (`Langfuse.trace`, `get_prompt`, `client`, `flush`, `shutdown`, `reset!`) with graceful degradation via null objects
29
+ - **Retry Support**: `get_prompt` supports configurable retries with exponential backoff (default: 2 retries)
30
+ - **Ruby 4.0 support**: CI matrix covers Ruby 3.1–4.0; explicit `base64` / `tsort` dependencies for Ruby 4.0 gem packaging
31
+
32
+ ### Fixed
33
+ - **OTel mode scores lost**: scores were exported as OTLP spans with `langfuse.score.*` attributes, which the server does not map to Langfuse scores. Scores now always route through the ingestion API (`score-create` batch), with trace/observation IDs normalized to OTel hex IDs so they attach to the correct entities
34
+ - **OTel flush failure drops scores**: when OTEL export of non-score events fails, score events in the same batch are now re-queued together with OTEL events (they were previously drained by `flush` and permanently lost on `raise`)
35
+ - **OTel ID mismatch**: observation-level scores referenced full UUIDs while spans used truncated hex, breaking attachment. IDs are now normalized on both sides
36
+ - **Span/Generation score missing trace_id**: `Span#score` and `Generation#score` now pass `trace_id` so the server can attach observation-level scores correctly
37
+ - **Process-level singleton**: `Langfuse.client` was thread-local (`Thread.current`), creating one client + flush thread per thread under Puma/Sidekiq. Now a single process-wide client guarded by a `Mutex`
38
+ - **Idempotent shutdown**: `shutdown` can be called multiple times safely
39
+ - **Body serialization**: event bodies now only camelCase top-level keys; user data under `input`/`output`/`metadata`/`usageDetails`/`costDetails`/`modelParameters` is passed through verbatim so user-provided keys are not mangled
40
+
41
+ ### Changed
42
+ - `Langfuse.client` is now process-wide instead of thread-local. Use `Langfuse.new` for isolated clients in tests
43
+ - Default `flush_interval` behavior unchanged, but the flush thread now also wakes on the `flush_at` threshold
44
+ - `Configuration` gains `environment`, `sample_rate`, `mask`, `flush_at`, `logger`, `shutdown_on_exit` attributes
21
45
 
22
46
  ## [0.1.5] - 2025-12-26
23
47
 
data/CLAUDE.md CHANGED
@@ -8,8 +8,14 @@ This is the official Ruby SDK for [Langfuse](https://langfuse.com) - an open-sou
8
8
 
9
9
  ## Common Commands
10
10
 
11
+ Ruby version is managed with [mise](https://mise.jdx.dev) via `.mise.toml`
12
+ (defaults to the latest stable Ruby). Run `mise install` after cloning, then
13
+ `bundle install`. With mise's shell integration active, the commands below work
14
+ directly; in a non-mise shell prefix them with `mise exec --`.
15
+
11
16
  ```bash
12
17
  # Install dependencies
18
+ mise install # install the pinned Ruby version
13
19
  bundle install
14
20
 
15
21
  # Run all RSpec tests
@@ -39,12 +45,13 @@ bundle exec rake release_gem
39
45
  ### Core Classes
40
46
 
41
47
  - **`Langfuse`** ([lib/langfuse.rb](lib/langfuse.rb)) - Module with class-level convenience methods (`trace`, `get_prompt`, `client`, `flush`, `shutdown`, `reset!`)
42
- - **`Langfuse::Client`** ([lib/langfuse/client.rb](lib/langfuse/client.rb)) - Main entry point. Handles API authentication, HTTP connections (via Faraday), event queuing, and background flush thread for auto-batching events.
48
+ - **`Langfuse::Client`** ([lib/langfuse/client.rb](lib/langfuse/client.rb)) - Main entry point. Handles API authentication, HTTP connections (via Faraday), event queuing, sampling/masking, and background flush thread for auto-batching events.
43
49
  - **`Langfuse::Trace`** ([lib/langfuse/trace.rb](lib/langfuse/trace.rb)) - Top-level container for a request/session.
44
50
  - **`Langfuse::Span`** ([lib/langfuse/span.rb](lib/langfuse/span.rb)) - Timed operation with enhanced type support.
45
- - **`Langfuse::Generation`** ([lib/langfuse/generation.rb](lib/langfuse/generation.rb)) - LLM call tracking.
51
+ - **`Langfuse::Generation`** ([lib/langfuse/generation.rb](lib/langfuse/generation.rb)) - LLM call tracking (supports `usage_details`/`cost_details` and prompt linking).
46
52
  - **`Langfuse::Event`** ([lib/langfuse/event.rb](lib/langfuse/event.rb)) - Point-in-time events.
47
53
  - **`Langfuse::Prompt`** ([lib/langfuse/prompt.rb](lib/langfuse/prompt.rb)) - Prompt templates with caching.
54
+ - **`Langfuse::OtelExporter`** ([lib/langfuse/otel_exporter.rb](lib/langfuse/otel_exporter.rb)) - Maps Langfuse events to OTLP/HTTP JSON when `ingestion_mode: :otel`.
48
55
  - **`Langfuse::NullTrace/NullGeneration/NullSpan`** ([lib/langfuse/null_objects.rb](lib/langfuse/null_objects.rb)) - Null objects for graceful degradation.
49
56
 
50
57
  ### Simplified API (Recommended)
@@ -63,10 +70,14 @@ Langfuse.get_prompt("my-prompt", variables: { name: "Alice" }, retries: 3)
63
70
 
64
71
  ### Event Flow
65
72
 
66
- 1. Observations (traces, spans, generations, events) are created via Client methods
67
- 2. Events are queued in `@event_queue` (thread-safe `Concurrent::Array`)
68
- 3. Background flush thread sends batched events to `/api/public/ingestion` endpoint
69
- 4. Manual flush available via `client.flush`; graceful shutdown via `client.shutdown`
73
+ 1. Observations (traces, spans, generations, events, scores) are created via Client methods
74
+ 2. Bodies are prepared via `Utils.prepare_event_body` (top-level camelCase; user data under `input`/`output`/`metadata`/etc. is left verbatim), then environment injection, masking, and sampling are applied
75
+ 3. Events are queued in `@event_queue` (thread-safe `Concurrent::Array`)
76
+ 4. Background flush thread wakes on `flush_interval` **or** when the queue reaches `flush_at`
77
+ 5. Flush path depends on `ingestion_mode`:
78
+ - `:legacy` → batched POST to `/api/public/ingestion` (chunked to 3.5 MB)
79
+ - `:otel` → non-score events via OTLP `/api/public/otel/v1/traces`; **scores always go through the ingestion API**, with IDs normalized to OTel hex so they attach correctly. On OTEL transport failure, both OTEL and score events from the batch are re-queued
80
+ 6. Manual flush via `client.flush`; idempotent shutdown via `client.shutdown` (plus optional `at_exit` hook)
70
81
 
71
82
  ### Observation Types
72
83
 
@@ -81,7 +92,7 @@ Enhanced types are implemented as spans with `as_type` metadata sent to the API.
81
92
  Client accepts config via:
82
93
  1. Constructor parameters
83
94
  2. `Langfuse.configure` block
84
- 3. Environment variables: `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, `LANGFUSE_HOST`, `LANGFUSE_FLUSH_INTERVAL`, `LANGFUSE_AUTO_FLUSH`
95
+ 3. Environment variables: `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, `LANGFUSE_HOST` / `LANGFUSE_BASE_URL`, `LANGFUSE_FLUSH_INTERVAL`, `LANGFUSE_FLUSH_AT`, `LANGFUSE_AUTO_FLUSH`, `LANGFUSE_TRACING_ENVIRONMENT`, `LANGFUSE_SAMPLE_RATE`, `LANGFUSE_DEBUG`, `LANGFUSE_INGESTION_MODE`
85
96
 
86
97
  ### Error Handling
87
98
 
@@ -95,6 +106,7 @@ Graceful degradation: When Langfuse is unavailable, `Langfuse.trace` yields a `N
95
106
  - Uses Faraday for HTTP with Basic Auth (public_key:secret_key)
96
107
  - Prompt names with special characters are auto-URL-encoded via `Utils.url_encode`
97
108
  - `trace-update` events merge into existing `trace-create` in queue (deduplication)
98
- - All keys are converted to camelCase before API submission via `Utils.deep_camelize_keys`
99
- - Thread-safe singleton client via `Thread.current[:langfuse_client]`
109
+ - Event bodies use `Utils.prepare_event_body` (top-level camelCase only; nested user data is not mangled)
110
+ - Process-wide singleton client via `Langfuse::CLIENT_MUTEX` (not thread-local); use `Langfuse.new` for isolated clients in tests
111
+ - In OTel mode, IDs are W3C hex (`generate_trace_id` / `generate_observation_id`); scores normalize refs via `OtelExporter.to_otel_trace_id` / `to_otel_span_id`
100
112
  - `get_prompt` supports configurable retries with exponential backoff
data/Gemfile CHANGED
@@ -7,4 +7,5 @@ gemspec
7
7
 
8
8
  gem 'rake', '~> 13.0'
9
9
  gem 'rspec', '~> 3.0'
10
+ gem 'simplecov', '~> 0.22', require: false
10
11
  gem 'yard', '~> 0.9'
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- langfuse-ruby (0.1.7)
4
+ langfuse-ruby (0.2.0)
5
+ base64 (>= 0.1.0)
5
6
  concurrent-ruby (~> 1.0)
6
7
  faraday (>= 1.8, < 3.0)
7
8
  faraday-multipart (~> 1.0)
@@ -21,11 +22,12 @@ GEM
21
22
  bigdecimal
22
23
  rexml
23
24
  diff-lcs (1.6.2)
25
+ docile (1.4.1)
24
26
  faraday (2.13.2)
25
27
  faraday-net_http (>= 2.0, < 3.5)
26
28
  json
27
29
  logger
28
- faraday-multipart (1.1.1)
30
+ faraday-multipart (1.2.0)
29
31
  multipart-post (~> 2.0)
30
32
  faraday-net_http (3.4.1)
31
33
  net-http (>= 0.5.0)
@@ -76,12 +78,18 @@ GEM
76
78
  parser (>= 3.3.7.2)
77
79
  prism (~> 1.4)
78
80
  ruby-progressbar (1.13.0)
81
+ simplecov (0.22.0)
82
+ docile (~> 1.1)
83
+ simplecov-html (~> 0.11)
84
+ simplecov_json_formatter (~> 0.1)
85
+ simplecov-html (0.13.2)
86
+ simplecov_json_formatter (0.1.4)
87
+ tsort (0.2.0)
79
88
  unicode-display_width (3.1.4)
80
89
  unicode-emoji (~> 4.0, >= 4.0.4)
81
- unicode-emoji (4.0.4)
90
+ unicode-emoji (4.2.0)
82
91
  uri (1.0.3)
83
- vcr (6.3.1)
84
- base64
92
+ vcr (6.4.0)
85
93
  webmock (3.25.1)
86
94
  addressable (>= 2.8.0)
87
95
  crack (>= 0.3.2)
@@ -98,6 +106,8 @@ DEPENDENCIES
98
106
  rake (~> 13.0)
99
107
  rspec (~> 3.0)
100
108
  rubocop (~> 1.0)
109
+ simplecov (~> 0.22)
110
+ tsort (>= 0.1.0)
101
111
  vcr (~> 6.0)
102
112
  webmock (~> 3.0)
103
113
  yard (~> 0.9)
data/Makefile CHANGED
@@ -1,4 +1,4 @@
1
- .PHONY: install test spec test-offline test-all lint lint-fix build release clean console help tag
1
+ .PHONY: install test spec test-offline test-all lint lint-fix build release clean console help tag coverage
2
2
 
3
3
  help: ## Show this help
4
4
  @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'
@@ -17,6 +17,10 @@ test-all: ## Run all tests (spec + offline)
17
17
 
18
18
  test: spec ## Alias for spec
19
19
 
20
+ coverage: ## Run tests and open coverage report
21
+ bundle exec rake spec
22
+ open coverage/index.html
23
+
20
24
  lint: ## Run RuboCop linter
21
25
  bundle exec rubocop
22
26
 
data/README.md CHANGED
@@ -16,6 +16,10 @@ Ruby SDK for [Langfuse](https://langfuse.com) - the open-source LLM engineering
16
16
 
17
17
  ## Installation
18
18
 
19
+ This gem requires Ruby >= 3.1 and is tested against Ruby 3.1–4.0. For
20
+ development, Ruby version is managed with [mise](https://mise.jdx.dev) (defaults
21
+ to the latest stable Ruby via `.mise.toml`).
22
+
19
23
  Add this line to your application's Gemfile:
20
24
 
21
25
  ```ruby
@@ -34,6 +38,17 @@ Or install it yourself as:
34
38
  $ gem install langfuse-ruby
35
39
  ```
36
40
 
41
+ ### Development setup with mise
42
+
43
+ ```bash
44
+ # Install mise (if not already installed), then trust the project config and
45
+ # install the pinned Ruby version.
46
+ brew install mise # macOS; see mise docs for other OSes
47
+ mise install # installs Ruby from .mise.toml
48
+ bundle install # install gem dependencies
49
+ bundle exec rake spec # run the test suite
50
+ ```
51
+
37
52
  ## Quick Start
38
53
 
39
54
  ### 1. Initialize the Client
@@ -88,6 +103,22 @@ All existing tracing APIs work unchanged. The SDK maps Langfuse events to
88
103
  OpenTelemetry spans with the appropriate `langfuse.*` and `gen_ai.*` attributes.
89
104
  No additional dependencies are required.
90
105
 
106
+ **Scores in OTel mode:** scores are not part of the OTLP trace mapping. The SDK
107
+ always sends them through the ingestion API (`/api/public/ingestion`) as
108
+ `score-create` events, and normalizes `trace_id` / `observation_id` to W3C hex
109
+ IDs so they attach to the correct OTel-ingested entities. If an OTEL export
110
+ fails mid-batch, both the OTEL events and any scores from that batch are
111
+ re-queued for retry.
112
+
113
+ ```ruby
114
+ # Scores work the same in both modes
115
+ client = Langfuse.new(ingestion_mode: :otel, ...)
116
+ trace = client.trace(name: "chat")
117
+ generation = trace.generation(name: "llm", model: "gpt-4o")
118
+ generation.score(name: "faithfulness", value: 0.9)
119
+ client.flush # traces/spans → OTLP; scores → ingestion API
120
+ ```
121
+
91
122
  ### 2. Basic Tracing
92
123
 
93
124
  ```ruby
@@ -176,16 +207,16 @@ end
176
207
  ### Other Class Methods
177
208
 
178
209
  ```ruby
179
- # Get the thread-safe singleton client
210
+ # Get the process-wide, thread-safe singleton client
180
211
  client = Langfuse.client
181
212
 
182
213
  # Manual flush (when not using block-based tracing)
183
214
  Langfuse.flush
184
215
 
185
- # Shutdown the client
216
+ # Shutdown the client (idempotent; also runs via at_exit when shutdown_on_exit is true)
186
217
  Langfuse.shutdown
187
218
 
188
- # Reset the singleton (useful for testing)
219
+ # Reset the singleton (useful for testing; prefer Langfuse.new for isolated clients)
189
220
  Langfuse.reset!
190
221
  ```
191
222
 
@@ -406,6 +437,97 @@ generation.score(
406
437
 
407
438
  ## Advanced Usage
408
439
 
440
+ ### Tracing Environment, Sampling and Masking
441
+
442
+ ```ruby
443
+ # Tag all events with a tracing environment (also via LANGFUSE_TRACING_ENVIRONMENT)
444
+ client = Langfuse.new(
445
+ public_key: "pk-lf-...",
446
+ secret_key: "sk-lf-...",
447
+ environment: "production"
448
+ )
449
+
450
+ # Sample a fraction of traces deterministically (also via LANGFUSE_SAMPLE_RATE)
451
+ # All events of a trace share the same keep/drop decision.
452
+ sampled_client = Langfuse.new(
453
+ public_key: "pk-lf-...",
454
+ secret_key: "sk-lf-...",
455
+ sample_rate: 0.1
456
+ )
457
+
458
+ # Mask sensitive fields before sending (applied to input/output/metadata)
459
+ masked_client = Langfuse.new(
460
+ public_key: "pk-lf-...",
461
+ secret_key: "sk-lf-...",
462
+ mask: ->(value) { value.to_s.gsub(/\b\d{16}\b, "***CARD***") }
463
+ )
464
+ ```
465
+
466
+ ### Batch flushing
467
+
468
+ Events are flushed in the background every `flush_interval` seconds, or as soon
469
+ as `flush_at` events are queued (default 15, env `LANGFUSE_FLUSH_AT`). Batches
470
+ are automatically split to respect the 3.5 MB ingestion API limit, and a
471
+ process-wide `at_exit` hook flushes pending events on shutdown.
472
+
473
+ ```ruby
474
+ client = Langfuse.new(
475
+ public_key: "pk-lf-...",
476
+ secret_key: "sk-lf-...",
477
+ flush_at: 50, # flush after 50 events
478
+ flush_interval: 10, # or every 10 seconds
479
+ shutdown_on_exit: true # flush on process exit (default)
480
+ )
481
+ ```
482
+
483
+ ### Scores with full fields
484
+
485
+ Scores can target a trace, an observation, a session, or a dataset run, and
486
+ carry metadata, a config reference, and an annotation queue link:
487
+
488
+ ```ruby
489
+ # Trace-level score
490
+ trace.score(name: "accuracy", value: 0.9, comment: "good")
491
+
492
+ # Observation-level score (trace_id is set automatically on Span/Generation)
493
+ generation.score(name: "faithfulness", value: 0.8, data_type: "NUMERIC")
494
+
495
+ # Session-level score
496
+ client.score(name: "csat", value: 5, session_id: "sess-1", data_type: "NUMERIC")
497
+
498
+ # Dataset-run score with metadata and config link
499
+ client.score(
500
+ name: "hallucination",
501
+ value: 0.2,
502
+ dataset_run_id: "run-1",
503
+ trace_id: "trace-1",
504
+ metadata: { evaluator: "llm-judge" },
505
+ config_id: "cfg-abc",
506
+ data_type: "NUMERIC"
507
+ )
508
+
509
+ # Categorical string value
510
+ client.score(name: "label", value: "good", trace_id: "t1", data_type: "CATEGORICAL")
511
+ ```
512
+
513
+ ### Generation usage details, cost details and prompt linking
514
+
515
+ ```ruby
516
+ # New v4 usage model (arbitrary keys, e.g. cache tokens)
517
+ gen = trace.generation(
518
+ name: "chat",
519
+ model: "gpt-4o",
520
+ usage_details: { input: 100, output: 50, cache_read: 30 },
521
+ cost_details: { input: 0.001, output: 0.003, total: 0.004 }
522
+ )
523
+ gen.end(output: "response")
524
+
525
+ # Link a generation to a prompt version (accepts a Langfuse::Prompt or a hash)
526
+ prompt = Langfuse.get_prompt("chat-prompt")
527
+ gen = trace.generation(name: "chat", model: "gpt-4o", prompt: prompt)
528
+ # or: prompt: { name: "chat-prompt", version: 3 }
529
+ ```
530
+
409
531
  ### Error Handling
410
532
 
411
533
  ```ruby
@@ -435,11 +557,16 @@ client = Langfuse.new(
435
557
  public_key: "pk-lf-...",
436
558
  secret_key: "sk-lf-...",
437
559
  host: "https://your-instance.langfuse.com",
438
- debug: true, # Enable debug logging
439
- timeout: 30, # Request timeout in seconds
440
- retries: 3, # Number of retry attempts
441
- flush_interval: 30, # Event flush interval in seconds (default: 5)
442
- auto_flush: true # Enable automatic flushing (default: true)
560
+ debug: true, # Enable debug logging (or LANGFUSE_DEBUG=true)
561
+ timeout: 30, # Request timeout in seconds
562
+ retries: 3, # Number of retry attempts
563
+ flush_interval: 30, # Event flush interval in seconds (default: 5)
564
+ flush_at: 50, # Flush once this many events are queued (default: 15)
565
+ auto_flush: true, # Enable automatic flushing (default: true)
566
+ environment: "prod", # Tracing environment (or LANGFUSE_TRACING_ENVIRONMENT)
567
+ sample_rate: 0.5, # Keep 50% of traces deterministically (or LANGFUSE_SAMPLE_RATE)
568
+ mask: ->(v) { v }, # Callable applied to input/output/metadata
569
+ shutdown_on_exit: true # Flush pending events on process exit (default: true)
443
570
  )
444
571
  ```
445
572
 
@@ -450,9 +577,14 @@ You can also configure the client using environment variables:
450
577
  ```bash
451
578
  export LANGFUSE_PUBLIC_KEY="pk-lf-..."
452
579
  export LANGFUSE_SECRET_KEY="sk-lf-..."
453
- export LANGFUSE_HOST="https://cloud.langfuse.com"
580
+ export LANGFUSE_HOST="https://cloud.langfuse.com" # or LANGFUSE_BASE_URL
454
581
  export LANGFUSE_FLUSH_INTERVAL=5
582
+ export LANGFUSE_FLUSH_AT=15
455
583
  export LANGFUSE_AUTO_FLUSH=true
584
+ export LANGFUSE_TRACING_ENVIRONMENT="production"
585
+ export LANGFUSE_SAMPLE_RATE=0.5
586
+ export LANGFUSE_DEBUG=false
587
+ export LANGFUSE_INGESTION_MODE=legacy # or otel
456
588
  ```
457
589
 
458
590
  ### Automatic Flush Control
@@ -43,6 +43,7 @@ Gem::Specification.new do |spec|
43
43
  spec.require_paths = ['lib']
44
44
 
45
45
  # Dependencies
46
+ spec.add_dependency 'base64', '>= 0.1.0' # stdlib in Ruby < 4.0, bundled gem since Ruby 4.0
46
47
  spec.add_dependency 'concurrent-ruby', '~> 1.0'
47
48
  spec.add_dependency 'faraday', '>= 1.8', '< 3.0'
48
49
  spec.add_dependency 'faraday-multipart', '~> 1.0'
@@ -54,6 +55,7 @@ Gem::Specification.new do |spec|
54
55
  spec.add_development_dependency 'rake', '~> 13.0'
55
56
  spec.add_development_dependency 'rspec', '~> 3.0'
56
57
  spec.add_development_dependency 'rubocop', '~> 1.0'
58
+ spec.add_development_dependency 'tsort', '>= 0.1.0' # stdlib in Ruby < 4.0, default gem since Ruby 4.0
57
59
  spec.add_development_dependency 'vcr', '~> 6.0'
58
60
  spec.add_development_dependency 'webmock', '~> 3.0'
59
61
  spec.add_development_dependency 'yard', '~> 0.9'