deepset-ai/haystack · error · ValueError
'response_fn' must return an assistant ChatMessage, got '{re
Error message
'response_fn' must return an assistant ChatMessage, got '{result.role.value}'. What it means
MockChatGenerator wraps a user-supplied `response_fn` whose return value is coerced into a ChatMessage. If the function returns a ChatMessage whose role is not ASSISTANT, haystack raises ValueError because a generator reply must come from the assistant role for downstream pipeline consumers.
Source
Thrown at haystack/components/generators/chat/mock.py:234
Return True if `response_fn` can be called as `response_fn(messages, tools)`.
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,
}
View on GitHub (pinned to e318778c9b)
Solutions
- Change response_fn to return ChatMessage.from_assistant(text) instead of from_user/from_tool
- If the recorded message has a non-assistant role, rebuild it: ChatMessage.from_assistant(message.text)
- Return a plain str, which _coerce_to_message automatically wraps with the assistant role
Example fix
// before
def response_fn(messages):
return ChatMessage.from_user("hello")
// after
def response_fn(messages):
return ChatMessage.from_assistant("hello") Defensive patterns
Strategy: validation
Validate before calling
def is_valid_response(r):
return isinstance(r, str) or (isinstance(r, ChatMessage) and r.role == ChatRole.ASSISTANT)
assert is_valid_response(response_fn(messages)) Type guard
def is_assistant_message(r) -> bool:
return isinstance(r, ChatMessage) and r.role == ChatRole.ASSISTANT Try / catch
try:
gen = MockChatGenerator(response_fn=response_fn)
gen.run([msg])
except ValueError as e:
if "response_fn" in str(e):
response_fn = lambda m: ChatMessage.from_assistant(str(m[-1].text)) Prevention
- Always use ChatMessage.from_assistant in mocks
- Return plain strings when role doesn't matter
- Unit-test response_fn output role
When it happens
Trigger: Passing `response_fn` to MockChatGenerator that returns a ChatMessage built with e.g. ChatMessage.from_user(...) or from_tool(...), or mutating a message's role before returning it.
Common situations: Developers replaying recorded messages (tool/user role) as canned responses, or building messages from logs where the stored role is 'user' or 'tool'.
Related errors
- 'dimension' must be a positive integer.
- 'dimension' must be a positive integer.
- Each ChatMessage response must have the 'assistant' role, go
- 'response_fn' must return a string or ChatMessage, got {type
- Cannot stream multiple responses, please set n=1.
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/c0f91214b5a91ffb.
Report an issue: GitHub.