langfuse-ruby 0.1.7 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +21 -10
- data/.github/workflows/release.yml +2 -2
- data/.mise.toml +8 -0
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +78 -10
- data/CLAUDE.md +54 -16
- data/Gemfile +1 -0
- data/Gemfile.lock +15 -5
- data/Makefile +6 -5
- data/README.md +338 -89
- data/Rakefile +0 -6
- data/docs/FINAL_SUMMARY.md +12 -185
- data/docs/PUBLISH_GUIDE.md +36 -272
- data/docs/README.md +12 -22
- data/docs/RELEASE_CHECKLIST.md +20 -145
- data/docs/V4.md +159 -0
- data/examples/basic_tracing.rb +11 -10
- data/examples/simplified_usage.rb +7 -6
- data/examples/v4_otel_tracing.rb +70 -0
- data/langfuse-ruby.gemspec +2 -0
- data/lib/langfuse/client.rb +757 -276
- data/lib/langfuse/event.rb +1 -17
- data/lib/langfuse/generation.rb +71 -124
- data/lib/langfuse/null_objects.rb +4 -0
- data/lib/langfuse/otel_exporter.rb +145 -63
- data/lib/langfuse/partial_updates.rb +30 -0
- data/lib/langfuse/prompt.rb +9 -83
- data/lib/langfuse/prompt_cache.rb +65 -0
- data/lib/langfuse/span.rb +29 -153
- data/lib/langfuse/span_wrappers.rb +32 -0
- data/lib/langfuse/template_compiler.rb +56 -0
- data/lib/langfuse/trace.rb +38 -164
- data/lib/langfuse/utils.rb +54 -20
- data/lib/langfuse/version.rb +1 -1
- data/lib/langfuse.rb +61 -56
- data/scripts/release.sh +12 -12
- metadata +37 -2
data/docs/RELEASE_CHECKLIST.md
CHANGED
|
@@ -1,161 +1,36 @@
|
|
|
1
1
|
# Langfuse Ruby SDK 发布检查清单
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 发布前
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- [ ]
|
|
7
|
-
- [ ]
|
|
8
|
-
- [ ]
|
|
9
|
-
- [ ]
|
|
5
|
+
- [ ] `bundle exec rspec`
|
|
6
|
+
- [ ] `bundle exec ruby scripts/test_offline.rb`
|
|
7
|
+
- [ ] `bundle exec rubocop`
|
|
8
|
+
- [ ] README / [V4.md](V4.md) / 示例与代码一致
|
|
9
|
+
- [ ] `lib/langfuse/version.rb`
|
|
10
|
+
- [ ] `CHANGELOG.md`(Unreleased → 新版本)
|
|
11
|
+
- [ ] `bundle install` 更新 `Gemfile.lock`
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
- [ ] 更新版本号 (`lib/langfuse/version.rb`)
|
|
13
|
-
- [ ] 更新变更日志 (`CHANGELOG.md`)
|
|
14
|
-
- [ ] 更新 README 如有必要
|
|
13
|
+
## 发布
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
- [ ] 更新 `langfuse.gemspec` 中的作者信息
|
|
18
|
-
- [ ] 更新 GitHub 仓库链接
|
|
19
|
-
- [ ] 更新邮箱地址
|
|
15
|
+
推荐:推送 `vX.Y.Z` tag,由 `.github/workflows/release.yml` 发布。
|
|
20
16
|
|
|
21
|
-
### 4. Git 仓库设置
|
|
22
|
-
- [ ] 初始化 Git 仓库 (`git init`)
|
|
23
|
-
- [ ] 添加远程仓库
|
|
24
|
-
- [ ] 提交所有更改
|
|
25
|
-
- [ ] 推送到 GitHub
|
|
26
|
-
|
|
27
|
-
## 🚀 发布步骤
|
|
28
|
-
|
|
29
|
-
### 方法 1: 使用 GitHub Actions (推荐)
|
|
30
|
-
|
|
31
|
-
> ⚠️ **重要**: 必须先运行 `bundle install` 更新 `Gemfile.lock`,否则 CI 会失败!
|
|
32
|
-
|
|
33
|
-
#### 前提条件
|
|
34
|
-
在 GitHub 仓库的 **Settings → Secrets and variables → Actions** 中配置:
|
|
35
|
-
- `RUBYGEMS_API_KEY` - RubyGems API 密钥(在 [rubygems.org](https://rubygems.org) → Settings → API keys 中获取)
|
|
36
|
-
|
|
37
|
-
> 注意:`GITHUB_TOKEN` 无需手动配置,GitHub Actions 会自动提供。
|
|
38
|
-
|
|
39
|
-
#### 发布流程
|
|
40
17
|
```bash
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# 2. 更新 Gemfile.lock(重要!)
|
|
45
|
-
bundle install
|
|
46
|
-
|
|
47
|
-
# 3. 提交更改
|
|
48
|
-
git add .
|
|
49
|
-
git commit -m "Bump version to x.x.x"
|
|
50
|
-
|
|
51
|
-
# 4. 推送代码
|
|
52
|
-
git push origin <branch>
|
|
53
|
-
|
|
54
|
-
# 5. 创建并推送标签(触发自动发布)
|
|
55
|
-
git tag vx.x.x
|
|
56
|
-
git push origin vx.x.x
|
|
18
|
+
git push origin HEAD
|
|
19
|
+
git tag vX.Y.Z
|
|
20
|
+
git push origin vX.Y.Z
|
|
57
21
|
```
|
|
58
22
|
|
|
59
|
-
|
|
60
|
-
- 运行测试 (rspec + offline tests)
|
|
61
|
-
- 构建 gem
|
|
62
|
-
- 发布到 RubyGems
|
|
63
|
-
- 创建 GitHub Release
|
|
23
|
+
需要仓库 secret:`RUBYGEMS_API_KEY`。
|
|
64
24
|
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
./scripts/release.sh
|
|
68
|
-
```
|
|
25
|
+
本地:
|
|
69
26
|
|
|
70
|
-
### 方法 3: 手动发布
|
|
71
27
|
```bash
|
|
72
|
-
# 1. 运行测试
|
|
73
|
-
bundle exec rspec
|
|
74
|
-
ruby scripts/test_offline.rb
|
|
75
|
-
|
|
76
|
-
# 2. 构建 gem
|
|
77
28
|
gem build langfuse-ruby.gemspec
|
|
78
|
-
|
|
79
|
-
# 3. 发布到 RubyGems
|
|
80
|
-
gem push langfuse-ruby-x.x.x.gem
|
|
81
|
-
|
|
82
|
-
# 4. 创建 Git 标签
|
|
83
|
-
git tag vx.x.x
|
|
84
|
-
git push origin main
|
|
85
|
-
git push origin vx.x.x
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## 📊 发布后验证
|
|
89
|
-
|
|
90
|
-
### 1. 检查 RubyGems
|
|
91
|
-
- [ ] 访问 https://rubygems.org/gems/langfuse
|
|
92
|
-
- [ ] 确认版本号正确
|
|
93
|
-
- [ ] 检查下载链接
|
|
94
|
-
|
|
95
|
-
### 2. 测试安装
|
|
96
|
-
```bash
|
|
97
|
-
# 从 RubyGems 安装
|
|
98
|
-
gem install langfuse-ruby
|
|
99
|
-
|
|
100
|
-
# 测试基本功能
|
|
101
|
-
ruby -e "require 'langfuse'; puts Langfuse::VERSION"
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### 3. 更新文档
|
|
105
|
-
- [ ] 更新 README 中的安装说明
|
|
106
|
-
- [ ] 更新项目网站(如有)
|
|
107
|
-
- [ ] 发布公告
|
|
108
|
-
|
|
109
|
-
## 🔧 常见问题解决
|
|
110
|
-
|
|
111
|
-
### RubyGems 认证问题
|
|
112
|
-
```bash
|
|
113
|
-
# 登录 RubyGems
|
|
114
|
-
gem signin
|
|
115
|
-
|
|
116
|
-
# 或设置 API 密钥
|
|
117
|
-
gem signin --key your_api_key
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### Git 标签问题
|
|
121
|
-
```bash
|
|
122
|
-
# 删除本地标签
|
|
123
|
-
git tag -d v0.1.0
|
|
124
|
-
|
|
125
|
-
# 删除远程标签
|
|
126
|
-
git push origin --delete v0.1.0
|
|
127
|
-
|
|
128
|
-
# 重新创建标签
|
|
129
|
-
git tag v0.1.0
|
|
130
|
-
git push origin v0.1.0
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### 版本冲突
|
|
134
|
-
```bash
|
|
135
|
-
# 检查现有版本
|
|
136
|
-
gem search langfuse
|
|
137
|
-
|
|
138
|
-
# 更新版本号后重新发布
|
|
139
|
-
# 编辑 lib/langfuse/version.rb
|
|
140
|
-
# 重新构建和发布
|
|
29
|
+
gem push langfuse-ruby-X.Y.Z.gem
|
|
141
30
|
```
|
|
142
31
|
|
|
143
|
-
##
|
|
144
|
-
|
|
145
|
-
如果遇到问题:
|
|
146
|
-
1. 查看 RubyGems 文档
|
|
147
|
-
2. 检查 GitHub Issues
|
|
148
|
-
3. 联系 Ruby 社区
|
|
149
|
-
4. 参考其他成功的 gem 项目
|
|
150
|
-
|
|
151
|
-
## 🎉 发布成功后
|
|
152
|
-
|
|
153
|
-
1. 🎊 庆祝!您的 gem 已成功发布
|
|
154
|
-
2. 📢 在社交媒体上分享
|
|
155
|
-
3. 📝 写博客文章介绍
|
|
156
|
-
4. 🔗 联系 Langfuse 团队添加到官方文档
|
|
157
|
-
5. 📈 监控使用情况和反馈
|
|
158
|
-
|
|
159
|
-
---
|
|
32
|
+
## 发布后
|
|
160
33
|
|
|
161
|
-
|
|
34
|
+
- [ ] https://rubygems.org/gems/langfuse-ruby 版本正确
|
|
35
|
+
- [ ] `gem install langfuse-ruby && ruby -e "require 'langfuse'; puts Langfuse::VERSION"`
|
|
36
|
+
- [ ] GitHub Release 已创建
|
data/docs/V4.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Langfuse v4 with the Ruby SDK
|
|
2
|
+
|
|
3
|
+
This SDK talks to **Langfuse v4** through OpenTelemetry (OTLP/HTTP JSON). The
|
|
4
|
+
tracing API (`Langfuse.trace`, `#generation`, `#span`, `#agent`, …) is the same
|
|
5
|
+
as in 0.2.0; only the transport, ID format, and how create/update events are
|
|
6
|
+
exported change.
|
|
7
|
+
|
|
8
|
+
Official references:
|
|
9
|
+
|
|
10
|
+
- [Migrate custom ingestion to Langfuse v4](https://langfuse.com/integrations/native/opentelemetry/migration-to-v4)
|
|
11
|
+
- [OpenTelemetry integration](https://langfuse.com/integrations/native/opentelemetry)
|
|
12
|
+
- [Observation types](https://langfuse.com/docs/observability/features/observation-types)
|
|
13
|
+
- [SDK overview](https://langfuse.com/docs/observability/sdk/overview)
|
|
14
|
+
|
|
15
|
+
On **Langfuse Cloud**, `POST /api/public/ingestion` stops accepting everything
|
|
16
|
+
except scores on **16 November 2026**. New work should use `:otel` now.
|
|
17
|
+
|
|
18
|
+
## Enable v4
|
|
19
|
+
|
|
20
|
+
`ingestion_mode` still defaults to `:legacy` so existing 0.2.0 apps do not
|
|
21
|
+
change transport on upgrade. Set it explicitly:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
Langfuse.configure do |config|
|
|
25
|
+
config.public_key = ENV.fetch("LANGFUSE_PUBLIC_KEY")
|
|
26
|
+
config.secret_key = ENV.fetch("LANGFUSE_SECRET_KEY")
|
|
27
|
+
config.host = ENV["LANGFUSE_HOST"] || ENV["LANGFUSE_BASE_URL"] || "https://us.cloud.langfuse.com"
|
|
28
|
+
config.ingestion_mode = :otel
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export LANGFUSE_INGESTION_MODE=otel
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Unrecognized values (for example `otlp`) used to silently behave like
|
|
37
|
+
`:legacy`. They now log a warning and fall back to `:legacy`. Values are
|
|
38
|
+
downcased (`OTEL` → `:otel`).
|
|
39
|
+
|
|
40
|
+
The SDK default host is **US Cloud** (`https://us.cloud.langfuse.com`). Use
|
|
41
|
+
`https://cloud.langfuse.com` (EU), `https://jp.cloud.langfuse.com`,
|
|
42
|
+
`https://hipaa.cloud.langfuse.com`, or your self-hosted origin.
|
|
43
|
+
|
|
44
|
+
No extra gems. The OTEL Faraday connection sends:
|
|
45
|
+
|
|
46
|
+
- `Authorization: Basic …` (public key : secret key)
|
|
47
|
+
- `x-langfuse-ingestion-version: 4` (real-time v4 path; without it, OTEL data
|
|
48
|
+
can be delayed up to ~10 minutes)
|
|
49
|
+
- `POST {host}/api/public/otel/v1/traces`
|
|
50
|
+
|
|
51
|
+
## Observations-first model
|
|
52
|
+
|
|
53
|
+
In v4 a **trace** is not a separately ingested record. It is the group of
|
|
54
|
+
**observations** that share a trace ID. The SDK still has a `Trace` object: in
|
|
55
|
+
`:otel` mode that object is exported as the **root span**
|
|
56
|
+
(`langfuse.internal.as_root = true`).
|
|
57
|
+
|
|
58
|
+
| You call | Exported as |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `client.trace(...)` / `Langfuse.trace(...)` | Root OTEL span; `langfuse.trace.name`, `user.id`, `session.id`, tags, metadata, `langfuse.trace.input` / `output` |
|
|
61
|
+
| `#generation` | Child span, `langfuse.observation.type = generation`, plus `gen_ai.*` |
|
|
62
|
+
| `#span` / `#agent` / `#tool` / … | Child span with the matching `langfuse.observation.type` |
|
|
63
|
+
| `#event` | Zero-duration child span, type `event` |
|
|
64
|
+
| `#score` / `client.score` | **Not** OTLP — `score-create` on `/api/public/ingestion`, IDs coerced to W3C hex |
|
|
65
|
+
|
|
66
|
+
v4 queries observations directly. For filters and observation-level evaluators:
|
|
67
|
+
|
|
68
|
+
1. Put **operation** input/output on that observation (`generation.end(output:)`,
|
|
69
|
+
`retriever.end(output:)`, …).
|
|
70
|
+
2. Put the **overall** request/response on the trace (`Langfuse.trace(..., input:)`
|
|
71
|
+
and `trace.update(output:)`). The SDK sends those as `langfuse.trace.input` /
|
|
72
|
+
`langfuse.trace.output` on the root span (still required by some legacy
|
|
73
|
+
trace-level LLM-as-a-judge configs).
|
|
74
|
+
3. Prefer typed observations (`#agent`, `#tool`, `#retriever`, …) over a generic
|
|
75
|
+
`#span` when the UI should filter or evaluate that step.
|
|
76
|
+
|
|
77
|
+
Do not export the same span ID twice to “update” it. The SDK collapses each
|
|
78
|
+
observation’s `*-create` and `*-update` events into **one** OTEL span before
|
|
79
|
+
flush, because v4 is append-only and a second export with the same ID becomes a
|
|
80
|
+
duplicate observation.
|
|
81
|
+
|
|
82
|
+
## Usage and cost
|
|
83
|
+
|
|
84
|
+
v4 cost uses `langfuse.observation.usage_details`, not the legacy `usage`
|
|
85
|
+
object. Prefer:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
generation.end(
|
|
89
|
+
output: text,
|
|
90
|
+
usage_details: { input: 100, output: 50, cache_read: 30, total: 180 },
|
|
91
|
+
cost_details: { input: 0.001, output: 0.003, total: 0.004 }
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If you only have a legacy `usage` hash, still pass it. In `:otel` mode the
|
|
96
|
+
exporter:
|
|
97
|
+
|
|
98
|
+
- maps `promptTokens` / `inputTokens` / `input` (and snake_case forms) to
|
|
99
|
+
`gen_ai.usage.prompt_tokens` / `completion_tokens` / `total_tokens`
|
|
100
|
+
- copies that into `langfuse.observation.usage_details` when you did not set
|
|
101
|
+
`usage_details` yourself
|
|
102
|
+
- **skips** usage whose `unit` is not tokens, so bytes/milliseconds are not
|
|
103
|
+
billed as tokens
|
|
104
|
+
|
|
105
|
+
`Span#generation` and `Generation#generation` forward `usage_details`,
|
|
106
|
+
`cost_details`, and `prompt` (this was broken before 0.2.1).
|
|
107
|
+
|
|
108
|
+
Link a prompt version with `prompt:` (`Langfuse::Prompt` or
|
|
109
|
+
`{ name:, version: }`) → `langfuse.observation.prompt.name` / `.version`.
|
|
110
|
+
|
|
111
|
+
## IDs
|
|
112
|
+
|
|
113
|
+
| Mode | Trace ID | Observation ID |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `:otel` | 32-char W3C hex | 16-char W3C hex |
|
|
116
|
+
| `:legacy` | UUID | UUID |
|
|
117
|
+
|
|
118
|
+
Do not mix modes for the same IDs in one project.
|
|
119
|
+
|
|
120
|
+
## Scores
|
|
121
|
+
|
|
122
|
+
Scores are not OTLP spans. In both modes they are `score-create` batches.
|
|
123
|
+
In `:otel` mode `trace_id` / `observation_id` are normalized with
|
|
124
|
+
`OtelExporter.to_otel_trace_id` / `to_otel_span_id` so they attach to hex
|
|
125
|
+
spans. If OTEL export fails, not-yet-sent OTEL chunks **and** that batch’s
|
|
126
|
+
scores are re-queued together.
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
generation.score(name: "faithfulness", value: 0.9, data_type: "NUMERIC")
|
|
130
|
+
trace.score(name: "user-satisfaction", value: 0.8)
|
|
131
|
+
client.score(name: "csat", value: 5, session_id: "sess-1")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Flush, errors, and limits
|
|
135
|
+
|
|
136
|
+
- Background flush on `flush_interval` (default 5s) **or** `flush_at` (default
|
|
137
|
+
15 events). `Langfuse.trace { ... }` flushes when the block ends.
|
|
138
|
+
- OTLP and ingestion payloads are chunked at 3.5 MB. HTTP 200 with
|
|
139
|
+
`partialSuccess` is logged as a warning.
|
|
140
|
+
- Transient failures (network, timeout, 429, 5xx) re-queue; 4xx validation/auth
|
|
141
|
+
drops the batch with a warning.
|
|
142
|
+
- Queue cap: `max_queue_size` (default 10_000); oldest events dropped.
|
|
143
|
+
- After `fork` (Puma workers) each process gets its own flush thread; inherited
|
|
144
|
+
events are dropped so the parent still owns them.
|
|
145
|
+
|
|
146
|
+
## Cutover checklist
|
|
147
|
+
|
|
148
|
+
1. Point `host` at the correct region or self-hosted v4 origin.
|
|
149
|
+
2. Set `ingestion_mode: :otel` (or `LANGFUSE_INGESTION_MODE=otel`).
|
|
150
|
+
3. Send a canary trace: root + one `generation` + one child (`retriever` /
|
|
151
|
+
`tool` / `span`), unique tag or metadata.
|
|
152
|
+
4. Confirm in the UI: all observations appear without the legacy delay,
|
|
153
|
+
hierarchy and timestamps look right, generation has model / usage / cost.
|
|
154
|
+
5. Confirm scores on that generation attach.
|
|
155
|
+
6. Stop dual-sending the same IDs through `:legacy` and `:otel`.
|
|
156
|
+
7. Move evaluators to **observation** level where possible; trace-level I/O is
|
|
157
|
+
compatibility-only in v4.
|
|
158
|
+
|
|
159
|
+
Runnable sample: [`examples/v4_otel_tracing.rb`](../examples/v4_otel_tracing.rb).
|
data/examples/basic_tracing.rb
CHANGED
|
@@ -7,7 +7,8 @@ require 'langfuse'
|
|
|
7
7
|
client = Langfuse.new(
|
|
8
8
|
public_key: ENV.fetch('LANGFUSE_PUBLIC_KEY', nil),
|
|
9
9
|
secret_key: ENV.fetch('LANGFUSE_SECRET_KEY', nil),
|
|
10
|
-
host: ENV['LANGFUSE_HOST'] || 'https://cloud.langfuse.com'
|
|
10
|
+
host: ENV['LANGFUSE_HOST'] || ENV['LANGFUSE_BASE_URL'] || 'https://us.cloud.langfuse.com',
|
|
11
|
+
ingestion_mode: :otel # Langfuse v4
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
puts '🚀 Starting basic tracing example...'
|
|
@@ -35,10 +36,10 @@ generation = trace.generation(
|
|
|
35
36
|
{ role: 'user', content: 'Hello, how are you?' }
|
|
36
37
|
],
|
|
37
38
|
output: { content: "I'm doing well, thank you! How can I help you today?" },
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
usage_details: {
|
|
40
|
+
input: 12,
|
|
41
|
+
output: 18,
|
|
42
|
+
total: 30
|
|
42
43
|
},
|
|
43
44
|
model_parameters: {
|
|
44
45
|
temperature: 0.7,
|
|
@@ -71,7 +72,7 @@ retrieval_span.generation(
|
|
|
71
72
|
model: 'text-embedding-ada-002',
|
|
72
73
|
input: 'What is machine learning?',
|
|
73
74
|
output: [0.1, 0.2, 0.3, 0.4, 0.5], # Simplified embedding
|
|
74
|
-
|
|
75
|
+
usage_details: { input: 5, total: 5 }
|
|
75
76
|
)
|
|
76
77
|
|
|
77
78
|
# End retrieval span
|
|
@@ -113,10 +114,10 @@ answer_gen = answer_span.generation(
|
|
|
113
114
|
output: {
|
|
114
115
|
content: 'Machine learning is a subset of artificial intelligence that enables computers to learn and improve from experience without being explicitly programmed. ML algorithms identify patterns in data and use these patterns to make predictions or decisions.'
|
|
115
116
|
},
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
usage_details: {
|
|
118
|
+
input: 85,
|
|
119
|
+
output: 45,
|
|
120
|
+
total: 130
|
|
120
121
|
}
|
|
121
122
|
)
|
|
122
123
|
|
|
@@ -7,7 +7,8 @@ require 'langfuse'
|
|
|
7
7
|
Langfuse.configure do |config|
|
|
8
8
|
config.public_key = ENV.fetch('LANGFUSE_PUBLIC_KEY', nil)
|
|
9
9
|
config.secret_key = ENV.fetch('LANGFUSE_SECRET_KEY', nil)
|
|
10
|
-
config.host = ENV['LANGFUSE_HOST'] || 'https://cloud.langfuse.com'
|
|
10
|
+
config.host = ENV['LANGFUSE_HOST'] || ENV['LANGFUSE_BASE_URL'] || 'https://us.cloud.langfuse.com'
|
|
11
|
+
config.ingestion_mode = :otel # Langfuse v4
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
puts '🚀 Simplified usage example...'
|
|
@@ -26,10 +27,10 @@ result = Langfuse.trace('simplified-chat', user_id: 'user-123', input: { message
|
|
|
26
27
|
|
|
27
28
|
# Simulate LLM response
|
|
28
29
|
response_content = "Hi there! How can I help you today?"
|
|
29
|
-
|
|
30
|
+
usage_details = { input: 10, output: 15, total: 25 }
|
|
30
31
|
|
|
31
|
-
# End the generation with output and
|
|
32
|
-
generation.end(output: response_content,
|
|
32
|
+
# End the generation with output and v4 usage_details (cost model)
|
|
33
|
+
generation.end(output: response_content, usage_details: usage_details)
|
|
33
34
|
|
|
34
35
|
# Update trace with final output
|
|
35
36
|
trace.update(output: response_content)
|
|
@@ -54,7 +55,7 @@ Langfuse.trace('document-qa', user_id: 'user-456') do |trace|
|
|
|
54
55
|
model: 'text-embedding-ada-002',
|
|
55
56
|
input: 'What is Ruby?',
|
|
56
57
|
output: [0.1, 0.2, 0.3],
|
|
57
|
-
|
|
58
|
+
usage_details: { input: 5, total: 5 }
|
|
58
59
|
)
|
|
59
60
|
|
|
60
61
|
retrieval.end(output: { documents: ['Ruby is a programming language...'] })
|
|
@@ -70,7 +71,7 @@ Langfuse.trace('document-qa', user_id: 'user-456') do |trace|
|
|
|
70
71
|
|
|
71
72
|
gen.end(
|
|
72
73
|
output: 'Ruby is a dynamic, object-oriented programming language.',
|
|
73
|
-
|
|
74
|
+
usage_details: { input: 50, output: 20, total: 70 }
|
|
74
75
|
)
|
|
75
76
|
|
|
76
77
|
answer_span.end(output: { answer: 'Ruby is a dynamic programming language.' })
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Langfuse v4 (OTLP) tracing example.
|
|
5
|
+
# Tracing APIs are the same as :legacy; only ingestion_mode: :otel changes the
|
|
6
|
+
# transport (OTLP/HTTP + x-langfuse-ingestion-version: 4).
|
|
7
|
+
#
|
|
8
|
+
# export LANGFUSE_PUBLIC_KEY=pk-lf-...
|
|
9
|
+
# export LANGFUSE_SECRET_KEY=sk-lf-...
|
|
10
|
+
# export LANGFUSE_HOST=https://us.cloud.langfuse.com # or EU / self-hosted
|
|
11
|
+
# ruby examples/v4_otel_tracing.rb
|
|
12
|
+
|
|
13
|
+
require 'langfuse'
|
|
14
|
+
|
|
15
|
+
Langfuse.configure do |config|
|
|
16
|
+
config.public_key = ENV.fetch('LANGFUSE_PUBLIC_KEY', nil)
|
|
17
|
+
config.secret_key = ENV.fetch('LANGFUSE_SECRET_KEY', nil)
|
|
18
|
+
config.host = ENV['LANGFUSE_HOST'] || ENV['LANGFUSE_BASE_URL'] || 'https://us.cloud.langfuse.com'
|
|
19
|
+
config.ingestion_mode = :otel
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
puts '🚀 Langfuse v4 / OTEL tracing example'
|
|
23
|
+
puts " host: #{Langfuse.client.host}"
|
|
24
|
+
puts " ingestion_mode: #{Langfuse.client.ingestion_mode}"
|
|
25
|
+
|
|
26
|
+
query = 'What is Ruby?'
|
|
27
|
+
|
|
28
|
+
Langfuse.trace(
|
|
29
|
+
'v4-document-qa',
|
|
30
|
+
user_id: 'user-123',
|
|
31
|
+
session_id: 'sess-456',
|
|
32
|
+
tags: %w[v4 example],
|
|
33
|
+
input: { query: query }
|
|
34
|
+
) do |trace|
|
|
35
|
+
agent = trace.agent(name: 'qa-agent', input: { query: query })
|
|
36
|
+
|
|
37
|
+
retrieval = agent.retriever(
|
|
38
|
+
name: 'vector-search',
|
|
39
|
+
input: { query: query, top_k: 3 }
|
|
40
|
+
)
|
|
41
|
+
docs = ['Ruby is a dynamic, object-oriented programming language.']
|
|
42
|
+
retrieval.end(output: { documents: docs })
|
|
43
|
+
|
|
44
|
+
generation = agent.generation(
|
|
45
|
+
name: 'openai-completion',
|
|
46
|
+
model: 'gpt-4o',
|
|
47
|
+
input: [
|
|
48
|
+
{ role: 'system', content: 'Answer using the retrieved context only.' },
|
|
49
|
+
{ role: 'user', content: query }
|
|
50
|
+
],
|
|
51
|
+
model_parameters: { temperature: 0.2 }
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
answer = 'Ruby is a dynamic, object-oriented programming language.'
|
|
55
|
+
generation.end(
|
|
56
|
+
output: answer,
|
|
57
|
+
usage_details: { input: 48, output: 16, total: 64 },
|
|
58
|
+
cost_details: { input: 0.0001, output: 0.0004, total: 0.0005 }
|
|
59
|
+
)
|
|
60
|
+
generation.score(name: 'faithfulness', value: 0.92, comment: 'Grounded in retrieved docs')
|
|
61
|
+
|
|
62
|
+
agent.end(output: { answer: answer })
|
|
63
|
+
trace.update(output: { answer: answer })
|
|
64
|
+
trace.score(name: 'user-satisfaction', value: 0.9)
|
|
65
|
+
|
|
66
|
+
puts "Created v4 trace: #{trace.id}"
|
|
67
|
+
puts "Trace URL: #{trace.get_url}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
puts '✅ Flushed. Check the Langfuse UI — observations should appear in real time.'
|
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'
|