Inconsistency in API - `add_node` accepts objects but `add_edge` requires string literals for the same nodes.
Checked other resources
- [x] This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
- [x] I added a clear and detailed title that summarizes the issue.
- [x] I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
- [x] I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.
Example Code
from typing import Annotated, List
from IPython.display import Image, display
from langchain_core.messages import AIMessage, BaseMessage
from langgraph.graph import END, START, StateGraph, add_messages
from pydantic import BaseModel, Field
class State(BaseModel):
messages: Annotated[List[BaseMessage], add_messages] = Field(default_factory=list, description="Conversation history to keep track of.")
class ClassNode:
def __call__(self, state: State) -> State:
return {"messages": AIMessage("I'm fine, thank's, what about you?")}
def __str__(self):
return self.__class__.__name__
intent_classifier = ClassNode()
workflow = StateGraph(State)
# workflow.add_edge(START, str(intent_classifier)) # This works because already a string is being passed.
workflow.add_edge(START, intent_classifier) # This does not work because an object is being passed, which is not welcomed.
workflow.add_node(intent_classifier) # This works because, api can handle objects when adding nodes.
graph = workflow.compile()
display(Image(graph.get_graph(xray=True).draw_mermaid_png()))
for message in graph.invoke({"messages": "Hi there, how's it going?"}).get("messages"):
message.pretty_print()
Error Message and Stack Trace (if applicable)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[11], line 29
26 workflow.add_edge(START, intent_classifier) # This does not work because an object is being passed, which is not welcomed.
27 workflow.add_node(intent_classifier) # This works because, api can handle objects when adding nodes.
---> 29 graph = workflow.compile()
31 display(Image(graph.get_graph(xray=True).draw_mermaid_png()))
33 for message in graph.invoke({"messages": "Hi there, how's it going?"}).get("messages"):
File ~/Documents/GitHub/Opinsane/.venv/lib/python3.12/site-packages/langgraph/graph/state.py:836, in StateGraph.compile(self, checkpointer, cache, store, interrupt_before, interrupt_after, debug, name)
833 interrupt_after = interrupt_after or []
835 # validate the graph
--> 836 self.validate(
837 interrupt=(
838 (interrupt_before if interrupt_before != "*" else []) + interrupt_after
839 if interrupt_after != "*"
840 else []
841 )
842 )
844 # prepare output channels
845 output_channels = (
846 "__root__"
847 if len(self.schemas[self.output_schema]) == 1
(...) 853 ]
854 )
File ~/Documents/GitHub/Opinsane/.venv/lib/python3.12/site-packages/langgraph/graph/state.py:791, in StateGraph.validate(self, interrupt)
789 for target in all_targets:
790 if target not in self.nodes and target != END:
--> 791 raise ValueError(f"Found edge ending at unknown node `{target}`")
792 # validate interrupts
793 if interrupt:
ValueError: Found edge ending at unknown node `ClassNode`
Description
Description
There's an inconsistency in LangGraph's API that creates an awkward developer experience. The add_node method accepts objects (callables), but add_edge only accepts string literals for node names, even when referencing the exact same nodes.
The Problem
When working with node objects (which is a common pattern for encapsulation), we can pass the object itself to add_node, but we're forced to use str(node) when referencing the same node in add_edge. This creates unnecessary duplication and breaks the natural flow of the API.
What works:
-
workflow.add_node(node_object)- Accepts the object ✅
What doesn't work:
-
workflow.add_edge(START, node_object)- ThrowsTypeError: 'ClassNode' object is not iterable❌ -
workflow.add_edge(node_object, END)- ThrowsTypeError: 'ClassNode' object is not iterable❌
Current workaround:
- Must use
workflow.add_edge(START, str(node_object))🤷
Why This Matters
-
Inconsistent API: If
add_nodecan handle objects,add_edgeshould too - Breaks encapsulation: Forces developers to manually convert objects to strings
- Error-prone: Mixing object references and string conversions increases the chance of mismatched node names
- Poor DX: Developers expect to use the same reference (the object) throughout the graph definition
Expected Behavior
add_edge should accept any object that implements __str__() and automatically convert it to a string internally, just like how add_node handles objects. This would allow:
workflow.add_node(intent_classifier) # Works
workflow.add_edge(START, intent_classifier) # Should work
workflow.add_edge(intent_classifier, END) # Should work
Benefits of Fixing This
- Consistent API: Same object can be used everywhere
-
Better encapsulation: Node name is defined once in the class via
__str__ -
Cleaner code: No need for
str()conversions scattered throughout - Type safety: Using object references reduces typos in string literals
-
Backward compatible:
str("already_a_string")returns the same string, so existing code won't break
System Info
System Information
OS: Darwin OS Version: Darwin Kernel Version 24.6.0: Mon Jul 14 11:29:54 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T8122 Python Version: 3.12.11 (main, Jul 8 2025, 20:41:49) [Clang 20.1.4 ]
Package Information
langchain_core: 1.0.2 langchain: 1.0.3 langchain_community: 0.4.1 langsmith: 0.4.38 langchain_classic: 1.0.0 langchain_openai: 1.0.1 langchain_text_splitters: 1.0.0 langgraph_sdk: 0.2.9
Optional packages not installed
langserve
Other Dependencies
aiohttp: 3.13.2 async-timeout: Installed. No version info available. claude-agent-sdk: Installed. No version info available. dataclasses-json: 0.6.7 httpx: 0.28.1 httpx-sse: 0.4.3 jsonpatch: 1.33 langchain-anthropic: Installed. No version info available. langchain-aws: Installed. No version info available. langchain-deepseek: Installed. No version info available. langchain-fireworks: Installed. No version info available. langchain-google-genai: Installed. No version info available. langchain-google-vertexai: Installed. No version info available. langchain-groq: Installed. No version info available. langchain-huggingface: Installed. No version info available. langchain-mistralai: Installed. No version info available. langchain-ollama: Installed. No version info available. langchain-perplexity: Installed. No version info available. langchain-together: Installed. No version info available. langchain-xai: Installed. No version info available. langgraph: 1.0.2 langsmith-pyo3: Installed. No version info available. numpy: 2.3.4 openai: 2.6.1 openai-agents: Installed. No version info available. opentelemetry-api: Installed. No version info available. opentelemetry-exporter-otlp-proto-http: Installed. No version info available. opentelemetry-sdk: Installed. No version info available. orjson: 3.11.4 packaging: 25.0 pydantic: 2.12.3 pydantic-settings: 2.11.0 pytest: Installed. No version info available. pyyaml: 6.0.3 PyYAML: 6.0.3 requests: 2.32.5 requests-toolbelt: 1.0.0 rich: 14.2.0 sqlalchemy: 2.0.44 SQLAlchemy: 2.0.44 tenacity: 9.1.2 tiktoken: 0.12.0 typing-extensions: 4.15.0 vcrpy: Installed. No version info available. zstandard: 0.25.0
I'd like to work on this issue.
I've implemented a fix for this issue and created PR #6391.
Summary:
- Added
_get_node_key()helper function to convert node objects to string keys - Updated
add_edge()to acceptstr | list[str] | Any | list[Any] - Fully backward compatible - all existing string usage unchanged
- Comprehensive tests added covering 5 scenarios (strings, objects, mixed usage, lists)
Testing:
- ✅ All existing tests pass (19/19)
- ✅ New test
test_add_edge_with_objectsadded - ✅ Linting and formatting checks pass
The PR is ready for review!
I've implemented a fix for this issue and created PR #6391.
Summary:
- Added
_get_node_key()helper function to convert node objects to string keys- Updated
add_edge()to acceptstr | list[str] | Any | list[Any]- Fully backward compatible - all existing string usage unchanged
- Comprehensive tests added covering 5 scenarios (strings, objects, mixed usage, lists)
Testing:
- ✅ All existing tests pass (19/19)
- ✅ New test
test_add_edge_with_objectsadded- ✅ Linting and formatting checks pass
The PR is ready for review!
That look's solid, perfect.
Shall I close the issue?
@emircetinmemis Thanks for the feedback!
I think we should wait for the maintainers to review and merge PR #6391 before closing this issue. Since the PR includes "Closes #6376", the issue will automatically close once merged.
This ensures the fix is properly reviewed and works as expected. 👍