fix(langchain): emit invoke_agent for create_agent, including nested agents - #391
fix(langchain): emit invoke_agent for create_agent, including nested agents#391AgentGymLeader wants to merge 18 commits into
Conversation
Assisted-by: OpenAI GPT-5
Runtime testing exposed that nested agents could be missed when LangGraph propagated the parent node name into langgraph_node. Because agent run-name resolution falls back to langgraph_node, restrict the lc_agent_name comparison to the explicit kwargs name. This keeps inherited node metadata from contaminating the comparison while allowing nested agents without an explicit run name.
Pull request dashboard statusWaiting on maintainers · refreshed 2026-09-02 14:52 UTC Merge when ready. Status above doesn't look right?
|
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR refines agent span classification for LangChain/LangGraph runs by using LangChain’s lc_agent_name signal while avoiding false positives for internal LangGraph nodes.
Changes:
- Add
lc_agent_name-aware agent detection logic inclassify_chain_runvia_has_agent_signals. - Avoid misclassifying internal LangGraph nodes as agents by comparing against the callback’s explicit run name.
- Add unit tests covering top-level agents, nested agents, internal nodes, and suppression behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| instrumentation/opentelemetry-instrumentation-genai-langchain/src/opentelemetry/instrumentation/genai/langchain/operation_mapping.py | Updates agent-signal detection to incorporate lc_agent_name with LangGraph-aware heuristics. |
| instrumentation/opentelemetry-instrumentation-genai-langchain/tests/test_operation_mapping.py | Adds tests validating new classification behavior and suppression rules. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Assisted-by: OpenAI Codex
Assisted-by: GPT-5
Assisted-by: OpenAI Codex
Assisted-by: ChatGPT 5.2
Distinguish confirmed agent ancestors from confirmed non-agent ancestors and unknown ancestry when classifying nested runs. Trim the classification corpus to focused three-state coverage. Assisted-by: ChatGPT 5.2
Assisted-by: OpenAI Codex
Not fixed: agents nested inside another agent I couldn't do from callback metadata; the measurement and three options are in the PR body. I resolved your four threads and the two Copilot ones. Reopen anything I closed too early. |
Compiled create_agent graphs carry their own unshadowed marker in graph.config, so patching Pregel.stream/astream lets a graph announce itself on a context stack that the callback handler claims when the root run starts. Nested agents then layer correctly regardless of what the enclosing agent's config merged over their callback metadata. Replaces the metadata/ancestry inference: create_agent_ancestry, the has_create_agent_marker plumbing, and the ls_integration / lc_agent_name branches in _has_agent_signals and resolve_agent_name are removed. Also covers langgraph's deprecated create_react_agent, which compiles its own graph and carries no marker, by tagging what the factory returns. Verified: three-level nesting, same-named nested agents, async, streaming, unnamed agents, agents invoked without config forwarding, and nested create_react_agent. No false positives for a named RunnableLambda or a plain subgraph invoked from a tool. User overrides (agent_name, otel_trace, otel_agent_span, run_name) behave as they do without this change. Prototype only - the existing tests for the removed inference still need to be dropped and the corpus tests reworked to drive real invocations. Assisted-by: Claude Opus 5
|
@lmolkova Folding your prototype in, and reworking the corpus tests through Hadn't clocked that the mock made those tests pass with no span at all. |
Ruff and the formatter over the modules added by the previous two commits. Applied here rather than by amending them so authorship stays intact.
lmolkova
left a comment
There was a problem hiding this comment.
thanks! a few more minor comments. Please update PR description to describe what PR does, it does not need to describe a history of changes it went through
…ain; announced roots always open their layer Resolve inherited agent metadata against every enclosing agent while preserving genuine metadata renames. Announced create_agent roots now bypass name-derived noise suppression and always emit their own layer, with nested and async regression coverage.
…compatibly - Re-add _find_nearest_agent as a thin accessor over _find_agent_context so existing callback tests pass - Import Self from typing_extensions so Python 3.10 test collection no longer fails - Apply ruff formatting
Place typing_extensions after the other third-party imports so the ruff isort hook is satisfied.
|
@lmolkova Updated the description to just describe what the PR does. The other comments are addressed and resolved too. |
…on, unset agent name, runtime ReAct detection, uninstrument ordering
… via public callbacks - add direct .stream()/.astream() tests covering announcement push/pop edge paths - remove unused _find_nearest_agent; re-express its coverage through on_chain_start behavior - update stale comments referencing the removed helper
What
Emits invoke_agent spans for LangChain create_agent graphs, including nested agents, which now get their own spans.
Callback metadata cannot distinguish a nested agent from its caller: LangChain merges the forwarded config over the inner agent's own, so the inherited fields describe the outer agent. create_agent root classification therefore no longer relies on callback metadata alone. Pregel.stream / astream are patched so a compiled graph announces itself on a context stack, and the callback handler claims the top unclaimed announcement when a root run starts. An inherited agent_name matching an enclosing agent is treated as inherited config, so deeply nested agents keep their own declared names. Deprecated create_react_agent and create_tool_calling_executor compile their own graphs without the marker, so they are tagged at the factory.
Tests
Classification tests drive real invocations through instrument() against a real tracer provider and assert on exported spans: names and parent/child nesting. Sync nested, three-level and renamed cases pass. The async nested case is a strict xfail exposing the pre-existing context-propagation gap above. create_agent tests run on the latest supported LangChain; the oldest supported version predates create_agent, so those tests skip there.