Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/core/src/tracing/anthropic-ai/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '../ai/gen-ai-attributes';
import { setTokenUsageAttributes } from '../ai/utils';
import type { AnthropicAiStreamingEvent } from './types';
import { mapAnthropicErrorToStatusMessage } from './utils';

/**
* State object used to accumulate information from a stream of Anthropic AI events.
Expand Down Expand Up @@ -59,7 +60,7 @@ function isErrorEvent(event: AnthropicAiStreamingEvent, span: Span): boolean {
// If the event is an error, set the span status and capture the error
// These error events are not rejected by the API by default, but are sent as metadata of the response
if (event.type === 'error') {
span.setStatus({ code: SPAN_STATUS_ERROR, message: event.error?.type ?? 'internal_error' });
span.setStatus({ code: SPAN_STATUS_ERROR, message: mapAnthropicErrorToStatusMessage(event.error?.type) });
captureException(event.error, {
mechanism: {
handled: false,
Expand Down Expand Up @@ -377,7 +378,7 @@ export function instrumentMessageStream<R extends { on: (...args: unknown[]) =>
});

if (span.isRecording()) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'stream_error' });
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
span.end();
}
});
Expand Down
25 changes: 24 additions & 1 deletion packages/core/src/tracing/anthropic-ai/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { captureException } from '../../exports';
import { SPAN_STATUS_ERROR } from '../../tracing';
import type { Span } from '../../types-hoist/span';
import type { SpanStatusType } from '../../types-hoist/spanStatus';
import {
GEN_AI_INPUT_MESSAGES_ATTRIBUTE,
GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE,
Expand Down Expand Up @@ -41,13 +42,35 @@ export function setMessagesAttribute(span: Span, messages: unknown): void {
});
}

const ANTHROPIC_ERROR_TYPE_TO_SPAN_STATUS: Record<string, SpanStatusType> = {
invalid_request_error: 'invalid_argument',
Copy link
Member

Choose a reason for hiding this comment

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

not invalid_request?

Copy link
Member Author

Choose a reason for hiding this comment

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

that doesn't exist in SpanStatusType

authentication_error: 'unauthenticated',
permission_error: 'permission_denied',
not_found_error: 'not_found',
request_too_large: 'failed_precondition',
rate_limit_error: 'resource_exhausted',
api_error: 'internal_error',
overloaded_error: 'unavailable',
};

/**
* Map an Anthropic API error type to a SpanStatusType value.
* @see https://docs.anthropic.com/en/api/errors#error-shapes
*/
export function mapAnthropicErrorToStatusMessage(errorType: string | undefined): SpanStatusType {
if (!errorType) {
return 'internal_error';
}
return ANTHROPIC_ERROR_TYPE_TO_SPAN_STATUS[errorType] || 'internal_error';
}

/**
* Capture error information from the response
* @see https://docs.anthropic.com/en/api/errors#error-shapes
*/
export function handleResponseError(span: Span, response: AnthropicAiResponse): void {
if (response.error) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: response.error.type || 'internal_error' });
span.setStatus({ code: SPAN_STATUS_ERROR, message: mapAnthropicErrorToStatusMessage(response.error.type) });

captureException(response.error, {
mechanism: {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/tracing/google-genai/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function isErrorChunk(chunk: GoogleGenAIResponse, span: Span): boolean {
const feedback = chunk?.promptFeedback;
if (feedback?.blockReason) {
const message = feedback.blockReasonMessage ?? feedback.blockReason;
span.setStatus({ code: SPAN_STATUS_ERROR, message: `Content blocked: ${message}` });
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
captureException(`Content blocked: ${message}`, {
mechanism: { handled: false, type: 'auto.ai.google_genai' },
});
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/tracing/langchain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
handleLLMError(error: Error, runId: string) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'llm_error' });
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
exitSpan(runId);
}

Expand Down Expand Up @@ -239,7 +239,7 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
handleChainError(error: Error, runId: string) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'chain_error' });
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
exitSpan(runId);
}

Expand Down Expand Up @@ -298,7 +298,7 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
handleToolError(error: Error, runId: string) {
const span = spanMap.get(runId);
if (span?.isRecording()) {
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'tool_error' });
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
exitSpan(runId);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/types-hoist/spanStatus.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type SpanStatusType =
export type SpanStatusType =
/** The operation completed successfully. */
| 'ok'
/** Deadline expired before operation could complete. */
Expand Down
28 changes: 27 additions & 1 deletion packages/core/test/lib/utils/anthropic-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
import { describe, expect, it } from 'vitest';
import { messagesFromParams, setMessagesAttribute, shouldInstrument } from '../../../src/tracing/anthropic-ai/utils';
import {
mapAnthropicErrorToStatusMessage,
messagesFromParams,
setMessagesAttribute,
shouldInstrument,
} from '../../../src/tracing/anthropic-ai/utils';
import type { Span } from '../../../src/types-hoist/span';

describe('anthropic-ai-utils', () => {
describe('mapAnthropicErrorToStatusMessage', () => {
it('maps known Anthropic error types to SpanStatusType values', () => {
expect(mapAnthropicErrorToStatusMessage('invalid_request_error')).toBe('invalid_argument');
expect(mapAnthropicErrorToStatusMessage('authentication_error')).toBe('unauthenticated');
expect(mapAnthropicErrorToStatusMessage('permission_error')).toBe('permission_denied');
expect(mapAnthropicErrorToStatusMessage('not_found_error')).toBe('not_found');
expect(mapAnthropicErrorToStatusMessage('request_too_large')).toBe('failed_precondition');
expect(mapAnthropicErrorToStatusMessage('rate_limit_error')).toBe('resource_exhausted');
expect(mapAnthropicErrorToStatusMessage('api_error')).toBe('internal_error');
expect(mapAnthropicErrorToStatusMessage('overloaded_error')).toBe('unavailable');
});

it('falls back to internal_error for unknown error types', () => {
expect(mapAnthropicErrorToStatusMessage('some_new_error')).toBe('internal_error');
});

it('falls back to internal_error for undefined', () => {
expect(mapAnthropicErrorToStatusMessage(undefined)).toBe('internal_error');
});
});

describe('shouldInstrument', () => {
it('should instrument known methods', () => {
expect(shouldInstrument('models.get')).toBe(true);
Expand Down
Loading