diff --git a/VERSION.txt b/VERSION.txt index b0d6a120b0..a5f3e61bdc 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -2.27.0-rc0 +2.27.0 diff --git a/haystack/components/agents/agent.py b/haystack/components/agents/agent.py index 00b41bfe6d..7cc6df791b 100644 --- a/haystack/components/agents/agent.py +++ b/haystack/components/agents/agent.py @@ -481,6 +481,44 @@ def from_dict(cls, data: dict[str, Any]) -> "Agent": return default_from_dict(cls, data) + def can_run(self, inputs: dict[str, Any]) -> bool: + """ + Check if the agent can run with the given inputs. + + If there's a snapshot input, the agent can always run because it can resume from the snapshot state without + needing any of the other inputs. Otherwise, if any of the main inputs (messages, user_prompt, system_prompt) + are connected as input, they must be provided at runtime. + + For the special case where none of the main inputs are connected, nor provided, and no prompts are set via + initialization, we return True, to raise an exception later in the run method. + + :param inputs: Inputs for the agent. + :returns: True if the agent can run, False otherwise. + """ + # If there's a snapshot, we can always run (we can resume from the snapshot state without needing any of the + # other inputs) + if inputs.get("snapshot") is not None: + return True + + # messages, user_prompt, or system_prompt are the main inputs that can trigger the agent to run. + # If any of them is connected as input it must be provided at runtime, otherwise the agent won't be triggered. + main_inputs = ["messages", "user_prompt", "system_prompt"] + for main_input in main_inputs: + if self.is_socket_connected(main_input) and inputs.get(main_input) is None: + return False + + # Either none of the main inputs are connected, or all connected inputs have a value at runtime + return True + + def is_socket_connected(self, socket_name: str) -> bool: + """ + Check if a socket is connected to any sender. + + :param socket_name: The name of the socket to check. + :returns: True if the socket is connected to at least one sender, False otherwise. + """ + return bool(self.__haystack_input__[socket_name].senders) + def _create_agent_span(self) -> Any: """ Create a span for the agent run. diff --git a/haystack/core/pipeline/component_checks.py b/haystack/core/pipeline/component_checks.py index 54a59dc28f..8111c70095 100644 --- a/haystack/core/pipeline/component_checks.py +++ b/haystack/core/pipeline/component_checks.py @@ -19,6 +19,10 @@ def can_component_run(component: dict, inputs: dict) -> bool: :param component: Component metadata and the component instance. :param inputs: Inputs for the component. """ + instance = component["instance"] + if hasattr(instance, "can_run") and callable(instance.can_run): + return instance.can_run(inputs) + received_all_mandatory_inputs = are_all_sockets_ready(component, inputs, only_check_mandatory=True) received_trigger = has_any_trigger(component, inputs)