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
- Ensure response_fn returns either a str or a ChatMessage
- Wrap dict/list results: ChatMessage.from_assistant(str) or ChatMessage().created from the appropriate constructor
- 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
- Don't forget return in lambdas
- Return str for simple canned replies
- Log type(result) when debugging mocks
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
- MockDocumentEmbedder expects a list of Documents as input.In
- MockTextEmbedder expects a string as an input. In case you w
- 'response_fn' must return an assistant ChatMessage, got '{re
- Unsupported tool result: {result.result}
- Invalid messages type. Expected list[ChatMessage] or str.
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/3b47667805d8c549.
Report an issue: GitHub.