langchain-ai/langchain · error · ValueError

The input to RunnablePassthrough.assign() must be a dict.

Error message

The input to RunnablePassthrough.assign() must be a dict.

What it means

RunnablePassthrough.assign() copies the input dict and merges in the assigned keys, so its input must be a dict. The sync _invoke raises this ValueError when a non-dict flows in (the type-ignore comment shows static types already forbid it, so at runtime it means the previous step emitted a non-dict).

Source

Thrown at libs/core/langchain_core/runnables/passthrough.py:493

        # add passthrough node and edges
        input_node = graph.first_node()
        output_node = graph.last_node()
        if input_node is not None and output_node is not None:
            passthrough_node = graph.add_node(_graph_passthrough)
            graph.add_edge(input_node, passthrough_node)
            graph.add_edge(passthrough_node, output_node)
        return graph

    def _invoke(
        self,
        value: dict[str, Any],
        run_manager: CallbackManagerForChainRun,
        config: RunnableConfig,
        **kwargs: Any,
    ) -> dict[str, Any]:
        if not isinstance(value, dict):
            msg = "The input to RunnablePassthrough.assign() must be a dict."  # type: ignore[unreachable]
            raise ValueError(msg)  # noqa: TRY004

        return {
            **value,
            **self.mapper.invoke(
                value,
                patch_config(config, callbacks=run_manager.get_child()),
                **kwargs,
            ),
        }

    @override
    def invoke(
        self,
        input: dict[str, Any],
        config: RunnableConfig | None = None,
        **kwargs: Any,
    ) -> dict[str, Any]:
        return self._call_with_config(self._invoke, input, config, **kwargs)

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Reorder the chain so assign runs on dict data, e.g. RunnablePassthrough.assign(context=retriever) | prompt | llm
  2. If the upstream returns a string, wrap it first (e.g. RunnableLambda(lambda s: {"text": s})) so assign receives a dict
  3. Call .invoke({"key": value}) with a dict input directly

Example fix

# before
chain = prompt | llm | RunnablePassthrough.assign(meta=lambda _: "v")
# llm output is a message, not a dict -> ValueError
# after
chain = RunnablePassthrough.assign(meta=lambda _: "v") | prompt | llm
Defensive patterns

Strategy: type-guard

Validate before calling

assert isinstance(value, dict), f"assign needs dict input, got {type(value)}"

Type guard

def is_dict_input(v: object) -> bool:
    return isinstance(v, dict)

Try / catch

try:
    chain.invoke(inputs)
except ValueError as e:
    if "RunnablePassthrough.assign()" in str(e):
        chain.invoke({"text": str(inputs)})

Prevention

When it happens

Trigger: Chaining .assign() after a runnable that returns a string or list (e.g. prompt | llm | RunnablePassthrough.assign(...)), or calling assign-wrapped chains directly with a string input.

Common situations: Putting assign after an LLM/chat model whose output is a message/string instead of before it; assuming assign passes through arbitrary payloads; mixing structured and unstructured steps in an LCEL chain.

Related errors


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