Skip to content

fix(otel): bridge Vercel AI SDK TTFT to completion_start_time#810

Open
ryanyue123 wants to merge 1 commit into
langfuse:mainfrom
ryanyue123:fix/otel-vercel-ai-ttft
Open

fix(otel): bridge Vercel AI SDK TTFT to completion_start_time#810
ryanyue123 wants to merge 1 commit into
langfuse:mainfrom
ryanyue123:fix/otel-vercel-ai-ttft

Conversation

@ryanyue123
Copy link
Copy Markdown

@ryanyue123 ryanyue123 commented May 23, 2026

Problem

LangfuseSpanProcessor doesn't set completionStartTime for Vercel AI SDK
spans, so TTFT is always blank in the UI.

The AI SDK emits ai.response.msToFirstChunk (streamText) and
ai.stream.msToFirstChunk (streamObject). Nothing reads them.

langfuse-vercel handled this in #379 and #401. Lost in the move to
@langfuse/otel.

Fix

Port the #379/#401 bridge. Same math, same attribute names. Old exporter set
a property on the ingestion event; new processor sets the attribute on the
span before OTLP export.

A user-set completion_start_time is preserved.

Verification

  • pnpm typecheck — green
  • pnpm lint — green
  • pnpm exec vitest run packages/otel/src/span-processor.test.ts — 19/19 pass (5 new)

Greptile Summary

This PR ports the TTFT (time-to-first-token) bridge from the old langfuse-vercel exporter into LangfuseSpanProcessor, so completionStartTime is populated for Vercel AI SDK spans in the new OTEL pipeline.

  • New logic in span-processor.ts: On onEnd, if the span's instrumentation scope is "ai" and langfuse.observation.completion_start_time is not already set, it reads ai.response.msToFirstChunk (streamText) or ai.stream.msToFirstChunk (streamObject) and computes startTime + ms to derive the absolute first-chunk timestamp.
  • Test coverage: Five new test cases covering the streamText path, the streamObject path, the no-op path when neither attribute is present, the scope guard (non-AI spans are skipped), and the pre-existing-value guard (user-set values are not overwritten).

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to spans from the Vercel AI SDK instrumentation scope, never mutates spans that already carry a user-set value, and is guarded by a type check on the numeric attribute before writing anything.

The logic is a faithful port of the same computation that existed in the old exporter, the math is verified by the new tests, the guard conditions (scope name, pre-existing attribute, typeof ms === 'number') are all correct, and the attribute format matches what the rest of the codebase produces via _serialize. No imports were added inside functions.

No files require special attention.

Sequence Diagram

sequenceDiagram
    participant SDK as Vercel AI SDK
    participant OTel as OpenTelemetry
    participant Proc as LangfuseSpanProcessor.onEnd
    participant Export as OTLP Exporter

    SDK->>OTel: "end span (ai.response.msToFirstChunk = N or ai.stream.msToFirstChunk = N)"
    OTel->>Proc: onEnd(span)
    Proc->>Proc: shouldExportSpan?
    alt "scope == 'ai' AND completion_start_time not set"
        Proc->>Proc: "ms = msToFirstChunk attribute"
        Proc->>Proc: "completion_start_time = JSON.stringify(new Date(startTime + ms))"
    end
    Proc->>Proc: applyMaskInPlace(span)
    Proc->>Export: processor.onEnd(span) with completion_start_time set
    Export->>Export: OTLP export to Langfuse
Loading

Reviews (1): Last reviewed commit: "fix(otel): bridge Vercel AI SDK TTFT to ..." | Re-trigger Greptile

`LangfuseSpanProcessor` did not convert the AI SDK's
`ai.response.msToFirstChunk` (streamText) / `ai.stream.msToFirstChunk`
(streamObject) attributes into `langfuse.observation.completion_start_time`,
so generation observations land in Langfuse with TTFT blank.

`langfuse-vercel` handled this in langfuse#379 and langfuse#401. Port the same arithmetic to
`LangfuseSpanProcessor` — old exporter set a property on the ingestion event,
new processor sets the attribute on the span before OTLP export.

A user-set `completion_start_time` is preserved.
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 23, 2026

@ryanyue123 is attempting to deploy a commit to the langfuse Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 23, 2026

CLA assistant check
All committers have signed the CLA.

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.

2 participants