feat(core): Instrument langgraph createReactAgent#20344
feat(core): Instrument langgraph createReactAgent#20344andreiborza wants to merge 1 commit intodevelopfrom
Conversation
6aa24b0 to
09a4132
Compare
size-limit report 📦
|
09a4132 to
e2d8d45
Compare
e2d8d45 to
7bcbcae
Compare
2dc7fd2 to
fcaa34b
Compare
fcaa34b to
b86d7cc
Compare
Add instrumentation for LangGraph's `createReactAgent` API with full span hierarchy: invoke_agent, gen_ai.chat, and execute_tool. createReactAgent wrapping: - Extract agent name, LLM model, and tools from params - Wrap compiled graph's invoke() with invoke_agent span - Wrap tool invoke() with execute_tool spans (name, type, description, arguments, result) - Inject LangChain callback handler + lc_agent_name metadata at invoke level for chat span creation and agent name propagation to all child spans - Suppress StateGraph.compile instrumentation inside createReactAgent to avoid duplicate spans LangChain callback handler improvements: - Read gen_ai.agent.name from metadata.lc_agent_name - Suppress chain and tool callback spans inside agent context to avoid duplicates with our direct instrumentation - Extract tool definitions from extraParams in handleChatModelStart - Use runName for tool name (set by LangChain's StructuredTool) - Add gen_ai.operation.name to tool spans - Extract ToolMessage .content in handleToolEnd BREAKING: addToolCallsAttributes now reads from message.tool_calls (LangChain's normalized format) instead of scanning message.content for Anthropic-style tool_use items. This fixes duplicate tool calls on Anthropic chat spans but changes the tool call format in gen_ai.response.tool_calls from Anthropic-native to LangChain- normalized (args instead of input, type: tool_call instead of tool_use). OTel module patching: - Patch @langchain/langgraph/prebuilt for createReactAgent (ESM + CJS file patches for dist/prebuilt/index.cjs) Exports: - instrumentCreateReactAgent from core, browser, cloudflare Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
b86d7cc to
d4c61a2
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d4c61a2. Configure here.
| expect.objectContaining({ | ||
| data: expect.objectContaining({ | ||
| [GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'execute_tool', | ||
| 'gen_ai.tool.name': 'multiply', |
There was a problem hiding this comment.
l: should we introduce a constant for this?
| runId: string, | ||
| _parentRunId?: string, | ||
| _extraParams?: Record<string, unknown>, | ||
| extraParams?: Record<string, unknown>, |
| _parentRunId?: string, | ||
| _tags?: string[], | ||
| _metadata?: Record<string, unknown>, | ||
| metadata?: Record<string, unknown>, |
| const agentName = metadata?.lc_agent_name; | ||
| if (typeof agentName === 'string') { | ||
| attrs[GEN_AI_AGENT_NAME_ATTRIBUTE] = agentName; | ||
| attrs[GEN_AI_PIPELINE_NAME_ATTRIBUTE] = agentName; |
There was a problem hiding this comment.
m: this sets the agent name as gen_ai.agent.name and gen_ai.pipeline.name for invoke_agent, chat and execute_tool spans. I think we discussed this for gen_ai.agent.name, are we sure we also want to do this for the pipeline name? Maybe we should align with python on this not sure if they even use this attribute
| // Skip chain spans when inside an agent context (createReactAgent). | ||
| // The agent already creates an invoke_agent span; internal chain steps | ||
| // (ChannelWrite, Branch, prompt, etc.) are noise. | ||
| if (metadata?.__sentry_langgraph__) { |
There was a problem hiding this comment.
h: are we actually testing somewhere that these suppressions work as expected? I think the current tests just assert on that certain spans are present, maybe we should explicitly assert on the number of spans or even that certain spans are not present
| ): (...args: unknown[]) => CompiledGraph { | ||
| return new Proxy(originalCompile, { | ||
| apply(target, thisArg, args: unknown[]): CompiledGraph { | ||
| // Skip when called from within createReactAgent to avoid duplicate instrumentation |
There was a problem hiding this comment.
m: should we also add a double-patch guard here as we do for other instrumentations? e.g. setting a sentry_patched property on the graph and then guarding for that as well. in case the patching gets called twice for some reason
| if (!existingCallbacks) { | ||
| invokeConfig.callbacks = [sentryCallbackHandler]; | ||
| } else if (Array.isArray(existingCallbacks) && !existingCallbacks.includes(sentryCallbackHandler)) { | ||
| invokeConfig.callbacks = [...existingCallbacks, sentryCallbackHandler]; |
There was a problem hiding this comment.
m: for createReactAgent we always create and pass in the langchain callback handler for the user. should we also do this for the plain StateGraph case? right now I think users need to do that manually else they don't get any chat spans

This PR adds instrumentation for LangGraph's
createReactAgentAPI.createReactAgent wrapping
invoke()withinvoke_agentspaninvoke()withexecute_toolspans (name, type, description, arguments, result)lc_agent_name+__sentry_langgraph__metadata at invoke level for chat span creation and agent name propagation to all child spansStateGraph.compileinstrumentation insidecreateReactAgentto avoid duplicate spansLangChain callback handler improvements
gen_ai.agent.namefrommetadata.lc_agent_name(convention from newer LangGraphcreateAgent, adopted for our supported versions)metadata.__sentry_langgraph__presence) to avoid duplicates with our direct instrumentationextraParamsinhandleChatModelStartand setsgen_ai.request.available_toolson chat spansrunNamefor tool name inhandleToolStart(set by LangChain'sStructuredTool.call()) — fixesunknown_toolissuegen_ai.operation.nameto tool spans.contentfrom ToolMessage objects inhandleToolEndinstead of serializing the full wrapperaddToolCallsAttributesnow prefersmessage.tool_calls(LangChain's normalized format) over scanningmessage.contentfor Anthropic-styletool_useitems, fixing duplicate tool calls on Anthropic chat spans. Falls back tomessage.contentscanning for older LangChain versions.OTel module patching
@langchain/langgraph/prebuiltforcreateReactAgent(ESM + CJS file patches fordist/prebuilt/index.cjs)Exports
instrumentCreateReactAgentfrom core, browser, cloudflareCloses: #19372