diff --git a/great_ai/helper/freeze_arguments.py b/great_ai/helper/freeze_arguments.py index 524906f..3085394 100644 --- a/great_ai/helper/freeze_arguments.py +++ b/great_ai/helper/freeze_arguments.py @@ -68,6 +68,12 @@ def freeze(value: Union[Sequence[Any], Mapping[str, Any], Set[Any], BaseModel]) if isinstance(value, set): return FrozenSet(value) + if isinstance(value, tuple): + values = tuple(freeze(item) for item in value) + if hasattr(type(value), "_fields"): # preserve named-tuple argument types + return type(value)(*values) + return values + if isinstance(value, BaseModel): # Keep the already-validated values, aliases, nested types and private # attributes. Reconstructing from model_dump() runs validators again. diff --git a/tests/test_cache_regressions.py b/tests/test_cache_regressions.py new file mode 100644 index 0000000..4edfa5c --- /dev/null +++ b/tests/test_cache_regressions.py @@ -0,0 +1,17 @@ +from typing import Dict, Tuple + +from great_ai import GreatAI + + +def test_nested_tuple_inputs_can_be_cached(isolated_context: None) -> None: + calls = 0 + + @GreatAI.create + def predict(value: Tuple[Dict[str, int]]) -> int: + nonlocal calls + calls += 1 + return value[0]["x"] + + assert predict(({"x": 7},)).output == 7 + assert predict(({"x": 7},)).output == 7 + assert calls == 1