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) - 1

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Wrap the response in `AIMessage(content=...)` inside `_generate`.
  2. If echoing input, coerce: `AIMessage(content=messages[-1].content)`.
  3. 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

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


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