-
Notifications
You must be signed in to change notification settings - Fork 46
fix(ai-sdk): improve and align attributes with gen-ai semantic-conventions #827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughAdds 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. Comment |
There was a problem hiding this 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 warningsPrettier 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
anytypes with specific types increateMockSpanWithUpdateparameter (line 27) and the removed helper functions' parameters (line 15)- Run
prettier --write packages/traceloop-sdk/test/ai-sdk-transformations.test.tsto fix formattingAfter 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 CICI 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 fileCI 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 attributesThe new assertion for
SpanAttributes.GEN_AI_PROVIDER_NAMEalongsideSpanAttributes.LLM_SYSTEMforai.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 inSemanticAttributes.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 redundantforceFlushin individual tests
afterEachalready callsawait traceloop.forceFlush();and resets the exporter, so the extraforceFlushcalls 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 assertionThe 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
transformModelIdsafely migratesai.model.idtoGEN_AI_REQUEST_MODELwithout clobbering an existing value.transformFunctionIdmapsai.telemetry.functionIdintoTRACELOOP_ENTITY_NAME, which keeps entity naming consistent.transformResponseMetadatacorrectly adaptersai.response.{id,model,finishReason}into the new GEN_AI_* response attributes.For
transformProviderMetadata, you introducegen_ai.provider.metadataas 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 toSpanAttributes, and- Use that constant here instead of the hardcoded string.
331-400: Tool definitions transformation is solid; consider richer support for non-function tools
transformToolsbuilds an OTel-compliantGEN_AI_TOOL_DEFINITIONSarray while still emitting the flatLLM_REQUEST_FUNCTIONS.*attributes. For function tools this is spot-on and matches the tests.For non-
type === "function"tools, you currently only record thetypein 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 forgen_ai.provider.metadata
transformProviderMetadatainai-sdk-transformations.tsemits agen_ai.provider.metadataattribute 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
SpanAttributesand 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.
📒 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.harpackages/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.harpackages/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.harpackages/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.harpackages/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.harpackages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Span-naming_1988960947/should-follow-OTel-pattern-operation-model_2170500561/recording.harpackages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Backward-compatibility_2327270860/should-maintain-all-deprecated-attributes-alongside-new-ones_410416891/recording.harpackages/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.tspackages/traceloop-sdk/test/ai-sdk-transformations.test.tspackages/traceloop-sdk/test/ai-sdk-integration.test.tspackages/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.tspackages/traceloop-sdk/test/ai-sdk-transformations.test.tspackages/traceloop-sdk/test/ai-sdk-integration.test.tspackages/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.harpackages/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.harpackages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Span-naming_1988960947/should-follow-OTel-pattern-operation-model_2170500561/recording.harpackages/traceloop-sdk/test/ai-sdk-otel-attributes.test.tspackages/traceloop-sdk/test/ai-sdk-transformations.test.tspackages/traceloop-sdk/test/ai-sdk-integration.test.tspackages/traceloop-sdk/recordings/AI-SDK-OTel-GenAI-Semantic-Conventions_247892713/Backward-compatibility_2327270860/should-maintain-all-deprecated-attributes-alongside-new-ones_410416891/recording.harpackages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.tspackages/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.tspackages/traceloop-sdk/test/ai-sdk-transformations.test.tspackages/traceloop-sdk/test/ai-sdk-integration.test.tspackages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.tspackages/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.tspackages/traceloop-sdk/test/ai-sdk-transformations.test.tspackages/traceloop-sdk/test/ai-sdk-integration.test.tspackages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.tspackages/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.tspackages/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.tspackages/traceloop-sdk/src/lib/tracing/ai-sdk-transformations.tspackages/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_TOKENSandGEN_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/responsesendpoint 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 secretsRequest/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 acceptableRecording 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 soundRecording 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 pathThis 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-sensitiveThe 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 testsThis 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 testsThe 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
SpanAttributesandtransformLLMSpansbehavior, 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-handledThe combination of
OTEL_PROVIDER_MAPPING,LEGACY_VENDOR_MAPPING, andtransformVendorcleanly:
- Normalizes provider identifiers into
GEN_AI_PROVIDER_NAME.- Keeps
LLM_SYSTEMin 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_MESSAGESThe updated
transformResponseText,transformResponseObject, andtransformResponseToolCallscorrectly:
- Build a structured assistant message with
parts(text or tool_call).- Populate
GEN_AI_OUTPUT_MESSAGESwith the JSON array of messages.- Keep
LLM_OUTPUT_MESSAGESin 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 intoGEN_AI_SYSTEM_INSTRUCTIONSand the combinedGEN_AI_INPUT_MESSAGES(plus deprecatedLLM_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**
transformPromptTokensandtransformCompletionTokensnow:
- Populate
GEN_AI_USAGE_INPUT_TOKENS/GEN_AI_USAGE_OUTPUT_TOKENS.- Preserve
LLM_USAGE_PROMPT_TOKENS/LLM_USAGE_COMPLETION_TOKENS.- Allow
calculateTotalTokensto 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
transformTelemetryMetadataproperly:
- 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 consistentThe new
SpanAttributesentries 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.
| /** | ||
| * 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; | ||
| }; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 fromHANDLED_SPAN_NAMESintransformAiSdkSpanAttributes), or - Changing the
operationMappingkeys 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" -C5Length 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.
| /* | ||
| * 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", | ||
| ); | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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
2533lines of code in13files - Skipped
0files when reviewing. - Skipped posting
10draft 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%<= threshold50%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%<= threshold50%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%<= threshold50%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%<= threshold50%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%<= threshold50%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%<= threshold50%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%<= threshold50%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%<= threshold50%None
Workflow ID: wflow_U8V7oo9LDxa3YBrV
You can customize by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.
Summary by CodeRabbit
Release Notes
New Features
Tests
Important
Align AI SDK attributes with OpenTelemetry GenAI semantic conventions, adding new attributes and maintaining backward compatibility.
SemanticAttributes.tsfor GenAI semantic conventions, includingGEN_AI_OPERATION_NAME,GEN_AI_PROVIDER_NAME,GEN_AI_REQUEST_MODEL,GEN_AI_RESPONSE_ID, and others.LLM_SYSTEM,LLM_REQUEST_MODEL, andLLM_PROMPTS.ai-sdk-transformations.tsto transform AI SDK attributes to new GenAI attributes, ensuring backward compatibility.addOperationName,transformModelId,transformFunctionId, andtransformResponseMetadata.ai-sdk-otel-attributes.test.tsto verify new GenAI attributes and backward compatibility.ai-sdk-integration.test.tsandai-sdk-transformations.test.tsto include new attributes and transformations..harfiles inrecordingsto test various scenarios, including provider name setting and operation name setting.This description was created by
for 7cea57e. You can customize this summary. It will automatically update as commits are pushed.