Skip to content

Conversation

@galkleinman
Copy link
Contributor

@galkleinman galkleinman commented Nov 19, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced comprehensive OpenTelemetry GenAI semantic attributes for enhanced AI/LLM observability and tracing.
    • Added Vector Database operation attributes for structured vector store monitoring.
    • Added Workflow tracking attributes for complex AI workflows.
    • Deprecated legacy LLM attributes with migration guidance to new standards.
  • Tests

    • Added extensive test coverage for GenAI Semantic Conventions compliance.

Important

Align AI SDK attributes with OpenTelemetry GenAI semantic conventions, adding new attributes and maintaining backward compatibility.

  • Attributes:
    • Add new attributes in SemanticAttributes.ts for GenAI semantic conventions, including GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, GEN_AI_RESPONSE_ID, and others.
    • Mark several attributes as deprecated, such as LLM_SYSTEM, LLM_REQUEST_MODEL, and LLM_PROMPTS.
  • Transformations:
    • Update ai-sdk-transformations.ts to transform AI SDK attributes to new GenAI attributes, ensuring backward compatibility.
    • Implement functions like addOperationName, transformModelId, transformFunctionId, and transformResponseMetadata.
  • Tests:
    • Add comprehensive tests in ai-sdk-otel-attributes.test.ts to verify new GenAI attributes and backward compatibility.
    • Update ai-sdk-integration.test.ts and ai-sdk-transformations.test.ts to include new attributes and transformations.
  • Recordings:
    • Add new .har files in recordings to test various scenarios, including provider name setting and operation name setting.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Walkthrough

Adds comprehensive OpenTelemetry GenAI semantic convention support with ~60 new attribute constants, deprecates legacy LLM attributes with migration guidance, and refactors transformation logic to emit both new and deprecated attributes concurrently for backward compatibility.

Changes

Cohort / File(s) Summary
Semantic Attribute Constants
packages/ai-semantic-conventions/src/SemanticAttributes.ts
Added ~60 new OpenTelemetry GenAI attributes (GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_, GEN_AI_RESPONSE_, GEN_AI_USAGE_, GEN_AI_INPUT_MESSAGES, GEN_AI_OUTPUT_MESSAGES, GEN_AI_TOOL_DEFINITIONS, GEN_AI_AGENT_NAME, GEN_AI_SYSTEM_INSTRUCTIONS); added non-standard LLM attributes (LLM_REQUEST_TYPE, LLM_USAGE_TOTAL_TOKENS, LLM_TOP_K, LLM_FREQUENCY_PENALTY, LLM_PRESENCE_PENALTY, LLM_CHAT_STOP_SEQUENCES, LLM_REQUEST_FUNCTIONS); added Vector DB attributes (VECTOR_DB_VENDOR, VECTOR_DB_QUERY_TOP_K, VECTOR_DB_TABLE_NAME, VECTOR_DB_ADD_, VECTOR_DB_DELETE_, VECTOR_DB_GET_); added Traceloop workflow attributes (TRACELOOP_SPAN_KIND, TRACELOOP_WORKFLOW_NAME, TRACELOOP_ENTITY_, TRACELOOP_ASSOCIATION_PROPERTIES); marked 12 legacy LLM_ attributes as deprecated with migration guidance.
AI SDK Transformation Logic
packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
Introduced OTEL provider mapping and legacy vendor mapping; added transformers for operation name derivation, model ID, function ID, provider/response metadata, response attributes (including finish reasons array), and structured tool definitions. Enhanced prompts handling to separate system instructions into GEN_AI_SYSTEM_INSTRUCTIONS and combine messages into GEN_AI_INPUT_MESSAGES. Upgraded token usage to populate GEN_AI_USAGE_INPUT_TOKENS and GEN_AI_USAGE_OUTPUT_TOKENS. Updated transformLLMSpans signature to accept optional spanName parameter and forward into transformAiSdkSpanAttributes. All new attributes coexist with deprecated counterparts for backward compatibility.
Integration Test Updates
packages/traceloop-sdk/test/ai-sdk-integration.test.ts
Updated span lookup from exact match ("text.generate") to prefix-based matching to accommodate transformed span names with model information. Enhanced telemetry assertions to validate new GEN_AI_* attributes (operation name, provider name, request/usage tokens) alongside deprecated LLM_* attributes. Adjusted model and prompt/response references to new SpanAttributes constants.
Test Suite: OTel GenAI Attributes
packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts
Added comprehensive test suite exercising OTel GenAI semantic conventions (operation.name, provider.name, tool.definitions, system_instructions, usage tokens, backward compatibility, span naming). Uses Polly for HTTP recording/replay with OpenAI and Anthropic providers. Validates presence and structure of new and deprecated attributes concurrently.
Transformation Unit Tests
packages/traceloop-sdk/test/ai-sdk-transformations.test.ts
Added assertion verifying empty-string provider handling: both GEN_AI_PROVIDER_NAME and deprecated provider attributes set to empty string when ai.model.provider is empty.
Test Recordings (HAR)
packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/.../recording.har (8 files)
Added test fixtures capturing OpenAI and Anthropic API interactions: backward compatibility (deprecated + new attributes), span naming patterns, operation name ("chat"), provider name attribution, system instructions separation, tool definitions structure, and token usage validation.

Sequence Diagram

sequenceDiagram
    participant AI as AI SDK Span
    participant Transform as Transformation Pipeline
    participant OTel as OTel Attributes
    participant Legacy as Legacy Attributes

    AI->>Transform: ai.model.id, ai.model.provider,<br/>ai.response.*, ai.telemetry.functionId
    
    rect rgb(200, 220, 240)
    Note over Transform: New GenAI Mappings
    Transform->>OTel: addOperationName() → GEN_AI_OPERATION_NAME
    Transform->>OTel: transformModelId() → GEN_AI_REQUEST_MODEL
    Transform->>OTel: transformProviderMetadata() → GEN_AI_PROVIDER_NAME
    Transform->>OTel: transformResponseMetadata() → GEN_AI_RESPONSE_ID/MODEL/FINISH_REASONS
    end
    
    rect rgb(220, 240, 200)
    Note over Transform: Enhanced Message/Tool Handling
    Transform->>OTel: transformPrompts() → GEN_AI_SYSTEM_INSTRUCTIONS,<br/>GEN_AI_INPUT_MESSAGES
    Transform->>OTel: transformTools() → GEN_AI_TOOL_DEFINITIONS
    Transform->>OTel: transformResponse*() → GEN_AI_OUTPUT_MESSAGES
    end
    
    rect rgb(240, 220, 200)
    Note over Transform: Token Usage
    Transform->>OTel: transformPromptTokens() → GEN_AI_USAGE_INPUT_TOKENS
    Transform->>OTel: transformCompletionTokens() → GEN_AI_USAGE_OUTPUT_TOKENS
    end
    
    rect rgb(220, 200, 220)
    Note over Transform: Backward Compatibility
    Transform->>Legacy: LLM_SYSTEM, LLM_REQUEST_MODEL,<br/>LLM_INPUT_MESSAGES, LLM_OUTPUT_MESSAGES,<br/>LLM_USAGE_PROMPT_TOKENS,<br/>LLM_USAGE_COMPLETION_TOKENS
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • ai-sdk-transformations.ts: Core transformation pipeline with multiple interdependent transformation functions and dual-attribute emission logic; verify backward compatibility semantics and token calculation accuracy.
  • Deprecation markers and migration paths: Ensure all 12 deprecated attributes correctly reference their GEN_AI_* equivalents in JSDoc; validate that legacy attribute values align with new ones.
  • Span naming logic: Review changes to span name construction with model appending and ensure alignment with OTel naming pattern.
  • Test coverage: Verify that the new ai-sdk-otel-attributes.test.ts suite adequately exercises all new attributes and that updated assertions in ai-sdk-integration.test.ts properly validate dual-attribute emission without test flakiness from prefix matching.

Possibly related PRs

Suggested reviewers

  • nirga
  • avivhalfon

Poem

🐰 Hop along the OTel path so bright,
New GenAI attributes take their flight!
Provider names and operation calls,
Legacy friends still answer the halls—
Backward compat hops, both old and new, 🌟
A rabbit's refactor, through and through!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: aligning AI SDK attributes with gen-ai semantic conventions, which accurately reflects the comprehensive updates to SemanticAttributes.ts adding new GEN_AI_* attributes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch gk/ai-sdk-semconv

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

Copy link
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/traceloop-sdk/test/ai-sdk-transformations.test.ts (1)

1-1753: Fix Prettier formatting and resolve ESLint warnings

Prettier detected code style issues, and ESLint identified unused imports and variables that should be cleaned up:

  • Remove unused imports: context (line 4), ASSOCATION_PROPERTIES_KEY (line 5), transformAiSdkSpanAttributes (line 8), transformAiSdkSpanNames (line 9)
  • Remove unused helper functions: createMockSpan (line 13), createMockSpanWithUpdate (line 25)
  • Replace any types with specific types in createMockSpanWithUpdate parameter (line 27) and the removed helper functions' parameters (line 15)
  • Run prettier --write packages/traceloop-sdk/test/ai-sdk-transformations.test.ts to fix formatting

After cleanup and formatting, re-run tests to confirm no snapshots or logic are affected.

packages/traceloop-sdk/test/ai-sdk-integration.test.ts (1)

1-326: Fix Prettier issues reported by CI

CI flagged code style issues in this file as well. Please run the formatter (e.g., prettier --write packages/traceloop-sdk/test/ai-sdk-integration.test.ts) so tests follow the repo’s formatting rules.

packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts (1)

1-703: Fix Prettier issues reported by CI for this file

CI reports style problems here as well. Please run the configured formatter (e.g., prettier --write packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts) so this file conforms to the project’s formatting rules.

🧹 Nitpick comments (6)
packages/traceloop-sdk/test/ai-sdk-transformations.test.ts (1)

1001-1015: Empty provider test correctly exercises new + deprecated attributes

The new assertion for SpanAttributes.GEN_AI_PROVIDER_NAME alongside SpanAttributes.LLM_SYSTEM for ai.model.provider: "" gives good coverage of the “present but empty” case and ensures both new and deprecated attributes are kept in sync. This matches the deprecation strategy in SemanticAttributes.ts.

If you anticipate additional edge-cases (e.g., whitespace-only provider strings), consider adding a small parametrized test to pin that behavior as well.

packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts (1)

71-87: Avoid redundant forceFlush in individual tests

afterEach already calls await traceloop.forceFlush(); and resets the exporter, so the extra forceFlush calls inside individual tests (for example, Line 86) are redundant. You can rely on the shared teardown unless a specific test needs an extra flush.

packages/traceloop-sdk/test/ai-sdk-integration.test.ts (1)

115-117: Align span-name expectation comment with actual assertion

The comments say the span name “should be transformed and include model name,” but the assertions only check startsWith("text.generate"). If you do want to enforce model presence, consider asserting that the name includes the model string; otherwise, update the comment to match the looser condition.

Also applies to: 210-212

packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts (2)

100-163: Model/response/metadata transforms look correct; centralize provider-metadata key

  • transformModelId safely migrates ai.model.id to GEN_AI_REQUEST_MODEL without clobbering an existing value.
  • transformFunctionId maps ai.telemetry.functionId into TRACELOOP_ENTITY_NAME, which keeps entity naming consistent.
  • transformResponseMetadata correctly adapters ai.response.{id,model,finishReason} into the new GEN_AI_* response attributes.

For transformProviderMetadata, you introduce gen_ai.provider.metadata as a new attribute but emit it as a bare string key. To keep all AI/LLM span attributes centralized (per SemanticAttributes.ts guidance), it would be better to:

  • Add a GEN_AI_PROVIDER_METADATA: "gen_ai.provider.metadata" constant to SpanAttributes, and
  • Use that constant here instead of the hardcoded string.

331-400: Tool definitions transformation is solid; consider richer support for non-function tools

transformTools builds an OTel-compliant GEN_AI_TOOL_DEFINITIONS array while still emitting the flat LLM_REQUEST_FUNCTIONS.* attributes. For function tools this is spot-on and matches the tests.

For non-type === "function" tools, you currently only record the type in the structured definition (details are only in the flat attributes). If you expect other tool types in the future, you might want to extend the structured payload to preserve name/description/parameters (where applicable) there as well.

packages/ai-semantic-conventions/src/SemanticAttributes.ts (1)

47-52: Add a SpanAttributes entry for gen_ai.provider.metadata

transformProviderMetadata in ai-sdk-transformations.ts emits a gen_ai.provider.metadata attribute directly as a string key. To fully follow the “centralize AI/LLM attributes here” guideline, consider adding:

GEN_AI_PROVIDER_METADATA: "gen_ai.provider.metadata",

to SpanAttributes and updating the transformer to reference it. This keeps all GenAI attribute names discoverable from a single place.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2561982 and 7cea57e.

📒 Files selected for processing (13)
  • packages/ai-semantic-conventions/src/SemanticAttributes.ts (1 hunks)
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Backward-compatibility_2327270860/should-maintain-all-deprecated-attributes-alongside-new-ones_410416891/recording.har (1 hunks)
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Span-naming_1988960947/should-follow-OTel-pattern-operation-model_2170500561/recording.har (1 hunks)
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-operation-name-attribute_34593844/should-set-operation-name-to-chat-for-generateText_962058512/recording.har (1 hunks)
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-provider-name-attribute_149611164/should-set-provider-name-to-anthropic-for-Anthropic_274976646/recording.har (1 hunks)
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-provider-name-attribute_149611164/should-set-provider-name-to-openai-for-OpenAI_936627494/recording.har (1 hunks)
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-system_instructions-attribute_2872379897/should-separate-system-instructions-from-input-messages_554757073/recording.har (1 hunks)
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-tool-definitions-attribute_2710797144/should-create-structured-tool-definitions-for-tools_2917601873/recording.har (1 hunks)
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-usage-tokens-attributes_1495437318/should-set-both-new-and-deprecated-token-attributes_3016620579/recording.har (1 hunks)
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts (14 hunks)
  • packages/traceloop-sdk/test/ai-sdk-integration.test.ts (5 hunks)
  • packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts (1 hunks)
  • packages/traceloop-sdk/test/ai-sdk-transformations.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/recordings/**

📄 CodeRabbit inference engine (CLAUDE.md)

Store HTTP interaction recordings for tests under recordings/ directories for Polly.js replay

Files:

  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-system_instructions-attribute_2872379897/should-separate-system-instructions-from-input-messages_554757073/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-operation-name-attribute_34593844/should-set-operation-name-to-chat-for-generateText_962058512/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-usage-tokens-attributes_1495437318/should-set-both-new-and-deprecated-token-attributes_3016620579/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-tool-definitions-attribute_2710797144/should-create-structured-tool-definitions-for-tools_2917601873/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-provider-name-attribute_149611164/should-set-provider-name-to-openai-for-OpenAI_936627494/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Span-naming_1988960947/should-follow-OTel-pattern-operation-model_2170500561/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Backward-compatibility_2327270860/should-maintain-all-deprecated-attributes-alongside-new-ones_410416891/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-provider-name-attribute_149611164/should-set-provider-name-to-anthropic-for-Anthropic_274976646/recording.har
packages/{instrumentation-*,traceloop-sdk}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Import AI/LLM semantic attribute constants from @traceloop/ai-semantic-conventions rather than hardcoding strings

Files:

  • packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts
  • packages/traceloop-sdk/test/ai-sdk-transformations.test.ts
  • packages/traceloop-sdk/test/ai-sdk-integration.test.ts
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
packages/traceloop-sdk/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

packages/traceloop-sdk/**/*.{ts,tsx}: Use the provided decorators (@workflow, @task, @agent) for workflow/task/agent spans instead of re-implementing them
For manual LLM operations, use trace.withLLMSpan from @traceloop/node-server-sdk

Files:

  • packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts
  • packages/traceloop-sdk/test/ai-sdk-transformations.test.ts
  • packages/traceloop-sdk/test/ai-sdk-integration.test.ts
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
packages/ai-semantic-conventions/src/SemanticAttributes.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Define all AI/LLM span attribute constants in packages/ai-semantic-conventions/src/SemanticAttributes.ts

Files:

  • packages/ai-semantic-conventions/src/SemanticAttributes.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: traceloop/openllmetry-js PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T22:08:07.023Z
Learning: Applies to packages/{instrumentation-*,traceloop-sdk}/**/*.{ts,tsx} : Import AI/LLM semantic attribute constants from traceloop/ai-semantic-conventions rather than hardcoding strings
Learnt from: CR
Repo: traceloop/openllmetry-js PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T22:08:07.023Z
Learning: Applies to packages/ai-semantic-conventions/src/SemanticAttributes.ts : Define all AI/LLM span attribute constants in packages/ai-semantic-conventions/src/SemanticAttributes.ts
📚 Learning: 2025-08-24T22:08:07.023Z
Learnt from: CR
Repo: traceloop/openllmetry-js PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T22:08:07.023Z
Learning: Applies to packages/{instrumentation-*,traceloop-sdk}/**/*.{ts,tsx} : Import AI/LLM semantic attribute constants from traceloop/ai-semantic-conventions rather than hardcoding strings

Applied to files:

  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-usage-tokens-attributes_1495437318/should-set-both-new-and-deprecated-token-attributes_3016620579/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-provider-name-attribute_149611164/should-set-provider-name-to-openai-for-OpenAI_936627494/recording.har
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Span-naming_1988960947/should-follow-OTel-pattern-operation-model_2170500561/recording.har
  • packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts
  • packages/traceloop-sdk/test/ai-sdk-transformations.test.ts
  • packages/traceloop-sdk/test/ai-sdk-integration.test.ts
  • packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Backward-compatibility_2327270860/should-maintain-all-deprecated-attributes-alongside-new-ones_410416891/recording.har
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
  • packages/ai-semantic-conventions/src/SemanticAttributes.ts
📚 Learning: 2025-08-24T22:08:07.023Z
Learnt from: CR
Repo: traceloop/openllmetry-js PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T22:08:07.023Z
Learning: Applies to packages/ai-semantic-conventions/src/SemanticAttributes.ts : Define all AI/LLM span attribute constants in packages/ai-semantic-conventions/src/SemanticAttributes.ts

Applied to files:

  • packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts
  • packages/traceloop-sdk/test/ai-sdk-transformations.test.ts
  • packages/traceloop-sdk/test/ai-sdk-integration.test.ts
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
  • packages/ai-semantic-conventions/src/SemanticAttributes.ts
📚 Learning: 2025-08-24T22:08:07.023Z
Learnt from: CR
Repo: traceloop/openllmetry-js PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T22:08:07.023Z
Learning: Applies to packages/instrumentation-*/**/*.{ts,tsx} : Instrumentations must create spans with appropriate AI/LLM semantic attributes for calls they wrap

Applied to files:

  • packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts
  • packages/traceloop-sdk/test/ai-sdk-transformations.test.ts
  • packages/traceloop-sdk/test/ai-sdk-integration.test.ts
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
  • packages/ai-semantic-conventions/src/SemanticAttributes.ts
📚 Learning: 2025-08-24T22:08:07.023Z
Learnt from: CR
Repo: traceloop/openllmetry-js PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T22:08:07.023Z
Learning: Applies to packages/traceloop-sdk/**/*.{ts,tsx} : For manual LLM operations, use trace.withLLMSpan from traceloop/node-server-sdk

Applied to files:

  • packages/traceloop-sdk/test/ai-sdk-integration.test.ts
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
📚 Learning: 2025-08-24T22:08:07.023Z
Learnt from: CR
Repo: traceloop/openllmetry-js PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-08-24T22:08:07.023Z
Learning: Applies to packages/traceloop-sdk/**/*.{ts,tsx} : Use the provided decorators (workflow, task, agent) for workflow/task/agent spans instead of re-implementing them

Applied to files:

  • packages/traceloop-sdk/test/ai-sdk-integration.test.ts
  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
  • packages/ai-semantic-conventions/src/SemanticAttributes.ts
📚 Learning: 2025-08-12T13:57:05.901Z
Learnt from: galzilber
Repo: traceloop/openllmetry-js PR: 643
File: packages/traceloop-sdk/test/datasets-final.test.ts:97-105
Timestamp: 2025-08-12T13:57:05.901Z
Learning: The traceloop-sdk uses a response transformer (`transformApiResponse` in `packages/traceloop-sdk/src/lib/utils/response-transformer.ts`) that converts snake_case API responses to camelCase for SDK interfaces. Raw API responses use snake_case but SDK consumers see camelCase fields.

Applied to files:

  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
📚 Learning: 2025-08-12T13:57:42.538Z
Learnt from: galzilber
Repo: traceloop/openllmetry-js PR: 643
File: packages/traceloop-sdk/test/datasets-final.test.ts:129-143
Timestamp: 2025-08-12T13:57:42.538Z
Learning: The Traceloop SDK includes a response transformer utility (transformApiResponse) that automatically converts snake_case API responses to camelCase for SDK consumers. This means interface definitions and tests should use camelCase field names (like datasetId, datasetSlug, createdAt, updatedAt) rather than the raw API's snake_case format.

Applied to files:

  • packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts
🧬 Code graph analysis (4)
packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts (3)
packages/traceloop-sdk/test/test-setup.ts (2)
  • getSharedExporter (37-39)
  • initializeSharedTraceloop (23-35)
packages/ai-semantic-conventions/src/SemanticAttributes.ts (1)
  • SpanAttributes (17-109)
packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts (1)
  • transformLLMSpans (621-653)
packages/traceloop-sdk/test/ai-sdk-transformations.test.ts (1)
packages/ai-semantic-conventions/src/SemanticAttributes.ts (1)
  • SpanAttributes (17-109)
packages/traceloop-sdk/test/ai-sdk-integration.test.ts (1)
packages/ai-semantic-conventions/src/SemanticAttributes.ts (1)
  • SpanAttributes (17-109)
packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts (1)
packages/ai-semantic-conventions/src/SemanticAttributes.ts (1)
  • SpanAttributes (17-109)
🪛 GitHub Actions: CI
packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts

[warning] 1-1: Code style issues found in this file. Run 'prettier --write' to fix.

packages/traceloop-sdk/test/ai-sdk-transformations.test.ts

[warning] 1-1: Code style issues found in this file. Run 'prettier --write' to fix.

packages/traceloop-sdk/test/ai-sdk-integration.test.ts

[warning] 1-1: Code style issues found in this file. Run 'prettier --write' to fix.

packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts

[warning] 1-1: Code style issues found in this file. Run 'prettier --write' to fix.

🔇 Additional comments (17)
packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-operation-name-attribute_34593844/should-set-operation-name-to-chat-for-generateText_962058512/recording.har (2)

1-172: Well-structured recording with proper token usage metadata.

The HAR recording is properly formatted and includes the necessary token usage data (9 input tokens, 10 output tokens) that aligns with the PR's GenAI semantic conventions for tracking GEN_AI_USAGE_INPUT_TOKENS and GEN_AI_USAGE_OUTPUT_TOKENS. The file is correctly placed in the recordings directory as per Polly.js guidelines.


26-33: No issues found—recording correctly uses the legitimate OpenAI Responses API.

The Responses API is a stateful API that combines the best capabilities from the chat completions and assistants APIs, and was released in March 2025. The recording correctly uses the /v1/responses endpoint with the "input" field format, which is the proper specification for this API. Your recording accurately represents the OpenAI API being instrumented.

packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-provider-name-attribute_149611164/should-set-provider-name-to-anthropic-for-Anthropic_274976646/recording.har (1)

1-170: HAR fixture looks good and free of secrets

Request/response only include content-type and standard Anthropic/Cloudflare headers, no auth headers or API keys. Suitable as a Polly recording for provider-name tests.

packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Backward-compatibility_2327270860/should-maintain-all-deprecated-attributes-alongside-new-ones_410416891/recording.har (1)

1-172: OpenAI backward-compatibility HAR is acceptable

Recording contains typical OpenAI/Cloudflare headers and a _cfuvid cookie but no API keys or Authorization header; appropriate as a test artifact for new + deprecated attributes.

packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-system_instructions-attribute_2872379897/should-separate-system-instructions-from-input-messages_554757073/recording.har (1)

1-172: System-instructions HAR is structurally sound

Recording accurately captures a system + user interaction for OpenAI responses, with no embedded API keys or Authorization headers. Good basis for testing separation of system instructions from input messages.

packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-tool-definitions-attribute_2710797144/should-create-structured-tool-definitions-for-tools_2917601873/recording.har (1)

1-144: Tool-definitions HAR appropriately exercises a failure path

This fixture captures a tools payload plus the 400 invalid_function_parameters error, which is valuable for validating GEN_AI_TOOL_DEFINITIONS handling even when the provider rejects the schema. No secrets or auth headers present.

packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-provider-name-attribute_149611164/should-set-provider-name-to-openai-for-OpenAI_936627494/recording.har (1)

1-172: OpenAI provider-name HAR is consistent and non-sensitive

The recording cleanly captures a basic OpenAI response scenario for provider-name mapping, with only standard headers and no API keys. Appropriate as a fixture for GEN_AI_PROVIDER_NAME tests.

packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Span-naming_1988960947/should-follow-OTel-pattern-operation-model_2170500561/recording.har (1)

1-172: Span-naming HAR is suitable for operation/model tests

This recording provides a minimal OpenAI interaction with full usage metadata, ideal for verifying span naming conventions without exposing secrets.

packages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/gen_ai-usage-tokens-attributes_1495437318/should-set-both-new-and-deprecated-token-attributes_3016620579/recording.har (1)

1-172: Token-usage HAR cleanly supports GEN_AI_USAGE_ testing*

The fixture captures detailed token usage fields without including secrets or API keys, matching the needs of tests that assert both new and deprecated token attributes.

packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts (1)

26-416: Strong semantic-conventions coverage and backward-compat tests

The suite does a good job exercising operation/provider, tools, system instructions, token usage, backward compatibility, and span naming against the transform pipeline. The expectations align with the new SpanAttributes and transformLLMSpans behavior, and the direct transformation test for tools is a nice touch.

packages/traceloop-sdk/test/ai-sdk-integration.test.ts (1)

107-175: Integration checks for new GEN_AI_ + legacy LLM_ attributes look good**

The updated OpenAI and Google tests correctly:

  • Find spans by name.startsWith("text.generate") to accommodate transformed names.
  • Assert GEN_AI_OPERATION_NAME === "chat" and provider name ("openai" / "gcp.vertex_ai").
  • Verify GEN_AI_REQUEST_MODEL, prompt/completion attributes, and both GEN/LLM token-usage attributes plus total tokens.

This gives solid end-to-end coverage of the new transformations while keeping backward compatibility.

Also applies to: 199-270

packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts (5)

40-78: Provider/vendor normalization and backward compatibility are well-handled

The combination of OTEL_PROVIDER_MAPPING, LEGACY_VENDOR_MAPPING, and transformVendor cleanly:

  • Normalizes provider identifiers into GEN_AI_PROVIDER_NAME.
  • Keeps LLM_SYSTEM in sync for backward compatibility.
  • Handles unknown and empty-string providers sensibly.

This matches the dual “new GEN_AI_* + deprecated LLM_*” pattern used elsewhere.

Also applies to: 543-585


165-223: Response text/object/tool-call transformations align with new GEN_AI_OUTPUT_MESSAGES

The updated transformResponseText, transformResponseObject, and transformResponseToolCalls correctly:

  • Build a structured assistant message with parts (text or tool_call).
  • Populate GEN_AI_OUTPUT_MESSAGES with the JSON array of messages.
  • Keep LLM_OUTPUT_MESSAGES in sync for backward compatibility.
  • Maintain the legacy flat completion attributes.

This matches the expectations in the new tests and keeps both representations available.

Also applies to: 225-276


402-502: System instructions and input messages separation matches spec while preserving compat

transformPrompts’s split of system messages into GEN_AI_SYSTEM_INSTRUCTIONS and the combined GEN_AI_INPUT_MESSAGES (plus deprecated LLM_INPUT_MESSAGES) is consistent with the new tests and keeps older consumers working.

You already note in comments that OTel recommends excluding system messages from GEN_AI_INPUT_MESSAGES; the current “all messages” approach is a reasonable compatibility trade-off until you can afford a breaking change.


504-541: Token usage mapping maintains new GEN_AI_ and legacy LLM_ in sync**

transformPromptTokens and transformCompletionTokens now:

  • Populate GEN_AI_USAGE_INPUT_TOKENS / GEN_AI_USAGE_OUTPUT_TOKENS.
  • Preserve LLM_USAGE_PROMPT_TOKENS / LLM_USAGE_COMPLETION_TOKENS.
  • Allow calculateTotalTokens to keep using the legacy fields.

This matches the new tests that assert equality between GEN_AI and LLM token counts.


587-619: Telemetry metadata normalization is consistent with association-properties model

transformTelemetryMetadata properly:

  • Collects all ai.telemetry.metadata.* keys.
  • Re-emits them under TRACELOOP_ASSOCIATION_PROPERTIES.*.
  • Cleans up the original keys.

That keeps the span attribute surface cleaner while still exposing metadata in a structured way.

packages/ai-semantic-conventions/src/SemanticAttributes.ts (1)

17-109: GenAI SpanAttributes expansion and deprecation hints look consistent

The new SpanAttributes entries for GEN_AI operation, provider, request/response, usage, messages, tools, and agent attributes, plus the LLM_* deprecation comments, look consistent and match how they’re consumed in the transform and test code. Having all of these centralized here is exactly what’s needed for the semconv alignment and future discoverability.

Comment on lines +80 to +99
/**
* Adds gen_ai.operation.name attribute based on AI SDK span name
*/
const addOperationName = (
spanName: string,
attributes: Record<string, any>,
): void => {
// Map AI SDK span names to OTel operation names
const operationMapping: Record<string, string> = {
[AI_GENERATE_TEXT_DO_GENERATE]: "chat",
[AI_GENERATE_OBJECT_DO_GENERATE]: "generate_content",
[AI_STREAM_TEXT_DO_STREAM]: "chat",
"ai.embed.doEmbed": "embeddings",
"ai.embedMany.doEmbed": "embeddings",
};

const operation = operationMapping[spanName] || "chat";
attributes[SpanAttributes.GEN_AI_OPERATION_NAME] = operation;
};

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify addOperationName sees the expected span names

addOperationName maps AI SDK span names like ai.generateText.doGenerate, ai.generateObject.doGenerate, and ai.streamText.doStream to OTel operations, but transformLLMSpans is called with span.name from transformAiSdkSpanAttributes, and transformAiSdkSpanNames may already have renamed the span to "text.generate ...", "object.generate ...", etc.

If transformAiSdkSpanNames runs before transformAiSdkSpanAttributes, the mapping entries based on the original AI SDK names will never match, and you’ll always fall back to "chat" for those operations (particularly generateObject and stream cases).

Consider either:

  • Passing a “raw” span-name key into transformLLMSpans (e.g., derive it from HANDLED_SPAN_NAMES in transformAiSdkSpanAttributes), or
  • Changing the operationMapping keys to match the renamed bases ("text.generate", "object.generate", "text.stream") if that’s what is actually observed at this stage.

Also applies to: 621-653, 673-692


🏁 Script executed:

#!/bin/bash
# Inspect how these transformers are wired to span processors to confirm call order.
rg -n "transformAiSdkSpanNames" -C5
rg -n "transformAiSdkSpanAttributes" -C5

Length of output: 8076


I need to see the function structure around these lines to understand the execution order:

I need to see the exact structure of the span processor to understand function flow:


---

🏁 Script executed:

```shell
#!/bin/bash
# Extract the relevant section of span-processor.ts to see function structure
sed -n '140,250p' packages/traceloop-sdk/src/lib/tracing/span-processor.ts

Length of output: 2737



---

🏁 Script executed:

```shell
#!/bin/bash
# Get more context to see which function contains line 169
sed -n '80,180p' packages/traceloop-sdk/src/lib/tracing/span-processor.ts

Length of output: 2915


Now I need to see what span names transformAiSdkSpanNames actually produces:


---

🏁 Script executed:

```shell
#!/bin/bash
# Extract transformAiSdkSpanNames function to see what it renames spans to
sed -n '673,694p' packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts

Length of output: 694



---

🏁 Script executed:

```shell
#!/bin/bash
# Find HANDLED_SPAN_NAMES definition
rg -n "HANDLED_SPAN_NAMES" -A 20 packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts | head -40

Length of output: 1588



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for tests related to addOperationName
rg -n "addOperationName\|operation.name\|GEN_AI_OPERATION_NAME" packages/traceloop-sdk/test/ai-sdk-transformations.test.ts -C 3

Length of output: 0



---

🏁 Script executed:

```shell
#!/bin/bash
# Check if embed span names are in HANDLED_SPAN_NAMES or if they pass through with original names
rg -n "embed\|Embed" packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts | head -20

Length of output: 0


Fix addOperationName mapping to use transformed span names instead of original AI SDK names

The issue is valid: transformAiSdkSpanNames runs in onSpanStart and renames AI SDK spans (e.g., "ai.generateText.doGenerate""text.generate"). By the time transformAiSdkSpanAttributes runs in onSpanEnd, the span name has already been transformed. When transformLLMSpans receives span.name, it passes the transformed name to addOperationName, which then fails to match against its original AI SDK name keys.

Update operationMapping keys to match the transformed names from HANDLED_SPAN_NAMES:

  • Change [AI_GENERATE_TEXT_DO_GENERATE] to "text.generate"
  • Change [AI_GENERATE_OBJECT_DO_GENERATE] to "object.generate"
  • Change [AI_STREAM_TEXT_DO_STREAM] to "text.stream"

The hardcoded "ai.embed.doEmbed" keys work because embed spans are not in HANDLED_SPAN_NAMES and keep their original names.

🤖 Prompt for AI Agents
In packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts around lines
80 to 99, addOperationName currently maps using original AI SDK span name
constants but onSpanStart already transforms span names; update operationMapping
keys to use the transformed names from HANDLED_SPAN_NAMES so matches succeed
(replace AI_GENERATE_TEXT_DO_GENERATE with "text.generate",
AI_GENERATE_OBJECT_DO_GENERATE with "object.generate", and
AI_STREAM_TEXT_DO_STREAM with "text.stream"), keep the embed keys as-is, and
ensure the default remains "chat" while assigning
attributes[SpanAttributes.GEN_AI_OPERATION_NAME] = operation.

Comment on lines +1 to +417
/*
* Comprehensive tests for OTel GenAI Semantic Conventions compliance
* Tests all new gen_ai.* attributes added for OTel compliance
*/

import * as assert from "assert";
import { openai as vercel_openai } from "@ai-sdk/openai";
import { anthropic as vercel_anthropic } from "@ai-sdk/anthropic";
import { generateText } from "ai";
import { SpanAttributes } from "@traceloop/ai-semantic-conventions";

import * as traceloop from "../src";

import { Polly, setupMocha as setupPolly } from "@pollyjs/core";
import NodeHttpAdapter from "@pollyjs/adapter-node-http";
import FetchAdapter from "@pollyjs/adapter-fetch";
import FSPersister from "@pollyjs/persister-fs";
import { initializeSharedTraceloop, getSharedExporter } from "./test-setup";

const memoryExporter = getSharedExporter();

Polly.register(NodeHttpAdapter);
Polly.register(FetchAdapter);
Polly.register(FSPersister);

describe("AI SDK OTel GenAI Semantic Conventions", function () {
// Increase timeout for all tests in this suite
this.timeout(10000);

setupPolly({
adapters: ["node-http", "fetch"],
persister: "fs",
recordIfMissing: process.env.RECORD_MODE === "NEW",
recordFailedRequests: true,
mode: process.env.RECORD_MODE === "NEW" ? "record" : "replay",
matchRequestsBy: {
headers: false,
url: {
protocol: true,
hostname: true,
pathname: true,
query: false,
},
},
logging: true,
});

before(async function () {
if (process.env.RECORD_MODE !== "NEW") {
// Set dummy API keys for replay mode
process.env.OPENAI_API_KEY = "test";
process.env.ANTHROPIC_API_KEY = "test";
process.env.GOOGLE_GENERATIVE_AI_API_KEY = "test";
}

initializeSharedTraceloop();
});

beforeEach(function () {
const { server } = this.polly as Polly;
server.any().on("beforePersist", (_req, recording) => {
recording.request.headers = recording.request.headers.filter(
({ name }: { name: string }) =>
!["authorization", "x-api-key", "anthropic-version"].includes(
name.toLowerCase(),
),
);
});
});

afterEach(async () => {
await traceloop.forceFlush();
memoryExporter.reset();
});

describe("gen_ai.operation.name attribute", () => {
it("should set operation.name to 'chat' for generateText", async () => {
await traceloop.withWorkflow({ name: "test_operation_name" }, async () => {
await generateText({
messages: [{ role: "user", content: "Say hello" }],
model: vercel_openai("gpt-3.5-turbo"),
experimental_telemetry: { isEnabled: true },
});
});

await traceloop.forceFlush();
const spans = memoryExporter.getFinishedSpans();
const aiSpan = spans.find((s) => s.name.startsWith("text.generate"));

assert.ok(aiSpan, "AI span not found");
assert.strictEqual(
aiSpan.attributes[SpanAttributes.GEN_AI_OPERATION_NAME],
"chat",
"Operation name should be 'chat'",
);
});
});

describe("gen_ai.provider.name attribute", () => {
it("should set provider.name to 'openai' for OpenAI", async () => {
await traceloop.withWorkflow({ name: "test_openai_provider" }, async () => {
await generateText({
messages: [{ role: "user", content: "Hello" }],
model: vercel_openai("gpt-3.5-turbo"),
experimental_telemetry: { isEnabled: true },
});
});

await traceloop.forceFlush();
const spans = memoryExporter.getFinishedSpans();
const aiSpan = spans.find((s) => s.name.startsWith("text.generate"));

assert.ok(aiSpan);
assert.strictEqual(
aiSpan.attributes[SpanAttributes.GEN_AI_PROVIDER_NAME],
"openai",
"Provider name should be 'openai' (OTel standard)",
);
});

it("should set provider.name to 'anthropic' for Anthropic", async () => {
await traceloop.withWorkflow(
{ name: "test_anthropic_provider" },
async () => {
await generateText({
messages: [{ role: "user", content: "Hello" }],
model: vercel_anthropic("claude-3-haiku-20240307"),
experimental_telemetry: { isEnabled: true },
});
},
);

await traceloop.forceFlush();
const spans = memoryExporter.getFinishedSpans();
const aiSpan = spans.find((s) => s.name.startsWith("text.generate"));

assert.ok(aiSpan);
assert.strictEqual(
aiSpan.attributes[SpanAttributes.GEN_AI_PROVIDER_NAME],
"anthropic",
"Provider name should be 'anthropic' (OTel standard)",
);
});
});

describe("gen_ai.tool.definitions attribute", () => {
it("should create structured tool.definitions via transformation", () => {
// Test transformation directly rather than full API call
// since tool schema validation is complex
const { transformLLMSpans } = require("../src/lib/tracing/ai-sdk-transformations");

const attributes: Record<string, any> = {
"ai.prompt.tools": [
{
type: "function",
name: "getWeather",
description: "Get the current weather for a location",
parameters: {
type: "object",
properties: {
location: { type: "string", description: "The city and state" },
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
default: "celsius",
},
},
required: ["location"],
},
},
],
};

transformLLMSpans(attributes);

// Check for gen_ai.tool.definitions (new OTel attribute)
const toolDefs = attributes[SpanAttributes.GEN_AI_TOOL_DEFINITIONS];
assert.ok(toolDefs, "tool.definitions should be set");

const parsed = JSON.parse(toolDefs);
assert.ok(Array.isArray(parsed), "tool.definitions should be an array");
assert.strictEqual(parsed.length, 1, "Should have 1 tool");
assert.strictEqual(parsed[0].type, "function", "Tool type should be 'function'");
assert.strictEqual(
parsed[0].function.name,
"getWeather",
"Tool name should be 'getWeather'",
);
assert.ok(
parsed[0].function.description,
"Tool should have description",
);
assert.ok(parsed[0].function.parameters, "Tool should have parameters");

// Also verify backward compatibility - flat format should still exist
assert.strictEqual(
attributes[`${SpanAttributes.LLM_REQUEST_FUNCTIONS}.0.name`],
"getWeather",
);
});
});

describe("gen_ai.system_instructions attribute", () => {
it("should separate system instructions from input messages", async () => {
await traceloop.withWorkflow(
{ name: "test_system_instructions" },
async () => {
await generateText({
messages: [
{
role: "system",
content: "You are a helpful assistant specialized in weather.",
},
{ role: "user", content: "What's the weather?" },
],
model: vercel_openai("gpt-3.5-turbo"),
experimental_telemetry: { isEnabled: true },
});
},
);

await traceloop.forceFlush();
const spans = memoryExporter.getFinishedSpans();
const aiSpan = spans.find((s) => s.name.startsWith("text.generate"));

assert.ok(aiSpan);

// Check for gen_ai.system_instructions (new OTel attribute)
const systemInstructions =
aiSpan.attributes[SpanAttributes.GEN_AI_SYSTEM_INSTRUCTIONS];
assert.ok(
systemInstructions,
"system_instructions should be set",
);

const parsed = JSON.parse(systemInstructions as string);
assert.ok(Array.isArray(parsed), "system_instructions should be an array");
assert.strictEqual(parsed.length, 1, "Should have 1 system message");
assert.strictEqual(parsed[0].role, "system");
assert.ok(
parsed[0].parts[0].content.includes("helpful assistant"),
"Should contain system message content",
);

// Check that input messages still include both (for backward compat)
const inputMessages =
aiSpan.attributes[SpanAttributes.GEN_AI_INPUT_MESSAGES];
assert.ok(inputMessages);
const inputParsed = JSON.parse(inputMessages as string);
assert.strictEqual(inputParsed.length, 2, "Input messages should include both");
});
});

describe("gen_ai.usage tokens attributes", () => {
it("should set both new and deprecated token attributes", async () => {
await traceloop.withWorkflow({ name: "test_token_attributes" }, async () => {
await generateText({
messages: [{ role: "user", content: "Count to 5" }],
model: vercel_openai("gpt-3.5-turbo"),
experimental_telemetry: { isEnabled: true },
});
});

await traceloop.forceFlush();
const spans = memoryExporter.getFinishedSpans();
const aiSpan = spans.find((s) => s.name.startsWith("text.generate"));

assert.ok(aiSpan);

// Check new OTel attributes
assert.ok(
aiSpan.attributes[SpanAttributes.GEN_AI_USAGE_INPUT_TOKENS],
"gen_ai.usage.input_tokens should be set",
);
assert.ok(
aiSpan.attributes[SpanAttributes.GEN_AI_USAGE_OUTPUT_TOKENS],
"gen_ai.usage.output_tokens should be set",
);

// Check deprecated attributes still exist (backward compatibility)
assert.ok(
aiSpan.attributes[SpanAttributes.LLM_USAGE_PROMPT_TOKENS],
"gen_ai.usage.prompt_tokens should still be set",
);
assert.ok(
aiSpan.attributes[SpanAttributes.LLM_USAGE_COMPLETION_TOKENS],
"gen_ai.usage.completion_tokens should still be set",
);

// Verify values match
assert.strictEqual(
aiSpan.attributes[SpanAttributes.GEN_AI_USAGE_INPUT_TOKENS],
aiSpan.attributes[SpanAttributes.LLM_USAGE_PROMPT_TOKENS],
"Input tokens should match prompt tokens",
);
assert.strictEqual(
aiSpan.attributes[SpanAttributes.GEN_AI_USAGE_OUTPUT_TOKENS],
aiSpan.attributes[SpanAttributes.LLM_USAGE_COMPLETION_TOKENS],
"Output tokens should match completion tokens",
);
});
});

describe("Backward compatibility", () => {
it("should maintain all deprecated attributes alongside new ones", async () => {
await traceloop.withWorkflow(
{ name: "test_backward_compatibility" },
async () => {
await generateText({
messages: [
{ role: "system", content: "You are helpful" },
{ role: "user", content: "Hello" },
],
model: vercel_openai("gpt-3.5-turbo"),
experimental_telemetry: { isEnabled: true },
});
},
);

await traceloop.forceFlush();
const spans = memoryExporter.getFinishedSpans();
const aiSpan = spans.find((s) => s.name.startsWith("text.generate"));

assert.ok(aiSpan);

// New attributes should exist
assert.ok(
aiSpan.attributes[SpanAttributes.GEN_AI_OPERATION_NAME],
"New: operation.name",
);
assert.ok(
aiSpan.attributes[SpanAttributes.GEN_AI_PROVIDER_NAME],
"New: provider.name",
);
assert.ok(
aiSpan.attributes[SpanAttributes.GEN_AI_USAGE_INPUT_TOKENS],
"New: usage.input_tokens",
);
assert.ok(
aiSpan.attributes[SpanAttributes.GEN_AI_USAGE_OUTPUT_TOKENS],
"New: usage.output_tokens",
);
assert.ok(
aiSpan.attributes[SpanAttributes.GEN_AI_INPUT_MESSAGES],
"New: input.messages",
);
assert.ok(
aiSpan.attributes[SpanAttributes.GEN_AI_OUTPUT_MESSAGES],
"New: output.messages",
);

// Deprecated attributes should still exist
assert.ok(
aiSpan.attributes[SpanAttributes.LLM_SYSTEM],
"Deprecated: gen_ai.system",
);
assert.ok(
aiSpan.attributes[SpanAttributes.LLM_USAGE_PROMPT_TOKENS],
"Deprecated: usage.prompt_tokens",
);
assert.ok(
aiSpan.attributes[SpanAttributes.LLM_USAGE_COMPLETION_TOKENS],
"Deprecated: usage.completion_tokens",
);
assert.ok(
aiSpan.attributes[SpanAttributes.LLM_INPUT_MESSAGES],
"Deprecated: LLM_INPUT_MESSAGES",
);
assert.ok(
aiSpan.attributes[SpanAttributes.LLM_OUTPUT_MESSAGES],
"Deprecated: LLM_OUTPUT_MESSAGES",
);

// Flat format prompts/completions should still exist
assert.ok(
aiSpan.attributes["gen_ai.prompt.0.role"],
"Flat format: prompt.0.role",
);
assert.ok(
aiSpan.attributes["gen_ai.completion.0.role"],
"Flat format: completion.0.role",
);
});
});

describe("Span naming", () => {
it("should follow OTel pattern: {operation} {model}", async function () {
this.timeout(10000); // Increase timeout for API call

// Clear any previous spans
memoryExporter.reset();

await traceloop.withWorkflow({ name: "test_span_naming" }, async () => {
await generateText({
messages: [{ role: "user", content: "Say hi" }],
model: vercel_openai("gpt-3.5-turbo"),
experimental_telemetry: { isEnabled: true },
});
});

await traceloop.forceFlush();
const spans = memoryExporter.getFinishedSpans();
const aiSpan = spans.find((s) => s.name.startsWith("text.generate"));

assert.ok(aiSpan, "AI span should exist");
// Should be like "text.generate gpt-3.5-turbo"
assert.ok(
aiSpan.name.includes("text.generate"),
"Span name should include operation",
);
assert.ok(
aiSpan.name.includes("gpt-3.5-turbo") || aiSpan.name === "text.generate",
"Span name should include model name when available",
);
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Prettier issues reported by CI

CI reports style issues for this file. Please run the repo’s formatter (e.g., pnpm lint or prettier --write packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts) to align with the configured style.

🧰 Tools
🪛 GitHub Actions: CI

[warning] 1-1: Code style issues found in this file. Run 'prettier --write' to fix.

🤖 Prompt for AI Agents
In packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts lines 1 to 417, CI
reported Prettier/formatting violations; run the project's formatter (e.g., pnpm
lint or prettier --write
packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts), stage the modified
file, and commit the changes so the file matches the repository's Prettier
configuration and CI will pass.

Copy link
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 7cea57e in 36 minutes and 41 seconds. Click for details.
  • Reviewed 2533 lines of code in 13 files
  • Skipped 0 files when reviewing.
  • Skipped posting 10 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.
1. packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts:83
  • Draft comment:
    Good use of an operation mapping to assign the new GEN_AI_OPERATION_NAME attribute. Consider adding a brief comment on why the default is chosen as ‘chat’ when no match is found.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
2. packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts:545
  • Draft comment:
    In transformVendor, the loop over OTEL_PROVIDER_MAPPING keys performs a lower-case conversion on the value in each iteration. Consider precomputing the provider value in lower case once to avoid repeated conversion in the loop.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 35% vs. threshold = 50% This is a valid micro-optimization - calling toLowerCase() once instead of potentially multiple times in a loop is a reasonable suggestion. However, I need to consider: 1) The loop has a break statement so it exits on first match, 2) The mapping has only ~12 entries, 3) This is a transformation function that likely runs once per span, 4) The performance impact is negligible. The comment is technically correct but this is a very minor optimization that may not be worth the code change. It's borderline - not obviously wrong, but also not a significant issue. The rules say comments should be actionable and clear, which this is, but also that they shouldn't be obvious or unimportant. While technically correct, this is a micro-optimization on a small loop (12 items) that exits early on match. The performance gain would be negligible in practice. This might fall under "obvious or unimportant" comments that should be removed. However, the comment is actionable, clear, and represents a legitimate code quality improvement. It's a simple one-line change that makes the code slightly more efficient and is a best practice. Even if the performance impact is small, it's still a valid refactoring suggestion. This is a borderline case - it's a valid, actionable code quality suggestion but the impact is minimal. Given the rules emphasize removing comments unless there's strong evidence they're useful, and this is a micro-optimization with negligible real-world impact, I'll lean toward removing it.
3. packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts:407
  • Draft comment:
    The transformPrompts function separates system instructions from input messages and then combines them for backward compatibility. It may be worth adding a comment explaining the rationale behind concatenating systemInstructions and inputMessages to support older consumers.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
4. packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts:332
  • Draft comment:
    In transformTools, the try-catch for JSON parsing of string tools suppresses errors. It might be useful to log (or at least comment) when a tool item fails parsing, so that unexpected formats can be diagnosed.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.
5. packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts:591
  • Draft comment:
    The transformTelemetryMetadata function iterates over all keys and then deletes them separately. This approach is safe, but ensure that no needed metadata is lost. A note in the comments might help clarify that these attributes are solely for association properties.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
6. packages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.ts:621
  • Draft comment:
    The overall transformation pipeline in transformLLMSpans is well structured, calling each helper in sequence. It might be beneficial to document the order dependency (e.g., vendor transformation after tokens) to help future maintainers understand why the order matters.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
7. packages/traceloop-sdk/test/ai-sdk-transformations.test.ts:40
  • Draft comment:
    Test helpers for creating mock spans are concise and effectively simulate updateName behavior. Ensure that any future changes to the span interface continue to be reflected in these helpers.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
8. packages/traceloop-sdk/test/ai-sdk-integration.test.ts:115
  • Draft comment:
    The integration test for OpenAI provider checks both new and deprecated attributes. This is excellent for backward compatibility. Ensure that any further changes to attribute naming conventions also update these tests.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
9. packages/traceloop-sdk/test/ai-sdk-otel-attributes.test.ts:76
  • Draft comment:
    The comprehensive test suite for GenAI semantic conventions is impressive. The tests cover multiple edge cases including invalid JSON and mixed content types. Consider adding a remark in the documentation explaining the necessity of keeping both new and deprecated attribute sets.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None
10. packages/traceloop-sdk/test/ai-sdk-transformations.test.ts:1019
  • Draft comment:
    The final tests that combine all attribute transformations give confidence in the end-to-end correctness. No issues noted; the removal of original AI SDK attributes is verified rigorously.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50% None

Workflow ID: wflow_U8V7oo9LDxa3YBrV

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

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