feat(backend): Standardize error handling with BlockSchemaInput & BlockSchemaOutput base class (#11257)

<!-- Clearly explain the need for these changes: -->

This PR addresses the need for consistent error handling across all
blocks in the AutoGPT platform. Previously, each block had to manually
define an `error` field in their output schema, leading to code
duplication and potential inconsistencies. Some blocks might forget to
include the error field, making error handling unpredictable.

### Changes 🏗️

<!-- Concisely describe all of the changes made in this pull request:
-->

- **Created `BlockSchemaOutput` base class**: New base class that
extends `BlockSchema` with a standardized `error` field
- **Created `BlockSchemaInput` base class**: Added for consistency and
future extensibility
- **Updated 140+ block implementations**: Changed all block `Output`
classes from `class Output(BlockSchema):` to `class
Output(BlockSchemaOutput):`
- **Removed manual error field definitions**: Eliminated hundreds of
duplicate `error: str = SchemaField(...)` definitions
- **Updated type annotations**: Changed `Block[BlockSchema,
BlockSchema]` to `Block[BlockSchemaInput, BlockSchemaOutput]` throughout
the codebase
- **Fixed imports**: Added `BlockSchemaInput` and `BlockSchemaOutput`
imports to all relevant files
- **Maintained backward compatibility**: Updated `EmptySchema` to
inherit from `BlockSchemaOutput`

**Key Benefits:**
- Consistent error handling across all blocks
- Reduced code duplication (removed ~200 lines of repetitive error field
definitions)
- Type safety improvements with distinct input/output schema types
- Blocks can still override error field with more specific descriptions
when needed

### 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:
  <!-- Put your test plan here: -->
- [x] Verified `poetry run format` passes (all linting, formatting, and
type checking)
- [x] Tested block instantiation works correctly (MediaDurationBlock,
UnrealTextToSpeechBlock)
- [x] Confirmed error fields are automatically present in all updated
blocks
- [x] Verified block loading system works (successfully loads 353+
blocks)
  - [x] Tested backward compatibility with EmptySchema
- [x] Confirmed blocks can still override error field with custom
descriptions
  - [x] Validated core schema inheritance chain works correctly

#### For configuration changes:

- [x] `.env.default` is updated or already compatible with my changes
- [x] `docker-compose.yml` is updated or already compatible with my
changes
- [x] I have included a list of my configuration changes in the PR
description (under **Changes**)

*Note: No configuration changes were needed for this refactoring.*

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Lluis Agusti <hi@llu.lu>
Co-authored-by: Ubbe <hi@ubbe.dev>
This commit is contained in:
Zamil Majdy
2025-10-30 19:28:08 +07:00
committed by GitHub
parent 3dc5208f71
commit 2f8cdf62ba
164 changed files with 1678 additions and 1074 deletions

View File

@@ -55,6 +55,8 @@ from backend.sdk import (
BlockCategory,
BlockOutput,
BlockSchema,
BlockSchemaInput,
BlockSchemaOutput,
CredentialsMetaInput,
SchemaField,
)
@@ -62,7 +64,7 @@ from ._config import my_provider
class MyBlock(Block):
class Input(BlockSchema):
class Input(BlockSchemaInput):
# Define input fields
credentials: CredentialsMetaInput = my_provider.credentials_field(
description="API credentials for My Provider"
@@ -80,11 +82,11 @@ class MyBlock(Block):
advanced=True # Hidden by default in UI
)
class Output(BlockSchema):
class Output(BlockSchemaOutput):
# Define output fields
results: list = SchemaField(description="List of results")
count: int = SchemaField(description="Total count")
error: str = SchemaField(description="Error message if failed")
# error output pin is already defined on BlockSchemaOutput
def __init__(self):
super().__init__(
@@ -145,7 +147,7 @@ The `ProviderBuilder` allows you to:
1. **Unique ID**: Generate using `uuid.uuid4()`
2. **Categories**: Choose from `BlockCategory` enum (e.g., SEARCH, AI, PRODUCTIVITY)
3. **async run()**: Main execution method that yields outputs
4. **Error handling**: Always include error output field
4. **Error handling**: Error output pin is already defined on BlockSchemaOutput
## Advanced Features
@@ -225,7 +227,7 @@ backend/blocks/my_provider/
## Best Practices
1. **Error Handling**: Always include an error field in outputs
1. **Error Handling**: Error output pin is already defined on BlockSchemaOutput
2. **Credentials**: Use the provider's `credentials_field()` method
3. **Validation**: Use SchemaField constraints (ge, le, min_length, etc.)
4. **Categories**: Choose appropriate categories for discoverability

View File

@@ -20,13 +20,13 @@ Follow these steps to create and test a new block:
Every block should contain the following:
```python
from backend.data.block import Block, BlockSchema, BlockOutput
from backend.data.block import Block, BlockSchemaInput, BlockSchemaOutput, BlockOutput
```
Example for the Wikipedia summary block:
```python
from backend.data.block import Block, BlockSchema, BlockOutput
from backend.data.block import Block, BlockSchemaInput, BlockSchemaOutput, BlockOutput
from backend.utils.get_request import GetRequest
import requests
@@ -42,12 +42,11 @@ Follow these steps to create and test a new block:
Example:
```python
class Input(BlockSchema):
class Input(BlockSchemaInput):
topic: str # The topic to get the Wikipedia summary for
class Output(BlockSchema):
class Output(BlockSchemaOutput):
summary: str # The summary of the topic from Wikipedia
error: str # Any error message if the request fails, error field needs to be named `error`.
```
4. **Implement the `__init__` method, including test data and mocks:**
@@ -173,14 +172,14 @@ from backend.data.model import (
Credentials,
)
from backend.data.block import Block, BlockOutput, BlockSchema
from backend.data.block import Block, BlockOutput, BlockSchemaInput, BlockSchemaOutput
from backend.data.model import CredentialsField
from backend.integrations.providers import ProviderName
# API Key auth:
class BlockWithAPIKeyAuth(Block):
class Input(BlockSchema):
class Input(BlockSchemaInput):
# Note that the type hint below is require or you will get a type error.
# The first argument is the provider name, the second is the credential type.
credentials: CredentialsMetaInput[
@@ -203,7 +202,7 @@ class BlockWithAPIKeyAuth(Block):
# OAuth:
class BlockWithOAuth(Block):
class Input(BlockSchema):
class Input(BlockSchemaInput):
# Note that the type hint below is require or you will get a type error.
# The first argument is the provider name, the second is the credential type.
credentials: CredentialsMetaInput[
@@ -226,7 +225,7 @@ class BlockWithOAuth(Block):
# API Key auth + OAuth:
class BlockWithAPIKeyAndOAuth(Block):
class Input(BlockSchema):
class Input(BlockSchemaInput):
# Note that the type hint below is require or you will get a type error.
# The first argument is the provider name, the second is the credential type.
credentials: CredentialsMetaInput[