langchain-ai/langchain · error · ValueError

Runnable {dep} has no last node

Error message

Runnable {dep} has no last node

What it means

Companion check in `RunnableEachBase.get_graph()`: after extending a dependency's trimmed sub-graph, a last node must exist so an edge can connect to the output schema node. If the dependency graph has a first node but no last node, a `ValueError` is raised. This points to a dependency whose `get_graph()` builds a graph with no terminal node.

Source

Thrown at libs/core/langchain_core/runnables/base.py:5109

            from langchain_core.runnables.graph import Graph  # noqa: PLC0415

            graph = Graph()
            input_node = graph.add_node(self.get_input_schema(config))
            output_node = graph.add_node(self.get_output_schema(config))
            for dep in deps:
                dep_graph = dep.get_graph()
                dep_graph.trim_first_node()
                dep_graph.trim_last_node()
                if not dep_graph:
                    graph.add_edge(input_node, output_node)
                else:
                    dep_first_node, dep_last_node = graph.extend(dep_graph)
                    if not dep_first_node:
                        msg = f"Runnable {dep} has no first node"
                        raise ValueError(msg)
                    if not dep_last_node:
                        msg = f"Runnable {dep} has no last node"
                        raise ValueError(msg)
                    graph.add_edge(input_node, dep_first_node)
                    graph.add_edge(dep_last_node, output_node)
        else:
            graph = super().get_graph(config)

        return graph

    @override
    def __eq__(self, other: object) -> bool:
        if isinstance(other, RunnableLambda):
            if hasattr(self, "func") and hasattr(other, "func"):
                return self.func == other.func
            if hasattr(self, "afunc") and hasattr(other, "afunc"):
                return self.afunc == other.afunc
            return False
        return False

    __hash__ = None  # type: ignore[assignment]

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Simplify the custom `get_graph` to a single node: `g = Graph(); g.add_node('dep', self); return g`.
  2. Ensure the graph ends with a node that has no outgoing edges (a definite terminal).
  3. Test the dependency graph directly: `g = dep.get_graph(); g.trim_first_node(); g.trim_last_node(); assert g.last_node() is not None`.
  4. Wrap the dependency in `RunnableLambda` to get a known-good graph.

Example fix

// before
class Dep(Runnable):
    def get_graph(self, config=None):
        g = Graph()
        g.add_edge(g.add_node('a', self), g.add_node('b', self))
        return g  # may lack a resolvable last node after trims

// after
class Dep(Runnable):
    def get_graph(self, config=None):
        g = Graph()
        g.add_node('Dep', self)
        return g
Defensive patterns

Strategy: validation

Validate before calling

def dep_has_terminal(dep) -> bool:
    g = dep.get_graph()
    g.trim_first_node()
    return g.last_node() is not None or not g.nodes

Try / catch

try:
    g = mapped.get_graph()
except ValueError as e:
    if 'has no last node' in str(e):
        # simplify the dep's graph to a single node
        raise
    raise

Prevention

When it happens

Trigger: A dependency of a `.map()`ed runnable whose hand-built `Graph` never establishes a terminal node (e.g. only edges added, or nodes added in an order the graph cannot resolve), then calling `.get_graph()` on the `RunnableEach`.

Common situations: Custom runnables constructing `Graph` objects manually with `add_edge` before `add_node`; partial `get_graph` overrides copied from old examples; graph visualization tooling in notebooks.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/5367ad2fc10ebb03. Report an issue: GitHub.