LangChain LCEL chain input schema mismatch — RunnableSequence type errors (2026) — Fix Guide (2026) | AI Error Hub
Home Providers LangChain LCEL input mismatch
LangChain LCEL · RunnableSequence Severity: High HTTP n/a

LangChain RunnableSequence — input schema mismatch between steps

LCEL is compositional but strict about shapes. Every arrow in a pipe passes structured data — when one runnable emits <code>{"answer": "..."}</code> and the next expects a bare string, the chain crashes with a type error deep in the trace.

By Ahmed R. · Senior AI Infrastructure Engineer Published 2026-07-26 Verified 2026-07-26

Quick fix (TL;DR)

Resolution: A LangChain RunnableSequence (created with the | operator) pipes each step's output as the next step's input. Failure modes: (a) a prompt template expects a dict with specific keys but gets a raw string; (b) a chat model emits an AIMessage that the next parser cannot handle; (c) parallel branches emit differently-shaped dicts. Fix by (a) using RunnablePassthrough.assign() to thread inputs through, (b) using itemgetter or lambdas to extract fields, and (c) inspecting chain.input_schema / chain.output_schema.

Real error messages you'll see

These are the exact strings returned by the LangChain framework and its integrations when this error occurs. Copy-paste-searching any of them should land on this page.

KeyError deep in the chain
Traceback (most recent call last):
  File "chain.py", line 42, in <module>
    chain.invoke("what is the capital of France")
  ...
  File ".../prompts/chat.py", line 385, in format_messages
    return self.format_prompt(**kwargs).to_messages()
  File ".../prompts/prompt.py", line 155, in format
    return self.template.format(**kwargs)
KeyError: 'question'

# The prompt template expected {"question": "..."} but got a bare string
TypeError on parser input
TypeError: Expected str, got AIMessage

# The parser needs .content extracted from the AIMessage first,
# or use StrOutputParser between model and parser
Wrong dict shape from RunnableParallel
ValidationError: 1 validation error for MyOutput
  field required (type=value_error.missing)

Reference

Common shape transitions in LCEL chains

Step typeInput shapeOutput shape
ChatPromptTemplatedict with template varsPromptValue
ChatModelPromptValue or list of messagesAIMessage
StrOutputParserAIMessage or strstr
JsonOutputParserAIMessage or strdict
PydanticOutputParserstrPydantic model
RunnablePassthroughanythingsame anything
RunnablePassthrough.assign()dictdict with new keys added
RunnableParallelanythingdict with one key per branch

Root causes, ranked by frequency

Based on developer reports across LangChain forums, GitHub issues, and Discord community during 2025–2026.

  • 26%
    Prompt expects dict, receives bare string. Common when chaining str | prompt | model instead of wrapping the string in {"question": ...}.
  • 18%
    Missing StrOutputParser between model and downstream text step. The next step expects a string but gets an AIMessage.
  • 14%
    RunnableParallel branch names not matching downstream keys. Branch outputs {"a": ..., "b": ...}; next step needs {"context": ..., "question": ...}.
  • 10%
    Pydantic output parser schema drift. Parser expects fields the model did not emit; validation fails silently as generic KeyError.
  • 8%
    Async / sync mismatch. Using ainvoke on a chain whose middle step has only a sync implementation forces sync coercion, occasionally shape-warping.
  • 7%
    Config / RunnableConfig not passed through. Custom runnable eats the config arg; downstream callbacks / tracing break, sometimes surfacing as type errors.
  • 7%
    Chained with an incompatible retriever. Retriever returns List[Document]; next step expects a formatted string.
  • 10%
    Version drift between langchain-core and integrations. Newer langchain-core tightens type checks; older integration packages produce loose outputs that no longer validate.

Fixes — copy-paste solutions

Fix #1

Wrap raw inputs with a dict-shaping runnable at the top of the chain

The prompt is almost never the first useful step — a shape-setter is.

When the user calls chain.invoke("some question"), you almost always want that string mapped into {"question": "..."} for the prompt template. Use a lambda, RunnablePassthrough, or a helper.

shape_setting.py
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o-mini")

prompt = ChatPromptTemplate.from_messages([
    ("system", "You answer briefly."),
    ("human", "{question}"),
])

# ❌ WRONG — bare string flows into prompt that expects a dict
# chain = prompt | model | StrOutputParser()
# chain.invoke("what is the capital of France")  # KeyError: 'question'

# ✓ RIGHT — shape the input first
chain = (
    RunnableLambda(lambda x: {"question": x} if isinstance(x, str) else x)
    | prompt
    | model
    | StrOutputParser()
)
print(chain.invoke("what is the capital of France"))
# > Paris

# ✓ ALTERNATIVE — call invoke with a dict directly
chain2 = prompt | model | StrOutputParser()
print(chain2.invoke({"question": "what is the capital of France"}))

# ✓ BEST for retrieval workflows — RunnablePassthrough.assign
retrieval_chain = (
    RunnablePassthrough.assign(
        # add 'context' key derived from 'question' key, keeping 'question'
        context=lambda x: retrieve_docs(x["question"])
    )
    | prompt_with_context
    | model
    | StrOutputParser()
)
The type error appears at the prompt step but is caused by the entry point of the chain. Inspect chain.input_schema.model_json_schema() to see what the chain expects.
Fix #2

Insert StrOutputParser wherever text is expected

The gap between a model output and a text-consuming step is the most common shape error.

Chat models emit AIMessage objects, not plain strings. Downstream string operations (format templates, regex, further prompts) need the .content extracted. StrOutputParser does exactly this and belongs after almost every chat model call.

parser_placement.py
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4o-mini")

# --- Multi-stage chain: research → refine ---

research_prompt = ChatPromptTemplate.from_template(
    "Research: {topic}. List 5 key facts."
)
refine_prompt = ChatPromptTemplate.from_template(
    "Given these research notes, write a 2-paragraph brief:\n\n{notes}"
)

# ❌ WRONG — research emits AIMessage, refine_prompt.format wants a string
# chain = research_prompt | model | (lambda ai: {"notes": ai}) | refine_prompt | model
# TypeError: expected str, got AIMessage

# ✓ RIGHT — StrOutputParser between the two model calls
chain = (
    research_prompt
    | model
    | StrOutputParser()                            # AIMessage -> str
    | (lambda notes: {"notes": notes})             # str -> dict for next prompt
    | refine_prompt
    | model
    | StrOutputParser()                            # final AIMessage -> str
)

result = chain.invoke({"topic": "carbon capture"})
print(result)

# --- For structured output, use JsonOutputParser or with_structured_output ---
structured_chain = (
    ChatPromptTemplate.from_template("Extract as JSON: {text}")
    | model
    | JsonOutputParser()                           # AIMessage -> dict
)
A common shortcut is to skip StrOutputParser in dev and add it when the chain first breaks. Add it up front instead — every hop where a model output flows into text-manipulation code needs a parser.
Fix #3

Debug shape errors with input_schema and get_graph

LangChain runnables expose their expected schema — read it.

Every LCEL runnable has .input_schema, .output_schema, and .get_graph(). Use these to introspect what each step expects before invoke-time.

introspect_chain.py
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("Explain {concept} to a {audience}")
model = ChatOpenAI(model="gpt-4o-mini")
chain = prompt | model | StrOutputParser()

# 1) What does the WHOLE chain expect / emit?
print(chain.input_schema.model_json_schema())
# {'title': 'PromptInput', 'type': 'object',
#  'properties': {'concept': {'type': 'string'}, 'audience': {'type': 'string'}},
#  'required': ['concept', 'audience']}

print(chain.output_schema.model_json_schema())
# {'type': 'string', 'title': 'StrOutputParserOutput'}

# 2) What does each STEP in the chain expect / emit?
graph = chain.get_graph()
print(graph.draw_ascii())          # visual pipeline
print(graph.draw_mermaid())        # mermaid syntax for docs

# 3) Try invoking with the schema in mind
chain.invoke({"concept": "gradient descent", "audience": "10-year-old"})

# 4) When a chain fails, isolate the failing hop
# Invoke each prefix separately:
step1 = prompt.invoke({"concept": "x", "audience": "y"})
print(type(step1))  # ChatPromptValue

step2 = model.invoke(step1)
print(type(step2))  # AIMessage

step3 = StrOutputParser().invoke(step2)
print(type(step3))  # str
When on-boarding new team members to LCEL, pair-review chain.input_schema and chain.output_schema for each significant chain in the codebase. It doubles as living documentation.

Prevention checklist

Ship these seven safeguards once and this error stops appearing in your logs.

  • Always start LCEL chains with a shape-setting step; never pipe a bare string into a prompt template.
  • Insert StrOutputParser() after every chat model call whose output flows into text-manipulation code.
  • Use RunnablePassthrough.assign() to thread values through the chain without losing them.
  • Inspect chain.input_schema and chain.output_schema as part of unit tests.
  • Pin langchain-core and integration package versions in pyproject.toml — schema tightness varies between minor versions.
  • Prefer with_structured_output() over JsonOutputParser when the output is structured — cleaner error surfaces.
  • Type-hint your custom RunnableLambda functions so IDE catches shape mismatches at write time.

Frequently asked questions

RunnablePassthrough passes its input through unchanged. RunnablePassthrough.assign(k=fn) adds new key(s) computed from the input to a dict, keeping existing keys. The assign form is essential for building retrieval chains where you need both the original question and the retrieved context.
Streaming imposes stricter shape requirements — every runnable in the chain must implement streaming natively or degrade to yielding the whole output at once. If your custom lambda breaks streaming, wrap it in a RunnableGenerator that yields chunks.
LCEL for all new work. The legacy LLMChain, SequentialChain, and related classes are deprecated in current LangChain and will be removed. LCEL offers better streaming, async, and observability.
Use Runnable[InputType, OutputType] as the annotation. For dict inputs, use TypedDict or Pydantic models. Example: chain: Runnable[QuestionInput, str] = prompt | model | StrOutputParser().
Yes — each runnable is invokable in isolation. Extract complex chains into named subchains and test each with representative inputs. Mock the model with FakeListChatModel for deterministic tests.

Get the weekly AI-error digest

New fixes, provider status recaps, and one deep tutorial — every Tuesday. 8,400+ engineers.