deepset-ai/haystack · error · TypeError

'response_fn' must return a string or ChatMessage, got {type

Error message

'response_fn' must return a string or ChatMessage, got {type(result)}.

What it means

`_coerce_to_message` only accepts str or ChatMessage from `response_fn`. Any other type (dict, list, None, model output object) raises TypeError, because MockChatGenerator has no way to convert arbitrary values into a ChatMessage.

Source

Thrown at haystack/components/generators/chat/mock.py:236

        Callables that accept a single positional argument are called as `response_fn(messages)` instead.
        """
        try:
            inspect.signature(response_fn).bind(None, None)
        except (TypeError, ValueError):
            # The callable rejects a second positional argument, or exposes no signature at all (some C callables).
            return False
        return True

    @staticmethod
    def _coerce_to_message(result: str | ChatMessage) -> ChatMessage:
        """Turn the output of `response_fn` into a `ChatMessage`, wrapping strings and requiring the assistant role."""
        if isinstance(result, str):
            return ChatMessage.from_assistant(result)
        if isinstance(result, ChatMessage):
            if result.role != ChatRole.ASSISTANT:
                raise ValueError(f"'response_fn' must return an assistant ChatMessage, got '{result.role.value}'.")
            return result
        raise TypeError(f"'response_fn' must return a string or ChatMessage, got {type(result)}.")

    @staticmethod
    def _estimate_usage(messages: list[ChatMessage], reply: ChatMessage) -> dict[str, int]:
        """
        Roughly estimate token usage as whitespace-separated word counts.

        This is an approximation (not real tokenization) intended to give downstream code realistic-looking metadata.
        """
        prompt_tokens = sum(len((message.text or "").split()) for message in messages)
        completion_tokens = len((reply.text or "").split())
        return {
            "prompt_tokens": prompt_tokens,
            "completion_tokens": completion_tokens,
            "total_tokens": prompt_tokens + completion_tokens,
        }

    def _build_meta(self, messages: list[ChatMessage], base: ChatMessage) -> dict[str, Any]:
        """Build the metadata attached to the returned reply, merging defaults, init meta, and per-response meta."""

View on GitHub (pinned to e318778c9b)

Solutions

  1. Ensure response_fn returns either a str or a ChatMessage
  2. Wrap dict/list results: ChatMessage.from_assistant(str) or ChatMessage().created from the appropriate constructor
  3. Debug with a print/log of type(result) inside response_fn to find what is actually returned

Example fix

// before
response_fn=lambda msgs: {"text": "hi"}
// after
response_fn=lambda msgs: "hi"
Defensive patterns

Strategy: type-guard

Validate before calling

result = response_fn(messages)
if not isinstance(result, (str, ChatMessage)):
    raise TypeError(f"response_fn returned {type(result)}")

Type guard

def is_str_or_chat_message(v) -> bool:
    return isinstance(v, (str, ChatMessage))

Try / catch

try:
    gen.run([msg])
except TypeError as e:
    if "response_fn" in str(e):
        logging.error("response_fn returned %s", type(result))

Prevention

When it happens

Trigger: `response_fn` returns None (forgot a return), a dict like {"text": ...}, a list of messages, or an SDK response object instead of str/ChatMessage.

Common situations: Lambdas that print or log instead of returning; copying response handling code from other SDKs that return raw API payloads; forgetting `return` in a one-line lambda.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/3b47667805d8c549. Report an issue: GitHub.