deepset-ai/haystack · error · ValueError

Invalid outputs_to_string config. When using 'source', 'hand

Error message

Invalid outputs_to_string config. When using 'source', 'handler' or 'raw_result' at the root level, no other keys are  allowed. Use individual output configs instead.

What it means

outputs_to_string supports two shapes: a single-output config whose keys are only source/handler/raw_result, or a per-output config keyed by output names. Mixing them — root-level source/handler/raw_result alongside any other key — is rejected with this ValueError at Tool construction.

Source

Thrown at haystack/tools/tool.py:174

                        )

        if self.outputs_to_string is not None:
            if "source" in self.outputs_to_string and not isinstance(self.outputs_to_string["source"], str):
                raise ValueError("outputs_to_string source must be a string.")
            if "handler" in self.outputs_to_string and not callable(self.outputs_to_string["handler"]):
                raise ValueError("outputs_to_string handler must be callable")
            if "raw_result" in self.outputs_to_string and not isinstance(self.outputs_to_string["raw_result"], bool):
                raise ValueError("outputs_to_string raw_result must be a boolean.")

            if (
                "source" in self.outputs_to_string
                or "handler" in self.outputs_to_string
                or "raw_result" in self.outputs_to_string
            ):
                # Single output configuration
                for key in self.outputs_to_string:
                    if key not in {"source", "handler", "raw_result"}:
                        raise ValueError(
                            "Invalid outputs_to_string config. "
                            "When using 'source', 'handler' or 'raw_result' at the root level, no other keys are "
                            " allowed. Use individual output configs instead."
                        )
            else:
                # Multiple outputs configuration
                for key, config in self.outputs_to_string.items():
                    if not isinstance(config, dict):
                        raise TypeError(f"outputs_to_string configuration for key '{key}' must be a dictionary")
                    if "raw_result" in config:
                        raise ValueError(
                            f"Invalid outputs_to_string configuration for key '{key}': "
                            f"'raw_result' is not supported in the multiple output format."
                        )
                    if "source" not in config:
                        raise ValueError(
                            f"Invalid outputs_to_string configuration for key '{key}': "
                            f"each output must have a 'source' defined."

View on GitHub (pinned to e318778c9b)

Solutions

  1. Use one output-name key per entry with no root-level keys: {"answer": {"handler": fn}} instead of {"source": "answer", "handler": fn} mixed with others.
  2. If one output needs config and others don't, give each output its own (possibly empty) dict.
  3. Split into two configs only if using two separate Tools; a single Tool must use a uniform shape.

Example fix

// before
outputs_to_string={"source": "answer", "handler": fmt, "documents": {"handler": doc_fmt}}

// after
outputs_to_string={"answer": {"handler": fmt}, "documents": {"handler": doc_fmt}}
Defensive patterns

Strategy: validation

Validate before calling

ots = outputs_to_string or {}
ROOT_KEYS = {"source", "handler", "raw_result"}
if (ROOT_KEYS & ots.keys()) and (ots.keys() - ROOT_KEYS):
    raise ValueError("outputs_to_string mixes root-level keys with per-output configs")

Type guard

def is_uniform_outputs_to_string(ots: dict | None) -> bool:
    if ots is None:
        return True
    ROOT = {"source", "handler", "raw_result"}
    return not (ROOT & ots.keys()) or not (ots.keys() - ROOT)

Try / catch

try:
    tool = Tool(name="t", function=f, outputs_to_string=ots)
except ValueError as e:
    if "Invalid outputs_to_string" in str(e):
        ots = {k: (v if isinstance(v, dict) else {k2: v for k2 in ("source", "handler", "raw_result") if k2 == k} or {}) for k, v in [("answer", ots)]}
    raise

Prevention

When it happens

Trigger: Tool(..., outputs_to_string={"source": "answer", "documents": {"handler": fn}}) — a root-level key plus an output-name key; or {"raw_result": True, "foo": {...}}.

Common situations: Merging two configs (one single-output, one multi-output) with dict update; incremental edits adding a per-output entry to an existing single-output config.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/f1c49b69a3e22140. Report an issue: GitHub.