{"record":{"id":"c0f91214b5a91ffb","repo":"deepset-ai/haystack","slug":"response-fn-must-return-an-assistant-chatmessage","errorCode":null,"errorMessage":"'response_fn' must return an assistant ChatMessage, got '{result.role.value}'.","messagePattern":"'response_fn' must return an assistant ChatMessage, got '(.+?)'\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/components/generators/chat/mock.py","lineNumber":234,"sourceCode":"        Return True if `response_fn` can be called as `response_fn(messages, tools)`.\n\n        Callables that accept a single positional argument are called as `response_fn(messages)` instead.\n        \"\"\"\n        try:\n            inspect.signature(response_fn).bind(None, None)\n        except (TypeError, ValueError):\n            # The callable rejects a second positional argument, or exposes no signature at all (some C callables).\n            return False\n        return True\n\n    @staticmethod\n    def _coerce_to_message(result: str | ChatMessage) -> ChatMessage:\n        \"\"\"Turn the output of `response_fn` into a `ChatMessage`, wrapping strings and requiring the assistant role.\"\"\"\n        if isinstance(result, str):\n            return ChatMessage.from_assistant(result)\n        if isinstance(result, ChatMessage):\n            if result.role != ChatRole.ASSISTANT:\n                raise ValueError(f\"'response_fn' must return an assistant ChatMessage, got '{result.role.value}'.\")\n            return result\n        raise TypeError(f\"'response_fn' must return a string or ChatMessage, got {type(result)}.\")\n\n    @staticmethod\n    def _estimate_usage(messages: list[ChatMessage], reply: ChatMessage) -> dict[str, int]:\n        \"\"\"\n        Roughly estimate token usage as whitespace-separated word counts.\n\n        This is an approximation (not real tokenization) intended to give downstream code realistic-looking metadata.\n        \"\"\"\n        prompt_tokens = sum(len((message.text or \"\").split()) for message in messages)\n        completion_tokens = len((reply.text or \"\").split())\n        return {\n            \"prompt_tokens\": prompt_tokens,\n            \"completion_tokens\": completion_tokens,\n            \"total_tokens\": prompt_tokens + completion_tokens,\n        }\n","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/components/generators/chat/mock.py#L216-L252","documentation":"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.","triggerScenarios":"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.","commonSituations":"Developers replaying recorded messages (tool/user role) as canned responses, or building messages from logs where the stored role is 'user' or 'tool'.","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"],"exampleFix":"// before\ndef response_fn(messages):\n    return ChatMessage.from_user(\"hello\")\n// after\ndef response_fn(messages):\n    return ChatMessage.from_assistant(\"hello\")","handlingStrategy":"validation","validationCode":"def is_valid_response(r):\n    return isinstance(r, str) or (isinstance(r, ChatMessage) and r.role == ChatRole.ASSISTANT)\nassert is_valid_response(response_fn(messages))","typeGuard":"def is_assistant_message(r) -> bool:\n    return isinstance(r, ChatMessage) and r.role == ChatRole.ASSISTANT","tryCatchPattern":"try:\n    gen = MockChatGenerator(response_fn=response_fn)\n    gen.run([msg])\nexcept ValueError as e:\n    if \"response_fn\" in str(e):\n        response_fn = lambda m: ChatMessage.from_assistant(str(m[-1].text))","preventionTips":["Always use ChatMessage.from_assistant in mocks","Return plain strings when role doesn't matter","Unit-test response_fn output role"],"tags":["python","haystack","mock","chat-message","validation"],"backgroundTag":"invalid-response-role","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}