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
- Reorder the chain so assign runs on dict data, e.g. RunnablePassthrough.assign(context=retriever) | prompt | llm
- If the upstream returns a string, wrap it first (e.g. RunnableLambda(lambda s: {"text": s})) so assign receives a dict
- 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
- Place assign() before dict-destroying steps (llm, str parsers) in LCEL chains
- Insert a RunnableLambda that re-wraps non-dict values into a dict before assign
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
- Expected a single list of messages. Got {input_val}.
- Expected str, BaseMessage, list[BaseMessage], or tuple[BaseM
- Expected str, BaseMessage, list[BaseMessage], or tuple[BaseM
- Missing keys {sorted(missing_keys)} in config['configurable'
- Expected keys {sorted(expected_keys)} do not match parameter
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/3212c689f86725d1.
Report an issue: GitHub.