Skip to content

Commit 3bb4325

Browse files
authored
fix(core): Align error span status message with core SpanStatusType for langchain/google-genai (#19863)
In case of exceptions we should set the span status to a known error value from our core `SpanStatusType`. Closes #19862
1 parent 3e5499a commit 3bb4325

File tree

6 files changed

+59
-9
lines changed

6 files changed

+59
-9
lines changed

packages/core/src/tracing/anthropic-ai/streaming.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '../ai/gen-ai-attributes';
1212
import { setTokenUsageAttributes } from '../ai/utils';
1313
import type { AnthropicAiStreamingEvent } from './types';
14+
import { mapAnthropicErrorToStatusMessage } from './utils';
1415

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

379380
if (span.isRecording()) {
380-
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'stream_error' });
381+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
381382
span.end();
382383
}
383384
});

packages/core/src/tracing/anthropic-ai/utils.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { captureException } from '../../exports';
22
import { SPAN_STATUS_ERROR } from '../../tracing';
33
import type { Span } from '../../types-hoist/span';
4+
import type { SpanStatusType } from '../../types-hoist/spanStatus';
45
import {
56
GEN_AI_INPUT_MESSAGES_ATTRIBUTE,
67
GEN_AI_INPUT_MESSAGES_ORIGINAL_LENGTH_ATTRIBUTE,
@@ -41,13 +42,35 @@ export function setMessagesAttribute(span: Span, messages: unknown): void {
4142
});
4243
}
4344

45+
const ANTHROPIC_ERROR_TYPE_TO_SPAN_STATUS: Record<string, SpanStatusType> = {
46+
invalid_request_error: 'invalid_argument',
47+
authentication_error: 'unauthenticated',
48+
permission_error: 'permission_denied',
49+
not_found_error: 'not_found',
50+
request_too_large: 'failed_precondition',
51+
rate_limit_error: 'resource_exhausted',
52+
api_error: 'internal_error',
53+
overloaded_error: 'unavailable',
54+
};
55+
56+
/**
57+
* Map an Anthropic API error type to a SpanStatusType value.
58+
* @see https://docs.anthropic.com/en/api/errors#error-shapes
59+
*/
60+
export function mapAnthropicErrorToStatusMessage(errorType: string | undefined): SpanStatusType {
61+
if (!errorType) {
62+
return 'internal_error';
63+
}
64+
return ANTHROPIC_ERROR_TYPE_TO_SPAN_STATUS[errorType] || 'internal_error';
65+
}
66+
4467
/**
4568
* Capture error information from the response
4669
* @see https://docs.anthropic.com/en/api/errors#error-shapes
4770
*/
4871
export function handleResponseError(span: Span, response: AnthropicAiResponse): void {
4972
if (response.error) {
50-
span.setStatus({ code: SPAN_STATUS_ERROR, message: response.error.type || 'internal_error' });
73+
span.setStatus({ code: SPAN_STATUS_ERROR, message: mapAnthropicErrorToStatusMessage(response.error.type) });
5174

5275
captureException(response.error, {
5376
mechanism: {

packages/core/src/tracing/google-genai/streaming.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function isErrorChunk(chunk: GoogleGenAIResponse, span: Span): boolean {
4646
const feedback = chunk?.promptFeedback;
4747
if (feedback?.blockReason) {
4848
const message = feedback.blockReasonMessage ?? feedback.blockReason;
49-
span.setStatus({ code: SPAN_STATUS_ERROR, message: `Content blocked: ${message}` });
49+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
5050
captureException(`Content blocked: ${message}`, {
5151
mechanism: { handled: false, type: 'auto.ai.google_genai' },
5252
});

packages/core/src/tracing/langchain/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
171171
handleLLMError(error: Error, runId: string) {
172172
const span = spanMap.get(runId);
173173
if (span?.isRecording()) {
174-
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'llm_error' });
174+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
175175
exitSpan(runId);
176176
}
177177

@@ -239,7 +239,7 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
239239
handleChainError(error: Error, runId: string) {
240240
const span = spanMap.get(runId);
241241
if (span?.isRecording()) {
242-
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'chain_error' });
242+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
243243
exitSpan(runId);
244244
}
245245

@@ -298,7 +298,7 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
298298
handleToolError(error: Error, runId: string) {
299299
const span = spanMap.get(runId);
300300
if (span?.isRecording()) {
301-
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'tool_error' });
301+
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
302302
exitSpan(runId);
303303
}
304304

packages/core/src/types-hoist/spanStatus.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type SpanStatusType =
1+
export type SpanStatusType =
22
/** The operation completed successfully. */
33
| 'ok'
44
/** Deadline expired before operation could complete. */

packages/core/test/lib/utils/anthropic-utils.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,34 @@
11
import { describe, expect, it } from 'vitest';
2-
import { messagesFromParams, setMessagesAttribute, shouldInstrument } from '../../../src/tracing/anthropic-ai/utils';
2+
import {
3+
mapAnthropicErrorToStatusMessage,
4+
messagesFromParams,
5+
setMessagesAttribute,
6+
shouldInstrument,
7+
} from '../../../src/tracing/anthropic-ai/utils';
38
import type { Span } from '../../../src/types-hoist/span';
49

510
describe('anthropic-ai-utils', () => {
11+
describe('mapAnthropicErrorToStatusMessage', () => {
12+
it('maps known Anthropic error types to SpanStatusType values', () => {
13+
expect(mapAnthropicErrorToStatusMessage('invalid_request_error')).toBe('invalid_argument');
14+
expect(mapAnthropicErrorToStatusMessage('authentication_error')).toBe('unauthenticated');
15+
expect(mapAnthropicErrorToStatusMessage('permission_error')).toBe('permission_denied');
16+
expect(mapAnthropicErrorToStatusMessage('not_found_error')).toBe('not_found');
17+
expect(mapAnthropicErrorToStatusMessage('request_too_large')).toBe('failed_precondition');
18+
expect(mapAnthropicErrorToStatusMessage('rate_limit_error')).toBe('resource_exhausted');
19+
expect(mapAnthropicErrorToStatusMessage('api_error')).toBe('internal_error');
20+
expect(mapAnthropicErrorToStatusMessage('overloaded_error')).toBe('unavailable');
21+
});
22+
23+
it('falls back to internal_error for unknown error types', () => {
24+
expect(mapAnthropicErrorToStatusMessage('some_new_error')).toBe('internal_error');
25+
});
26+
27+
it('falls back to internal_error for undefined', () => {
28+
expect(mapAnthropicErrorToStatusMessage(undefined)).toBe('internal_error');
29+
});
30+
});
31+
632
describe('shouldInstrument', () => {
733
it('should instrument known methods', () => {
834
expect(shouldInstrument('models.get')).toBe(true);

0 commit comments

Comments
 (0)