chore: consolidate outputs to state string (#11902)

This commit is contained in:
David S. Batista
2026-07-08 13:27:19 +02:00
committed by GitHub
parent 8f06d602da
commit f317d87079
2 changed files with 66 additions and 37 deletions
+33 -36
View File
@@ -380,6 +380,33 @@ def _check_duplicate_tool_names(tools: list[Tool] | None) -> None:
raise ValueError(f"Duplicate tool names found: {duplicate_tool_names}")
def _convert_handler(config: dict[str, Any], converter: Callable[[Any], Any]) -> dict[str, Any]:
"""
Copies a single output config, converting its "handler" entry (if present) via `converter`.
:param config: A single output configuration dictionary that may contain a "handler" key.
:param converter: `serialize_callable` or `deserialize_callable`, applied to the "handler" value.
:returns: A copy of `config` with the "handler" value converted, if present.
"""
new_config = config.copy()
if "handler" in config:
new_config["handler"] = converter(config["handler"])
return new_config
def _convert_handler_in_configs(
configs: dict[str, dict[str, Any]], converter: Callable[[Any], Any]
) -> dict[str, dict[str, Any]]:
"""
Applies `_convert_handler` to every config in a dictionary of named output configs.
:param configs: A mapping of keys to output configuration dictionaries.
:param converter: `serialize_callable` or `deserialize_callable`, applied to each "handler" value.
:returns: A new mapping with the same keys, each config converted via `_convert_handler`.
"""
return {key: _convert_handler(config, converter) for key, config in configs.items()}
def _serialize_outputs_to_state(outputs_to_state: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
"""
Serializes the outputs_to_state dictionary, converting any callable handlers to their string representation.
@@ -387,13 +414,7 @@ def _serialize_outputs_to_state(outputs_to_state: dict[str, dict[str, Any]]) ->
:param outputs_to_state: The outputs_to_state dictionary to serialize.
:returns: The serialized outputs_to_state dictionary.
"""
serialized_outputs = {}
for key, config in outputs_to_state.items():
serialized_config = config.copy()
if "handler" in config:
serialized_config["handler"] = serialize_callable(config["handler"])
serialized_outputs[key] = serialized_config
return serialized_outputs
return _convert_handler_in_configs(outputs_to_state, serialize_callable)
def _deserialize_outputs_to_state(outputs_to_state: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
@@ -403,13 +424,7 @@ def _deserialize_outputs_to_state(outputs_to_state: dict[str, dict[str, Any]]) -
:param outputs_to_state: The outputs_to_state dictionary to deserialize.
:returns: The deserialized outputs_to_state dictionary.
"""
deserialized_outputs = {}
for key, config in outputs_to_state.items():
deserialized_config = config.copy()
if "handler" in config:
deserialized_config["handler"] = deserialize_callable(config["handler"])
deserialized_outputs[key] = deserialized_config
return deserialized_outputs
return _convert_handler_in_configs(outputs_to_state, deserialize_callable)
def _serialize_outputs_to_string(outputs_to_string: dict[str, Any]) -> dict[str, Any]:
@@ -421,19 +436,10 @@ def _serialize_outputs_to_string(outputs_to_string: dict[str, Any]) -> dict[str,
"""
if "source" in outputs_to_string or "handler" in outputs_to_string or "raw_result" in outputs_to_string:
# Single output configuration
serialized_outputs = outputs_to_string.copy()
if "handler" in outputs_to_string:
serialized_outputs["handler"] = serialize_callable(outputs_to_string["handler"])
return serialized_outputs
return _convert_handler(outputs_to_string, serialize_callable)
# Multiple outputs configuration
serialized_outputs = {}
for key, config in outputs_to_string.items():
serialized_config = config.copy()
if "handler" in config:
serialized_config["handler"] = serialize_callable(config["handler"])
serialized_outputs[key] = serialized_config
return serialized_outputs
return _convert_handler_in_configs(outputs_to_string, serialize_callable)
def _deserialize_outputs_to_string(outputs_to_string: dict[str, Any]) -> dict[str, Any]:
@@ -445,16 +451,7 @@ def _deserialize_outputs_to_string(outputs_to_string: dict[str, Any]) -> dict[st
"""
if "source" in outputs_to_string or "handler" in outputs_to_string or "raw_result" in outputs_to_string:
# Single output configuration
deserialized_outputs = outputs_to_string.copy()
if "handler" in outputs_to_string:
deserialized_outputs["handler"] = deserialize_callable(outputs_to_string["handler"])
return deserialized_outputs
return _convert_handler(outputs_to_string, deserialize_callable)
# Multiple outputs configuration
deserialized_outputs = {}
for key, config in outputs_to_string.items():
deserialized_config = config.copy()
if "handler" in config:
deserialized_config["handler"] = deserialize_callable(config["handler"])
deserialized_outputs[key] = deserialized_config
return deserialized_outputs
return _convert_handler_in_configs(outputs_to_string, deserialize_callable)
+33 -1
View File
@@ -3,13 +3,19 @@
# SPDX-License-Identifier: Apache-2.0
import re
from typing import Any
import pytest
from haystack.dataclasses import TextContent
from haystack.tools import Tool, _check_duplicate_tool_names
from haystack.tools.errors import ToolInvocationError
from haystack.tools.tool import _deserialize_outputs_to_string, _serialize_outputs_to_string
from haystack.tools.tool import (
_deserialize_outputs_to_state,
_deserialize_outputs_to_string,
_serialize_outputs_to_state,
_serialize_outputs_to_string,
)
def get_weather_report(city: str) -> str:
@@ -248,6 +254,32 @@ class TestTool:
"temp": {"source": "temperature", "handler": format_string},
}
def test_serialize_outputs_to_state(self):
config: dict[str, dict[str, Any]] = {
"documents": {"source": "docs", "handler": format_string},
"summary": {"source": "docs", "handler": get_weather_report},
"raw_docs": {"source": "docs"},
}
serialized = _serialize_outputs_to_state(config)
assert serialized == {
"documents": {"source": "docs", "handler": "test_tool.format_string"},
"summary": {"source": "docs", "handler": "test_tool.get_weather_report"},
"raw_docs": {"source": "docs"},
}
def test_deserialize_outputs_to_state(self):
serialized = {
"documents": {"source": "docs", "handler": "test_tool.format_string"},
"summary": {"source": "docs", "handler": "test_tool.get_weather_report"},
"raw_docs": {"source": "docs"},
}
deserialized = _deserialize_outputs_to_state(serialized)
assert deserialized == {
"documents": {"source": "docs", "handler": format_string},
"summary": {"source": "docs", "handler": get_weather_report},
"raw_docs": {"source": "docs"},
}
def test_inputs_from_state_validation_with_invalid_parameter(self):
"""Test that inputs_from_state is validated against the parameters schema"""
with pytest.raises(