Skip to content

fix: check ai.operationId attribute for operation name transformation#891

Open
obs-gh-trevorColby wants to merge 7 commits intotraceloop:mainfrom
obs-gh-trevorColby:fix/operation-name-timing-bug
Open

fix: check ai.operationId attribute for operation name transformation#891
obs-gh-trevorColby wants to merge 7 commits intotraceloop:mainfrom
obs-gh-trevorColby:fix/operation-name-timing-bug

Conversation

@obs-gh-trevorColby
Copy link
Copy Markdown

@obs-gh-trevorColby obs-gh-trevorColby commented Feb 24, 2026

Summary

This PR fixes an issue where the gen_ai.operation.name attribute was not being set for AI SDK spans due to a timing bug in the span name transformation.

Problem

The transformOperationName function checks the span name to determine the gen_ai.operation.name attribute (e.g., "chat" for generateText operations). However, by the time this function runs in onSpanEnd, the span name has already been transformed by transformAiSdkSpanNames in onSpanStart.

Transformation timeline:

  1. onSpanStarttransformAiSdkSpanNames runs → span name changes from "ai.generateText" to "run.ai"
  2. onSpanEndtransformOperationName runs → checks span name for "generateText"never matches

Because the span name has already been transformed to "run.ai", the check for "generateText" fails and gen_ai.operation.name is never set.

Solution

This fix checks the ai.operationId attribute first, which is set by the Vercel AI SDK and contains the original operation ID (e.g., "ai.generateText"). This attribute is not transformed and reliably contains the original operation identifier.

Updated logic:

  1. Check ai.operationId attribute (set by Vercel AI SDK, contains original operation ID)
  2. Fall back to span name if ai.operationId is not available
  3. Use the resulting value to determine operation name

Example

Before (broken)

Span name: "run.ai" (already transformed)
ai.operationId: "ai.generateText"
gen_ai.operation.name: undefined  ❌

After (fixed)

Span name: "run.ai" (already transformed)
ai.operationId: "ai.generateText"
gen_ai.operation.name: "chat"  ✅

Backwards Compatibility

  • The function still checks the span name as a fallback if ai.operationId is not present
  • Tool call spans still work (they check both nameToCheck and spanName for .tool suffix)

Related Issue

Fixes #882


Pull Request opened by Augment Code with guidance from the PR author


Important

Fixes gen_ai.operation.name setting by checking ai.operationId before span name in transformOperationName function.

  • Behavior:
    • Fixes transformOperationName in ai-sdk-transformations.ts to check ai.operationId before span name for setting gen_ai.operation.name.
    • Ensures gen_ai.operation.name is set correctly even if span name is transformed.
  • Logic:
    • Checks ai.operationId first, falls back to span name if not available.
    • Handles cases for generateText, streamText, generateObject, streamObject, and tool calls.
  • Misc:
    • Updates comments and documentation in ai-sdk-transformations.ts for clarity.

This description was created by Ellipsis for e9ba33a. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • Improvements
    • Prefer explicit AI operation IDs for more accurate operation naming.
    • Better detection of generation, streaming, and tool-invocation patterns for clearer trace grouping.
    • Automatic labeling of LLM request types to surface richer semantics in traces.
    • Improved fallbacks to more reliably capture tool executions and mixed streaming/generation workflows.

The transformOperationName function checks the span name to determine the
gen_ai.operation.name attribute. However, by the time this function runs
in onSpanEnd, the span name has already been transformed (e.g., from
"ai.generateText" to "run.ai") by transformAiSdkSpanNames in onSpanStart.

This means the check for 'generateText', 'streamText', etc. never matches,
and the gen_ai.operation.name attribute is never set to 'chat'.

This fix checks the ai.operationId attribute first, which is set by the
Vercel AI SDK and contains the original operation ID (e.g., "ai.generateText").
This allows the function to correctly identify the operation type.

Fixes traceloop#882
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Feb 24, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4eeb16ec-ab17-44ea-916f-4e1dc6cde130

📥 Commits

Reviewing files that changed from the base of the PR and between b056184 and 1484297.

📒 Files selected for processing (1)
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts

📝 Walkthrough

Walkthrough

Prefer ai.operationId over the (potentially rewritten) span name when deriving gen_ai.operation.name; add a helper to populate SpanAttributes.LLM_REQUEST_TYPE and extend tool-span detection (including ai.toolCall and names ending with .tool) in AI SDK span transformations. Also add SpanAttributes.AI_OPERATION_ID = "ai.operationId".

Changes

Cohort / File(s) Summary
AI SDK span transformations
packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
Introduce transformLlmRequestType(attributes, nameToCheck?) and import LLMRequestTypeValues; derive nameToCheck preferentially from attributes[SpanAttributes.AI_OPERATION_ID] when it's a string, falling back to span name; extend tool detection to respect ai.toolCall and names ending with .tool; ensure transformLlmRequestType runs after ATTR_GEN_AI_OPERATION_NAME is determined. Note: a duplicate helper definition was introduced in the same module.
AI semantic attributes
packages/ai-semantic-conventions/src/SemanticAttributes.ts
Add exported attribute SpanAttributes.AI_OPERATION_ID = "ai.operationId" for use in AI SDK span transformations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I peeked at ai.operationId before names could flee,
I hop to label chat or tool so traces agree,
I whisper LLM type where it was missing from sight,
Two helpers, one careful hop — now spans look right! ✨

🚥 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 accurately describes the main change: introducing logic to check the ai.operationId attribute for operation name transformation, which directly addresses the timing bug identified in issue #882.
Linked Issues check ✅ Passed All objectives from issue #882 are met: the PR adds ai.operationId attribute to SemanticAttributes, implements preferential checking of ai.operationId in transformOperationName with span name fallback, handles generateText/streamText/generateObject/streamObject cases, and includes tool-call detection.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the timing bug in operation name transformation for AI SDK spans. Both modified files address the issue with appropriate scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

Copy link
Copy Markdown
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

Important

Looks good to me! 👍

Reviewed everything up to e9ba33a in 13 seconds. Click for details.
  • Reviewed 53 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.

Workflow ID: wflow_RYsirwZNEWXy8dqr

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts`:
- Around line 508-520: operationId (from AI_OPERATION_ID) may be non-string
which makes nameToCheck.includes(...) throw; guard and coerce before using
includes by ensuring operationId is a string (e.g., check typeof operationId ===
"string" or coerce with String(operationId)) and only set nameToCheck to
operationId when it's a string, otherwise fall back to spanName, then run the
existing includes checks on nameToCheck; update the logic around
AI_OPERATION_ID, operationId, nameToCheck and the includes checks to perform
this type guard.
- Around line 505-508: Add the AI_OPERATION_ID constant to the SpanAttributes
export in SemanticAttributes.ts (e.g., add AI_OPERATION_ID: "ai.operationId" to
the SpanAttributes object/type) and then update ai-sdk-transformations.ts to
import that constant instead of using the hardcoded string; replace the local
const AI_OPERATION_ID = "ai.operationId" with an import of AI_OPERATION_ID from
`@traceloop/ai-semantic-conventions` and use that imported symbol when reading
attributes[AI_OPERATION_ID] in the existing logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ddb1241 and e9ba33a.

📒 Files selected for processing (1)
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts

obs-gh-trevorColby and others added 3 commits February 23, 2026 16:27
The ai.operationId attribute may be non-string in some cases, which would
cause nameToCheck.includes() to throw a runtime error.

This fix adds a proper type guard to ensure operationId is a string before
using it, falling back to spanName if it's not a string.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts (1)

534-535: 🛠️ Refactor suggestion | 🟠 Major

Replace hardcoded ai.operationId with a semantic-convention constant (already raised earlier).

const AI_OPERATION_ID = "ai.operationId" reintroduces a hardcoded semantic attribute key in traceloop-sdk. Please define/export this key in packages/ai-semantic-conventions/src/SemanticAttributes.ts and import it here instead.

#!/bin/bash
# Verify whether AI_OPERATION_ID exists in semantic conventions
# and where ai.operationId is currently hardcoded.
rg -n 'AI_OPERATION_ID|ai\.operationId' packages/ai-semantic-conventions/src/SemanticAttributes.ts packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts

As per coding guidelines: packages/{instrumentation-*,traceloop-sdk}/**/*.{ts,tsx} must import AI/LLM semantic attribute constants from @traceloop/ai-semantic-conventions rather than hardcoding strings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts` around
lines 534 - 535, The code reintroduces a hardcoded semantic key by declaring
const AI_OPERATION_ID = "ai.operationId" in ai-sdk-transformations.ts; remove
that hardcoded constant, add and export a named constant AI_OPERATION_ID in
packages/ai-semantic-conventions/src/SemanticAttributes.ts (matching existing
semantic attribute exports), then import { AI_OPERATION_ID } from
'@traceloop/ai-semantic-conventions' into ai-sdk-transformations.ts and use it
where operationIdValue is assigned so the module uses the shared
semantic-conventions constant instead of a magic string.
🧹 Nitpick comments (1)
packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts (1)

511-519: Operation classification logic is duplicated in two places; consider centralizing it.

The same generateText/streamText/generateObject/streamObject checks are repeated in transformLlmRequestType and transformOperationName. Extract one classifier helper and derive both outputs from it to prevent future drift.

Also applies to: 547-550

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts` around
lines 511 - 519, The generateText/streamText/generateObject/streamObject check
is duplicated between transformLlmRequestType and transformOperationName; create
a single helper (e.g., classifyLlmRequestType or isChatLlmOperation) that takes
the operation name (nameToCheck) and returns the LLMRequestTypeValues (or a
boolean for CHAT) and replace the inline includes() checks in both
transformLlmRequestType and transformOperationName with calls to that helper so
both functions derive their result from one classifier; also replace the other
duplicate occurrence near the second block of similar checks with the same
helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts`:
- Around line 494-502: The JSDoc above transformLlmRequestType is inaccurate: it
claims to transform gen_ai.operation.name but the function actually sets
llm.request.type (and reads ai.operationId/span name); update the comment to
describe transformLlmRequestType’s real behavior — that it derives and sets the
llm.request.type attribute from span name or ai.operationId (handling prior
onSpanStart transformations) and reference the linked issue; ensure the doc
mentions the function name transformLlmRequestType and the attributes
llm.request.type and ai.operationId so readers can find the logic.

---

Duplicate comments:
In `@packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts`:
- Around line 534-535: The code reintroduces a hardcoded semantic key by
declaring const AI_OPERATION_ID = "ai.operationId" in ai-sdk-transformations.ts;
remove that hardcoded constant, add and export a named constant AI_OPERATION_ID
in packages/ai-semantic-conventions/src/SemanticAttributes.ts (matching existing
semantic attribute exports), then import { AI_OPERATION_ID } from
'@traceloop/ai-semantic-conventions' into ai-sdk-transformations.ts and use it
where operationIdValue is assigned so the module uses the shared
semantic-conventions constant instead of a magic string.

---

Nitpick comments:
In `@packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts`:
- Around line 511-519: The generateText/streamText/generateObject/streamObject
check is duplicated between transformLlmRequestType and transformOperationName;
create a single helper (e.g., classifyLlmRequestType or isChatLlmOperation) that
takes the operation name (nameToCheck) and returns the LLMRequestTypeValues (or
a boolean for CHAT) and replace the inline includes() checks in both
transformLlmRequestType and transformOperationName with calls to that helper so
both functions derive their result from one classifier; also replace the other
duplicate occurrence near the second block of similar checks with the same
helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 82f4d4d2-ddc0-4326-95eb-88f820714ddb

📥 Commits

Reviewing files that changed from the base of the PR and between 330e622 and ed83e01.

📒 Files selected for processing (1)
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts

obs-gh-trevorColby and others added 3 commits March 4, 2026 13:24
- Add AI_OPERATION_ID to SpanAttributes in @traceloop/ai-semantic-conventions
- Update ai-sdk-transformations.ts to import AI_OPERATION_ID from SpanAttributes
- Remove local hardcoded AI_OPERATION_ID constant

This centralizes the attribute key definition in the semantic conventions
package for better maintainability and consistency across instrumentations.
The JSDoc incorrectly stated the function transforms gen_ai.operation.name,
but it actually derives and sets the llm.request.type attribute.

Updated the comment to accurately describe:
- Function name: transformLlmRequestType
- What it does: derives and sets llm.request.type attribute
- How it works: examines span name or ai.operationId attribute
- Why dual approach: handles prior onSpanStart transformations
- References the linked issue traceloop#882
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.

gen_ai.operation.name attribute not set for AI SDK spans due to span name transformation timing

2 participants