feat(web): migrate Langfuse SDK to v5 (Closes #1453)#1463
feat(web): migrate Langfuse SDK to v5 (Closes #1453)#1463Harsh23Kashyap wants to merge 3 commits into
Conversation
…panProcessor Replaces the deprecated langfuse-vercel `LangfuseExporter` with `@langfuse/otel`'s `LangfuseSpanProcessor` via `@opentelemetry/sdk-node`. Env vars unchanged. Refs sourcebot-dev#1453.
Replaces the `LangfuseWeb` import from `langfuse` with `LangfuseBrowserClient` from `@langfuse/browser`. Score call signature unchanged; traceId and name preserved so repeated feedback updates rather than duplicates. Refs sourcebot-dev#1453.
…tel,browser} Drops `langfuse-vercel` (deprecated; merged into `@langfuse/otel`) and the v3 `langfuse` browser SDK in favor of `@langfuse/otel` (server) and `@langfuse/browser` (client). Adds `@opentelemetry/sdk-node` as a peer of `@langfuse/otel` so the v5 `NodeSDK` pattern can be used in `instrumentation.ts`. Refs sourcebot-dev#1453.
WalkthroughThe Langfuse integration migrates from legacy packages to scoped browser and OpenTelemetry packages. Server instrumentation now uses ChangesLangfuse SDK migration
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 68ea842. Configure here.
| ], | ||
| }); | ||
| sdk.start(); | ||
| registerOTel({ serviceName: 'sourcebot' }); |
There was a problem hiding this comment.
Dual OTel SDKs break Langfuse export
High Severity
Langfuse is initialized with a standalone NodeSDK instance, but registerOTel is called separately without its Langfuse processor. This separation means AI SDK traces, which rely on registerOTel, are no longer processed by Langfuse, preventing Ask traces from appearing. OpenTelemetry's global tracer provider only supports one active setup, causing this conflict.
Reviewed by Cursor Bugbot for commit 68ea842. Configure here.
| "@opentelemetry/api-logs": "^0.203.0", | ||
| "@opentelemetry/instrumentation": "^0.203.0", | ||
| "@opentelemetry/sdk-logs": "^0.203.0", | ||
| "@opentelemetry/sdk-node": "^0.55.0", |
There was a problem hiding this comment.
Incompatible sdk-node for Langfuse otel
High Severity
@langfuse/otel expects OpenTelemetry v2.x dependencies, but @opentelemetry/sdk-node@^0.55.0 pulls in v1.x components. This version mismatch can cause LangfuseSpanProcessor to fail or silently mis-export spans at runtime.
Reviewed by Cursor Bugbot for commit 68ea842. Configure here.
| ], | ||
| }); | ||
| sdk.start(); | ||
| registerOTel({ serviceName: 'sourcebot' }); |
There was a problem hiding this comment.
NodeSDK import unsafe on edge
Medium Severity
@opentelemetry/sdk-node and LangfuseSpanProcessor are imported and started without a NEXT_RUNTIME === 'nodejs' guard, while Sentry in the same file is runtime-gated. instrumentation.ts loads for edge as well; a Node-only SDK at module scope can fail edge registration or pull Node APIs into the edge bundle.
Reviewed by Cursor Bugbot for commit 68ea842. Configure here.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/web/src/ee/features/chat/components/chatThread/answerCard.tsx (1)
93-97: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winProvide a deterministic
idto enable score updates.The PR objective states: "Preserve boolean score data and deterministic score IDs so repeated feedback updates existing scores." However, the
idfield is missing from thelangfuseBrowser?.score()call. Without it, Langfuse will auto-generate a new ID for each click, creating multiple conflicting score records instead of updating the existing one.Ensure the ID is globally unique but deterministic for this specific trace/message so it doesn't overwrite scores on other traces.
🛠️ Proposed fix
langfuseBrowser?.score({ traceId: traceId, name: 'user_feedback', value: feedbackType === 'like' ? 1 : 0, + id: `user-feedback-${traceId}`, })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/src/ee/features/chat/components/chatThread/answerCard.tsx` around lines 93 - 97, Add a deterministic, globally unique id to the langfuseBrowser.score call in the feedback handler, deriving it from the specific trace and message identifiers so repeated feedback updates the same score without affecting other traces. Preserve the existing user_feedback name and boolean-derived score value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/instrumentation.ts`:
- Around line 3-24: Update register so Edge execution never imports or
instantiates the Node-only NodeSDK, and avoid registering multiple global
OpenTelemetry providers. Prefer removing NodeSDK and passing the
LangfuseSpanProcessor directly through registerOTel while preserving the
existing environment guard; if NodeSDK is retained, dynamically import it only
inside a NEXT_RUNTIME === 'nodejs' branch and do not call registerOTel
afterward.
---
Outside diff comments:
In `@packages/web/src/ee/features/chat/components/chatThread/answerCard.tsx`:
- Around line 93-97: Add a deterministic, globally unique id to the
langfuseBrowser.score call in the feedback handler, deriving it from the
specific trace and message identifiers so repeated feedback updates the same
score without affecting other traces. Preserve the existing user_feedback name
and boolean-derived score value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a3c156c2-49bb-4e5d-bb6c-2b4d5d67cf18
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (3)
packages/web/package.jsonpackages/web/src/ee/features/chat/components/chatThread/answerCard.tsxpackages/web/src/instrumentation.ts
| import { LangfuseSpanProcessor } from '@langfuse/otel'; | ||
| import { NodeSDK } from '@opentelemetry/sdk-node'; | ||
|
|
||
| export async function register() { | ||
| if ( | ||
| process.env.LANGFUSE_SECRET_KEY && | ||
| process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY | ||
| ) { | ||
| console.log("Registering Langfuse"); | ||
| registerOTel({ | ||
| const sdk = new NodeSDK({ | ||
| serviceName: 'sourcebot', | ||
| traceExporter: new LangfuseExporter({ | ||
| secretKey: process.env.LANGFUSE_SECRET_KEY, | ||
| publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, | ||
| baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL, | ||
| }), | ||
| spanProcessors: [ | ||
| new LangfuseSpanProcessor({ | ||
| secretKey: process.env.LANGFUSE_SECRET_KEY, | ||
| publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, | ||
| baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL, | ||
| }), | ||
| ], | ||
| }); | ||
| sdk.start(); | ||
| registerOTel({ serviceName: 'sourcebot' }); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift
Prevent Edge runtime crashes and OpenTelemetry provider conflicts.
There are two critical issues in this OpenTelemetry setup:
- Edge Runtime Crash:
NodeSDKrelies on Node.js native APIs. Becauseregister()runs on bothnodejsandedgeruntimes, importing and instantiatingNodeSDKat the top level will break the Edge build or crash at runtime. - Duplicate OTel Providers: Calling
sdk.start()and then immediately callingregisterOTel()will instantiate two global tracer providers, leading to conflicts where Next.js route tracing is lost or OTel logs warnings.
If @vercel/otel's registerOTel is required for Next.js tracing, you should omit NodeSDK entirely and pass the LangfuseSpanProcessor directly to registerOTel. If you must use NodeSDK, you must dynamically import it and guard it behind a NEXT_RUNTIME === 'nodejs' check.
🛠️ Proposed fix (using `@vercel/otel` directly)
-import { LangfuseSpanProcessor } from '`@langfuse/otel`';
-import { NodeSDK } from '`@opentelemetry/sdk-node`';
export async function register() {
if (
process.env.LANGFUSE_SECRET_KEY &&
- process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY
+ process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY &&
+ process.env.NEXT_RUNTIME === 'nodejs'
) {
console.log("Registering Langfuse");
- const sdk = new NodeSDK({
- serviceName: 'sourcebot',
- spanProcessors: [
- new LangfuseSpanProcessor({
- secretKey: process.env.LANGFUSE_SECRET_KEY,
- publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
- baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
- }),
- ],
- });
- sdk.start();
- registerOTel({ serviceName: 'sourcebot' });
+ const { LangfuseSpanProcessor } = await import('`@langfuse/otel`');
+
+ registerOTel({
+ serviceName: 'sourcebot',
+ spanProcessors: [
+ new LangfuseSpanProcessor({
+ secretKey: process.env.LANGFUSE_SECRET_KEY,
+ publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,
+ baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL,
+ }),
+ ],
+ });
} else {
+ registerOTel({ serviceName: 'sourcebot' });
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { LangfuseSpanProcessor } from '@langfuse/otel'; | |
| import { NodeSDK } from '@opentelemetry/sdk-node'; | |
| export async function register() { | |
| if ( | |
| process.env.LANGFUSE_SECRET_KEY && | |
| process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY | |
| ) { | |
| console.log("Registering Langfuse"); | |
| registerOTel({ | |
| const sdk = new NodeSDK({ | |
| serviceName: 'sourcebot', | |
| traceExporter: new LangfuseExporter({ | |
| secretKey: process.env.LANGFUSE_SECRET_KEY, | |
| publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, | |
| baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL, | |
| }), | |
| spanProcessors: [ | |
| new LangfuseSpanProcessor({ | |
| secretKey: process.env.LANGFUSE_SECRET_KEY, | |
| publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, | |
| baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL, | |
| }), | |
| ], | |
| }); | |
| sdk.start(); | |
| registerOTel({ serviceName: 'sourcebot' }); | |
| } | |
| export async function register() { | |
| if ( | |
| process.env.LANGFUSE_SECRET_KEY && | |
| process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY && | |
| process.env.NEXT_RUNTIME === 'nodejs' | |
| ) { | |
| console.log("Registering Langfuse"); | |
| const { LangfuseSpanProcessor } = await import('`@langfuse/otel`'); | |
| registerOTel({ | |
| serviceName: 'sourcebot', | |
| spanProcessors: [ | |
| new LangfuseSpanProcessor({ | |
| secretKey: process.env.LANGFUSE_SECRET_KEY, | |
| publicKey: process.env.NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, | |
| baseUrl: process.env.NEXT_PUBLIC_LANGFUSE_BASE_URL, | |
| }), | |
| ], | |
| }); | |
| } else { | |
| registerOTel({ serviceName: 'sourcebot' }); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/instrumentation.ts` around lines 3 - 24, Update register so
Edge execution never imports or instantiates the Node-only NodeSDK, and avoid
registering multiple global OpenTelemetry providers. Prefer removing NodeSDK and
passing the LangfuseSpanProcessor directly through registerOTel while preserving
the existing environment guard; if NodeSDK is retained, dynamically import it
only inside a NEXT_RUNTIME === 'nodejs' branch and do not call registerOTel
afterward.


Summary
Migrates Sourcebot's Langfuse integration from the deprecated
langfuse-vercelpackage to the official Langfuse JS SDK v5 surfaces.packages/web/src/instrumentation.ts): the deprecatedlangfuse-vercelLangfuseExporteris replaced with@langfuse/otel'sLangfuseSpanProcessor, wired through@opentelemetry/sdk-node'sNodeSDK. Env var names (LANGFUSE_SECRET_KEY,NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY,NEXT_PUBLIC_LANGFUSE_BASE_URL) are unchanged.packages/web/src/ee/features/chat/components/chatThread/answerCard.tsx): the v3langfusepackage'sLangfuseWebis replaced with@langfuse/browser'sLangfuseBrowserClient. The.score(...)call signature is preserved (sametraceId,name,value) so repeated feedback updates rather than creating duplicate scores on the Langfuse server.packages/web/package.json): dropslangfuseandlangfuse-vercel; adds@langfuse/otel,@langfuse/browser, and@opentelemetry/sdk-node(peer of@langfuse/otel).The migration is a 1:1 functional swap: no public APIs change, no data model changes, no env var renames.
Why this contribution
hassiebp(Langfuse maintainer) on 2026-07-15 with a clear migration path and an offer to help review. The packagelangfuse-vercelis deprecated upstream and Sourcebot is the only consumer on this branch.Test plan
yarn installsucceeds; new deps^5.9.1resolve correctlyyarn build(the workspace's pre-existing typecheck / build) clean on the migration surfacesyarn workspace @sourcebot/web lintclean on the migration surfacesRisk
packages/web(3 errors inwithAuth.test.ts— these pre-existed onmainand are not caused by this PR). Per current state, they are out of scope here; the maintainer can decide priority.@langfuse/browsermay require a recent self-hosted API; if you run self-hosted and browser feedback breaks, the env var isNEXT_PUBLIC_LANGFUSE_BASE_URL. If that affects you, please comment and I'll gate browser feedback behind an env flag in a follow-up.Out of scope
packages/weblangfusepackage inpackage.json(already removed in this PR)Closes #1453
Note
Medium Risk
Changes how OpenTelemetry is bootstrapped for Langfuse (NodeSDK plus Vercel OTel), which could affect trace delivery or duplicate instrumentation if misconfigured; browser feedback depends on Langfuse API compatibility with v5 clients.
Overview
Replaces deprecated Langfuse v3 packages with Langfuse JS SDK v5 while keeping the same env vars and user-facing behavior.
Server tracing in
instrumentation.tsno longer useslangfuse-vercel's exporter throughregisterOTelalone. It starts an OpenTelemetryNodeSDKwith@langfuse/otel'sLangfuseSpanProcessor, then still callsregisterOTelfor thesourcebotservice name.Chat answer feedback in
answerCard.tsxswitches fromLangfuseWebtoLangfuseBrowserClient; theuser_feedback.score({ traceId, name, value })call is unchanged.Dependencies: removes
langfuseandlangfuse-vercel; adds@langfuse/browser,@langfuse/otel, and@opentelemetry/sdk-node.Reviewed by Cursor Bugbot for commit 68ea842. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit