{"record":{"id":"fa8cec00921cbf47","repo":"deepset-ai/haystack","slug":"outputs-to-state-source-for-key-key-must-be-a","errorCode":null,"errorMessage":"outputs_to_state source for key '{key}' must be a string.","messagePattern":"outputs_to_state source for key '(.+?)' must be a string\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/tools/tool.py","lineNumber":143,"sourceCode":"        if self.async_function is not None and not inspect.iscoroutinefunction(self.async_function):\n            raise ValueError(\n                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\"]):","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/tools/tool.py#L125-L161","documentation":"When an outputs_to_state entry includes a \"source\" key, its value must be a string naming the tool output to read. __post_init__ raises ValueError when config[\"source\"] is present but not a str (e.g. a list, int, or None explicitly passed).","triggerScenarios":"Tool(..., outputs_to_state={\"k\": {\"source\": 42}}) or {\"k\": {\"source\": [\"out1\", \"out2\"]}} — a non-string source value.","commonSituations":"Confusing source with the state-key mapping of multiple outputs; programmatically generated configs where the source is a list of outputs; typos yielding a non-str default value.","solutions":["Set \"source\" to the exact string name of one tool output: {\"k\": {\"source\": \"documents\"}}.","To map multiple outputs, create one entry per output/state key rather than a list source.","Omit \"source\" entirely if the whole tool result should feed the state key."],"exampleFix":"// before\noutputs_to_state={\"k\": {\"source\": [\"doc\", \"answer\"]}}\n\n// after\noutputs_to_state={\"docs\": {\"source\": \"doc\"}, \"ans\": {\"source\": \"answer\"}}","handlingStrategy":"type-guard","validationCode":"for k, cfg in outputs_to_state.items():\n    if \"source\" in cfg and not isinstance(cfg[\"source\"], str):\n        raise TypeError(f\"source for '{k}' must be a string\")","typeGuard":"def has_valid_sources(ots: dict) -> bool:\n    return all(isinstance(c.get(\"source\", \"\"), str) for c in ots.values())","tryCatchPattern":"try:\n    tool = Tool(name=\"t\", function=f, outputs_to_state=ots)\nexcept ValueError as e:\n    if \"source\" in str(e):\n        logger.error(f\"Fix outputs_to_state source types: {e}\")\n    raise","preventionTips":["Store source names as plain strings, one output per entry","Don't pass lists of outputs as a single source","Validate configs loaded from YAML/JSON for types before constructing Tools"],"tags":["python","tool","config","validation"],"backgroundTag":"invalid-config-shape","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}