langchain-ai/langchain · error · ValueError

Expected generate to return a ChatResult, but got {type(chat

Error message

Expected generate to return a ChatResult, but got {type(chat_result)} instead.

What it means

`ValueError` in `FakeChatModel`-family `_stream`: `_generate` returned something that is not a `ChatResult`. The fake bridging code calls the model's own `_generate` and slices `chat_result.generations[0].message`, so it hard-requires the documented `ChatResult` return type; a custom subclass returning e.g. a message or string breaks it.

Source

Thrown at libs/core/langchain_core/language_models/fake_chat_models.py:281

        generation = ChatGeneration(message=message_)
        return ChatResult(generations=[generation])

    def _stream(
        self,
        messages: list[BaseMessage],
        stop: list[str] | None = None,
        run_manager: CallbackManagerForLLMRun | None = None,
        **kwargs: Any,
    ) -> 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)

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Return `ChatResult(generations=[ChatGeneration(message=AIMessage(content=...))])` from `_generate`.
  2. Or override `_stream` directly instead of `_generate` if you want chunk-level control.
  3. Use `FakeListChatModel`/`FakeMessagesListChatModel` as-is rather than subclassing with incompatible shapes.
  4. Add a unit test asserting the return type of your override.

Example fix

# before
def _generate(self, messages, **kw):
    return AIMessage(content="hi")

# after
from langchain_core.outputs import ChatResult, ChatGeneration
def _generate(self, messages, **kw):
    return ChatResult(generations=[ChatGeneration(message=AIMessage(content="hi"))])
Defensive patterns

Strategy: type-guard

Validate before calling

from langchain_core.outputs import ChatResult
result = fake._generate(messages)
if not isinstance(result, ChatResult):
    raise TypeError(f"_generate must return ChatResult, got {type(result)}")

Type guard

from langchain_core.outputs import ChatResult
def returns_chat_result(value: object) -> bool:
    return isinstance(value, ChatResult)

Try / catch

try:
    for chunk in fake.stream(messages):
        process(chunk)
except ValueError as e:
    if "Expected generate to return a ChatResult" in str(e):
        raise TypeError("fix fake model _generate return type") from e
    raise

Prevention

When it happens

Trigger: Subclassing a fake chat model (`FakeChatModel`, used for testing) and overriding `_generate` to return an `AIMessage`, a `str`, a `ChatResult`-like dict, or `None`, then calling `stream`/`invoke` with streaming.

Common situations: Test doubles that shortcut `_generate` to return a plain message; refactors of test fakes after upgrading langchain-core; copying examples that predate the `ChatResult` contract.

Related errors


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