Skip to content

fix(langchain): handle ToolNode object in create_react_agent instrumentation#3846

Open
SanjanaB123 wants to merge 3 commits intotraceloop:mainfrom
SanjanaB123:fix/langchain-toolnode-iterable
Open

fix(langchain): handle ToolNode object in create_react_agent instrumentation#3846
SanjanaB123 wants to merge 3 commits intotraceloop:mainfrom
SanjanaB123:fix/langchain-toolnode-iterable

Conversation

@SanjanaB123
Copy link
Copy Markdown

@SanjanaB123 SanjanaB123 commented Mar 24, 2026

Summary

Reproduction

from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """Get weather for a city"""
    return "Sunny"

tool_node = ToolNode([get_weather])
for t in tool_node:  # TypeError: 'ToolNode' object is not iterable
    print(t)
  • I have added tests that cover my changes.
  • If adding a new instrumentation or changing an existing one, I've added screenshots from some observability platform showing the change.
  • PR name follows conventional commits format: feat(instrumentation): ... or fix(instrumentation): ....
  • (If applicable) I have updated the documentation accordingly.

Summary by CodeRabbit

  • Bug Fixes

    • Improved tool-definition handling to support alternative tool container types, preventing a TypeError and ensuring GenAI tool definitions are properly captured in telemetry.
  • Tests

    • Added a regression test to validate agent creation with the alternative tool container and to verify semantic telemetry attributes are recorded.

ToolNode (from langgraph-prebuilt) is not iterable, but patch.py
assumed tools was always a list. Check for tools_by_name attribute
to extract tools from ToolNode objects.

Fixes traceloop#3841
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

This change updates the agent wrapper to handle LangGraph ToolNode inputs by iterating tools.tools_by_name.values() when present, and adds a regression test confirming create_react_agent accepts ToolNode instances and records expected GenAI semantic attributes.

Changes

Cohort / File(s) Summary
Tool iteration logic fix
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/patch.py
Adjusts tool-extraction to detect tools_by_name on tools and iterate tools.tools_by_name.values() when available; falls back to previous iterable behavior otherwise.
ToolNode integration test
packages/opentelemetry-instrumentation-langchain/tests/test_langgraph.py
Adds test_create_react_agent_with_toolnode to validate ToolNode can be passed to create_react_agent and that GenAI attributes (operation, agent name, and GEN_AI_TOOL_DEFINITIONS containing get_weather and get_time) are set. Minor docstring/formatting tweaks in an existing mock.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 I nibbled at code with curious paws,
Found a ToolNode with iterable flaws,
I checked for tools_by_name, then hopped through the list,
Now agents run smooth — a rabbit-approved fix! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main fix: handling ToolNode objects in create_react_agent instrumentation, which directly addresses the changeset.
Linked Issues check ✅ Passed The PR successfully implements the exact fix specified in issue #3841: checking for tools_by_name attribute and iterating tools.tools_by_name.values() when present, with comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the ToolNode iteration issue: the patch logic modification and the regression test. No unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/langchain-toolnode-iterable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/opentelemetry-instrumentation-langchain/tests/test_langgraph.py (2)

827-838: Consider extracting MockChatModel to reduce duplication.

MockChatModel is defined identically in three tests (test_create_react_agent_span, test_create_agent_with_system_prompt, and this test). Extracting it to a module-level fixture or helper class would reduce maintenance burden.

♻️ Proposed refactor

Add a module-level mock class or fixture at the top of the file:

class MockChatModel(BaseChatModel):
    """Mock chat model for testing agent creation."""

    `@property`
    def _llm_type(self) -> str:
        return "mock"

    def _generate(self, messages, stop=None, run_manager=None, **kwargs):
        return ChatResult(
            generations=[ChatGeneration(message=AIMessage(content="Mock"))]
        )

    def bind_tools(self, tools, **kwargs):
        return self

Then use MockChatModel() directly in each test without redefining it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opentelemetry-instrumentation-langchain/tests/test_langgraph.py`
around lines 827 - 838, Extract the duplicate MockChatModel class into a single
module-level helper or fixture so tests reuse it instead of redefining; create
the class named MockChatModel once at top of the file (or as a pytest fixture
returning MockChatModel()), then update each test that currently defines
MockChatModel (test_create_react_agent_span,
test_create_agent_with_system_prompt, and test_langgraph.py's current test) to
instantiate or reference that shared MockChatModel rather than declaring the
class locally.

811-871: Well-structured regression test for issue #3841.

The test properly validates that ToolNode objects are handled without raising TypeError, and verifies that tool definitions are correctly extracted via tools_by_name.values().

Consider adding a comment or separate test case for edge cases like an empty ToolNode (i.e., ToolNode([])) to ensure the instrumentation handles that gracefully, though this is optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/opentelemetry-instrumentation-langchain/tests/test_langgraph.py`
around lines 811 - 871, Add a new regression test similar to
test_create_react_agent_with_toolnode that constructs an empty ToolNode
(ToolNode([])), calls create_react_agent with model=MockChatModel() and
tools=that empty ToolNode, then assert no exception is raised and that the
created span (find by "create_agent" in spans) contains
GenAIAttributes.GEN_AI_TOOL_DEFINITIONS (which should parse to an empty
list/object); reuse the same MockChatModel, span_exporter usage and JSON-parsing
pattern from test_create_react_agent_with_toolnode to validate the
instrumentation handles empty ToolNode gracefully.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/opentelemetry-instrumentation-langchain/tests/test_langgraph.py`:
- Around line 827-838: Extract the duplicate MockChatModel class into a single
module-level helper or fixture so tests reuse it instead of redefining; create
the class named MockChatModel once at top of the file (or as a pytest fixture
returning MockChatModel()), then update each test that currently defines
MockChatModel (test_create_react_agent_span,
test_create_agent_with_system_prompt, and test_langgraph.py's current test) to
instantiate or reference that shared MockChatModel rather than declaring the
class locally.
- Around line 811-871: Add a new regression test similar to
test_create_react_agent_with_toolnode that constructs an empty ToolNode
(ToolNode([])), calls create_react_agent with model=MockChatModel() and
tools=that empty ToolNode, then assert no exception is raised and that the
created span (find by "create_agent" in spans) contains
GenAIAttributes.GEN_AI_TOOL_DEFINITIONS (which should parse to an empty
list/object); reuse the same MockChatModel, span_exporter usage and JSON-parsing
pattern from test_create_react_agent_with_toolnode to validate the
instrumentation handles empty ToolNode gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3bd08649-87a6-4003-a96c-847d60eaa0e7

📥 Commits

Reviewing files that changed from the base of the PR and between 33ca6da and 2500817.

⛔ Files ignored due to path filters (1)
  • packages/opentelemetry-instrumentation-langchain/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • packages/opentelemetry-instrumentation-langchain/tests/test_langgraph.py

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.

🐛 Bug Report: TypeError: 'ToolNode' object is not iterable in patch.py when using langgraph-supervisor with create_react_agent

1 participant