langfuse-ruby 0.2.0 → 0.2.1

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.
data/README.md CHANGED
@@ -2,17 +2,20 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/langfuse-ruby.svg)](https://badge.fury.io/rb/langfuse-ruby) [![CI](https://github.com/ai-firstly/langfuse-ruby/workflows/CI/badge.svg)](https://github.com/ai-firstly/langfuse-ruby/actions/workflows/ci.yml) [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1.0-red.svg)](https://www.ruby-lang.org/) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
4
4
 
5
- Ruby SDK for [Langfuse](https://langfuse.com) - the open-source LLM engineering platform. This SDK provides comprehensive tracing, prompt management, and evaluation capabilities for LLM applications.
5
+ Ruby SDK for [Langfuse](https://langfuse.com) the open-source LLM engineering platform. Tracing, prompt management, and evaluation for Ruby LLM apps, with first-class support for the **Langfuse v4** observations-first data model.
6
+
7
+ **New projects should use `ingestion_mode: :otel` (Langfuse v4).** That path sends traces over OTLP/HTTP (`/api/public/otel/v1/traces`) with `x-langfuse-ingestion-version: 4`, so data shows up in real time and observation-level evaluators, cost, and the Observations API v2 work as designed. The tracing API (`Langfuse.trace`, `#generation`, `#span`, `#agent`, …) is unchanged; only the transport and ID format differ.
8
+
9
+ On Langfuse Cloud, `POST /api/public/ingestion` stops accepting everything except scores on **16 November 2026**. Self-hosted v4 should use OTEL as well. See the [Langfuse v4 guide](docs/V4.md) and the official [custom-ingestion migration](https://langfuse.com/integrations/native/opentelemetry/migration-to-v4).
6
10
 
7
11
  ## Features
8
12
 
9
- - 🔍 **Tracing**: Complete observability for LLM applications with traces, spans, and generations
10
- - 📝 **Prompt Management**: Version control and deployment of prompts with caching
11
- - 📊 **Evaluation**: Built-in evaluators and custom scoring capabilities
12
- - 🎯 **Events**: Generic event tracking for custom application events and logging
13
- - 🚀 **Async Processing**: Background event processing with automatic batching
14
- - 🔒 **Type Safety**: Comprehensive error handling and validation
15
- - 🎯 **Framework Integration**: Easy integration with popular Ruby frameworks
13
+ - **Langfuse v4 / OpenTelemetry**: OTLP ingestion, W3C hex IDs, `usage_details` / `cost_details`, observation types (`agent`, `tool`, `retriever`, …)
14
+ - 🔍 **Tracing**: Traces, spans, generations, events, and typed observations
15
+ - 📝 **Prompt Management**: Versioned prompts with a bounded cache and stale-on-outage reads
16
+ - 📊 **Evaluation**: Built-in evaluators and scores (trace, observation, session, dataset run)
17
+ - 🚀 **Async Processing**: Background batching, queue bounds, fork-safe flush, `at_exit` shutdown
18
+ - 🔒 **Resilience**: Typed errors, Retry-After + jittered backoff, null-object degradation for `Langfuse.trace`
16
19
 
17
20
  ## Installation
18
21
 
@@ -49,102 +52,121 @@ bundle install # install gem dependencies
49
52
  bundle exec rake spec # run the test suite
50
53
  ```
51
54
 
52
- ## Quick Start
55
+ ## Quick Start (Langfuse v4)
53
56
 
54
- ### 1. Initialize the Client
57
+ ### 1. Configure the client for v4
55
58
 
56
- ```ruby
57
- require 'langfuse'
59
+ The SDK default host is **US Cloud** (`https://us.cloud.langfuse.com`). Override
60
+ `host` / `LANGFUSE_HOST` / `LANGFUSE_BASE_URL` for EU, Japan, HIPAA, or
61
+ self-hosted. `ingestion_mode` still defaults to `:legacy` for compatibility;
62
+ set it to `:otel` for v4.
58
63
 
59
- # Initialize with API keys
60
- client = Langfuse.new(
61
- public_key: "pk-lf-...",
62
- secret_key: "sk-lf-...",
63
- host: "https://cloud.langfuse.com" # Optional, defaults to cloud.langfuse.com
64
- )
64
+ ```ruby
65
+ require "langfuse"
65
66
 
66
- # Or configure globally
67
67
  Langfuse.configure do |config|
68
- config.public_key = "pk-lf-..."
69
- config.secret_key = "sk-lf-..."
70
- config.host = "https://cloud.langfuse.com"
68
+ config.public_key = ENV.fetch("LANGFUSE_PUBLIC_KEY")
69
+ config.secret_key = ENV.fetch("LANGFUSE_SECRET_KEY")
70
+ config.host = ENV["LANGFUSE_HOST"] || ENV["LANGFUSE_BASE_URL"] || "https://us.cloud.langfuse.com"
71
+ config.ingestion_mode = :otel # Langfuse v4 — required for real-time OTEL ingestion
71
72
  end
72
73
 
73
- client = Langfuse.new
74
+ # Equivalent:
75
+ # Langfuse.new(..., ingestion_mode: :otel)
76
+ # LANGFUSE_INGESTION_MODE=otel
74
77
  ```
75
78
 
76
- ### OpenTelemetry (OTEL) Ingestion Mode
79
+ | Region | `host` |
80
+ | --- | --- |
81
+ | US (SDK default) | `https://us.cloud.langfuse.com` |
82
+ | EU | `https://cloud.langfuse.com` |
83
+ | Japan | `https://jp.cloud.langfuse.com` |
84
+ | HIPAA | `https://hipaa.cloud.langfuse.com` |
85
+ | Self-hosted | your Langfuse origin (no trailing path) |
77
86
 
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.
87
+ No extra gems are required. The SDK maps traces/spans/generations to OTLP JSON
88
+ and sets `x-langfuse-ingestion-version: 4` on the OTEL connection.
89
+
90
+ ### 2. Trace an LLM call (recommended API)
91
+
92
+ v4 is **observations-first**: a trace is the set of observations that share a
93
+ `trace_id`. Put the overall request/response on the trace (root span) **and**
94
+ on the generation/span that actually produced them. Prefer `usage_details` /
95
+ `cost_details` over the legacy `usage` hash — v4 uses `usage_details` for cost.
82
96
 
83
97
  ```ruby
84
- # Via constructor
85
- client = Langfuse.new(
86
- public_key: "pk-lf-...",
87
- secret_key: "sk-lf-...",
88
- ingestion_mode: :otel
89
- )
98
+ Langfuse.trace("chat-completion", user_id: "user-123", session_id: "sess-456",
99
+ input: { message: "Hello, world!" }) do |trace|
100
+ generation = trace.generation(
101
+ name: "openai-completion",
102
+ model: "gpt-4o",
103
+ input: [{ role: "user", content: "Hello, world!" }],
104
+ model_parameters: { temperature: 0.7, max_tokens: 100 }
105
+ )
90
106
 
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
107
+ response = call_llm(...) # your code
108
+
109
+ generation.end(
110
+ output: response.content,
111
+ usage_details: { input: 12, output: 18, total: 30 }, # v4 cost model
112
+ cost_details: { input: 0.0001, output: 0.0006, total: 0.0007 }
113
+ )
114
+ generation.score(name: "faithfulness", value: 0.9)
97
115
 
98
- # Via environment variable
99
- # LANGFUSE_INGESTION_MODE=otel
116
+ trace.update(output: response.content)
117
+ end # flush is automatic in the block form
100
118
  ```
101
119
 
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.
120
+ Scores always go through `/api/public/ingestion` (`score-create`), even in
121
+ `:otel` mode. Trace/observation IDs are normalized to W3C hex so they attach to
122
+ the OTEL-ingested spans. If an OTEL export fails mid-batch, both the OTEL
123
+ events and that batch's scores are re-queued.
105
124
 
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.
125
+ ### 3. Nested work with v4 observation types
126
+
127
+ Typed observations (`agent`, `tool`, `chain`, `retriever`, `embedding`,
128
+ `evaluator`, `guardrail`) are spans with `langfuse.observation.type` set. They
129
+ filter and evaluate correctly in the v4 UI.
112
130
 
113
131
  ```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
- ```
132
+ Langfuse.trace("document-qa", user_id: "user-456", input: { query: "What is Ruby?" }) do |trace|
133
+ agent = trace.agent(name: "qa-agent", input: { query: "What is Ruby?" })
121
134
 
122
- ### 2. Basic Tracing
135
+ retrieval = agent.retriever(name: "vector-search", input: { query: "What is Ruby?", top_k: 5 })
136
+ retrieval.end(output: { documents: ["Ruby is a programming language..."] })
123
137
 
124
- ```ruby
125
- # Create a trace
126
- trace = client.trace(
127
- name: "chat-completion",
128
- user_id: "user123",
129
- session_id: "session456",
130
- environment: "production"
131
- )
138
+ gen = agent.generation(
139
+ name: "openai-completion",
140
+ model: "gpt-4o",
141
+ input: [{ role: "user", content: "What is Ruby?" }],
142
+ prompt: Langfuse.get_prompt("qa-system") # optional prompt link
143
+ )
144
+ gen.end(
145
+ output: "Ruby is a dynamic programming language.",
146
+ usage_details: { input: 50, output: 20, total: 70 }
147
+ )
132
148
 
133
- # Add a generation (LLM call)
134
- generation = trace.generation(
135
- name: "openai-completion",
136
- model: "gpt-3.5-turbo",
137
- input: [{ role: "user", content: "Hello, world!" }],
138
- model_parameters: { temperature: 0.7, max_tokens: 100 }
139
- )
149
+ agent.end(output: { answer: "Ruby is a dynamic programming language." })
150
+ trace.update(output: { answer: "Ruby is a dynamic programming language." })
151
+ end
152
+ ```
140
153
 
141
- generation.end(output: 'Hello! How can I help you today?', usage: { prompt_tokens: 10, completion_tokens: 15, total_tokens: 25 })
154
+ ### What `:otel` changes (and what it does not)
142
155
 
143
- trace.update(output: 'Hello! How can I help you today?')
156
+ | | `:otel` (v4, recommended) | `:legacy` |
157
+ | --- | --- | --- |
158
+ | Transport | OTLP/HTTP JSON `/api/public/otel/v1/traces` | batched POST `/api/public/ingestion` |
159
+ | Header | `x-langfuse-ingestion-version: 4` | none |
160
+ | IDs | W3C 32-char trace / 16-char span hex | UUIDs |
161
+ | Create + update | collapsed into **one** span (v4 is append-only) | sent as separate events |
162
+ | Usage | `usage` normalized into `langfuse.observation.usage_details` + `gen_ai.usage.*` | legacy `usage` object |
163
+ | Scores | still the ingestion API, IDs coerced to hex | ingestion API |
164
+ | Tracing API | identical | identical |
144
165
 
145
- # Flush events (optional - happens automatically)
146
- client.flush
147
- ```
166
+ Do not dual-send the same IDs through both modes into one project. Switch with
167
+ `ingestion_mode:` / `LANGFUSE_INGESTION_MODE` (values are downcased; a typo
168
+ falls back to `:legacy` with a warning). Full attribute mapping, evaluator
169
+ notes, and a cutover checklist: [docs/V4.md](docs/V4.md).
148
170
 
149
171
  ## Simplified Usage (Recommended)
150
172
 
@@ -155,10 +177,11 @@ For most use cases, you can use the simplified class-level API with automatic fl
155
177
  ```ruby
156
178
  require 'langfuse'
157
179
 
158
- # Configure once
180
+ # Configure once — v4 / OTEL is the recommended ingestion path
159
181
  Langfuse.configure do |config|
160
- config.public_key = ENV['LANGFUSE_PUBLIC_KEY']
161
- config.secret_key = ENV['LANGFUSE_SECRET_KEY']
182
+ config.public_key = ENV["LANGFUSE_PUBLIC_KEY"]
183
+ config.secret_key = ENV["LANGFUSE_SECRET_KEY"]
184
+ config.ingestion_mode = :otel
162
185
  end
163
186
 
164
187
  # Use block-based tracing - flush happens automatically!
@@ -174,7 +197,10 @@ Langfuse.trace("my-trace", user_id: "user-1", input: { message: "Hello" }) do |t
174
197
  response = call_openai(...)
175
198
 
176
199
  # Record the response
177
- generation.end(output: response.content, usage: response.usage)
200
+ generation.end(
201
+ output: response.content,
202
+ usage_details: { input: 10, output: 15, total: 25 } # or usage: response.usage
203
+ )
178
204
  trace.update(output: response.content)
179
205
  end # Automatic flush here!
180
206
  ```
@@ -220,7 +246,7 @@ Langfuse.shutdown
220
246
  Langfuse.reset!
221
247
  ```
222
248
 
223
- ### 3. Nested Spans
249
+ ## Nested Spans
224
250
 
225
251
  ```ruby
226
252
  trace = client.trace(name: "document-qa")
@@ -264,7 +290,46 @@ llm_gen = answer_span.generation(
264
290
  ]
265
291
  )
266
292
 
267
- answer_span.end(output: { answer: "Machine learning is a subset of AI..." }, usage: { prompt_tokens: 50, completion_tokens: 30, total_tokens: 80 })
293
+ llm_gen.end(
294
+ output: { answer: "Machine learning is a subset of AI..." },
295
+ usage_details: { input: 50, output: 30, total: 80 }
296
+ )
297
+ answer_span.end(output: { answer: "Machine learning is a subset of AI..." })
298
+ ```
299
+
300
+ ## Observation Types (v4)
301
+
302
+ Langfuse v4 queries **observations** directly. Use a specific type so traces
303
+ filter and evaluate correctly. Helpers exist on `Client`, `Trace`, `Span`, and
304
+ `Generation`. `as_type:` on `#span` does the same thing; an explicit `as_type:`
305
+ passed to a typed helper cannot override that helper's type.
306
+
307
+ | Helper | `langfuse.observation.type` | Use for |
308
+ | --- | --- | --- |
309
+ | `#span` | `span` | generic timed work |
310
+ | `#generation` | `generation` | LLM calls (model, tokens, cost, prompt link) |
311
+ | `#event` | `event` | point-in-time logs |
312
+ | `#agent` | `agent` | orchestration / tool-calling loops |
313
+ | `#tool` | `tool` | a single function or API call |
314
+ | `#chain` | `chain` | stitching retrieval → generation, etc. |
315
+ | `#retriever` | `retriever` | vector store / DB lookups |
316
+ | `#embedding` | `embedding` | embedding model calls (`model` / `usage` go into metadata) |
317
+ | `#evaluator` / `#evaluator_obs` | `evaluator` | scoring functions (`Client#evaluator` is an alias of `#evaluator_obs`) |
318
+ | `#guardrail` | `guardrail` | safety / moderation |
319
+
320
+ ```ruby
321
+ trace = client.trace(name: "support-agent", user_id: "u1")
322
+
323
+ agent = trace.agent(name: "planner", input: { question: "Reset my password" })
324
+ tool = agent.tool(name: "lookup-user", input: { email: "a@example.com" })
325
+ tool.end(output: { user_id: "u1" })
326
+
327
+ guard = agent.guardrail(name: "content-filter", input: { text: "Reset my password" })
328
+ guard.end(output: { blocked: false })
329
+
330
+ gen = agent.generation(name: "reply", model: "gpt-4o", input: [...])
331
+ gen.end(output: "I can help with that.", usage_details: { input: 40, output: 12, total: 52 })
332
+ agent.end(output: { reply: "I can help with that." })
268
333
  ```
269
334
 
270
335
  ## Events
@@ -320,6 +385,11 @@ puts compiled
320
385
 
321
386
  > **Note**: Prompt names containing special characters (like `/`, spaces, `?`, etc.) are automatically URL-encoded. You don't need to manually encode them.
322
387
 
388
+ Fetched prompts are cached per client for `cache_ttl_seconds` (default 60). The
389
+ cache is bounded (200 entries), TTLs use a monotonic clock, and if a refetch
390
+ fails while an expired entry exists, the stale copy is served with a warning —
391
+ a Langfuse outage does not break prompt resolution for prompts seen before.
392
+
323
393
  ### Create Prompts
324
394
 
325
395
  ```ruby
@@ -459,7 +529,7 @@ sampled_client = Langfuse.new(
459
529
  masked_client = Langfuse.new(
460
530
  public_key: "pk-lf-...",
461
531
  secret_key: "sk-lf-...",
462
- mask: ->(value) { value.to_s.gsub(/\b\d{16}\b, "***CARD***") }
532
+ mask: ->(value) { value.to_s.gsub(/\b\d{16}\b/, "***CARD***") }
463
533
  )
464
534
  ```
465
535
 
@@ -470,12 +540,23 @@ as `flush_at` events are queued (default 15, env `LANGFUSE_FLUSH_AT`). Batches
470
540
  are automatically split to respect the 3.5 MB ingestion API limit, and a
471
541
  process-wide `at_exit` hook flushes pending events on shutdown.
472
542
 
543
+ The queue is bounded by `max_queue_size` (default 10,000, env
544
+ `LANGFUSE_MAX_QUEUE_SIZE`): while Langfuse is unreachable the oldest events are
545
+ dropped with a warning instead of growing memory without bound, and batches
546
+ that fail with a permanent error (4xx) are dropped rather than retried forever.
547
+ `shutdown` lets the flush thread finish its current send before returning, and
548
+ after a `fork` (e.g. Puma workers) each process recreates its own flush thread.
549
+
550
+ `update` and `end` send only the fields you changed, so ending a generation does
551
+ not re-upload its prompt, input or model parameters.
552
+
473
553
  ```ruby
474
554
  client = Langfuse.new(
475
555
  public_key: "pk-lf-...",
476
556
  secret_key: "sk-lf-...",
477
557
  flush_at: 50, # flush after 50 events
478
558
  flush_interval: 10, # or every 10 seconds
559
+ max_queue_size: 20_000, # drop the oldest events beyond this many queued
479
560
  shutdown_on_exit: true # flush on process exit (default)
480
561
  )
481
562
  ```
@@ -510,7 +591,7 @@ client.score(
510
591
  client.score(name: "label", value: "good", trace_id: "t1", data_type: "CATEGORICAL")
511
592
  ```
512
593
 
513
- ### Generation usage details, cost details and prompt linking
594
+ ### Generation usage details, cost details and prompt linking (v4)
514
595
 
515
596
  ```ruby
516
597
  # New v4 usage model (arbitrary keys, e.g. cache tokens)
@@ -556,20 +637,49 @@ end
556
637
  client = Langfuse.new(
557
638
  public_key: "pk-lf-...",
558
639
  secret_key: "sk-lf-...",
559
- host: "https://your-instance.langfuse.com",
640
+ host: "https://us.cloud.langfuse.com", # US default; EU: cloud.langfuse.com
641
+ ingestion_mode: :otel, # Langfuse v4 (default remains :legacy)
560
642
  debug: true, # Enable debug logging (or LANGFUSE_DEBUG=true)
561
643
  timeout: 30, # Request timeout in seconds
562
644
  retries: 3, # Number of retry attempts
563
645
  flush_interval: 30, # Event flush interval in seconds (default: 5)
564
646
  flush_at: 50, # Flush once this many events are queued (default: 15)
647
+ max_queue_size: 10_000, # Drop the oldest events beyond this many queued (default: 10_000)
565
648
  auto_flush: true, # Enable automatic flushing (default: true)
566
649
  environment: "prod", # Tracing environment (or LANGFUSE_TRACING_ENVIRONMENT)
567
650
  sample_rate: 0.5, # Keep 50% of traces deterministically (or LANGFUSE_SAMPLE_RATE)
568
651
  mask: ->(v) { v }, # Callable applied to input/output/metadata
569
- shutdown_on_exit: true # Flush pending events on process exit (default: true)
652
+ shutdown_on_exit: true, # Flush pending events on process exit (default: true)
653
+ http_adapter: :net_http_persistent # Faraday adapter (default: Faraday's default)
570
654
  )
571
655
  ```
572
656
 
657
+ ### Retries
658
+
659
+ Timeouts, network errors, `429` and `5xx` responses are retried up to `retries`
660
+ times (default 3). A `Retry-After` header is honored (seconds or HTTP date,
661
+ capped at 10 s); otherwise the delay grows exponentially from 0.5 s with ±50%
662
+ jitter, so many processes do not retry in lockstep. Client errors such as `401`
663
+ and `422` are not retried, since repeating them cannot help.
664
+
665
+ ### Connection reuse
666
+
667
+ By default every request opens a new TLS connection. To keep connections alive
668
+ between flushes, add a pooling adapter to your `Gemfile` and select it with
669
+ `http_adapter`:
670
+
671
+ ```ruby
672
+ # Gemfile
673
+ gem "faraday-net_http_persistent"
674
+
675
+ Langfuse.configure do |config|
676
+ config.http_adapter = :net_http_persistent
677
+ end
678
+ ```
679
+
680
+ If the adapter is not available, the client logs a warning and falls back to
681
+ Faraday's default adapter instead of raising.
682
+
573
683
  ### Environment Variables
574
684
 
575
685
  You can also configure the client using environment variables:
@@ -577,14 +687,15 @@ You can also configure the client using environment variables:
577
687
  ```bash
578
688
  export LANGFUSE_PUBLIC_KEY="pk-lf-..."
579
689
  export LANGFUSE_SECRET_KEY="sk-lf-..."
580
- export LANGFUSE_HOST="https://cloud.langfuse.com" # or LANGFUSE_BASE_URL
690
+ export LANGFUSE_HOST="https://us.cloud.langfuse.com" # or LANGFUSE_BASE_URL; EU: https://cloud.langfuse.com
581
691
  export LANGFUSE_FLUSH_INTERVAL=5
582
692
  export LANGFUSE_FLUSH_AT=15
693
+ export LANGFUSE_MAX_QUEUE_SIZE=10000
583
694
  export LANGFUSE_AUTO_FLUSH=true
584
695
  export LANGFUSE_TRACING_ENVIRONMENT="production"
585
696
  export LANGFUSE_SAMPLE_RATE=0.5
586
697
  export LANGFUSE_DEBUG=false
587
- export LANGFUSE_INGESTION_MODE=legacy # or otel
698
+ export LANGFUSE_INGESTION_MODE=otel # v4; use `legacy` only for pre-v4 self-hosted
588
699
  ```
589
700
 
590
701
  ### Automatic Flush Control
@@ -671,6 +782,7 @@ client.shutdown
671
782
  Langfuse.configure do |config|
672
783
  config.public_key = Rails.application.credentials.langfuse_public_key
673
784
  config.secret_key = Rails.application.credentials.langfuse_secret_key
785
+ config.ingestion_mode = :otel
674
786
  config.debug = Rails.env.development?
675
787
  end
676
788
 
@@ -726,18 +838,22 @@ end
726
838
 
727
839
  Check out the `examples/` directory for more comprehensive examples:
728
840
 
729
- - [Basic Tracing](examples/basic_tracing.rb)
730
- - [Prompt Management](examples/prompt_management.rb)
731
- - [Evaluation Pipeline](examples/evaluation_pipeline.rb)
732
- - [Rails Integration](examples/rails_integration.rb)
841
+ - [Langfuse v4 / OTEL tracing](examples/v4_otel_tracing.rb) (recommended)
842
+ - [Simplified usage](examples/simplified_usage.rb)
843
+ - [Basic tracing](examples/basic_tracing.rb)
844
+ - [Prompt management](examples/prompt_management.rb)
845
+ - [Events](examples/event_usage.rb)
846
+ - [Auto-flush control](examples/auto_flush_control.rb)
847
+ - [Connection config](examples/connection_config_demo.rb)
733
848
 
734
849
  ## Documentation
735
850
 
736
- For more detailed information, please refer to the [documentation](docs/README.md).
737
-
851
+ - [Langfuse v4 usage](docs/V4.md) — OTEL ingestion, observations-first model, cutover checklist
852
+ - [Documentation index](docs/README.md)
738
853
  - [Publishing Guide](docs/PUBLISH_GUIDE.md)
739
854
  - [Release Checklist](docs/RELEASE_CHECKLIST.md)
740
- - [Examples](examples/)
855
+ - [Official Langfuse docs](https://langfuse.com/docs)
856
+ - [Migrate custom ingestion to v4](https://langfuse.com/integrations/native/opentelemetry/migration-to-v4)
741
857
 
742
858
  ## Development
743
859
 
@@ -773,4 +889,5 @@ The gem is available as open source under the terms of the [MIT License](https:/
773
889
 
774
890
  - [Langfuse Ruby SDK Documentation](https://rubydoc.info/gems/langfuse-ruby)
775
891
  - [RubyGems](https://rubygems.org/gems/langfuse-ruby)
776
- - [Langfuse Documentation](https://langfuse.com/docs)
892
+ - [Langfuse Documentation](https://langfuse.com/docs)
893
+ - [Langfuse v4 / OpenTelemetry](https://langfuse.com/integrations/native/opentelemetry)
data/Rakefile CHANGED
@@ -8,12 +8,6 @@ RSpec::Core::RakeTask.new(:spec)
8
8
 
9
9
  task default: :spec
10
10
 
11
- # Custom release task
12
- desc 'Release gem to RubyGems'
13
- task release_gem: [:build] do
14
- sh "gem push langfuse-ruby-#{Langfuse::VERSION}.gem"
15
- end
16
-
17
11
  # Offline test task
18
12
  desc 'Run offline tests'
19
13
  task :test_offline do