From d674cb80e237393a7f6a4a9e9344805f89237d6b Mon Sep 17 00:00:00 2001 From: Swifty Date: Wed, 12 Nov 2025 14:58:42 +0100 Subject: [PATCH] refactor(backend): Improve Block Error Handling (#11366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need a way to differentiate between serious errors that cause on call alerts and block errors. This PR address this need by ensuring all errors that occur during execution of a block are of Subtype BlockError ### Changes 🏗️ - Introduced BlockErrors and its subtypes - Updated current errors that are emitted by blocks to use BlockError - Update executor manager, to errors emitted when running a block that are not of type BlockError to BlockUnknownError ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] checked tests still work - [x] Ensured block error message is readable and useful --- .../backend/backend/data/block.py | 37 +++++++++++++++++-- .../backend/backend/util/exceptions.py | 29 +++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/autogpt_platform/backend/backend/data/block.py b/autogpt_platform/backend/backend/data/block.py index c7d0f9067c..2f05f444ae 100644 --- a/autogpt_platform/backend/backend/data/block.py +++ b/autogpt_platform/backend/backend/data/block.py @@ -29,6 +29,13 @@ from backend.data.model import NodeExecutionStats from backend.integrations.providers import ProviderName from backend.util import json from backend.util.cache import cached +from backend.util.exceptions import ( + BlockError, + BlockExecutionError, + BlockInputError, + BlockOutputError, + BlockUnknownError, +) from backend.util.settings import Config from .model import ( @@ -542,9 +549,25 @@ class Block(ABC, Generic[BlockSchemaInputType, BlockSchemaOutputType]): ) async def execute(self, input_data: BlockInput, **kwargs) -> BlockOutput: + try: + async for output_name, output_data in self._execute(input_data, **kwargs): + yield output_name, output_data + except Exception as ex: + if not isinstance(ex, BlockError): + raise BlockUnknownError( + message=str(ex), + block_name=self.name, + block_id=self.id, + ) from ex + else: + raise ex + + async def _execute(self, input_data: BlockInput, **kwargs) -> BlockOutput: if error := self.input_schema.validate_data(input_data): - raise ValueError( - f"Unable to execute block with invalid input data: {error}" + raise BlockInputError( + message=f"Unable to execute block with invalid input data: {error}", + block_name=self.name, + block_id=self.id, ) async for output_name, output_data in self.run( @@ -552,11 +575,17 @@ class Block(ABC, Generic[BlockSchemaInputType, BlockSchemaOutputType]): **kwargs, ): if output_name == "error": - raise RuntimeError(output_data) + raise BlockExecutionError( + message=output_data, block_name=self.name, block_id=self.id + ) if self.block_type == BlockType.STANDARD and ( error := self.output_schema.validate_field(output_name, output_data) ): - raise ValueError(f"Block produced an invalid output data: {error}") + raise BlockOutputError( + message=f"Block produced an invalid output data: {error}", + block_name=self.name, + block_id=self.id, + ) yield output_name, output_data def is_triggered_by_event_type( diff --git a/autogpt_platform/backend/backend/util/exceptions.py b/autogpt_platform/backend/backend/util/exceptions.py index 1d992fc6e0..0ea4bf2b0a 100644 --- a/autogpt_platform/backend/backend/util/exceptions.py +++ b/autogpt_platform/backend/backend/util/exceptions.py @@ -1,6 +1,35 @@ from typing import Mapping +class BlockError(Exception): + """An error occurred during the running of a block""" + + def __init__(self, message: str, block_name: str, block_id: str) -> None: + super().__init__(message) + self.message = message + self.block_name = block_name + self.block_id = block_id + + def __str__(self): + return f"raised by {self.block_name} with message: {self.message}. block_id: {self.block_id}" + + +class BlockInputError(BlockError, ValueError): + """The block had incorrect inputs, resulting in an error condition""" + + +class BlockOutputError(BlockError, ValueError): + """The block had incorrect outputs, resulting in an error condition""" + + +class BlockExecutionError(BlockError, ValueError): + """The block failed to execute at runtime, resulting in a handled error""" + + +class BlockUnknownError(BlockError): + """Critical unknown error with block handling""" + + class MissingConfigError(Exception): """The attempted operation requires configuration which is not available"""