From a020dd1375c66d4c85052ac6551f653c05c62560 Mon Sep 17 00:00:00 2001
From: Mohammed Razak <mohammedrazak2001@gmail.com>
Date: Thu, 13 Mar 2025 01:19:44 +0530
Subject: [PATCH 1/4] Add compatibility checks for Callable types

---
 haystack/core/type_utils.py           | 30 ++++++++++++--
 test/core/pipeline/test_type_utils.py | 59 ++++++++++++++++++++++++++-
 2 files changed, 84 insertions(+), 5 deletions(-)

diff --git a/haystack/core/type_utils.py b/haystack/core/type_utils.py
index 162fcc759f..ba093283ca 100644
--- a/haystack/core/type_utils.py
+++ b/haystack/core/type_utils.py
@@ -1,7 +1,7 @@
 # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
 #
 # SPDX-License-Identifier: Apache-2.0
-
+import collections.abc
 from typing import Any, TypeVar, Union, get_args, get_origin
 
 from haystack import logging
@@ -65,15 +65,37 @@ def _strict_types_are_compatible(sender, receiver):  # pylint: disable=too-many-
     sender_args = get_args(sender)
     receiver_args = get_args(receiver)
 
+    # Handle Callable types
+    if sender_origin == receiver_origin == collections.abc.Callable:
+        return _check_callable_compatibility(sender_args, receiver_args)
+
     # Handle bare types
     if not sender_args and sender_origin:
         sender_args = (Any,)
     if not receiver_args and receiver_origin:
         receiver_args = (Any,) * (len(sender_args) if sender_args else 1)
-    if len(sender_args) > len(receiver_args):
-        return False
 
-    return all(_strict_types_are_compatible(*args) for args in zip(sender_args, receiver_args))
+    return not (len(sender_args) > len(receiver_args)) and all(
+        _strict_types_are_compatible(*args) for args in zip(sender_args, receiver_args)
+    )
+
+
+def _check_callable_compatibility(sender_args, receiver_args):
+    """Helper function to check compatibility of Callable types"""
+    if not receiver_args:
+        return True
+    if not sender_args:
+        sender_args = ([Any] * len(receiver_args[0]), Any)
+    # Standard Callable has two elements in args: argument list and return type
+    if len(sender_args) != 2 or len(receiver_args) != 2:
+        return False
+    # Return types must be compatible
+    if not _strict_types_are_compatible(sender_args[1], receiver_args[1]):
+        return False
+    # Input Arguments must be of same length
+    if len(sender_args[0]) != len(receiver_args[0]):
+        return False
+    return all(_strict_types_are_compatible(sender_args[0][i], receiver_args[0][i]) for i in range(len(sender_args[0])))
 
 
 def _type_name(type_):
diff --git a/test/core/pipeline/test_type_utils.py b/test/core/pipeline/test_type_utils.py
index 9151b2770c..628d16d9c9 100644
--- a/test/core/pipeline/test_type_utils.py
+++ b/test/core/pipeline/test_type_utils.py
@@ -3,7 +3,7 @@
 # SPDX-License-Identifier: Apache-2.0
 from enum import Enum
 from pathlib import Path
-from typing import Any, Dict, Iterable, List, Literal, Mapping, Optional, Sequence, Set, Tuple, Union
+from typing import Any, Callable, Dict, Iterable, List, Literal, Mapping, Optional, Sequence, Set, Tuple, Union
 
 import pytest
 
@@ -384,6 +384,7 @@ def test_container_of_primitive_to_bare_container_strict(sender_type, receiver_t
         pytest.param(Dict[Any, Any], Dict, id="dict-of-any-to-bare-dict"),
         pytest.param(Set[Any], Set, id="set-of-any-to-bare-set"),
         pytest.param(Tuple[Any], Tuple, id="tuple-of-any-to-bare-tuple"),
+        pytest.param(Callable[[Any], Any], Callable, id="callable-of-any-to-bare-callable"),
     ],
 )
 def test_container_of_any_to_bare_container_strict(sender_type, receiver_type):
@@ -438,3 +439,59 @@ def test_nested_container_compatibility(sender_type, receiver_type):
     assert _types_are_compatible(sender_type, receiver_type)
     # Bare container types should not be compatible with their typed counterparts
     assert not _types_are_compatible(receiver_type, sender_type)
+
+
+@pytest.mark.parametrize(
+    "sender_type,receiver_type",
+    [
+        pytest.param(Callable[[int, str], bool], Callable, id="callable-to-bare-callable"),
+        pytest.param(Callable[[List], int], Callable[[List], Any], id="callable-list-int-to-any"),
+    ],
+)
+def test_callable_compatibility(sender_type, receiver_type):
+    assert _types_are_compatible(sender_type, receiver_type)
+    assert not _types_are_compatible(receiver_type, sender_type)
+
+
+@pytest.mark.parametrize(
+    "sender_type,receiver_type",
+    [
+        pytest.param(
+            Callable[[Callable[[int], str]], List[str]], Callable, id="callable-with-nested-types-to-bare-callable"
+        ),
+        pytest.param(
+            Callable[[Callable[[int], str]], List[str]],
+            Callable[[Callable[[Any], Any]], Any],
+            id="double-nested-callable",
+        ),
+        pytest.param(
+            Callable[[Callable[[Callable[[int], str]], bool]], str],
+            Callable[[Callable[[Callable[[Any], Any]], Any]], Any],
+            id="triple-nested-callable",
+        ),
+    ],
+)
+def test_nested_callable_compatibility(sender_type, receiver_type):
+    assert _types_are_compatible(sender_type, receiver_type)
+    # Bare callable container types should not be compatible with their typed counterparts
+    assert not _types_are_compatible(receiver_type, sender_type)
+
+
+@pytest.mark.parametrize(
+    "sender_type,receiver_type",
+    [
+        pytest.param(
+            Callable[[int, str], bool], Callable[[int, str], List], id="callable-to-callable-with-different-return-type"
+        ),
+        pytest.param(
+            Callable[[int, int], bool], Callable[[int, str], bool], id="callable-to-callable-with-different-args-type"
+        ),
+        pytest.param(
+            Callable[[int, str], bool], Callable[[int, str, int], bool], id="callable-to-callable-with-different-args"
+        ),
+        pytest.param(Callable[[int, str], bool], Callable[[int], bool], id="callable-to-callable-with-fewer-args"),
+    ],
+)
+def test_always_incompatible_callable_types(sender_type, receiver_type):
+    assert not _types_are_compatible(sender_type, receiver_type)
+    assert not _types_are_compatible(receiver_type, sender_type)

From c5f8dfa97f2618f6580af94922ceeadc8ddd0db2 Mon Sep 17 00:00:00 2001
From: Mohammed Razak <mohammedrazak2001@gmail.com>
Date: Thu, 13 Mar 2025 01:25:09 +0530
Subject: [PATCH 2/4] add release notes

---
 .../add-callable-type-compatibility-a3746cd0cfd8fc10.yaml     | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 releasenotes/notes/add-callable-type-compatibility-a3746cd0cfd8fc10.yaml

diff --git a/releasenotes/notes/add-callable-type-compatibility-a3746cd0cfd8fc10.yaml b/releasenotes/notes/add-callable-type-compatibility-a3746cd0cfd8fc10.yaml
new file mode 100644
index 0000000000..2ffe92cdb6
--- /dev/null
+++ b/releasenotes/notes/add-callable-type-compatibility-a3746cd0cfd8fc10.yaml
@@ -0,0 +1,4 @@
+---
+features:
+  - |
+    Add compatibility for Callable types.

From dacd5bb5da86103bc0793de48da53098663f8332 Mon Sep 17 00:00:00 2001
From: Michele Pangrazzi <xmikex83@gmail.com>
Date: Wed, 26 Mar 2025 16:38:58 +0100
Subject: [PATCH 3/4] Update test_type_utils.py

Fix license header
---
 test/core/pipeline/test_type_utils.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/core/pipeline/test_type_utils.py b/test/core/pipeline/test_type_utils.py
index 628d16d9c9..e6dcf1baa3 100644
--- a/test/core/pipeline/test_type_utils.py
+++ b/test/core/pipeline/test_type_utils.py
@@ -1,6 +1,7 @@
 # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
 #
 # SPDX-License-Identifier: Apache-2.0
+
 from enum import Enum
 from pathlib import Path
 from typing import Any, Callable, Dict, Iterable, List, Literal, Mapping, Optional, Sequence, Set, Tuple, Union

From 59a88bd984fdf4a89639dcd6de0daff43ffcf847 Mon Sep 17 00:00:00 2001
From: Michele Pangrazzi <xmikex83@gmail.com>
Date: Wed, 26 Mar 2025 16:45:20 +0100
Subject: [PATCH 4/4] Update type_utils.py

Fix license header
---
 haystack/core/type_utils.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/haystack/core/type_utils.py b/haystack/core/type_utils.py
index 3991818173..40dc6e4d4c 100644
--- a/haystack/core/type_utils.py
+++ b/haystack/core/type_utils.py
@@ -1,6 +1,7 @@
 # SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
 #
 # SPDX-License-Identifier: Apache-2.0
+
 import collections.abc
 from typing import Any, TypeVar, Union, get_args, get_origin