UNPKG

@anthropic-ai/sdk

Version:
180 lines (173 loc) • 6.7 kB
import type { Attributes, Span } from '@opentelemetry/api'; import type { BaseAnthropic } from '../../../client'; import { APIError } from '../../../core/error'; import { Stream } from '../../../core/streaming'; import { buildHeaders, type NullableHeaders } from '../../../internal/headers'; import type { ApiCallResult, ApiCallSpan, ApiCallSpanSettings } from '../../../internal/tracing'; import { loggerFor } from '../../../internal/utils/log'; import type { BetaMessageTokensCount } from '../../../resources/beta/messages/messages'; import type { BetaManagedAgentsSession } from '../../../resources/beta/sessions/sessions'; import type { MessageTokensCount } from '../../../resources/messages/messages'; import { ATTR_ANTHROPIC_BETA, ATTR_ANTHROPIC_MESSAGE_COUNT_TOKENS_INPUT_TOKENS, ATTR_ANTHROPIC_REQUEST_SERVICE_TIER, ATTR_ANTHROPIC_THINKING_BUDGET_TOKENS, ATTR_ANTHROPIC_THINKING_TYPE, ATTR_GEN_AI_AGENT_NAME, ATTR_GEN_AI_OPERATION_NAME, ATTR_GEN_AI_OUTPUT_TYPE, ATTR_GEN_AI_REQUEST_MAX_TOKENS, ATTR_GEN_AI_REQUEST_MODEL, ATTR_GEN_AI_REQUEST_REASONING_LEVEL, ATTR_GEN_AI_REQUEST_STREAM, GEN_AI_OPERATION_NAME_VALUE_CHAT, GEN_AI_OUTPUT_TYPE_VALUE_JSON, RESPONSE_HEADER_ATTRIBUTES, } from './attributes'; import { setMessageContentAttributes } from './content'; import { setResponseAttributes } from './response'; import type { AnyMessage, MessageCreateBody } from './types'; /** * Sets the IDs in the response headers of `result` on `span`, the call span of any API call. * A failure to record, here and below, is logged and never fails the API call. */ export function setAnthropicSpanAttributes(client: BaseAnthropic, span: Span, result: ApiCallResult): void { if (!span.isRecording()) { return; } try { const error = result.status === 'error' ? result.error : undefined; const headers = result.response?.headers ?? (error instanceof APIError ? error.headers : undefined); for (const [attribute, header] of RESPONSE_HEADER_ATTRIBUTES) { const value = headers?.get(header); if (value) { span.setAttribute(attribute, value); } } } catch (problem) { loggerFor(client).debug('could not record the API call on its span', problem); } } /** * The attributes that a `messages.create` span starts with, from the request's `body` and * `headers`. A sampler sees only the attributes that a span starts with. */ export function messagesCreateSpanSettings( client: BaseAnthropic, body: MessageCreateBody, headers: NullableHeaders, ): ApiCallSpanSettings { const attributes: Attributes = { [ATTR_GEN_AI_OPERATION_NAME]: GEN_AI_OPERATION_NAME_VALUE_CHAT, [ATTR_GEN_AI_REQUEST_MODEL]: body.model, }; try { // The client sends its default headers with the method's, which replace those of the same name. const betas = (buildHeaders([client._options.defaultHeaders, headers]).values.get('anthropic-beta') ?? '') .split(',') .map((beta) => beta.trim()) .filter(Boolean); if (betas.length) { attributes[ATTR_ANTHROPIC_BETA] = betas; } attributes[ATTR_GEN_AI_REQUEST_MAX_TOKENS] = body.max_tokens; if (body.stream === true) { attributes[ATTR_GEN_AI_REQUEST_STREAM] = true; } // The beta method has already moved a deprecated `output_format` here. if (body.output_config?.format?.type === 'json_schema') { attributes[ATTR_GEN_AI_OUTPUT_TYPE] = GEN_AI_OUTPUT_TYPE_VALUE_JSON; } const effort = body.output_config?.effort; if (effort != null) { attributes[ATTR_GEN_AI_REQUEST_REASONING_LEVEL] = effort; } if (body.service_tier != null) { attributes[ATTR_ANTHROPIC_REQUEST_SERVICE_TIER] = body.service_tier; } const thinking = body.thinking; if (thinking != null) { attributes[ATTR_ANTHROPIC_THINKING_TYPE] = thinking.type; } if (thinking?.type === 'enabled') { attributes[ATTR_ANTHROPIC_THINKING_BUDGET_TOKENS] = thinking.budget_tokens; } } catch (problem) { loggerFor(client).debug('could not record the request on its span', problem); } return { route: '/v1/messages', attributes }; } /** * In content mode, sets the request's content on `call`. The span does not start with it, because * the size limit counts it together with the response's content. Returns `call`. */ export function recordMessagesCreateRequest( client: BaseAnthropic, call: ApiCallSpan | undefined, body: MessageCreateBody, ): ApiCallSpan | undefined { if (!call?.span.isRecording()) { return call; } try { const { contentMode, maxContentBytes } = client.openTelemetry.traces; if (contentMode === 'content') { setMessageContentAttributes(call, body, undefined, maxContentBytes); } } catch (problem) { loggerFor(client).debug('could not record the request on its span', problem); } return call; } /** * Sets the response attributes and, in content mode, the response's content on `call`. A streamed * response is recorded as the application reads it (`endAnthropicApiCallSpanWhenStreamEnds`). */ export function recordMessagesCreateResponse( client: BaseAnthropic, call: ApiCallSpan | undefined, body: MessageCreateBody, data: AnyMessage | Stream<unknown>, ): void { if (!call?.span.isRecording() || data instanceof Stream) { return; } const span = call.span; try { setResponseAttributes(span, data, data.usage, true); const { contentMode, maxContentBytes } = client.openTelemetry.traces; if (contentMode === 'content') { setMessageContentAttributes(call, body, data.content, maxContentBytes); } } catch (problem) { loggerFor(client).debug('could not record the response on its span', problem); } } /** Sets the token count of a `messages.countTokens` response on `call`. */ export function recordCountTokensResponse( client: BaseAnthropic, call: ApiCallSpan | undefined, count: MessageTokensCount | BetaMessageTokensCount, ): void { try { call?.span.setAttribute(ATTR_ANTHROPIC_MESSAGE_COUNT_TOKENS_INPUT_TOKENS, count.input_tokens); } catch (problem) { loggerFor(client).debug('could not record the response on its span', problem); } } /** Sets the Agent's name and model on `call`, as the created `session` resolved them. */ export function recordSessionsCreateResponse( client: BaseAnthropic, call: ApiCallSpan | undefined, session: BetaManagedAgentsSession, ): void { try { call?.span.setAttributes({ [ATTR_GEN_AI_AGENT_NAME]: session.agent.name, [ATTR_GEN_AI_REQUEST_MODEL]: session.agent.model.id, }); } catch (problem) { loggerFor(client).debug('could not record the response on its span', problem); } }