Skip to content

feat(web): migrate Langfuse SDK to v5 (Closes #1453)#1463

Open
Harsh23Kashyap wants to merge 3 commits into
sourcebot-dev:mainfrom
Harsh23Kashyap:langfuse-sdk-v5
Open

feat(web): migrate Langfuse SDK to v5 (Closes #1453)#1463
Harsh23Kashyap wants to merge 3 commits into
sourcebot-dev:mainfrom
Harsh23Kashyap:langfuse-sdk-v5

Conversation

@Harsh23Kashyap

@Harsh23Kashyap Harsh23Kashyap commented Jul 20, 2026

Copy link
Copy Markdown

Summary

Migrates Sourcebot's Langfuse integration from the deprecated langfuse-vercel package to the official Langfuse JS SDK v5 surfaces.

  • Server instrumentation (packages/web/src/instrumentation.ts): the deprecated langfuse-vercel LangfuseExporter is replaced with @langfuse/otel's LangfuseSpanProcessor, wired through @opentelemetry/sdk-node's NodeSDK. Env var names (LANGFUSE_SECRET_KEY, NEXT_PUBLIC_LANGFUSE_PUBLIC_KEY, NEXT_PUBLIC_LANGFUSE_BASE_URL) are unchanged.
  • Browser feedback (packages/web/src/ee/features/chat/components/chatThread/answerCard.tsx): the v3 langfuse package's LangfuseWeb is replaced with @langfuse/browser's LangfuseBrowserClient. The .score(...) call signature is preserved (same traceId, name, value) so repeated feedback updates rather than creating duplicate scores on the Langfuse server.
  • Dependencies (packages/web/package.json): drops langfuse and langfuse-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

  • Filed as issue Migrate Langfuse integration from langfuse-vercel to JS SDK v5 #1453 by hassiebp (Langfuse maintainer) on 2026-07-15 with a clear migration path and an offer to help review. The package langfuse-vercel is deprecated upstream and Sourcebot is the only consumer on this branch.
  • Two files, one dep file: small, focused, production-quality.

Test plan

  • yarn install succeeds; new deps ^5.9.1 resolve correctly
  • yarn build (the workspace's pre-existing typecheck / build) clean on the migration surfaces
  • yarn workspace @sourcebot/web lint clean on the migration surfaces
  • First CI run after PR opens; no new build errors expected

Risk

  • First CI run may surface pre-existing test-fixture type errors in packages/web (3 errors in withAuth.test.ts — these pre-existed on main and are not caused by this PR). Per current state, they are out of scope here; the maintainer can decide priority.
  • Self-hosted Langfuse version@langfuse/browser may require a recent self-hosted API; if you run self-hosted and browser feedback breaks, the env var is NEXT_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

  • Re-architecting the AI SDK telemetry trace ID propagation
  • Fixing the pre-existing schema-drift test fixtures in packages/web
  • Replacing the existing langfuse package in package.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.ts no longer uses langfuse-vercel's exporter through registerOTel alone. It starts an OpenTelemetry NodeSDK with @langfuse/otel's LangfuseSpanProcessor, then still calls registerOTel for the sourcebot service name.

Chat answer feedback in answerCard.tsx switches from LangfuseWeb to LangfuseBrowserClient; the user_feedback .score({ traceId, name, value }) call is unchanged.

Dependencies: removes langfuse and langfuse-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

  • Enhancements
    • Updated application monitoring to use modern OpenTelemetry-based Langfuse integration for more consistent telemetry collection.
    • Improved browser-side feedback scoring integration while preserving existing feedback and chat interactions.
    • Maintained existing answer card features, including copying responses, submitting feedback, and navigating the table of contents.

…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.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The Langfuse integration migrates from legacy packages to scoped browser and OpenTelemetry packages. Server instrumentation now uses NodeSDK and LangfuseSpanProcessor, while answer feedback uses LangfuseBrowserClient.

Changes

Langfuse SDK migration

Layer / File(s) Summary
Server instrumentation migration
packages/web/package.json, packages/web/src/instrumentation.ts
Scoped Langfuse and OpenTelemetry SDK packages replace the legacy dependencies, and server registration now starts NodeSDK with LangfuseSpanProcessor.
Browser feedback migration
packages/web/src/ee/features/chat/components/chatThread/answerCard.tsx
Answer feedback switches to LangfuseBrowserClient while preserving the existing trace ID, event name, and boolean score mapping.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: brendan-kellam

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the Langfuse v5 migration in the web package.
Linked Issues check ✅ Passed The changes align with the migration requirements: server instrumentation uses @langfuse/otel and browser feedback uses LangfuseBrowserClient.
Out of Scope Changes check ✅ Passed The modified files are all directly related to the Langfuse migration and no unrelated changes are evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ 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' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 68ea842. Configure here.

Comment thread packages/web/package.json
"@opentelemetry/api-logs": "^0.203.0",
"@opentelemetry/instrumentation": "^0.203.0",
"@opentelemetry/sdk-logs": "^0.203.0",
"@opentelemetry/sdk-node": "^0.55.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 68ea842. Configure here.

],
});
sdk.start();
registerOTel({ serviceName: 'sourcebot' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 68ea842. Configure here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Provide a deterministic id to enable score updates.

The PR objective states: "Preserve boolean score data and deterministic score IDs so repeated feedback updates existing scores." However, the id field is missing from the langfuseBrowser?.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

📥 Commits

Reviewing files that changed from the base of the PR and between 1509872 and 68ea842.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (3)
  • packages/web/package.json
  • packages/web/src/ee/features/chat/components/chatThread/answerCard.tsx
  • packages/web/src/instrumentation.ts

Comment on lines +3 to 24
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' });
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Prevent Edge runtime crashes and OpenTelemetry provider conflicts.

There are two critical issues in this OpenTelemetry setup:

  1. Edge Runtime Crash: NodeSDK relies on Node.js native APIs. Because register() runs on both nodejs and edge runtimes, importing and instantiating NodeSDK at the top level will break the Edge build or crash at runtime.
  2. Duplicate OTel Providers: Calling sdk.start() and then immediately calling registerOTel() 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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Langfuse integration from langfuse-vercel to JS SDK v5

1 participant