Skip to content

Commit 4404ea1

Browse files
fix(langchain): prefer runnable name over langgraph_node for chain step names
langgraph_node is inherited by every run nested inside a node, so preferring it over the runnable's own name relabelled all of a node's internal LCEL runs (RunnableSequence/Prompt/...) — and nested sub-graphs — with the parent node's name, collapsing the trace tree. Flip the precedence to `name or langgraph_node or id` to match the TS handler; LangGraph already sets `name` to the node name at node boundaries, so nodes stay identifiable while the inner structure keeps its real names. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 65f6ea7 commit 4404ea1

2 files changed

Lines changed: 32 additions & 6 deletions

File tree

src/openlayer/lib/integrations/langchain_callback.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -758,12 +758,20 @@ def _handle_chain_start(
758758
serialized = serialized or {}
759759
metadata = metadata or {}
760760

761-
# LangGraph injects ``metadata["langgraph_node"]`` for each graph node.
762-
# Prefer it so graph nodes are identifiable in the trace rather than
763-
# showing the generic chain/runnable name.
761+
# Resolve the chain's display name. Prefer the runnable's own ``name``
762+
# (e.g. a node's name like "callAgent", or "RunnableSequence"/"Prompt"
763+
# for nested LCEL runs), then fall back to LangGraph's injected
764+
# ``metadata["langgraph_node"]`` only when no name is available, then the
765+
# serialized id. This mirrors the TS handler's ``name ?? langgraph_node
766+
# ?? id`` precedence. ``langgraph_node`` is inherited by *every* run
767+
# nested inside a node, so preferring it over ``name`` would relabel all
768+
# of a node's internal LCEL runs (RunnableSequence/Prompt/...) — and even
769+
# nested sub-graphs — with the parent node's name. LangGraph already sets
770+
# ``name`` to the node name at node boundaries, so this keeps nodes
771+
# identifiable while preserving the inner structure's real names.
764772
langgraph_node = metadata.get("langgraph_node")
765773
chain_name = (
766-
langgraph_node or name or (serialized.get("id", [])[-1] if serialized.get("id") else None) or "Chain"
774+
name or langgraph_node or (serialized.get("id", [])[-1] if serialized.get("id") else None) or "Chain"
767775
)
768776

769777
# Skip chains marked as hidden (e.g., internal LangGraph chains)

tests/lib/integrations/test_langchain_callback.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,19 +448,37 @@ def test_chat_model_step_named_from_ls_provider(self) -> None:
448448
# Item 3: LangGraph metadata (langgraph_node + thread_id -> session_id)
449449
# --------------------------------------------------------------------------- #
450450
class TestLangGraphMetadata:
451-
def test_langgraph_node_names_chain_step(self) -> None:
451+
def test_langgraph_node_names_chain_step_when_no_explicit_name(self) -> None:
452+
# When the runnable carries no name, fall back to langgraph_node so graph
453+
# nodes stay identifiable.
452454
handler = OpenlayerHandler()
453455
run_id = uuid.uuid4()
454456
handler.on_chain_start(
455457
serialized={"id": ["langgraph", "utils", "RunnableCallable"]},
456458
inputs={"messages": []},
457459
run_id=run_id,
458-
name="RunnableCallable",
459460
metadata={"langgraph_node": "agent"},
460461
)
461462
step = handler.steps[run_id]
462463
assert step.name == "agent"
463464

465+
def test_explicit_name_wins_over_langgraph_node(self) -> None:
466+
# An explicit runnable name takes precedence over langgraph_node (matches
467+
# the TS handler's `name ?? langgraph_node ?? id`). langgraph_node is
468+
# inherited by every run nested inside a node, so preferring it would
469+
# relabel a node's internal LCEL runs with the node name.
470+
handler = OpenlayerHandler()
471+
run_id = uuid.uuid4()
472+
handler.on_chain_start(
473+
serialized={"id": ["langchain_core", "runnables", "RunnableSequence"]},
474+
inputs={"messages": []},
475+
run_id=run_id,
476+
name="RunnableSequence",
477+
metadata={"langgraph_node": "agent"},
478+
)
479+
step = handler.steps[run_id]
480+
assert step.name == "RunnableSequence"
481+
464482
def test_chain_name_unchanged_without_langgraph_node(self) -> None:
465483
handler = OpenlayerHandler()
466484
run_id = uuid.uuid4()

0 commit comments

Comments
 (0)