langfuse-ruby 0.1.6 → 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 +4 -4
- data/.github/workflows/ci.yml +21 -10
- data/.github/workflows/release.yml +4 -2
- data/.mise.toml +8 -0
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +34 -10
- data/CLAUDE.md +21 -9
- data/Gemfile +1 -0
- data/Gemfile.lock +15 -5
- data/Makefile +8 -4
- data/README.md +171 -9
- data/langfuse-ruby.gemspec +2 -0
- data/lib/langfuse/client.rb +384 -62
- data/lib/langfuse/generation.rb +36 -4
- data/lib/langfuse/otel_exporter.rb +325 -0
- data/lib/langfuse/span.rb +1 -0
- data/lib/langfuse/trace.rb +16 -6
- data/lib/langfuse/utils.rb +32 -0
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +17 -6
- metadata +32 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b2c65ac52311951b2658204cbf3f76afff090465ef9e500718f59f194dbcea9
|
|
4
|
+
data.tar.gz: db397fdd6ca24db06b67c92eed232663d7845504d9dc0e7759098ebdbe88e620
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a1ba2f150b0a6d16ccc99d26144455d6fcb66d95a7e9d644bb50d12de91c95fa7751b6b3145f507a74da1b367eacaf3d43a91073a249815695f7260fc2c66d56
|
|
7
|
+
data.tar.gz: 6d27a0bd423f14637597acfc243eaf4c74e3e533eb87ceab62e46fffc8249bd3043866ec6d5a0450b8eb7bc82b29c4e553fdfe3ae9c4c3d468d5fa241b0a3c68
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -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@
|
|
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@
|
|
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: '
|
|
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
|
|
@@ -9,14 +9,16 @@ 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
|
-
- uses: actions/checkout@
|
|
16
|
+
- uses: actions/checkout@v6
|
|
15
17
|
|
|
16
18
|
- name: Set up Ruby
|
|
17
19
|
uses: ruby/setup-ruby@v1
|
|
18
20
|
with:
|
|
19
|
-
ruby-version: '
|
|
21
|
+
ruby-version: '4.0'
|
|
20
22
|
bundler-cache: true
|
|
21
23
|
|
|
22
24
|
- name: Run tests
|
data/.mise.toml
ADDED
data/.rubocop.yml
CHANGED
|
@@ -47,7 +47,7 @@ Metrics/CyclomaticComplexity:
|
|
|
47
47
|
Max: 20
|
|
48
48
|
|
|
49
49
|
Metrics/PerceivedComplexity:
|
|
50
|
-
Max:
|
|
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:
|
|
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
|
-
- **
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **
|
|
17
|
-
|
|
18
|
-
- **
|
|
19
|
-
-
|
|
20
|
-
-
|
|
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.
|
|
68
|
-
3.
|
|
69
|
-
4.
|
|
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
|
-
-
|
|
99
|
-
-
|
|
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
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
langfuse-ruby (0.
|
|
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.
|
|
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
|
|
90
|
+
unicode-emoji (4.2.0)
|
|
82
91
|
uri (1.0.3)
|
|
83
|
-
vcr (6.
|
|
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
|
|
|
@@ -26,7 +30,7 @@ lint-fix: ## Run RuboCop with auto-correct
|
|
|
26
30
|
build: ## Build the gem
|
|
27
31
|
bundle exec rake build
|
|
28
32
|
|
|
29
|
-
release: tag
|
|
33
|
+
release: tag ## Release: tag + build + push to RubyGems
|
|
30
34
|
bundle exec rake release_gem
|
|
31
35
|
|
|
32
36
|
clean: ## Remove built gem files
|
|
@@ -39,7 +43,7 @@ console: ## Start an IRB console with the gem loaded
|
|
|
39
43
|
tag: ## Create and push a version tag. Usage: make tag [VERSION=x.y.z]
|
|
40
44
|
@git fetch --tags; \
|
|
41
45
|
if [ -z "$(VERSION)" ]; then \
|
|
42
|
-
LATEST=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
|
|
46
|
+
LATEST=$$(git tag -l 'v[0-9]*' --sort=-v:refname | head -n1); \
|
|
43
47
|
if [ -z "$$LATEST" ]; then \
|
|
44
48
|
NEW_TAG="v0.0.1"; \
|
|
45
49
|
else \
|
|
@@ -51,7 +55,7 @@ tag: ## Create and push a version tag. Usage: make tag [VERSION=x.y.z]
|
|
|
51
55
|
fi; \
|
|
52
56
|
else \
|
|
53
57
|
NEW_TAG="v$(VERSION)"; \
|
|
54
|
-
LATEST=$$(git tag -l 'v*' --sort=-v:refname | head -n1); \
|
|
58
|
+
LATEST=$$(git tag -l 'v[0-9]*' --sort=-v:refname | head -n1); \
|
|
55
59
|
if [ "$$LATEST" = "$$NEW_TAG" ]; then \
|
|
56
60
|
echo "Tag $$NEW_TAG already exists on remote, deleting and re-pushing..."; \
|
|
57
61
|
git tag -d "$$NEW_TAG" 2>/dev/null || true; \
|
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
|
|
@@ -58,6 +73,52 @@ end
|
|
|
58
73
|
client = Langfuse.new
|
|
59
74
|
```
|
|
60
75
|
|
|
76
|
+
### OpenTelemetry (OTEL) Ingestion Mode
|
|
77
|
+
|
|
78
|
+
Langfuse v4 introduces a faster data model powered by OpenTelemetry. To use
|
|
79
|
+
it, enable the OTEL ingestion mode. This sends data via the OTLP/HTTP JSON
|
|
80
|
+
endpoint (`/api/public/otel/v1/traces`) with the `x-langfuse-ingestion-version: 4`
|
|
81
|
+
header for real-time ingestion and observation-level online evaluators.
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# Via constructor
|
|
85
|
+
client = Langfuse.new(
|
|
86
|
+
public_key: "pk-lf-...",
|
|
87
|
+
secret_key: "sk-lf-...",
|
|
88
|
+
ingestion_mode: :otel
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Via global configuration
|
|
92
|
+
Langfuse.configure do |config|
|
|
93
|
+
config.public_key = "pk-lf-..."
|
|
94
|
+
config.secret_key = "sk-lf-..."
|
|
95
|
+
config.ingestion_mode = :otel
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Via environment variable
|
|
99
|
+
# LANGFUSE_INGESTION_MODE=otel
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
All existing tracing APIs work unchanged. The SDK maps Langfuse events to
|
|
103
|
+
OpenTelemetry spans with the appropriate `langfuse.*` and `gen_ai.*` attributes.
|
|
104
|
+
No additional dependencies are required.
|
|
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
|
+
|
|
61
122
|
### 2. Basic Tracing
|
|
62
123
|
|
|
63
124
|
```ruby
|
|
@@ -146,16 +207,16 @@ end
|
|
|
146
207
|
### Other Class Methods
|
|
147
208
|
|
|
148
209
|
```ruby
|
|
149
|
-
# Get the thread-safe singleton client
|
|
210
|
+
# Get the process-wide, thread-safe singleton client
|
|
150
211
|
client = Langfuse.client
|
|
151
212
|
|
|
152
213
|
# Manual flush (when not using block-based tracing)
|
|
153
214
|
Langfuse.flush
|
|
154
215
|
|
|
155
|
-
# Shutdown the client
|
|
216
|
+
# Shutdown the client (idempotent; also runs via at_exit when shutdown_on_exit is true)
|
|
156
217
|
Langfuse.shutdown
|
|
157
218
|
|
|
158
|
-
# Reset the singleton (useful for testing)
|
|
219
|
+
# Reset the singleton (useful for testing; prefer Langfuse.new for isolated clients)
|
|
159
220
|
Langfuse.reset!
|
|
160
221
|
```
|
|
161
222
|
|
|
@@ -376,6 +437,97 @@ generation.score(
|
|
|
376
437
|
|
|
377
438
|
## Advanced Usage
|
|
378
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
|
+
|
|
379
531
|
### Error Handling
|
|
380
532
|
|
|
381
533
|
```ruby
|
|
@@ -405,11 +557,16 @@ client = Langfuse.new(
|
|
|
405
557
|
public_key: "pk-lf-...",
|
|
406
558
|
secret_key: "sk-lf-...",
|
|
407
559
|
host: "https://your-instance.langfuse.com",
|
|
408
|
-
debug: true,
|
|
409
|
-
timeout: 30,
|
|
410
|
-
retries: 3,
|
|
411
|
-
flush_interval: 30,
|
|
412
|
-
|
|
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)
|
|
413
570
|
)
|
|
414
571
|
```
|
|
415
572
|
|
|
@@ -420,9 +577,14 @@ You can also configure the client using environment variables:
|
|
|
420
577
|
```bash
|
|
421
578
|
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
|
|
422
579
|
export LANGFUSE_SECRET_KEY="sk-lf-..."
|
|
423
|
-
export LANGFUSE_HOST="https://cloud.langfuse.com"
|
|
580
|
+
export LANGFUSE_HOST="https://cloud.langfuse.com" # or LANGFUSE_BASE_URL
|
|
424
581
|
export LANGFUSE_FLUSH_INTERVAL=5
|
|
582
|
+
export LANGFUSE_FLUSH_AT=15
|
|
425
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
|
|
426
588
|
```
|
|
427
589
|
|
|
428
590
|
### Automatic Flush Control
|
data/langfuse-ruby.gemspec
CHANGED
|
@@ -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'
|