|
1 | 1 | import type { Client } from '../client';
|
2 | 2 | import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes';
|
3 | 3 | import type { Event } from '../types-hoist/event';
|
4 |
| -import type { Span, SpanAttributes, SpanJSON, SpanOrigin } from '../types-hoist/span'; |
| 4 | +import type { Span, SpanAttributes, SpanAttributeValue, SpanJSON, SpanOrigin } from '../types-hoist/span'; |
5 | 5 | import { spanToJSON } from './spanUtils';
|
6 | 6 | import type { ProviderMetadata } from './vercel-ai-attributes';
|
7 | 7 | import {
|
@@ -245,32 +245,78 @@ function addProviderMetadataToAttributes(attributes: SpanAttributes): void {
|
245 | 245 | try {
|
246 | 246 | const providerMetadataObject = JSON.parse(providerMetadata) as ProviderMetadata;
|
247 | 247 | if (providerMetadataObject.openai) {
|
248 |
| - attributes['gen_ai.usage.input_tokens.cached'] = providerMetadataObject.openai.cachedPromptTokens; |
249 |
| - attributes['gen_ai.usage.output_tokens.reasoning'] = providerMetadataObject.openai.reasoningTokens; |
250 |
| - attributes['gen_ai.usage.output_tokens.prediction_accepted'] = |
251 |
| - providerMetadataObject.openai.acceptedPredictionTokens; |
252 |
| - attributes['gen_ai.usage.output_tokens.prediction_rejected'] = |
253 |
| - providerMetadataObject.openai.rejectedPredictionTokens; |
254 |
| - attributes['gen_ai.conversation.id'] = providerMetadataObject.openai.responseId; |
| 248 | + setAttributeIfDefined( |
| 249 | + attributes, |
| 250 | + 'gen_ai.usage.input_tokens.cached', |
| 251 | + providerMetadataObject.openai.cachedPromptTokens, |
| 252 | + ); |
| 253 | + setAttributeIfDefined( |
| 254 | + attributes, |
| 255 | + 'gen_ai.usage.output_tokens.reasoning', |
| 256 | + providerMetadataObject.openai.reasoningTokens, |
| 257 | + ); |
| 258 | + setAttributeIfDefined( |
| 259 | + attributes, |
| 260 | + 'gen_ai.usage.output_tokens.prediction_accepted', |
| 261 | + providerMetadataObject.openai.acceptedPredictionTokens, |
| 262 | + ); |
| 263 | + setAttributeIfDefined( |
| 264 | + attributes, |
| 265 | + 'gen_ai.usage.output_tokens.prediction_rejected', |
| 266 | + providerMetadataObject.openai.rejectedPredictionTokens, |
| 267 | + ); |
| 268 | + setAttributeIfDefined(attributes, 'gen_ai.conversation.id', providerMetadataObject.openai.responseId); |
255 | 269 | }
|
256 | 270 |
|
257 | 271 | if (providerMetadataObject.anthropic) {
|
258 |
| - attributes['gen_ai.usage.input_tokens.cached'] = providerMetadataObject.anthropic.cacheReadInputTokens; |
259 |
| - attributes['gen_ai.usage.input_tokens.cache_write'] = providerMetadataObject.anthropic.cacheCreationInputTokens; |
| 272 | + setAttributeIfDefined( |
| 273 | + attributes, |
| 274 | + 'gen_ai.usage.input_tokens.cached', |
| 275 | + providerMetadataObject.anthropic.cacheReadInputTokens, |
| 276 | + ); |
| 277 | + setAttributeIfDefined( |
| 278 | + attributes, |
| 279 | + 'gen_ai.usage.input_tokens.cache_write', |
| 280 | + providerMetadataObject.anthropic.cacheCreationInputTokens, |
| 281 | + ); |
260 | 282 | }
|
261 | 283 |
|
262 | 284 | if (providerMetadataObject.bedrock?.usage) {
|
263 |
| - attributes['gen_ai.usage.input_tokens.cached'] = providerMetadataObject.bedrock.usage.cacheReadInputTokens; |
264 |
| - attributes['gen_ai.usage.input_tokens.cache_write'] = |
265 |
| - providerMetadataObject.bedrock.usage.cacheWriteInputTokens; |
| 285 | + setAttributeIfDefined( |
| 286 | + attributes, |
| 287 | + 'gen_ai.usage.input_tokens.cached', |
| 288 | + providerMetadataObject.bedrock.usage.cacheReadInputTokens, |
| 289 | + ); |
| 290 | + setAttributeIfDefined( |
| 291 | + attributes, |
| 292 | + 'gen_ai.usage.input_tokens.cache_write', |
| 293 | + providerMetadataObject.bedrock.usage.cacheWriteInputTokens, |
| 294 | + ); |
266 | 295 | }
|
267 | 296 |
|
268 | 297 | if (providerMetadataObject.deepseek) {
|
269 |
| - attributes['gen_ai.usage.input_tokens.cached'] = providerMetadataObject.deepseek.promptCacheHitTokens; |
270 |
| - attributes['gen_ai.usage.input_tokens.cache_miss'] = providerMetadataObject.deepseek.promptCacheMissTokens; |
| 298 | + setAttributeIfDefined( |
| 299 | + attributes, |
| 300 | + 'gen_ai.usage.input_tokens.cached', |
| 301 | + providerMetadataObject.deepseek.promptCacheHitTokens, |
| 302 | + ); |
| 303 | + setAttributeIfDefined( |
| 304 | + attributes, |
| 305 | + 'gen_ai.usage.input_tokens.cache_miss', |
| 306 | + providerMetadataObject.deepseek.promptCacheMissTokens, |
| 307 | + ); |
271 | 308 | }
|
272 | 309 | } catch {
|
273 | 310 | // Ignore
|
274 | 311 | }
|
275 | 312 | }
|
276 | 313 | }
|
| 314 | + |
| 315 | +/** |
| 316 | + * Sets an attribute only if the value is not null or undefined. |
| 317 | + */ |
| 318 | +function setAttributeIfDefined(attributes: SpanAttributes, key: string, value: SpanAttributeValue | undefined): void { |
| 319 | + if (value != null) { |
| 320 | + attributes[key] = value; |
| 321 | + } |
| 322 | +} |
0 commit comments