Skip to content

Commit

Permalink
chore: Remove all mentions of Canals (deepset-ai#6844)
Browse files Browse the repository at this point in the history
* Remove unnecessary Connection class

* Remove all mentions of canals

* Add release notes
  • Loading branch information
silvanocerza authored Jan 29, 2024
1 parent 9211f53 commit f5e6133
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 59 deletions.
30 changes: 15 additions & 15 deletions haystack/core/component/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@
"""

import logging
import inspect
from typing import Protocol, runtime_checkable, Any
from types import new_class
import logging
from copy import deepcopy
from types import new_class
from typing import Any, Protocol, runtime_checkable

from haystack.core.component.sockets import InputSocket, OutputSocket, _empty
from haystack.core.errors import ComponentError
Expand Down Expand Up @@ -123,20 +123,20 @@ def __call__(cls, *args, **kwargs):
# Component instance, so we take the chance and set up the I/O sockets

# If `component.set_output_types()` was called in the component constructor,
# `__canals_output__` is already populated, no need to do anything.
if not hasattr(instance, "__canals_output__"):
# If that's not the case, we need to populate `__canals_output__`
# `__haystack_output__` is already populated, no need to do anything.
if not hasattr(instance, "__haystack_output__"):
# If that's not the case, we need to populate `__haystack_output__`
#
# If the `run` method was decorated, it has a `_output_types_cache` field assigned
# that stores the output specification.
# We deepcopy the content of the cache to transfer ownership from the class method
# to the actual instance, so that different instances of the same class won't share this data.
instance.__canals_output__ = deepcopy(getattr(instance.run, "_output_types_cache", {}))
instance.__haystack_output__ = deepcopy(getattr(instance.run, "_output_types_cache", {}))

# Create the sockets if set_input_types() wasn't called in the constructor.
# If it was called and there are some parameters also in the `run()` method, these take precedence.
if not hasattr(instance, "__canals_input__"):
instance.__canals_input__ = {}
if not hasattr(instance, "__haystack_input__"):
instance.__haystack_input__ = {}
run_signature = inspect.signature(getattr(cls, "run"))
for param in list(run_signature.parameters)[1:]: # First is 'self' and it doesn't matter.
if run_signature.parameters[param].kind not in (
Expand All @@ -146,7 +146,7 @@ def __call__(cls, *args, **kwargs):
socket_kwargs = {"name": param, "type": run_signature.parameters[param].annotation}
if run_signature.parameters[param].default != inspect.Parameter.empty:
socket_kwargs["default_value"] = run_signature.parameters[param].default
instance.__canals_input__[param] = InputSocket(**socket_kwargs)
instance.__haystack_input__[param] = InputSocket(**socket_kwargs)
return instance


Expand Down Expand Up @@ -178,9 +178,9 @@ def set_input_type(self, instance, name: str, type: Any, default: Any = _empty):
:param type: type of the input socket.
:param default: default value of the input socket, defaults to _empty
"""
if not hasattr(instance, "__canals_input__"):
instance.__canals_input__ = {}
instance.__canals_input__[name] = InputSocket(name=name, type=type, default_value=default)
if not hasattr(instance, "__haystack_input__"):
instance.__haystack_input__ = {}
instance.__haystack_input__[name] = InputSocket(name=name, type=type, default_value=default)

def set_input_types(self, instance, **types):
"""
Expand Down Expand Up @@ -223,7 +223,7 @@ def run(self, value_0: str, value_1: Optional[str] = None, **kwargs):
parameter mandatory as specified in `set_input_types`.
"""
instance.__canals_input__ = {name: InputSocket(name=name, type=type_) for name, type_ in types.items()}
instance.__haystack_input__ = {name: InputSocket(name=name, type=type_) for name, type_ in types.items()}

def set_output_types(self, instance, **types):
"""
Expand All @@ -245,7 +245,7 @@ def run(self, value: int):
return {"output_1": 1, "output_2": "2"}
```
"""
instance.__canals_output__ = {name: OutputSocket(name=name, type=type_) for name, type_ in types.items()}
instance.__haystack_output__ = {name: OutputSocket(name=name, type=type_) for name, type_ in types.items()}

def output_types(self, **types):
"""
Expand Down
7 changes: 3 additions & 4 deletions haystack/core/component/sockets.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# SPDX-FileCopyrightText: 2022-present deepset GmbH <[email protected]>
#
# SPDX-License-Identifier: Apache-2.0
from typing import get_args, List, Type, Any
import logging
from dataclasses import dataclass, field
from typing import Any, List, Type, get_args

from haystack.core.component.types import CANALS_VARIADIC_ANNOTATION

from haystack.core.component.types import HAYSTACK_VARIADIC_ANNOTATION

logger = logging.getLogger(__name__)

Expand All @@ -30,7 +29,7 @@ def is_mandatory(self):
def __post_init__(self):
try:
# __metadata__ is a tuple
self.is_variadic = self.type.__metadata__[0] == CANALS_VARIADIC_ANNOTATION
self.is_variadic = self.type.__metadata__[0] == HAYSTACK_VARIADIC_ANNOTATION
except AttributeError:
self.is_variadic = False
if self.is_variadic:
Expand Down
9 changes: 5 additions & 4 deletions haystack/core/component/types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import TypeVar, Iterable
from typing_extensions import TypeAlias, Annotated # Python 3.8 compatibility
from typing import Iterable, TypeVar

CANALS_VARIADIC_ANNOTATION = "__canals__variadic_t"
from typing_extensions import Annotated, TypeAlias # Python 3.8 compatibility

HAYSTACK_VARIADIC_ANNOTATION = "__haystack__variadic_t"

# # Generic type variable used in the Variadic container
T = TypeVar("T")
Expand All @@ -11,4 +12,4 @@
# This type doesn't do anything else than "marking" the contained
# type so it can be used in the `InputSocket` creation where we
# check that its annotation equals to CANALS_VARIADIC_ANNOTATION
Variadic: TypeAlias = Annotated[Iterable[T], CANALS_VARIADIC_ANNOTATION]
Variadic: TypeAlias = Annotated[Iterable[T], HAYSTACK_VARIADIC_ANNOTATION]
34 changes: 17 additions & 17 deletions haystack/core/pipeline/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(
self,
metadata: Optional[Dict[str, Any]] = None,
max_loops_allowed: int = 100,
debug_path: Union[Path, str] = Path(".canals_debug/"),
debug_path: Union[Path, str] = Path(".haystack_debug/"),
):
"""
Creates the Pipeline.
Expand Down Expand Up @@ -118,7 +118,7 @@ def from_dict(cls: Type[T], data: Dict[str, Any], **kwargs) -> T:
"""
metadata = data.get("metadata", {})
max_loops_allowed = data.get("max_loops_allowed", 100)
debug_path = Path(data.get("debug_path", ".canals_debug/"))
debug_path = Path(data.get("debug_path", ".haystack_debug/"))
pipe = cls(metadata=metadata, max_loops_allowed=max_loops_allowed, debug_path=debug_path)
components_to_reuse = kwargs.get("components", {})
for name, component_data in data.get("components", {}).items():
Expand Down Expand Up @@ -191,8 +191,8 @@ def add_component(self, name: str, instance: Component) -> None:
)

# Create the component's input and output sockets
input_sockets = getattr(instance, "__canals_input__", {})
output_sockets = getattr(instance, "__canals_output__", {})
input_sockets = getattr(instance, "__haystack_input__", {})
output_sockets = getattr(instance, "__haystack_output__", {})

# Add component to the graph, disconnected
logger.debug("Adding component '%s' (%s)", name, instance)
Expand Down Expand Up @@ -456,16 +456,16 @@ def _validate_input(self, data: Dict[str, Any]):
if component_name not in self.graph.nodes:
raise ValueError(f"Component named {component_name} not found in the pipeline.")
instance = self.graph.nodes[component_name]["instance"]
for socket_name, socket in instance.__canals_input__.items():
for socket_name, socket in instance.__haystack_input__.items():
if socket.senders == [] and socket.is_mandatory and socket_name not in component_inputs:
raise ValueError(f"Missing input for component {component_name}: {socket_name}")
for input_name in component_inputs.keys():
if input_name not in instance.__canals_input__:
if input_name not in instance.__haystack_input__:
raise ValueError(f"Input {input_name} not found in component {component_name}.")

for component_name in self.graph.nodes:
instance = self.graph.nodes[component_name]["instance"]
for socket_name, socket in instance.__canals_input__.items():
for socket_name, socket in instance.__haystack_input__.items():
component_inputs = data.get(component_name, {})
if socket.senders == [] and socket.is_mandatory and socket_name not in component_inputs:
raise ValueError(f"Missing input for component {component_name}: {socket_name}")
Expand Down Expand Up @@ -509,7 +509,7 @@ def run( # noqa: C901, PLR0912 pylint: disable=too-many-branches
for component_input, input_value in component_inputs.items():
# Handle mutable input data
data[component_name][component_input] = copy(input_value)
if instance.__canals_input__[component_input].is_variadic:
if instance.__haystack_input__[component_input].is_variadic:
# Components that have variadic inputs need to receive lists as input.
# We don't want to force the user to always pass lists, so we convert single values to lists here.
# If it's already a list we assume the component takes a variadic input of lists, so we
Expand All @@ -524,12 +524,12 @@ def run( # noqa: C901, PLR0912 pylint: disable=too-many-branches
for node_name in self.graph.nodes:
component = self.graph.nodes[node_name]["instance"]

if len(component.__canals_input__) == 0:
if len(component.__haystack_input__) == 0:
# Component has no input, can run right away
to_run.append((node_name, component))
continue

for socket in component.__canals_input__.values():
for socket in component.__haystack_input__.values():
if not socket.senders or socket.is_variadic:
# Component has at least one input not connected or is variadic, can run right away.
to_run.append((node_name, component))
Expand All @@ -552,12 +552,12 @@ def run( # noqa: C901, PLR0912 pylint: disable=too-many-branches
while len(to_run) > 0:
name, comp = to_run.pop(0)

if any(socket.is_variadic for socket in comp.__canals_input__.values()) and not getattr( # type: ignore
if any(socket.is_variadic for socket in comp.__haystack_input__.values()) and not getattr( # type: ignore
comp, "is_greedy", False
):
there_are_non_variadics = False
for _, other_comp in to_run:
if not any(socket.is_variadic for socket in other_comp.__canals_input__.values()): # type: ignore
if not any(socket.is_variadic for socket in other_comp.__haystack_input__.values()): # type: ignore
there_are_non_variadics = True
break

Expand All @@ -566,7 +566,7 @@ def run( # noqa: C901, PLR0912 pylint: disable=too-many-branches
waiting_for_input.append((name, comp))
continue

if name in last_inputs and len(comp.__canals_input__) == len(last_inputs[name]): # type: ignore
if name in last_inputs and len(comp.__haystack_input__) == len(last_inputs[name]): # type: ignore
# This component has all the inputs it needs to run
res = comp.run(**last_inputs[name])

Expand Down Expand Up @@ -640,7 +640,7 @@ def run( # noqa: C901, PLR0912 pylint: disable=too-many-branches
# This is our last resort, if there's no lazy variadic waiting for input
# we're stuck for real and we can't make any progress.
for name, comp in waiting_for_input:
is_variadic = any(socket.is_variadic for socket in comp.__canals_input__.values()) # type: ignore
is_variadic = any(socket.is_variadic for socket in comp.__haystack_input__.values()) # type: ignore
if is_variadic and not getattr(comp, "is_greedy", False):
break
else:
Expand Down Expand Up @@ -671,22 +671,22 @@ def run( # noqa: C901, PLR0912 pylint: disable=too-many-branches
last_inputs[name] = {}

# Lazy variadics must be removed only if there's nothing else to run at this stage
is_variadic = any(socket.is_variadic for socket in comp.__canals_input__.values()) # type: ignore
is_variadic = any(socket.is_variadic for socket in comp.__haystack_input__.values()) # type: ignore
if is_variadic and not getattr(comp, "is_greedy", False):
there_are_only_lazy_variadics = True
for other_name, other_comp in waiting_for_input:
if name == other_name:
continue
there_are_only_lazy_variadics &= any(
socket.is_variadic for socket in other_comp.__canals_input__.values() # type: ignore
socket.is_variadic for socket in other_comp.__haystack_input__.values() # type: ignore
) and not getattr(other_comp, "is_greedy", False)

if not there_are_only_lazy_variadics:
continue

# Find the first component that has all the inputs it needs to run
has_enough_inputs = True
for input_socket in comp.__canals_input__.values(): # type: ignore
for input_socket in comp.__haystack_input__.values(): # type: ignore
if input_socket.is_mandatory and input_socket.name not in last_inputs[name]:
has_enough_inputs = False
break
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
Remove all mentions of Canals by renaming some variables.
`__canals_input__` and `__canals_ouput__` have been renamed respectively to `__haystack_input__` and `__haystack_ouput__`.
`CANALS_VARIADIC_ANNOTATION` has been renamed to `HAYSTACK_VARIADIC_ANNOTATION` and it's value changed from `__canals__variadic_t` to `__haystack__variadic_t`.
Default Pipeline `debug_path` has been changed from `.canals_debug` to `.haystack_debug`.
8 changes: 4 additions & 4 deletions test/components/builders/test_dynamic_chat_prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ def test_initialization(self):

# we have inputs that contain: prompt_source, template_variables + runtime_variables
expected_keys = set(runtime_variables + ["prompt_source", "template_variables"])
assert set(builder.__canals_input__.keys()) == expected_keys
assert set(builder.__haystack_input__.keys()) == expected_keys

# response is always prompt regardless of chat mode
assert set(builder.__canals_output__.keys()) == {"prompt"}
assert set(builder.__haystack_output__.keys()) == {"prompt"}

# prompt_source is a list of ChatMessage
assert builder.__canals_input__["prompt_source"].type == List[ChatMessage]
assert builder.__haystack_input__["prompt_source"].type == List[ChatMessage]

# output is always prompt, but the type is different depending on the chat mode
assert builder.__canals_output__["prompt"].type == List[ChatMessage]
assert builder.__haystack_output__["prompt"].type == List[ChatMessage]

def test_non_empty_chat_messages(self):
prompt_builder = DynamicChatPromptBuilder(runtime_variables=["documents"])
Expand Down
10 changes: 5 additions & 5 deletions test/components/builders/test_dynamic_prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest
from jinja2 import TemplateSyntaxError

from haystack import component, Document, Pipeline
from haystack import Document, Pipeline, component
from haystack.components.builders import DynamicPromptBuilder


Expand All @@ -16,16 +16,16 @@ def test_initialization(self):
# regardless of the chat mode
# we have inputs that contain: prompt_source, template_variables + runtime_variables
expected_keys = set(runtime_variables + ["prompt_source", "template_variables"])
assert set(builder.__canals_input__.keys()) == expected_keys
assert set(builder.__haystack_input__.keys()) == expected_keys

# response is always prompt regardless of chat mode
assert set(builder.__canals_output__.keys()) == {"prompt"}
assert set(builder.__haystack_output__.keys()) == {"prompt"}

# prompt_source is a list of ChatMessage or a string
assert builder.__canals_input__["prompt_source"].type == str
assert builder.__haystack_input__["prompt_source"].type == str

# output is always prompt, but the type is different depending on the chat mode
assert builder.__canals_output__["prompt"].type == str
assert builder.__haystack_output__["prompt"].type == str

def test_processing_a_simple_template_with_provided_variables(self):
runtime_variables = ["var1", "var2", "var3"]
Expand Down
4 changes: 2 additions & 2 deletions test/components/routers/test_conditional_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ def test_router_initialized(self, routes):
router = ConditionalRouter(routes)

assert router.routes == routes
assert set(router.__canals_input__.keys()) == {"query", "streams"}
assert set(router.__canals_output__.keys()) == {"query", "streams"}
assert set(router.__haystack_input__.keys()) == {"query", "streams"}
assert set(router.__haystack_output__.keys()) == {"query", "streams"}

def test_router_evaluate_condition_expressions(self, router):
# first route should be selected
Expand Down
15 changes: 7 additions & 8 deletions test/core/component/test_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@

import pytest

from haystack.core.component import component
from haystack.core.component import Component, InputSocket, OutputSocket, component
from haystack.core.errors import ComponentError
from haystack.core.component import InputSocket, OutputSocket, Component


def test_correct_declaration():
Expand Down Expand Up @@ -106,7 +105,7 @@ def run(self, **kwargs):
return {"value": 1}

comp = MockComponent()
assert comp.__canals_input__ == {"value": InputSocket("value", Any)}
assert comp.__haystack_input__ == {"value": InputSocket("value", Any)}
assert comp.run() == {"value": 1}


Expand All @@ -127,7 +126,7 @@ def run(self, value: int):
return {"value": 1}

comp = MockComponent()
assert comp.__canals_output__ == {"value": OutputSocket("value", int)}
assert comp.__haystack_output__ == {"value": OutputSocket("value", int)}


def test_output_types_decorator_with_compatible_type():
Expand All @@ -145,7 +144,7 @@ def from_dict(cls, data):
return cls()

comp = MockComponent()
assert comp.__canals_output__ == {"value": OutputSocket("value", int)}
assert comp.__haystack_output__ == {"value": OutputSocket("value", int)}


def test_component_decorator_set_it_as_component():
Expand Down Expand Up @@ -174,8 +173,8 @@ def run(self, value: int = 42):
return {"value": value}

comp = MockComponent()
assert comp.__canals_input__["value"].default_value == 42
assert not comp.__canals_input__["value"].is_mandatory
assert comp.__haystack_input__["value"].default_value == 42
assert not comp.__haystack_input__["value"].is_mandatory


def test_keyword_only_args():
Expand All @@ -188,5 +187,5 @@ def run(self, *, arg: int):
return {"value": arg}

comp = MockComponent()
component_inputs = {name: {"type": socket.type} for name, socket in comp.__canals_input__.items()}
component_inputs = {name: {"type": socket.type} for name, socket in comp.__haystack_input__.items()}
assert component_inputs == {"arg": {"type": int}}

0 comments on commit f5e6133

Please sign in to comment.