Skip to content

Tool outputs_to_string fails when multi-output tools use "source", "handler", or "raw_result" as output keys #13109

Description

@rautaditya2606

Describe the bug

When configuring outputs_to_string on a Tool using the multiple-output format, if any of the output keys are named "source", "handler", or "raw_result", the tool fails across initialization, agent execution, and serialization.

This happens because outputs_to_string uses a heuristic that checks whether any of those three keys exist in outputs_to_string to decide whether it's a single-output or multi-output configuration. If a multi-output tool produces an output named "source" (very common for search, retrievers, citation tools) and maps it to a config dict, Tool.__post_init__ assumes it's a single-output config and expects a string, raising a ValueError.

If initialization is bypassed, agent execution in _build_tool_result_message also hits this heuristic and crashes with TypeError: unhashable type: 'dict', and serialization in _serialize_outputs_to_string crashes with AttributeError (or skips serializing handlers).

To Reproduce

from haystack.tools import Tool

def search_tool_fn(query: str) -> dict:
    return {"source": "https://example.com", "text": "result text"}

tool = Tool(
    name="search",
    description="search tool",
    parameters={"type": "object", "properties": {"query": {"type": "string"}}},
    function=search_tool_fn,
    outputs_to_string={
        "source": {"source": "source"},
        "text": {"source": "text"},
    },
)

Error message

Traceback (most recent call last):
  File "test_repro.py", line 6, in <module>
    tool = Tool(
  File "<string>", line 11, in __init__
  File "haystack/tools/tool.py", line 160, in __post_init__
    raise ValueError("outputs_to_string source must be a string.")
ValueError: outputs_to_string source must be a string.

And when executed by an Agent:

  File "haystack/components/agents/tool_calling.py", line 150, in _build_tool_result_message
    tool_result = _process_tool_output(outputs_config, result, tool_call, raise_on_failure=raise_on_failure)
  File "haystack/components/agents/tool_calling.py", line 118, in _process_tool_output
    value = result.get(source_key) if source_key is not None and isinstance(result, dict) else result
TypeError: unhashable type: 'dict'

Expected behavior

Tool should accept "source", "handler", and "raw_result" as valid output keys in multi-output mode, stringify each configured output, and serialize/deserialize cleanly.

Single-output vs multi-output format should be differentiated by inspecting whether the dictionary values are configuration dictionaries rather than checking top-level key names.

System:

  • OS: Linux
  • Haystack version: main (3.x)

Activity

  1. rautaditya2606 commented on Oct 5, 2026

    @rautaditya2606
    ContributorAuthor

    I'd like to work on this! I have the fix implemented and tested locally with unit tests and release notes ready, but I'm currently unable to open a PR since I've hit the community open PR limit.

    Branch is ready on my fork: https://github.com/rautaditya2606/haystack/tree/fix-tool-outputs-to-string-keys

    Happy to open the PR as soon as one of my existing PRs wraps up, or let me know if you'd prefer me to open it sooner.

  2. 0xamlab commented on Oct 7, 2026

    @0xamlab

    Reproduced on main: Tool(... outputs_to_string={"source": {"source": ...}}) raises ValueError: outputs_to_string source must be a string. at haystack/tools/tool.py:161. With your branch the same script constructs, invokes, and serializes cleanly, and pytest test/tools/test_tool.py test/tools/test_tools_utils.py test/components/agents/test_tool_calling.py -q passes (125). The _is_multi_output_config helper correctly switches on value shape instead of reserved key names. One request: the branch also refactors warm_up/invoke_async and rewrites test_tools_utils.py; splitting that out keeps this PR focused and reviewable.

  3. added a commit that references this issue on Oct 10, 2026
    34b6a77
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions