Skip to content

feat(core): Instrument langgraph createReactAgent#20344

Open
andreiborza wants to merge 1 commit intodevelopfrom
ab/langgraph-agents
Open

feat(core): Instrument langgraph createReactAgent#20344
andreiborza wants to merge 1 commit intodevelopfrom
ab/langgraph-agents

Conversation

@andreiborza
Copy link
Copy Markdown
Member

@andreiborza andreiborza commented Apr 16, 2026

This PR adds instrumentation for LangGraph's createReactAgent API.

createReactAgent wrapping

  • Extracts agent name, LLM model, and tools from params
  • Wraps compiled graph's invoke() with invoke_agent span
  • Wraps tool invoke() with execute_tool spans (name, type, description, arguments, result)
  • Injects LangChain callback handler + lc_agent_name + __sentry_langgraph__ metadata at invoke level for chat span creation and agent name propagation to all child spans
  • Suppresses StateGraph.compile instrumentation inside createReactAgent to avoid duplicate spans

LangChain callback handler improvements

  • Reads gen_ai.agent.name from metadata.lc_agent_name (convention from newer LangGraph createAgent, adopted for our supported versions)
  • Suppresses chain and tool callback spans inside agent context (based on metadata.__sentry_langgraph__ presence) to avoid duplicates with our direct instrumentation
  • Extracts tool definitions from extraParams in handleChatModelStart and sets gen_ai.request.available_tools on chat spans
  • Uses runName for tool name in handleToolStart (set by LangChain's StructuredTool.call()) — fixes unknown_tool issue
  • Adds gen_ai.operation.name to tool spans
  • Extracts .content from ToolMessage objects in handleToolEnd instead of serializing the full wrapper
  • addToolCallsAttributes now prefers message.tool_calls (LangChain's normalized format) over scanning message.content for Anthropic-style tool_use items, fixing duplicate tool calls on Anthropic chat spans. Falls back to message.content scanning for older LangChain versions.

OTel module patching

  • Patches @langchain/langgraph/prebuilt for createReactAgent (ESM + CJS file patches for dist/prebuilt/index.cjs)

Exports

  • instrumentCreateReactAgent from core, browser, cloudflare

Closes: #19372

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 16, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 25.88 kB - -
@sentry/browser - with treeshaking flags 24.35 kB - -
@sentry/browser (incl. Tracing) 43.77 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 45.47 kB - -
@sentry/browser (incl. Tracing, Profiling) 48.7 kB - -
@sentry/browser (incl. Tracing, Replay) 82.89 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 72.4 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 87.58 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 99.83 kB - -
@sentry/browser (incl. Feedback) 42.7 kB - -
@sentry/browser (incl. sendFeedback) 30.55 kB - -
@sentry/browser (incl. FeedbackAsync) 35.55 kB - -
@sentry/browser (incl. Metrics) 27.16 kB - -
@sentry/browser (incl. Logs) 27.29 kB - -
@sentry/browser (incl. Metrics & Logs) 27.98 kB - -
@sentry/react 27.62 kB - -
@sentry/react (incl. Tracing) 46.01 kB - -
@sentry/vue 30.7 kB - -
@sentry/vue (incl. Tracing) 45.58 kB - -
@sentry/svelte 25.89 kB - -
CDN Bundle 28.55 kB - -
CDN Bundle (incl. Tracing) 44.82 kB - -
CDN Bundle (incl. Logs, Metrics) 29.93 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 45.91 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 68.83 kB - -
CDN Bundle (incl. Tracing, Replay) 81.78 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 82.85 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 87.29 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 88.36 kB - -
CDN Bundle - uncompressed 83.4 kB - -
CDN Bundle (incl. Tracing) - uncompressed 134.03 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 87.55 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 137.44 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 210.91 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 251.26 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 254.66 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 264.18 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 267.56 kB - -
@sentry/nextjs (client) 48.54 kB - -
@sentry/sveltekit (client) 44.18 kB - -
@sentry/node-core 57.97 kB +0.01% +5 B 🔺
@sentry/node 175.69 kB +0.52% +895 B 🔺
@sentry/node - without tracing 97.93 kB +0.03% +23 B 🔺
@sentry/aws-serverless 115.16 kB +0.01% +8 B 🔺

View base workflow run

@andreiborza andreiborza force-pushed the ab/langgraph-agents branch from 09a4132 to e2d8d45 Compare April 16, 2026 14:44
@andreiborza andreiborza marked this pull request as ready for review April 20, 2026 04:09
Comment thread packages/core/src/tracing/langgraph/index.ts Outdated
Comment thread packages/core/src/tracing/langchain/index.ts Outdated
@andreiborza andreiborza force-pushed the ab/langgraph-agents branch from e2d8d45 to 7bcbcae Compare April 20, 2026 05:33
Comment thread packages/core/src/tracing/langgraph/index.ts
Comment thread packages/core/src/tracing/langchain/utils.ts
@andreiborza andreiborza force-pushed the ab/langgraph-agents branch 3 times, most recently from 2dc7fd2 to fcaa34b Compare April 20, 2026 06:09
Comment thread packages/core/src/tracing/langgraph/index.ts
@andreiborza andreiborza force-pushed the ab/langgraph-agents branch from fcaa34b to b86d7cc Compare April 20, 2026 06:47
Comment thread packages/node/src/integrations/tracing/langgraph/instrumentation.ts
Comment thread packages/core/src/tracing/langgraph/utils.ts
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>
@andreiborza andreiborza force-pushed the ab/langgraph-agents branch from b86d7cc to d4c61a2 Compare April 20, 2026 07:17
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread packages/core/src/tracing/langgraph/index.ts
expect.objectContaining({
data: expect.objectContaining({
[GEN_AI_OPERATION_NAME_ATTRIBUTE]: 'execute_tool',
'gen_ai.tool.name': 'multiply',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: should we introduce a constant for this?

runId: string,
_parentRunId?: string,
_extraParams?: Record<string, unknown>,
extraParams?: Record<string, unknown>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: why did you rename this?

_parentRunId?: string,
_tags?: string[],
_metadata?: Record<string, unknown>,
metadata?: Record<string, unknown>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: same question here

const agentName = metadata?.lc_agent_name;
if (typeof agentName === 'string') {
attrs[GEN_AI_AGENT_NAME_ATTRIBUTE] = agentName;
attrs[GEN_AI_PIPELINE_NAME_ATTRIBUTE] = agentName;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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__) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(core): Instrument langgraph createReactAgent

2 participants