Tutorial: Adding a New Integration Block to the AutoGPT Platform¶
In this tutorial, we’ll walk through the process of adding a new integration block to the AutoGPT Platform, using a fictional "Example API" as our case study. This guide ensures every step is clear, from defining the block to integrating it across both backend and frontend files. By the end, you’ll have a fully functional integration complete with credentials, API interactions, webhook support, and a polished user interface. Let’s dive in.
Understanding Blocks in the AutoGPT Platform¶
Before we start coding, it’s worth understanding what a block is in the AutoGPT Platform. A block is a modular, reusable unit of functionality that powers workflows. There are two main types: standard blocks, which users can execute manually or call within workflows to perform tasks like API requests, and trigger blocks, which activate automatically in response to external events, such as webhooks. For this tutorial, we’ll create both an ExampleBlock
(a standard block) and an ExampleTriggerBlock
(a trigger block) to showcase the full scope of integration.
To keep our blocks effective, we’ll design them with modularity in mind, focusing each on a single task. We’ll also ensure they’re reusable across workflows, handle errors gracefully, and include test cases for validation. With that foundation, let’s move on to setting up our file structure.
File Layout for the Integration¶
Adding a new integration requires updates across multiple files in the AutoGPT Platform’s backend and frontend. Here’s the complete layout we’ll work with:
Backend Files¶
The backend handles the core logic, credentials, and server-side interactions. We’ll modify or create the following files:
backend/.env.example
: Adds an environment variable for the Example API key.backend/blocks/example/_api.py
: Defines the API client for interacting with the Example API.backend/blocks/example/_auth.py
: Sets up credentials and test credentials for authentication.backend/blocks/example/example.py
: Implements the standard block (ExampleBlock
).backend/blocks/example/triggers.py
: Implements the trigger block (ExampleTriggerBlock
).backend/data/block_cost_config.py
: Tracks usage costs for the block.backend/integrations/credentials_store.py
: Registers Example API credentials in the platform’s credential store.backend/integrations/providers.py
: Defines the provider name for the Example API.backend/integrations/webhooks/example.py
: Manages webhook functionality.backend/util/settings.py
: Adds the API key to the platform’s secrets model.
Frontend Files¶
The frontend ensures users can interact with the integration through the UI. We’ll update these files:
frontend/src/components/integrations/credentials-input.tsx
: Adds an icon for the Example API provider.frontend/src/components/integrations/credentials-provider.tsx
: Sets the display name for the provider in the UI.frontend/src/lib/autogpt-server-api/types.ts
: Registers the provider name as a constant for API interactions.
This structure keeps everything organized and ensures the integration spans both backend logic and frontend presentation. With the layout in place, let’s start building.
Setting Up a Credential Provider¶
Credentials are essential for authenticating our blocks with the Example API. Here’s how we set them up across multiple files.
Defining the Provider Name¶
First, we need to register the Example API as a recognized provider in the platform. In backend/integrations/providers.py
, add this line:
EXAMPLE_PROVIDER = "example-provider"
This addition to the ProviderName
enum uniquely identifies the Example API, allowing the platform to link credentials and blocks to it.
Defining Credentials Input and Test Credentials¶
Next, in backend/blocks/example/_auth.py
, we define how credentials are structured and provide a mock credential for testing:
ExampleCredentialsInput = CredentialsMetaInput[
Literal[ProviderName.EXAMPLE_PROVIDER], Literal["api_key"]
]
This code creates ExampleCredentialsInput
, specifying that it’s tied to the "example-provider" and expects an API key. The use of Literal
ensures type safety, making it clear what credential type is required.
For testing, we also define:
TEST_CREDENTIALS = APIKeyCredentials(
id="9191c4f0-498f-4235-a79c-59c0e37454d4",
provider="example-provider",
api_key=SecretStr("mock-example-api-key"),
title="Mock Example API key",
expires_at=None,
)
This mock credential includes a unique ID, the provider name, a secure API key (hidden via SecretStr
), a title, and no expiration. It’s perfect for testing without needing a real key.
Updating the Credentials Store¶
To make real credentials available, update backend/integrations/credentials_store.py
:
example_credentials = APIKeyCredentials(
id="a2b7f68f-aa6a-4995-99ec-b45b40d33498",
provider="example-provider",
api_key=SecretStr(settings.secrets.example_api_key),
title="Use Credits for Example",
expires_at=None,
)
DEFAULT_CREDENTIALS.append(example_credentials)
This code creates a live credential using an API key from the environment, adds it to the platform’s credential list, and ensures it’s accessible to blocks.
Configuring Environment Variables¶
In backend/.env.example
, add:
EXAMPLE_API_KEY=
This line prompts users to provide their API key in their .env
file. Then, in backend/util/settings.py
, update the Secrets
model:
example_api_key: str = Field(default="", description="Example API Key")
This addition loads the key from the environment, keeping it secure and optional with a default empty value.
Creating an API Client¶
The API client, defined in backend/blocks/example/_api.py
, handles communication with the Example API. Here’s how we build it.
Initializing the Client¶
Start with:
class ExampleClient:
API_BASE_URL = "https://api.example.com/v1"
def __init__(self, credentials: Optional[APIKeyCredentials] = None, custom_requests: Optional[Requests] = None):
headers = {"Content-Type": "application/json"}
if credentials:
headers["Authorization"] = credentials.auth_header()
self._requests = custom_requests or Requests(extra_headers=headers, trusted_origins=["https://api.example.com"])
This class sets a base URL and initializes with optional credentials and a custom request handler (useful for testing). It builds headers with a JSON content type and, if credentials are provided, an authorization header. The Requests
utility ensures secure, standardized HTTP requests.
Implementing API Methods¶
Add a method to create resources:
def create_resource(self, data: Dict) -> Dict:
try:
response = self._requests.post(f"{self.API_BASE_URL}/resources", json=data)
return self._handle_response(response)
except Exception as e:
raise ExampleAPIException(f"Failed to create resource: {str(e)}", 500)
This method sends a POST request with data (e.g., {"name": "Hello"}
), processes the response, and handles errors with a custom exception, ensuring robust error reporting.
Building the Standard Block¶
In backend/blocks/example/example.py
, we define ExampleBlock
:
Defining Input and Output Schemas¶
Start with:
class ExampleBlock(Block):
class Input(BlockSchema):
name: str = SchemaField(description="The name of the example block")
greetings: list[str] = SchemaField(default=["Hello", "Hi", "Hey"])
is_funny: bool = SchemaField(default=True)
credentials: ExampleCredentialsInput = CredentialsField()
class Output(BlockSchema):
response: dict[str, Any] = SchemaField()
all_responses: list[dict[str, Any]] = SchemaField()
greeting_count: int = SchemaField()
error: str = SchemaField()
The Input
schema accepts a name, a list of greetings, a boolean, and credentials, while Output
defines what the block returns: individual responses, all responses, a greeting count, and an error message.
Implementing the Run Method¶
Add:
def run(self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs) -> BlockOutput:
rtn_all_responses = []
for greeting in input_data.greetings:
full_greeting = self.my_static_method(greeting)
message = self.my_function_that_can_be_mocked(full_greeting, credentials)
rtn_all_responses.append({"message": message})
yield "response", {"message": message}
yield "all_responses", rtn_all_responses
yield "greeting_count", len(input_data.greetings)
This method loops through greetings, processes them, calls the API, and yields results incrementally. The my_function_that_can_be_mocked
method, defined as:
def my_function_that_can_be_mocked(self, input: str, credentials: APIKeyCredentials) -> str:
client = ExampleClient(credentials=credentials)
resource_data = {"name": input, "type": "greeting"}
response = client.create_resource(resource_data)
return f"API response: {response.get('message', 'Hello, world!')}"
This connects to the API client, sends data, and formats the response.
Managing Webhooks¶
In backend/integrations/webhooks/example.py
, set up webhook support:
Defining Event Types¶
Define:
class ExampleWebhookEventType(StrEnum):
EXAMPLE_EVENT = "example_event"
ANOTHER_EXAMPLE_EVENT = "another_example_event"
This enum lists possible webhook events from the Example API.
Creating the Webhook Manager¶
Add:
class ExampleWebhookManager(ManualWebhookManagerBase):
PROVIDER_NAME = ProviderName.EXAMPLE_PROVIDER
WebhookEventType = ExampleWebhookEventType
BASE_URL = "https://api.example.com"
This class ties the webhook manager to the Example API provider and event types.
Validating Payloads¶
Implement:
async def validate_payload(cls, webhook: integrations.Webhook, request: Request) -> tuple[dict, str]:
payload = await request.json()
event_type = payload.get("webhook_type", ExampleWebhookEventType.EXAMPLE_EVENT)
return payload, event_type
This async method extracts and validates webhook payloads, defaulting to "example_event"
.
Adding a Trigger Block¶
In backend/blocks/example/triggers.py
, define ExampleTriggerBlock
:
class ExampleTriggerBlock(Block):
class Input(BlockSchema):
payload: dict = SchemaField(hidden=True)
class Output(BlockSchema):
event_data: dict = SchemaField(description="The contents of the example webhook event.")
webhook_config = BlockManualWebhookConfig(
provider="example_provider",
webhook_type=ExampleWebhookEventType.EXAMPLE_EVENT,
)
def run(self, input_data: Input, **kwargs) -> BlockOutput:
logger.info("Example trigger block run with payload: %s", input_data.payload)
yield "event_data", input_data.payload
This block takes a hidden payload, outputs event data, links to the webhook config, and logs and yields the payload when triggered.
Updating the Frontend¶
Finally, integrate the provider into the frontend.
Adding a Provider Icon¶
In frontend/src/components/integrations/credentials-input.tsx
, add:
example: fallbackIcon
This assigns a fallback icon to the "example" provider.
Setting the Display Name¶
In frontend/src/components/integrations/credentials-provider.tsx
, add:
example: "Example"
This sets "Example" as the UI display name.
Registering the Provider Constant¶
In frontend/src/lib/autogpt-server-api/types.ts
, add:
EXAMPLE: "example"
This ensures the provider is recognized in API interactions.
Final Steps¶
To wrap up, update backend/data/block_cost_config.py
with a cost entry for ExampleBlock
, test both blocks with sample inputs, and document their usage in the platform’s docs. You’ve now added a complete integration to the AutoGPT Platform, spanning backend logic, API calls, webhooks, and a user-friendly frontend. Happy coding!