diff --git a/MIGRATION.md b/MIGRATION.md index 438b2572f682..ff01d0d8a266 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -256,6 +256,12 @@ Affected SDKs: All SDKs. If you reference these attributes in custom instrumentation, `beforeSendSpan`, dashboards, or alerts, update them to the new names. +### LangGraph no longer emits `create_agent` spans + +Affected SDKs: All server-side SDKs. + +The LangGraph instrumentation no longer emits `gen_ai.create_agent` spans when a graph is compiled. `gen_ai.invoke_agent` and `gen_ai.execute_tool` spans are unaffected. If you reference `create_agent` spans in dashboards or alerts, update them accordingly. + ### `thirdPartyErrorFilterIntegration` filters internal frames by default Affected SDKs: All SDKs. diff --git a/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/test.ts index 6d3cffa6d14e..6615c79430b4 100644 --- a/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/cloudflare-integration-tests/suites/tracing/langgraph/test.ts @@ -29,28 +29,11 @@ it('traces langgraph compile and invoke operations', async ({ signal }) => { const container = envelope[1]?.[1]?.[1] as any; expect(container).toBeDefined(); - expect(container.items).toHaveLength(2); + expect(container.items).toHaveLength(1); expect(container.items.map((span: SerializedStreamedSpan) => span.name).sort()).toEqual([ - 'create_agent weather_assistant', 'invoke_agent weather_assistant', ]); - const createAgentSpan = container.items.find( - (span: SerializedStreamedSpan) => span.name === 'create_agent weather_assistant', - ); - expect(createAgentSpan).toBeDefined(); - expect(createAgentSpan!.status).toBe('ok'); - expect(createAgentSpan!.attributes[GEN_AI_OPERATION_NAME]).toEqual({ - type: 'string', - value: 'create_agent', - }); - expect(createAgentSpan!.attributes['sentry.op']).toEqual({ type: 'string', value: 'gen_ai.create_agent' }); - expect(createAgentSpan!.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.ai.langgraph' }); - expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME]).toEqual({ - type: 'string', - value: 'weather_assistant', - }); - const invokeAgentSpan = container.items.find( (span: SerializedStreamedSpan) => span.name === 'invoke_agent weather_assistant', ); diff --git a/dev-packages/deno-integration-tests/suites/orchestrion-langgraph/test.ts b/dev-packages/deno-integration-tests/suites/orchestrion-langgraph/test.ts index 5fd109a5a52b..ec4d010110f4 100644 --- a/dev-packages/deno-integration-tests/suites/orchestrion-langgraph/test.ts +++ b/dev-packages/deno-integration-tests/suites/orchestrion-langgraph/test.ts @@ -4,8 +4,8 @@ import { tracingChannel } from 'node:diagnostics_channel'; import type { DenoClient } from '@sentry/deno'; import { init, startSpan } from '@sentry/deno'; import { assert } from 'https://deno.land/std@0.212.0/assert/assert.ts'; -import { assertExists } from 'https://deno.land/std@0.212.0/assert/assert_exists.ts'; import { assertEquals } from 'https://deno.land/std@0.212.0/assert/assert_equals.ts'; +import { assertExists } from 'https://deno.land/std@0.212.0/assert/assert_exists.ts'; import { resetGlobals, transactionSink, withTimeout } from '../../src/index.ts'; Deno.test('langgraph instrumentation: included in default integrations (Deno 2.8.0+)', () => { @@ -15,7 +15,7 @@ Deno.test('langgraph instrumentation: included in default integrations (Deno 2.8 assert(names.includes('LangGraph'), `LangGraph should be in defaults, got ${names.join(', ')}`); }); -Deno.test('langgraph instrumentation: orchestrion stateGraphCompile channel produces a nested create_agent span', async () => { +Deno.test('langgraph instrumentation: orchestrion stateGraphCompile channel wraps the compiled graph invoke', async () => { resetGlobals(); const sink = transactionSink(); init({ @@ -27,13 +27,17 @@ Deno.test('langgraph instrumentation: orchestrion stateGraphCompile channel prod const channel = tracingChannel('orchestrion:@langchain/langgraph:stateGraphCompile'); - // `arguments[0]` is the compile options; `name` names the agent span. + const originalInvoke = () => Promise.resolve('result'); + const compiledGraph = { invoke: originalInvoke }; + // `arguments[0]` is the compile options; `name` names the wrapped invoke_agent span. const ctx: Record = { arguments: [{ name: 'my-agent' }] }; - startSpan({ name: 'parent', op: 'test' }, () => { + await startSpan({ name: 'parent', op: 'test' }, async () => { channel.start.runStores(ctx, () => undefined); - ctx.result = {}; + ctx.result = compiledGraph; channel.end.publish(ctx); + assert(compiledGraph.invoke !== originalInvoke, "compiled graph's invoke should be wrapped"); + await compiledGraph.invoke(); }); const parent = await withTimeout( @@ -42,13 +46,13 @@ Deno.test('langgraph instrumentation: orchestrion stateGraphCompile channel prod "'parent' transaction", ); - const aiSpan = parent.spans?.find(s => s.op === 'gen_ai.create_agent'); + const invokeAgentSpan = parent.spans?.find(s => s.op === 'gen_ai.invoke_agent'); assertExists( - aiSpan, - `expected a gen_ai.create_agent child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`, + invokeAgentSpan, + `expected a gen_ai.invoke_agent child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`, ); - assertEquals(aiSpan!.description, 'create_agent my-agent'); - assertEquals(aiSpan!.data?.['gen_ai.operation.name'], 'create_agent'); - assertEquals(aiSpan!.data?.['gen_ai.agent.name'], 'my-agent'); - assertEquals(aiSpan!.data?.['sentry.origin'], 'auto.ai.langgraph'); + assertEquals(invokeAgentSpan!.description, 'invoke_agent my-agent'); + assertEquals(invokeAgentSpan!.data?.['gen_ai.operation.name'], 'invoke_agent'); + assertEquals(invokeAgentSpan!.data?.['gen_ai.agent.name'], 'my-agent'); + assertEquals(invokeAgentSpan!.data?.['sentry.origin'], 'auto.ai.langgraph'); }); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts index 16d51d5f1962..91481ae3d82c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts @@ -31,21 +31,12 @@ describe('LangGraph integration', () => { .expect({ transaction: { transaction: 'langgraph-test' } }) .expect({ span: container => { - expect(container.items).toHaveLength(3); + expect(container.items).toHaveLength(2); expect(container.items.map(span => span.name).sort()).toEqual([ - 'create_agent weather_assistant', 'invoke_agent weather_assistant', 'invoke_agent weather_assistant', ]); - const createAgentSpan = container.items.find(span => span.name === 'create_agent weather_assistant'); - expect(createAgentSpan).toBeDefined(); - expect(createAgentSpan!.status).toBe('ok'); - expect(createAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); - expect(createAgentSpan!.attributes['sentry.origin'].value).toBe('auto.ai.langgraph'); - expect(createAgentSpan!.attributes[GEN_AI_OPERATION_NAME].value).toBe('create_agent'); - expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('weather_assistant'); - const invokeAgentSpans = container.items.filter(span => span.name === 'invoke_agent weather_assistant'); expect(invokeAgentSpans).toHaveLength(2); for (const span of invokeAgentSpans) { @@ -70,11 +61,7 @@ describe('LangGraph integration', () => { .expect({ transaction: { transaction: 'langgraph-test' } }) .expect({ span: container => { - expect(container.items).toHaveLength(3); - const createAgentSpan = container.items.find(span => span.name === 'create_agent weather_assistant'); - expect(createAgentSpan).toBeDefined(); - expect(createAgentSpan!.status).toBe('ok'); - expect(createAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); + expect(container.items).toHaveLength(2); const weatherTodaySpan = container.items.find(span => getStringAttributeValue(span.attributes[GEN_AI_INPUT_MESSAGES]?.value)?.includes( @@ -110,20 +97,12 @@ describe('LangGraph integration', () => { .expect({ transaction: { transaction: 'langgraph-tools-test' } }) .expect({ span: container => { - expect(container.items).toHaveLength(4); + expect(container.items).toHaveLength(2); expect(container.items.map(span => span.name).sort()).toEqual([ - 'create_agent tool_agent', - 'create_agent tool_calling_agent', 'invoke_agent tool_agent', 'invoke_agent tool_calling_agent', ]); - const toolAgentSpan = container.items.find(span => span.name === 'create_agent tool_agent'); - expect(toolAgentSpan).toBeDefined(); - expect(toolAgentSpan!.status).toBe('ok'); - expect(toolAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); - expect(toolAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('tool_agent'); - const toolAgentInvokeSpan = container.items.find(span => span.name === 'invoke_agent tool_agent'); expect(toolAgentInvokeSpan).toBeDefined(); expect(toolAgentInvokeSpan!.status).toBe('ok'); @@ -138,12 +117,6 @@ describe('LangGraph integration', () => { expect(toolAgentInvokeSpan!.attributes[GEN_AI_USAGE_OUTPUT_TOKENS].value).toBe(15); expect(toolAgentInvokeSpan!.attributes[GEN_AI_USAGE_TOTAL_TOKENS].value).toBe(40); - const toolCallingAgentSpan = container.items.find(span => span.name === 'create_agent tool_calling_agent'); - expect(toolCallingAgentSpan).toBeDefined(); - expect(toolCallingAgentSpan!.status).toBe('ok'); - expect(toolCallingAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); - expect(toolCallingAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('tool_calling_agent'); - const toolCallingInvokeSpan = container.items.find(span => span.name === 'invoke_agent tool_calling_agent'); expect(toolCallingInvokeSpan).toBeDefined(); expect(toolCallingInvokeSpan!.status).toBe('ok'); @@ -170,11 +143,7 @@ describe('LangGraph integration', () => { .expect({ transaction: { transaction: 'langgraph-thread-id-test' } }) .expect({ span: container => { - expect(container.items).toHaveLength(4); - const createAgentSpan = container.items.find(span => span.name === 'create_agent thread_test_agent'); - expect(createAgentSpan).toBeDefined(); - expect(createAgentSpan!.status).toBe('ok'); - expect(createAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); + expect(container.items).toHaveLength(3); const firstThreadSpan = container.items.find( span => span.attributes[GEN_AI_CONVERSATION_ID]?.value === 'thread_abc123_session_1', @@ -215,7 +184,7 @@ describe('LangGraph integration', () => { .expect({ transaction: { transaction: 'main' } }) .expect({ span: container => { - expect(container.items).toHaveLength(2); + expect(container.items).toHaveLength(1); const invokeAgentSpan = container.items.find(span => span.name === 'invoke_agent test-agent'); expect(invokeAgentSpan).toBeDefined(); @@ -247,12 +216,7 @@ describe('LangGraph integration', () => { }) .expect({ span: container => { - expect(container.items).toHaveLength(3); - const createAgentSpan = container.items.find(span => span.name === 'create_agent resume_agent'); - expect(createAgentSpan).toBeDefined(); - expect(createAgentSpan!.status).toBe('ok'); - expect(createAgentSpan!.attributes['sentry.op'].value).toBe('gen_ai.create_agent'); - expect(createAgentSpan!.attributes[GEN_AI_AGENT_NAME].value).toBe('resume_agent'); + expect(container.items).toHaveLength(2); const invokeAgentSpan = container.items.find( span => span.attributes[GEN_AI_CONVERSATION_ID]?.value === 'resume-thread-1', @@ -290,7 +254,7 @@ describe('LangGraph integration', () => { { role: 'user', content: 'Follow-up question' }, ]); - expect(container.items).toHaveLength(2); + expect(container.items).toHaveLength(1); const invokeAgentSpan = container.items.find( span => span.attributes[GEN_AI_INPUT_MESSAGES]?.value === expectedMessages, ); diff --git a/packages/core/src/server-exports.ts b/packages/core/src/server-exports.ts index 14486692830e..de095f47afb7 100644 --- a/packages/core/src/server-exports.ts +++ b/packages/core/src/server-exports.ts @@ -98,7 +98,6 @@ export { instrumentCreateReactAgent, instrumentStateGraph, instrumentCompiledGraphInvoke, - _INTERNAL_getLangGraphCreateAgentSpanOptions, } from './tracing/langgraph'; export { wrapToolsWithSpans, extractLLMFromParams, extractAgentNameFromParams } from './tracing/langgraph/utils'; export { LANGGRAPH_INTEGRATION_NAME } from './tracing/langgraph/constants'; diff --git a/packages/core/src/tracing/langgraph/index.ts b/packages/core/src/tracing/langgraph/index.ts index c5cdd753f79c..9a5963579990 100644 --- a/packages/core/src/tracing/langgraph/index.ts +++ b/packages/core/src/tracing/langgraph/index.ts @@ -20,7 +20,6 @@ import { shouldEnableTruncation, } from '../ai/utils'; import { stringify } from '../../utils/string'; -import type { SpanAttributeValue } from '../../types/span'; import { createLangChainCallbackHandler } from '../langchain'; import type { BaseChatModel, LangChainMessage } from '../langchain/types'; import { normalizeLangChainMessages } from '../langchain/utils'; @@ -41,40 +40,8 @@ let _insideCreateReactAgent = false; const SENTRY_PATCHED = '__sentry_patched__'; /** - * Builds the span options for a LangGraph `create_agent` span. - * - * @internal Exported so the diagnostics-channel (orchestrion) instrumentation can open the same span - * as the prototype-patching path below without re-declaring the semantic attribute keys. - */ -export function _INTERNAL_getLangGraphCreateAgentSpanOptions(agentName?: string): { - op: string; - name: string; - attributes: Record; -} { - const attributes: Record = { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: LANGGRAPH_ORIGIN, - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'gen_ai.create_agent', - [GEN_AI_OPERATION_NAME]: 'create_agent', - }; - - if (agentName) { - attributes[GEN_AI_AGENT_NAME] = agentName; - } - - return { - op: 'gen_ai.create_agent', - name: agentName ? `create_agent ${agentName}` : 'create_agent', - attributes, - }; -} - -/** - * Instruments StateGraph's compile method to create spans for agent creation and invocation - * - * Wraps the compile() method to: - * - Create a `gen_ai.create_agent` span when compile() is called - * - Automatically wrap the invoke() method on the returned compiled graph with a `gen_ai.invoke_agent` span - * + * Instruments StateGraph's compile method to wrap the returned compiled graph's invoke() with a + * `gen_ai.invoke_agent` span. */ export function instrumentStateGraphCompile( originalCompile: (...args: unknown[]) => CompiledGraph, @@ -93,42 +60,23 @@ export function instrumentStateGraphCompile( return Reflect.apply(target, thisArg, args); } - return startSpan(_INTERNAL_getLangGraphCreateAgentSpanOptions(), span => { - try { - const compiledGraph = Reflect.apply(target, thisArg, args); - const compileOptions = args.length > 0 ? (args[0] as Record) : {}; + const compiledGraph = Reflect.apply(target, thisArg, args); + const compileOptions = args.length > 0 ? (args[0] as Record) : {}; - // Extract graph name - if (compileOptions?.name && typeof compileOptions.name === 'string') { - span.setAttribute(GEN_AI_AGENT_NAME, compileOptions.name); - span.updateName(`create_agent ${compileOptions.name}`); - } - - // Instrument agent invoke method on the compiled graph - const originalInvoke = compiledGraph.invoke; - if (originalInvoke && typeof originalInvoke === 'function') { - compiledGraph.invoke = instrumentCompiledGraphInvoke( - originalInvoke.bind(compiledGraph) as (...args: unknown[]) => Promise, - compiledGraph, - compileOptions, - options, - undefined, - sentryHandler, - ); - } + // Instrument agent invoke method on the compiled graph + const originalInvoke = compiledGraph.invoke; + if (originalInvoke && typeof originalInvoke === 'function') { + compiledGraph.invoke = instrumentCompiledGraphInvoke( + originalInvoke.bind(compiledGraph) as (...args: unknown[]) => Promise, + compiledGraph, + compileOptions, + options, + undefined, + sentryHandler, + ); + } - return compiledGraph; - } catch (error) { - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); - captureException(error, { - mechanism: { - handled: false, - type: 'auto.ai.langgraph.error', - }, - }); - throw error; - } - }); + return compiledGraph; }, }); diff --git a/packages/server-utils/src/integrations/tracing-channel/langgraph.ts b/packages/server-utils/src/integrations/tracing-channel/langgraph.ts index 524dd1174060..871985e138ec 100644 --- a/packages/server-utils/src/integrations/tracing-channel/langgraph.ts +++ b/packages/server-utils/src/integrations/tracing-channel/langgraph.ts @@ -1,7 +1,6 @@ import * as diagnosticsChannel from 'node:diagnostics_channel'; import type { CompiledGraph, IntegrationFn, LangGraphOptions } from '@sentry/core'; import { - _INTERNAL_getLangGraphCreateAgentSpanOptions, createLangChainCallbackHandler, debug, defineIntegration, @@ -10,13 +9,10 @@ import { instrumentCompiledGraphInvoke, LANGGRAPH_INTEGRATION_NAME, resolveAIRecordingOptions, - startInactiveSpan, - waitForTracingChannelBinding, wrapToolsWithSpans, } from '@sentry/core'; import { DEBUG_BUILD } from '../../debug-build'; import { CHANNELS } from '../../orchestrion/channels'; -import { bindTracingChannelToSpan } from '../../tracing-channel'; // Same name as the OTel integration by design, so the OTel 'LangGraph' integration is // deduplicated out of the default set. @@ -34,8 +30,8 @@ interface CreateReactAgentChannelContext { let subscribed = false; -// `createReactAgent` compiles a `StateGraph` internally; suppress the `create_agent` span for that -// nested compile so a react agent gets a single `invoke_agent` span, matching the OTel path. +// `createReactAgent` compiles a `StateGraph` internally. When set, the compile subscriber skips that +// nested graph so its `invoke` is wrapped once (by the createReactAgent handler), not twice. let insideCreateReactAgent = false; const _langGraphIntegration = ((options: LangGraphOptions = {}) => { @@ -51,70 +47,52 @@ const _langGraphIntegration = ((options: LangGraphOptions = {}) => { const resolvedOptions = resolveAIRecordingOptions(options); const sentryHandler = createLangChainCallbackHandler(resolvedOptions); - // `bindTracingChannelToSpan` needs the async-context binding that `initOpenTelemetry()` registers - // after `setupOnce` runs, so wait for it before subscribing. - waitForTracingChannelBinding(() => { - // StateGraph.compile → `create_agent` span, then wrap the returned graph's `invoke`. - DEBUG_BUILD && - debug.log(`[orchestrion:langgraph] subscribing to channel "${CHANNELS.LANGGRAPH_STATE_GRAPH_COMPILE}"`); - bindTracingChannelToSpan( - diagnosticsChannel.tracingChannel(CHANNELS.LANGGRAPH_STATE_GRAPH_COMPILE), - data => { - if (insideCreateReactAgent) { - return undefined; - } - const compileOptions = getFirstArgObject(data.arguments); - const name = typeof compileOptions?.name === 'string' ? compileOptions.name : undefined; - - return startInactiveSpan(_INTERNAL_getLangGraphCreateAgentSpanOptions(name)); - }, - { - beforeSpanEnd: (_span, data) => { - wrapCompiledGraphInvoke( - data.result, - getFirstArgObject(data.arguments) ?? {}, - resolvedOptions, - null, - sentryHandler, - ); - }, - }, - ); - - // createReactAgent has no `create_agent` span of its own; it only wraps tools and the returned - // graph's `invoke`. Tools are wrapped at `start` (before the agent runs), invoke at `end`. - DEBUG_BUILD && - debug.log(`[orchestrion:langgraph] subscribing to channel "${CHANNELS.LANGGRAPH_CREATE_REACT_AGENT}"`); - const reactAgentChannel = diagnosticsChannel.tracingChannel( - CHANNELS.LANGGRAPH_CREATE_REACT_AGENT, - ); - reactAgentChannel.start.subscribe(message => { - // `createReactAgent` runs synchronously and compiles a `StateGraph` internally, so the flag - // must be on for the duration and off by `end`. It's set here (never in a branch that can - // throw) and cleared in both `end` and `error`, so it can neither stick on across calls nor - // stay off during this call's nested compile. Tool wrapping is guarded for the same reason. - insideCreateReactAgent = true; - try { - const { arguments: args } = message as CreateReactAgentChannelContext; - const params = getFirstArgObject(args); - if (params && Array.isArray(params.tools) && params.tools.length > 0) { - wrapToolsWithSpans(params.tools, resolvedOptions, extractAgentNameFromParams(args) ?? undefined); - } - } catch (error) { - DEBUG_BUILD && debug.error('[orchestrion:langgraph] failed to wrap createReactAgent tools', error); + // StateGraph.compile returns synchronously; wrap the returned graph's `invoke` at `end`. + DEBUG_BUILD && + debug.log(`[orchestrion:langgraph] subscribing to channel "${CHANNELS.LANGGRAPH_STATE_GRAPH_COMPILE}"`); + diagnosticsChannel + .tracingChannel(CHANNELS.LANGGRAPH_STATE_GRAPH_COMPILE) + .end.subscribe(message => { + if (insideCreateReactAgent) { + return; } + const { arguments: args, result } = message as CompileChannelContext; + wrapCompiledGraphInvoke(result, getFirstArgObject(args) ?? {}, resolvedOptions, null, sentryHandler); }); - reactAgentChannel.end.subscribe(message => { - insideCreateReactAgent = false; - const { arguments: args, result } = message as CreateReactAgentChannelContext; - const agentName = extractAgentNameFromParams(args) ?? undefined; - const compileOptions = agentName ? { name: agentName } : {}; - wrapCompiledGraphInvoke(result, compileOptions, resolvedOptions, extractLLMFromParams(args), sentryHandler); - }); - // Make sure a thrown `createReactAgent` doesn't leave the suppression flag stuck on. - reactAgentChannel.error.subscribe(() => { - insideCreateReactAgent = false; - }); + + // createReactAgent only wraps tools and the returned graph's `invoke`. Tools are wrapped at + // `start` (before the agent runs), invoke at `end`. + DEBUG_BUILD && + debug.log(`[orchestrion:langgraph] subscribing to channel "${CHANNELS.LANGGRAPH_CREATE_REACT_AGENT}"`); + const reactAgentChannel = diagnosticsChannel.tracingChannel( + CHANNELS.LANGGRAPH_CREATE_REACT_AGENT, + ); + reactAgentChannel.start.subscribe(message => { + // `createReactAgent` runs synchronously and compiles a `StateGraph` internally, so the flag + // must be on for the duration and off by `end`. It's set here (never in a branch that can + // throw) and cleared in both `end` and `error`, so it can neither stick on across calls nor + // stay off during this call's nested compile. Tool wrapping is guarded for the same reason. + insideCreateReactAgent = true; + try { + const { arguments: args } = message as CreateReactAgentChannelContext; + const params = getFirstArgObject(args); + if (params && Array.isArray(params.tools) && params.tools.length > 0) { + wrapToolsWithSpans(params.tools, resolvedOptions, extractAgentNameFromParams(args) ?? undefined); + } + } catch (error) { + DEBUG_BUILD && debug.error('[orchestrion:langgraph] failed to wrap createReactAgent tools', error); + } + }); + reactAgentChannel.end.subscribe(message => { + insideCreateReactAgent = false; + const { arguments: args, result } = message as CreateReactAgentChannelContext; + const agentName = extractAgentNameFromParams(args) ?? undefined; + const compileOptions = agentName ? { name: agentName } : {}; + wrapCompiledGraphInvoke(result, compileOptions, resolvedOptions, extractLLMFromParams(args), sentryHandler); + }); + // Make sure a thrown `createReactAgent` doesn't leave the suppression flag stuck on. + reactAgentChannel.error.subscribe(() => { + insideCreateReactAgent = false; }); }, };