{"record":{"id":"9436c4ffe2a12883","repo":"deepset-ai/haystack","slug":"outputs-to-state-handler-for-key-key-must-be-c","errorCode":null,"errorMessage":"outputs_to_state handler for key '{key}' must be callable","messagePattern":"outputs_to_state handler for key '(.+?)' must be callable","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/tools/tool.py","lineNumber":145,"sourceCode":"                f\"`async_function` must be a coroutine function defined with `async def`. \"\n                f\"Got '{getattr(self.async_function, '__name__', repr(self.async_function))}'.\"\n            )\n\n        # Check that the parameters define a valid JSON schema\n        try:\n            Draft202012Validator.check_schema(self.parameters)\n        except SchemaError as e:\n            raise ValueError(\"The provided parameters do not define a valid JSON schema\") from e\n\n        # Validate outputs structure if provided\n        if self.outputs_to_state is not None:\n            for key, config in self.outputs_to_state.items():\n                if not isinstance(config, dict):\n                    raise TypeError(f\"outputs_to_state configuration for key '{key}' must be a dictionary\")\n                if \"source\" in config and not isinstance(config[\"source\"], str):\n                    raise ValueError(f\"outputs_to_state source for key '{key}' must be a string.\")\n                if \"handler\" in config and not callable(config[\"handler\"]):\n                    raise ValueError(f\"outputs_to_state handler for key '{key}' must be callable\")\n\n            # Validate that outputs_to_state source keys exist as valid tool outputs\n            valid_outputs: set[str] | None = self._get_valid_outputs()\n            if valid_outputs is not None:\n                for state_key, config in self.outputs_to_state.items():\n                    source = config.get(\"source\")\n                    if source is not None and source not in valid_outputs:\n                        raise ValueError(\n                            f\"outputs_to_state: '{self.name}' maps state key '{state_key}' to unknown output '{source}'\"\n                            f\"Valid outputs are: {valid_outputs}.\"\n                        )\n\n        if self.outputs_to_string is not None:\n            if \"source\" in self.outputs_to_string and not isinstance(self.outputs_to_string[\"source\"], str):\n                raise ValueError(\"outputs_to_string source must be a string.\")\n            if \"handler\" in self.outputs_to_string and not callable(self.outputs_to_string[\"handler\"]):\n                raise ValueError(\"outputs_to_string handler must be callable\")\n            if \"raw_result\" in self.outputs_to_string and not isinstance(self.outputs_to_string[\"raw_result\"], bool):","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/tools/tool.py#L127-L163","documentation":"When an outputs_to_state entry includes a \"handler\" key, its value must be callable — a function invoked to process the output before storing it. __post_init__ raises ValueError if \"handler\" exists but is not callable (string, None, dict, etc.).","triggerScenarios":"Tool(..., outputs_to_state={\"k\": {\"handler\": \"my_func_name\"}}) or {\"k\": {\"handler\": None}} — passing a function name/placeholder instead of the function object.","commonSituations":"Passing a string function name hoping for late resolution; forgetting to import the handler so a variable holds the wrong object; YAML/JSON-driven config where handlers are serialized as strings.","solutions":["Pass the actual callable: {\"k\": {\"handler\": my_handler}} with my_handler imported/defined.","If configuration comes from strings, resolve to callables via a registry dict before constructing the Tool.","Remove the \"handler\" key if no transformation is needed."],"exampleFix":"// before\noutputs_to_state={\"k\": {\"handler\": \"format_doc\"}}\n\n// after\nfrom mymod import format_doc\noutputs_to_state={\"k\": {\"handler\": format_doc}}","handlingStrategy":"type-guard","validationCode":"for k, cfg in outputs_to_state.items():\n    if \"handler\" in cfg and not callable(cfg[\"handler\"]):\n        raise TypeError(f\"handler for '{k}' must be callable\")","typeGuard":"from collections.abc import Callable\n\ndef has_valid_handlers(ots: dict) -> bool:\n    return all(callable(c.get(\"handler\", lambda: None)) for c in ots.values())","tryCatchPattern":"try:\n    tool = Tool(name=\"t\", function=f, outputs_to_state=ots)\nexcept ValueError as e:\n    if \"handler\" in str(e):\n        logger.error(f\"Resolve handler callables: {e}\")\n    raise","preventionTips":["Import the handler and pass the function object, never its name string","For string-based configs, resolve names through a handler registry first","Keep handlers as top-level named functions for picklability and testability"],"tags":["python","tool","config","callable"],"backgroundTag":"handler-not-callable","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}