Skip to content

Commit 10ff603

Browse files
committed
fix(v10/core): Don't capture caller-handled LangChain errors
Backport of: #23593
1 parent c6f21ee commit 10ff603

5 files changed

Lines changed: 18 additions & 50 deletions

File tree

dev-packages/node-integration-tests/suites/tracing/langchain/test.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ describe('LangChain integration', () => {
3333
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
3434
test('creates langchain related spans with genAI recording disabled', async () => {
3535
await createRunner()
36-
.ignore('event')
3736
.expect({ transaction: { transaction: 'main' } })
3837
.expect({
3938
span: container => {
@@ -90,7 +89,6 @@ describe('LangChain integration', () => {
9089

9190
test('does not create duplicate spans from double module patching', async () => {
9291
await createRunner()
93-
.ignore('event')
9492
.expect({ transaction: { transaction: 'main' } })
9593
.expect({
9694
span: container => {
@@ -111,7 +109,6 @@ describe('LangChain integration', () => {
111109
createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
112110
test('creates langchain related spans with genAI recording enabled', async () => {
113111
await createRunner()
114-
.ignore('event')
115112
.expect({ transaction: { transaction: 'main' } })
116113
.expect({
117114
span: container => {
@@ -170,7 +167,6 @@ describe('LangChain integration', () => {
170167
createEsmAndCjsTests(__dirname, 'scenario-tools.mjs', 'instrument.mjs', (createRunner, test) => {
171168
test('creates langchain spans with tool calls', async () => {
172169
await createRunner()
173-
.ignore('event')
174170
.expect({ transaction: { transaction: 'main' } })
175171
.expect({
176172
span: container => {
@@ -250,7 +246,6 @@ describe('LangChain integration', () => {
250246
createEsmTests(__dirname, 'scenario-openai-before-langchain.mjs', 'instrument.mjs', (createRunner, test) => {
251247
test('demonstrates timing issue with duplicate spans', async () => {
252248
await createRunner()
253-
.ignore('event')
254249
.expect({ transaction: { transaction: 'main' } })
255250
.expect({
256251
span: container => {
@@ -287,7 +282,6 @@ describe('LangChain integration', () => {
287282
(createRunner, test) => {
288283
test('extracts system instructions from messages', async () => {
289284
await createRunner()
290-
.ignore('event')
291285
.expect({ transaction: { transaction: 'main' } })
292286
.expect({
293287
span: container => {
@@ -311,7 +305,6 @@ describe('LangChain integration', () => {
311305
createEsmAndCjsTests(__dirname, 'scenario-chain.mjs', 'instrument.mjs', (createRunner, test) => {
312306
test('uses runName for chain spans instead of unknown_chain', async () => {
313307
await createRunner()
314-
.ignore('event')
315308
.expect({ transaction: { transaction: 'main' } })
316309
.expect({
317310
span: container => {
@@ -357,7 +350,6 @@ describe('LangChain integration', () => {
357350
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument.mjs', (createRunner, test) => {
358351
test('creates embedding spans with genAI recording disabled', async () => {
359352
await createRunner()
360-
.ignore('event')
361353
.expect({ transaction: { transaction: 'main' } })
362354
.expect({
363355
span: container => {
@@ -394,7 +386,6 @@ describe('LangChain integration', () => {
394386

395387
test('does not create duplicate embedding spans from double module patching', async () => {
396388
await createRunner()
397-
.ignore('event')
398389
.expect({ transaction: { transaction: 'main' } })
399390
.expect({
400391
span: container => {
@@ -413,7 +404,6 @@ describe('LangChain integration', () => {
413404
createEsmAndCjsTests(__dirname, 'scenario-embeddings.mjs', 'instrument-with-pii.mjs', (createRunner, test) => {
414405
test('creates embedding spans with genAI recording enabled', async () => {
415406
await createRunner()
416-
.ignore('event')
417407
.expect({ transaction: { transaction: 'main' } })
418408
.expect({
419409
span: container => {

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { captureException } from '../../exports';
21
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
32
import { startSpan } from '../../tracing/trace';
43
import type { SpanAttributeValue } from '../../types/span';
@@ -93,12 +92,9 @@ export function instrumentEmbeddingMethod(
9392
return new Proxy(originalMethod, {
9493
apply(target, thisArg, args: unknown[]): Promise<unknown> {
9594
return startSpan(_INTERNAL_getLangChainEmbeddingsSpanOptions(thisArg, args[0], options), () => {
96-
return Reflect.apply(target, thisArg, args).then(undefined, error => {
97-
captureException(error, {
98-
mechanism: { handled: false, type: 'auto.ai.langchain' },
99-
});
100-
throw error;
101-
});
95+
// On rejection `startSpan` marks the span failed and rethrows to the caller, so we don't
96+
// record the error ourselves.
97+
return Reflect.apply(target, thisArg, args);
10298
});
10399
},
104100
}) as (...args: unknown[]) => Promise<unknown>;

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

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/* eslint-disable max-lines */
2-
import { captureException } from '../../exports';
32
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../../semanticAttributes';
43
import { SPAN_STATUS_ERROR } from '../../tracing';
54
import { startSpanManual } from '../../tracing/trace';
@@ -183,19 +182,14 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
183182
},
184183

185184
// LLM Error Handler - note: handleLLMError with capital LLM
186-
handleLLMError(error: Error, runId: string) {
185+
handleLLMError(_error: Error, runId: string) {
186+
// The error is surfaced to the caller (invoke() rejects), so we only mark the span failed and
187+
// do not record it.
187188
const span = spanMap.get(runId);
188189
if (span?.isRecording()) {
189190
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
190191
exitSpan(runId);
191192
}
192-
193-
captureException(error, {
194-
mechanism: {
195-
handled: false,
196-
type: `${LANGCHAIN_ORIGIN}.llm_error_handler`,
197-
},
198-
});
199193
},
200194

201195
// Chain Start Handler
@@ -257,19 +251,14 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
257251
},
258252

259253
// Chain Error Handler
260-
handleChainError(error: Error, runId: string) {
254+
handleChainError(_error: Error, runId: string) {
255+
// The error is surfaced to the caller (invoke() rejects), so we only mark the span failed and
256+
// do not record it.
261257
const span = spanMap.get(runId);
262258
if (span?.isRecording()) {
263259
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
264260
exitSpan(runId);
265261
}
266-
267-
captureException(error, {
268-
mechanism: {
269-
handled: false,
270-
type: `${LANGCHAIN_ORIGIN}.chain_error_handler`,
271-
},
272-
});
273262
},
274263

275264
// Tool Start Handler
@@ -335,19 +324,14 @@ export function createLangChainCallbackHandler(options: LangChainOptions = {}):
335324
},
336325

337326
// Tool Error Handler
338-
handleToolError(error: Error, runId: string) {
327+
handleToolError(_error: Error, runId: string) {
328+
// The error is surfaced to the caller (invoke() rejects), so we only mark the span failed and
329+
// do not record it.
339330
const span = spanMap.get(runId);
340331
if (span?.isRecording()) {
341332
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
342333
exitSpan(runId);
343334
}
344-
345-
captureException(error, {
346-
mechanism: {
347-
handled: false,
348-
type: `${LANGCHAIN_ORIGIN}.tool_error_handler`,
349-
},
350-
});
351335
},
352336

353337
// LangChain BaseCallbackHandler required methods

packages/core/test/lib/tracing/langchain-embeddings.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,17 +75,15 @@ describe('instrumentEmbeddingMethod', () => {
7575
expect(capturedSpanConfig!.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]).toBe('["doc1","doc2"]');
7676
});
7777

78-
it('captures exception on failure', async () => {
78+
it('rethrows the error to the caller without capturing it', async () => {
7979
const error = new Error('API error');
8080
const original = vi.fn().mockRejectedValue(error);
8181
const wrapped = instrumentEmbeddingMethod(original);
8282

8383
const instance = { constructor: { name: 'OpenAIEmbeddings' }, model: 'error-model' };
8484
await expect(wrapped.call(instance, 'test')).rejects.toThrow('API error');
8585

86-
expect(captureException).toHaveBeenCalledWith(error, {
87-
mechanism: { handled: false, type: 'auto.ai.langchain' },
88-
});
86+
expect(captureException).not.toHaveBeenCalled();
8987
});
9088

9189
it('infers system from class name', async () => {

packages/server-utils/src/integrations/tracing-channel/langchain.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ const _langChainChannelIntegration = ((options: LangChainOptions = {}) => {
9292
waitForTracingChannelBinding(() => {
9393
for (const channelName of langchainEmbeddingsChannels) {
9494
DEBUG_BUILD && debug.log(`[orchestrion:langchain] subscribing to channel "${channelName}"`);
95-
bindTracingChannelToSpan(
96-
diagnosticsChannel.tracingChannel<EmbeddingsChannelContext>(channelName),
97-
data => createEmbeddingsSpan(data, options),
98-
{ captureError: () => ({ mechanism: { handled: false, type: 'auto.ai.langchain' } }) },
95+
// Embedding errors reject to the caller, so we only open the span (which
96+
// bindTracingChannelToSpan still marks failed on error) and do not capture them.
97+
bindTracingChannelToSpan(diagnosticsChannel.tracingChannel<EmbeddingsChannelContext>(channelName), data =>
98+
createEmbeddingsSpan(data, options),
9999
);
100100
}
101101
});

0 commit comments

Comments
 (0)