langchain-ai/langchain · error · ValueError
Expected invoke to return an AIMessage, but got {type(messag
Error message
Expected invoke to return an AIMessage, but got {type(message)} instead. What it means
`ValueError` in the fake chat model streaming bridge: `chat_result.generations[0].message` is not an `AIMessage`. The streaming code chunks string content off an assistant message; other message types (e.g. a parrot-style echo of a `HumanMessage`) violate that expectation.
Source
Thrown at libs/core/langchain_core/language_models/fake_chat_models.py:290
) -> Iterator[ChatGenerationChunk]:
chat_result = self._generate(
messages, stop=stop, run_manager=run_manager, **kwargs
)
if not isinstance(chat_result, ChatResult):
msg = ( # type: ignore[unreachable]
f"Expected generate to return a ChatResult, "
f"but got {type(chat_result)} instead."
)
raise ValueError(msg) # noqa: TRY004
message = chat_result.generations[0].message
if not isinstance(message, AIMessage):
msg = (
f"Expected invoke to return an AIMessage, "
f"but got {type(message)} instead."
)
raise ValueError(msg) # noqa: TRY004
content = message.content
if content:
# Use a regular expression to split on whitespace with a capture group
# so that we can preserve the whitespace in the output.
if not isinstance(content, str):
msg = "Expected content to be a string."
raise ValueError(msg)
content_chunks = cast("list[str]", re.split(r"(\s)", content))
for idx, token in enumerate(content_chunks):
chunk = ChatGenerationChunk(
message=AIMessageChunk(content=token, id=message.id)
)
if (
idx == len(content_chunks) - 1View on GitHub (pinned to e32fa9a52e)
Solutions
- Wrap the response in `AIMessage(content=...)` inside `_generate`.
- If echoing input, coerce: `AIMessage(content=messages[-1].content)`.
- Assert message types in test fixtures before wiring them into the fake.
Example fix
# before return ChatResult(generations=[ChatGeneration(message=messages[-1])]) # may be HumanMessage # after from langchain_core.messages import AIMessage return ChatResult(generations=[ChatGeneration(message=AIMessage(content=messages[-1].content))])
Defensive patterns
Strategy: type-guard
Validate before calling
from langchain_core.messages import AIMessage
msg = fake._generate(messages).generations[0].message
if not isinstance(msg, AIMessage):
raise TypeError(f"fake must produce AIMessage, got {type(msg)}") Type guard
from langchain_core.messages import AIMessage
def is_ai_message(m: object) -> bool:
return isinstance(m, AIMessage) Try / catch
try:
list(fake.stream(messages))
except ValueError as e:
if "Expected invoke to return an AIMessage" in str(e):
raise TypeError("wrap fake output in AIMessage(content=...)") from e
raise Prevention
- Echo fakes should coerce input content into `AIMessage(content=...)`.
- Never return `messages[-1]` raw when it can be a `HumanMessage`.
- Type-annotate fake `_generate` return values in tests.
When it happens
Trigger: A fake/custom `_generate` that returns a `ChatResult` whose first generation holds a `HumanMessage`, `SystemMessage`, or `ToolMessage` instead of an `AIMessage`, followed by a streaming call.
Common situations: Echo/parrot test fakes that return `messages[-1]` unfiltered (when the last input message is human); building canned responses with the wrong message class; test fixtures recorded from prompts rather than responses.
Related errors
- Expected generate to return a ChatResult, but got {type(chat
- Expected content to be a string.
- Invalid input type {type(model_input)}. Must be a PromptValu
- messages list cannot be empty.
- AsyncTextProjection received a non-string final value
AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14).
Data as JSON: /api/errors/70e89e76b73392b6.
Report an issue: GitHub.