From ca0620b102316c42e7d301c50759f27048ef7a8c Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Wed, 11 Feb 2026 16:13:52 +0100 Subject: [PATCH 1/7] fix type import and image generator --- .../backend/api/features/store/image_gen.py | 19 ++++++++++--------- .../backend/backend/blocks/__init__.py | 4 ++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/autogpt_platform/backend/backend/api/features/store/image_gen.py b/autogpt_platform/backend/backend/api/features/store/image_gen.py index 087a7895ba..64ac203182 100644 --- a/autogpt_platform/backend/backend/api/features/store/image_gen.py +++ b/autogpt_platform/backend/backend/api/features/store/image_gen.py @@ -7,15 +7,6 @@ from replicate.client import Client as ReplicateClient from replicate.exceptions import ReplicateError from replicate.helpers import FileOutput -from backend.blocks.ideogram import ( - AspectRatio, - ColorPalettePreset, - IdeogramModelBlock, - IdeogramModelName, - MagicPromptOption, - StyleType, - UpscaleOption, -) from backend.data.graph import GraphBaseMeta from backend.data.model import CredentialsMetaInput, ProviderName from backend.integrations.credentials_store import ideogram_credentials @@ -50,6 +41,16 @@ async def generate_agent_image_v2(graph: GraphBaseMeta | AgentGraph) -> io.Bytes if not ideogram_credentials.api_key: raise ValueError("Missing Ideogram API key") + from backend.blocks.ideogram import ( + AspectRatio, + ColorPalettePreset, + IdeogramModelBlock, + IdeogramModelName, + MagicPromptOption, + StyleType, + UpscaleOption, + ) + name = graph.name description = f"{name} ({graph.description})" if graph.description else name diff --git a/autogpt_platform/backend/backend/blocks/__init__.py b/autogpt_platform/backend/backend/blocks/__init__.py index 5876be0893..d9446280a6 100644 --- a/autogpt_platform/backend/backend/blocks/__init__.py +++ b/autogpt_platform/backend/backend/blocks/__init__.py @@ -125,12 +125,12 @@ def _all_subclasses(cls: type[T]) -> list[type[T]]: # ============== Block access helper functions ============== # -def get_blocks() -> dict[str, Type[AnyBlockSchema]]: +def get_blocks() -> dict[str, Type["AnyBlockSchema"]]: return load_all_blocks() # Note on the return type annotation: https://github.com/microsoft/pyright/issues/10281 -def get_block(block_id: str) -> AnyBlockSchema | None: +def get_block(block_id: str) -> "AnyBlockSchema | None": cls = get_blocks().get(block_id) return cls() if cls else None From cad54a9f3e3ac346cc78bb5b2658845bcff5f36e Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Wed, 11 Feb 2026 17:12:43 +0100 Subject: [PATCH 2/7] eliminate more cross-imports --- .../backend/backend/api/external/v1/routes.py | 2 +- .../backend/api/features/builder/db.py | 19 +++++++----- .../backend/api/features/builder/model.py | 2 +- .../backend/api/features/builder/routes.py | 2 +- .../backend/api/features/library/db.py | 3 +- .../backend/api/features/library/model.py | 7 +++-- .../backend/backend/api/features/v1.py | 8 +++-- .../backend/backend/blocks/_base.py | 29 +++++++++---------- .../backend/backend/data/execution.py | 3 +- .../backend/backend/data/graph.py | 9 ++---- .../backend/backend/data/model.py | 7 +++++ .../backend/backend/executor/manager.py | 10 +++++-- .../backend/backend/executor/scheduler.py | 3 +- .../backend/backend/executor/utils.py | 15 +++++----- 14 files changed, 67 insertions(+), 52 deletions(-) diff --git a/autogpt_platform/backend/backend/api/external/v1/routes.py b/autogpt_platform/backend/backend/api/external/v1/routes.py index bae61124e2..8ef2da3e31 100644 --- a/autogpt_platform/backend/backend/api/external/v1/routes.py +++ b/autogpt_platform/backend/backend/api/external/v1/routes.py @@ -12,11 +12,11 @@ import backend.api.features.store.cache as store_cache import backend.api.features.store.model as store_model import backend.blocks from backend.api.external.middleware import require_permission -from backend.blocks._base import BlockInput, CompletedBlockOutput from backend.data import execution as execution_db from backend.data import graph as graph_db from backend.data import user as user_db from backend.data.auth.base import APIAuthorizationInfo +from backend.data.model import BlockInput, CompletedBlockOutput from backend.executor.utils import add_graph_execution from backend.util.settings import Settings diff --git a/autogpt_platform/backend/backend/api/features/builder/db.py b/autogpt_platform/backend/backend/api/features/builder/db.py index 7b228c043d..e8d35b0bb5 100644 --- a/autogpt_platform/backend/backend/api/features/builder/db.py +++ b/autogpt_platform/backend/backend/api/features/builder/db.py @@ -10,9 +10,14 @@ import backend.api.features.library.db as library_db import backend.api.features.library.model as library_model import backend.api.features.store.db as store_db import backend.api.features.store.model as store_model -import backend.blocks._base from backend.blocks import load_all_blocks -from backend.blocks._base import AnyBlockSchema, BlockCategory, BlockInfo, BlockSchema +from backend.blocks._base import ( + AnyBlockSchema, + BlockCategory, + BlockInfo, + BlockSchema, + BlockType, +) from backend.blocks.llm import LlmModel from backend.data.db import query_raw_with_schema from backend.integrations.providers import ProviderName @@ -22,7 +27,7 @@ from backend.util.models import Pagination from .model import ( BlockCategoryResponse, BlockResponse, - BlockType, + BlockTypeFilter, CountResponse, FilterType, Provider, @@ -88,7 +93,7 @@ def get_block_categories(category_blocks: int = 3) -> list[BlockCategoryResponse def get_blocks( *, category: str | None = None, - type: BlockType | None = None, + type: BlockTypeFilter | None = None, provider: ProviderName | None = None, page: int = 1, page_size: int = 50, @@ -669,9 +674,9 @@ async def get_suggested_blocks(count: int = 5) -> list[BlockInfo]: for block_type in load_all_blocks().values(): block: AnyBlockSchema = block_type() if block.disabled or block.block_type in ( - backend.blocks._base.BlockType.INPUT, - backend.blocks._base.BlockType.OUTPUT, - backend.blocks._base.BlockType.AGENT, + BlockType.INPUT, + BlockType.OUTPUT, + BlockType.AGENT, ): continue # Find the execution count for this block diff --git a/autogpt_platform/backend/backend/api/features/builder/model.py b/autogpt_platform/backend/backend/api/features/builder/model.py index f9b883d5bc..8aa8ed06ed 100644 --- a/autogpt_platform/backend/backend/api/features/builder/model.py +++ b/autogpt_platform/backend/backend/api/features/builder/model.py @@ -15,7 +15,7 @@ FilterType = Literal[ "my_agents", ] -BlockType = Literal["all", "input", "action", "output"] +BlockTypeFilter = Literal["all", "input", "action", "output"] class SearchEntry(BaseModel): diff --git a/autogpt_platform/backend/backend/api/features/builder/routes.py b/autogpt_platform/backend/backend/api/features/builder/routes.py index 15b922178d..091f477178 100644 --- a/autogpt_platform/backend/backend/api/features/builder/routes.py +++ b/autogpt_platform/backend/backend/api/features/builder/routes.py @@ -88,7 +88,7 @@ async def get_block_categories( ) async def get_blocks( category: Annotated[str | None, fastapi.Query()] = None, - type: Annotated[builder_model.BlockType | None, fastapi.Query()] = None, + type: Annotated[builder_model.BlockTypeFilter | None, fastapi.Query()] = None, provider: Annotated[ProviderName | None, fastapi.Query()] = None, page: Annotated[int, fastapi.Query()] = 1, page_size: Annotated[int, fastapi.Query()] = 50, diff --git a/autogpt_platform/backend/backend/api/features/library/db.py b/autogpt_platform/backend/backend/api/features/library/db.py index 1c7219cd6e..9204794595 100644 --- a/autogpt_platform/backend/backend/api/features/library/db.py +++ b/autogpt_platform/backend/backend/api/features/library/db.py @@ -12,12 +12,11 @@ import backend.api.features.store.image_gen as store_image_gen import backend.api.features.store.media as store_media import backend.data.graph as graph_db import backend.data.integrations as integrations_db -from backend.blocks._base import BlockInput from backend.data.db import transaction from backend.data.execution import get_graph_execution from backend.data.graph import GraphSettings from backend.data.includes import AGENT_PRESET_INCLUDE, library_agent_include -from backend.data.model import CredentialsMetaInput +from backend.data.model import BlockInput, CredentialsMetaInput from backend.integrations.creds_manager import IntegrationCredentialsManager from backend.integrations.webhooks.graph_lifecycle_hooks import ( on_graph_activate, diff --git a/autogpt_platform/backend/backend/api/features/library/model.py b/autogpt_platform/backend/backend/api/features/library/model.py index 69bbe85486..cf2a7cd876 100644 --- a/autogpt_platform/backend/backend/api/features/library/model.py +++ b/autogpt_platform/backend/backend/api/features/library/model.py @@ -6,9 +6,12 @@ import prisma.enums import prisma.models import pydantic -from backend.blocks._base import BlockInput from backend.data.graph import GraphModel, GraphSettings, GraphTriggerInfo -from backend.data.model import CredentialsMetaInput, is_credentials_field_name +from backend.data.model import ( + BlockInput, + CredentialsMetaInput, + is_credentials_field_name, +) from backend.util.json import loads as json_loads from backend.util.models import Pagination diff --git a/autogpt_platform/backend/backend/api/features/v1.py b/autogpt_platform/backend/backend/api/features/v1.py index 713bbaa7d6..371967eba7 100644 --- a/autogpt_platform/backend/backend/api/features/v1.py +++ b/autogpt_platform/backend/backend/api/features/v1.py @@ -41,7 +41,6 @@ from backend.api.model import ( UploadFileResponse, ) from backend.blocks import get_block, get_blocks -from backend.blocks._base import BlockInput, CompletedBlockOutput from backend.data import execution as execution_db from backend.data import graph as graph_db from backend.data.auth import api_key as api_key_db @@ -55,7 +54,12 @@ from backend.data.credit import ( set_auto_top_up, ) from backend.data.graph import GraphSettings -from backend.data.model import CredentialsMetaInput, UserOnboarding +from backend.data.model import ( + BlockInput, + CompletedBlockOutput, + CredentialsMetaInput, + UserOnboarding, +) from backend.data.notifications import NotificationPreference, NotificationPreferenceDTO from backend.data.onboarding import ( FrontendOnboardingStep, diff --git a/autogpt_platform/backend/backend/blocks/_base.py b/autogpt_platform/backend/backend/blocks/_base.py index 626b99fc9f..e70fdf76f2 100644 --- a/autogpt_platform/backend/backend/blocks/_base.py +++ b/autogpt_platform/backend/backend/blocks/_base.py @@ -1,7 +1,6 @@ import inspect import logging from abc import ABC, abstractmethod -from collections.abc import AsyncGenerator as AsyncGen from enum import Enum from typing import ( TYPE_CHECKING, @@ -21,6 +20,16 @@ import jsonref import jsonschema from pydantic import BaseModel +from backend.data.model import ( + BlockInput, + BlockOutput, + BlockOutputEntry, + Credentials, + CredentialsFieldInfo, + CredentialsMetaInput, + SchemaField, + is_credentials_field_name, +) from backend.integrations.providers import ProviderName from backend.util import json from backend.util.exceptions import ( @@ -32,30 +41,18 @@ from backend.util.exceptions import ( ) from backend.util.settings import Config -from ..data.model import ( - ContributorDetails, - Credentials, - CredentialsFieldInfo, - CredentialsMetaInput, - SchemaField, - is_credentials_field_name, -) - logger = logging.getLogger(__name__) if TYPE_CHECKING: from backend.data.execution import ExecutionContext - from backend.data.model import NodeExecutionStats + from backend.data.model import ContributorDetails, NodeExecutionStats from ..data.graph import Link app_config = Config() -BlockInput = dict[str, Any] # Input: 1 input pin consumes 1 data. -BlockOutputEntry = tuple[str, Any] # Output data should be a tuple of (name, value). -BlockOutput = AsyncGen[BlockOutputEntry, None] # Output: 1 output pin produces n data. + BlockTestOutput = BlockOutputEntry | tuple[str, Callable[[Any], bool]] -CompletedBlockOutput = dict[str, list[Any]] # Completed stream, collected as a dict. class BlockType(Enum): @@ -426,7 +423,7 @@ class Block(ABC, Generic[BlockSchemaInputType, BlockSchemaOutputType]): self, id: str = "", description: str = "", - contributors: list[ContributorDetails] = [], + contributors: list["ContributorDetails"] = [], categories: set[BlockCategory] | None = None, input_schema: Type[BlockSchemaInputType] = EmptyInputSchema, output_schema: Type[BlockSchemaOutputType] = EmptyOutputSchema, diff --git a/autogpt_platform/backend/backend/data/execution.py b/autogpt_platform/backend/backend/data/execution.py index 800edb00e3..821820f998 100644 --- a/autogpt_platform/backend/backend/data/execution.py +++ b/autogpt_platform/backend/backend/data/execution.py @@ -40,7 +40,8 @@ from pydantic import BaseModel, ConfigDict, JsonValue, ValidationError from pydantic.fields import Field from backend.blocks import get_block, get_io_block_ids, get_webhook_block_ids -from backend.blocks._base import BlockInput, BlockType, CompletedBlockOutput +from backend.blocks._base import BlockType +from backend.data.model import BlockInput, CompletedBlockOutput from backend.util import type as type_utils from backend.util.exceptions import DatabaseError from backend.util.json import SafeJson diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index 1de7335773..4ced05982b 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -24,13 +24,7 @@ from pydantic import BaseModel, BeforeValidator, Field from pydantic.fields import computed_field from backend.blocks import get_block, get_blocks -from backend.blocks._base import ( - AnyBlockSchema, - Block, - BlockInput, - BlockType, - EmptySchema, -) +from backend.blocks._base import AnyBlockSchema, Block, BlockType, EmptySchema from backend.blocks.agent import AgentExecutorBlock from backend.blocks.io import AgentInputBlock, AgentOutputBlock from backend.blocks.llm import LlmModel @@ -38,6 +32,7 @@ from backend.data.db import prisma as db from backend.data.dynamic_fields import is_tool_pin, sanitize_pin_name from backend.data.includes import MAX_GRAPH_VERSIONS_FETCH from backend.data.model import ( + BlockInput, CredentialsFieldInfo, CredentialsMetaInput, is_credentials_field_name, diff --git a/autogpt_platform/backend/backend/data/model.py b/autogpt_platform/backend/backend/data/model.py index 7bdfef059b..45601075b8 100644 --- a/autogpt_platform/backend/backend/data/model.py +++ b/autogpt_platform/backend/backend/data/model.py @@ -10,6 +10,7 @@ from typing import ( TYPE_CHECKING, Annotated, Any, + AsyncGenerator, Callable, ClassVar, Generic, @@ -168,6 +169,12 @@ T = TypeVar("T") logger = logging.getLogger(__name__) +BlockInput = dict[str, Any] # Input: 1 input pin <- 1 data. +BlockOutputEntry = tuple[str, Any] # Output data should be a tuple of (name, value). +BlockOutput = AsyncGenerator[BlockOutputEntry, None] # Output: 1 output pin -> N data. +CompletedBlockOutput = dict[str, list[Any]] # Completed stream, collected as a dict. + + class BlockSecret: def __init__(self, key: Optional[str] = None, value: Optional[str] = None): if value is not None: diff --git a/autogpt_platform/backend/backend/executor/manager.py b/autogpt_platform/backend/backend/executor/manager.py index 2446d84412..50f8bff108 100644 --- a/autogpt_platform/backend/backend/executor/manager.py +++ b/autogpt_platform/backend/backend/executor/manager.py @@ -17,7 +17,7 @@ from prometheus_client import Gauge, start_http_server from redis.asyncio.lock import Lock as AsyncRedisLock from backend.blocks import get_block -from backend.blocks._base import BlockInput, BlockOutput, BlockOutputEntry, BlockSchema +from backend.blocks._base import BlockSchema from backend.blocks.agent import AgentExecutorBlock from backend.blocks.io import AgentOutputBlock from backend.data import redis_client as redis @@ -34,7 +34,13 @@ from backend.data.execution import ( NodesInputMasks, ) from backend.data.graph import Link, Node -from backend.data.model import GraphExecutionStats, NodeExecutionStats +from backend.data.model import ( + BlockInput, + BlockOutput, + BlockOutputEntry, + GraphExecutionStats, + NodeExecutionStats, +) from backend.data.notifications import ( AgentRunData, LowBalanceData, diff --git a/autogpt_platform/backend/backend/executor/scheduler.py b/autogpt_platform/backend/backend/executor/scheduler.py index b0609827e6..3d02b34f9c 100644 --- a/autogpt_platform/backend/backend/executor/scheduler.py +++ b/autogpt_platform/backend/backend/executor/scheduler.py @@ -24,9 +24,8 @@ from dotenv import load_dotenv from pydantic import BaseModel, Field, ValidationError from sqlalchemy import MetaData, create_engine -from backend.blocks._base import BlockInput from backend.data.execution import GraphExecutionWithNodes -from backend.data.model import CredentialsMetaInput +from backend.data.model import BlockInput, CredentialsMetaInput from backend.executor import utils as execution_utils from backend.monitoring import ( NotificationJobArgs, diff --git a/autogpt_platform/backend/backend/executor/utils.py b/autogpt_platform/backend/backend/executor/utils.py index 68def74706..ff54398b68 100644 --- a/autogpt_platform/backend/backend/executor/utils.py +++ b/autogpt_platform/backend/backend/executor/utils.py @@ -9,13 +9,7 @@ from typing import Mapping, Optional, cast from pydantic import BaseModel, JsonValue, ValidationError from backend.blocks import get_block -from backend.blocks._base import ( - Block, - BlockCostType, - BlockInput, - BlockOutputEntry, - BlockType, -) +from backend.blocks._base import Block, BlockCostType, BlockType from backend.data import execution as execution_db from backend.data import graph as graph_db from backend.data import human_review as human_review_db @@ -35,7 +29,12 @@ from backend.data.execution import ( NodesInputMasks, ) from backend.data.graph import GraphModel, Node -from backend.data.model import USER_TIMEZONE_NOT_SET, CredentialsMetaInput +from backend.data.model import ( + USER_TIMEZONE_NOT_SET, + BlockInput, + BlockOutputEntry, + CredentialsMetaInput, +) from backend.data.rabbitmq import Exchange, ExchangeType, Queue, RabbitMQConfig from backend.util.clients import ( get_async_execution_event_bus, From 16c8b2a6e3fffe12a53bf7df399b34a58258c9c9 Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Wed, 11 Feb 2026 17:31:27 +0100 Subject: [PATCH 3/7] maybe this works? --- autogpt_platform/backend/backend/data/__init__.py | 13 ++++++++++--- autogpt_platform/backend/backend/data/graph.py | 6 ++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/autogpt_platform/backend/backend/data/__init__.py b/autogpt_platform/backend/backend/data/__init__.py index c98667e362..45f8f8ea85 100644 --- a/autogpt_platform/backend/backend/data/__init__.py +++ b/autogpt_platform/backend/backend/data/__init__.py @@ -1,8 +1,15 @@ -from backend.api.features.library.model import LibraryAgentPreset - from .graph import NodeModel from .integrations import Webhook # noqa: F401 # Resolve Webhook forward references NodeModel.model_rebuild() -LibraryAgentPreset.model_rebuild() + + +def _rebuild_library_agent_preset() -> None: + """Deferred model rebuild to avoid circular import.""" + from backend.api.features.library.model import LibraryAgentPreset + + LibraryAgentPreset.model_rebuild() + + +_rebuild_library_agent_preset() diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index 4ced05982b..16cfeb9faa 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -24,7 +24,7 @@ from pydantic import BaseModel, BeforeValidator, Field from pydantic.fields import computed_field from backend.blocks import get_block, get_blocks -from backend.blocks._base import AnyBlockSchema, Block, BlockType, EmptySchema +from backend.blocks._base import Block, BlockType, EmptySchema from backend.blocks.agent import AgentExecutorBlock from backend.blocks.io import AgentInputBlock, AgentOutputBlock from backend.blocks.llm import LlmModel @@ -47,6 +47,8 @@ from .db import BaseDbModel, query_raw_with_schema, transaction from .includes import AGENT_GRAPH_INCLUDE, AGENT_NODE_INCLUDE if TYPE_CHECKING: + from backend.blocks._base import AnyBlockSchema + from .execution import NodesInputMasks from .integrations import Webhook @@ -122,7 +124,7 @@ class Node(BaseDbModel): return self.metadata.get("credentials_optional", False) @property - def block(self) -> AnyBlockSchema | "_UnknownBlockBase": + def block(self) -> "AnyBlockSchema | _UnknownBlockBase": """Get the block for this node. Returns UnknownBlock if block is deleted/missing.""" block = get_block(self.block_id) if not block: From 85d97a9d5c9596e41361a5f4f63bed365a835b36 Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Wed, 11 Feb 2026 17:40:29 +0100 Subject: [PATCH 4/7] or this? --- autogpt_platform/backend/backend/data/__init__.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/autogpt_platform/backend/backend/data/__init__.py b/autogpt_platform/backend/backend/data/__init__.py index 45f8f8ea85..7bd5cdf218 100644 --- a/autogpt_platform/backend/backend/data/__init__.py +++ b/autogpt_platform/backend/backend/data/__init__.py @@ -1,14 +1,12 @@ -from .graph import NodeModel -from .integrations import Webhook # noqa: F401 - -# Resolve Webhook forward references -NodeModel.model_rebuild() - - def _rebuild_library_agent_preset() -> None: """Deferred model rebuild to avoid circular import.""" from backend.api.features.library.model import LibraryAgentPreset + from .graph import NodeModel + from .integrations import Webhook # noqa: F401 + + # Resolve Webhook forward references + NodeModel.model_rebuild() LibraryAgentPreset.model_rebuild() From 6591b2171cab482ad4041de7425aaee5ff333b24 Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Thu, 12 Feb 2026 10:07:43 +0100 Subject: [PATCH 5/7] fixed! :) --- .../backend/backend/data/__init__.py | 12 ----------- .../backend/backend/data/graph.py | 7 +------ .../backend/backend/data/graph_test.py | 1 - .../backend/backend/data/integrations.py | 15 ++++++-------- .../webhooks/graph_lifecycle_hooks.py | 20 ++++++++++--------- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/autogpt_platform/backend/backend/data/__init__.py b/autogpt_platform/backend/backend/data/__init__.py index 7bd5cdf218..8b13789179 100644 --- a/autogpt_platform/backend/backend/data/__init__.py +++ b/autogpt_platform/backend/backend/data/__init__.py @@ -1,13 +1 @@ -def _rebuild_library_agent_preset() -> None: - """Deferred model rebuild to avoid circular import.""" - from backend.api.features.library.model import LibraryAgentPreset - from .graph import NodeModel - from .integrations import Webhook # noqa: F401 - - # Resolve Webhook forward references - NodeModel.model_rebuild() - LibraryAgentPreset.model_rebuild() - - -_rebuild_library_agent_preset() diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index 16cfeb9faa..2fb5030bb1 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -50,7 +50,6 @@ if TYPE_CHECKING: from backend.blocks._base import AnyBlockSchema from .execution import NodesInputMasks - from .integrations import Webhook logger = logging.getLogger(__name__) @@ -141,12 +140,10 @@ class NodeModel(Node): graph_version: int webhook_id: Optional[str] = None - webhook: Optional["Webhook"] = None + # webhook: Optional["Webhook"] = None # deprecated @staticmethod def from_db(node: AgentNode, for_export: bool = False) -> "NodeModel": - from .integrations import Webhook - obj = NodeModel( id=node.id, block_id=node.agentBlockId, @@ -155,7 +152,6 @@ class NodeModel(Node): graph_id=node.agentGraphId, graph_version=node.agentGraphVersion, webhook_id=node.webhookId, - webhook=Webhook.from_db(node.Webhook) if node.Webhook else None, ) obj.input_links = [Link.from_db(link) for link in node.Input or []] obj.output_links = [Link.from_db(link) for link in node.Output or []] @@ -188,7 +184,6 @@ class NodeModel(Node): # Remove webhook info stripped_node.webhook_id = None - stripped_node.webhook = None return stripped_node diff --git a/autogpt_platform/backend/backend/data/graph_test.py b/autogpt_platform/backend/backend/data/graph_test.py index 2272049446..442c8ed4be 100644 --- a/autogpt_platform/backend/backend/data/graph_test.py +++ b/autogpt_platform/backend/backend/data/graph_test.py @@ -323,7 +323,6 @@ async def test_clean_graph(server: SpinTestServer): # Verify webhook info is removed (if any nodes had it) for node in cleaned_graph.nodes: assert node.webhook_id is None - assert node.webhook is None @pytest.mark.asyncio(loop_scope="session") diff --git a/autogpt_platform/backend/backend/data/integrations.py b/autogpt_platform/backend/backend/data/integrations.py index 5f44f928bd..fe60c39531 100644 --- a/autogpt_platform/backend/backend/data/integrations.py +++ b/autogpt_platform/backend/backend/data/integrations.py @@ -1,5 +1,5 @@ import logging -from typing import TYPE_CHECKING, AsyncGenerator, Literal, Optional, overload +from typing import AsyncGenerator, Literal, Optional, overload from prisma.models import AgentNode, AgentPreset, IntegrationWebhook from prisma.types import ( @@ -22,9 +22,6 @@ from backend.integrations.webhooks.utils import webhook_ingress_url from backend.util.exceptions import NotFoundError from backend.util.json import SafeJson -if TYPE_CHECKING: - from backend.api.features.library.model import LibraryAgentPreset - from .db import BaseDbModel from .graph import NodeModel @@ -75,11 +72,6 @@ class WebhookWithRelations(Webhook): "AgentNodes and AgentPresets must be included in " "IntegrationWebhook query with relations" ) - # LibraryAgentPreset import is moved to TYPE_CHECKING to avoid circular import: - # integrations.py → library/model.py → integrations.py (for Webhook) - # Runtime import is used in WebhookWithRelations.from_db() method instead - # Import at runtime to avoid circular dependency - from backend.api.features.library.model import LibraryAgentPreset return WebhookWithRelations( **Webhook.from_db(webhook).model_dump(), @@ -90,6 +82,11 @@ class WebhookWithRelations(Webhook): ) +# LibraryAgentPreset import must be after WebhookWithRelations definition to avoid +# broken circular import: +# integrations.py → library/model.py → integrations.py (for Webhook) +from backend.api.features.library.model import LibraryAgentPreset # noqa: E402 + # --------------------- CRUD functions --------------------- # diff --git a/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py b/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py index c656d67eac..99eee404b9 100644 --- a/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py +++ b/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional, cast, overload from backend.blocks._base import BlockSchema from backend.data.graph import set_node_webhook +from backend.data.integrations import get_webhook from backend.integrations.creds_manager import IntegrationCredentialsManager from . import get_webhook_manager, supports_webhooks @@ -113,31 +114,32 @@ async def on_node_deactivate( webhooks_manager = get_webhook_manager(provider) - if node.webhook_id: - logger.debug(f"Node #{node.id} has webhook_id {node.webhook_id}") - if not node.webhook: - logger.error(f"Node #{node.id} has webhook_id but no webhook object") - raise ValueError("node.webhook not included") + if webhook_id := node.webhook_id: + logger.warning( + f"Node #{node.id} still attached to webhook #{webhook_id} - " + "did migration by `migrate_legacy_triggered_graphs` fail? " + "Triggered nodes are deprecated since Significant-Gravitas/AutoGPT#10418." + ) + webhook = await get_webhook(webhook_id) # Detach webhook from node logger.debug(f"Detaching webhook from node #{node.id}") updated_node = await set_node_webhook(node.id, None) # Prune and deregister the webhook if it is no longer used anywhere - webhook = node.webhook logger.debug( f"Pruning{' and deregistering' if credentials else ''} " - f"webhook #{webhook.id}" + f"webhook #{webhook_id}" ) await webhooks_manager.prune_webhook_if_dangling( - user_id, webhook.id, credentials + user_id, webhook_id, credentials ) if ( cast(BlockSchema, block.input_schema).get_credentials_fields() and not credentials ): logger.warning( - f"Cannot deregister webhook #{webhook.id}: credentials " + f"Cannot deregister webhook #{webhook_id}: credentials " f"#{webhook.credentials_id} not available " f"({webhook.provider.value} webhook ID: {webhook.provider_webhook_id})" ) From d927e4b61136d1f0d42111171db11bd5c3feff06 Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Thu, 12 Feb 2026 10:21:10 +0100 Subject: [PATCH 6/7] fix generate_block_docs.py --- .../backend/scripts/generate_block_docs.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/autogpt_platform/backend/scripts/generate_block_docs.py b/autogpt_platform/backend/scripts/generate_block_docs.py index bb60eddb5d..25ad0a3be7 100644 --- a/autogpt_platform/backend/scripts/generate_block_docs.py +++ b/autogpt_platform/backend/scripts/generate_block_docs.py @@ -24,7 +24,10 @@ import sys from collections import defaultdict from dataclasses import dataclass, field from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any, Type + +if TYPE_CHECKING: + from backend.blocks._base import AnyBlockSchema # Add backend to path for imports backend_dir = Path(__file__).parent.parent @@ -242,9 +245,9 @@ def file_path_to_title(file_path: str) -> str: return apply_fixes(name.replace("_", " ").title()) -def extract_block_doc(block_cls: type) -> BlockDoc: +def extract_block_doc(block_cls: Type["AnyBlockSchema"]) -> BlockDoc: """Extract documentation data from a block class.""" - block = block_cls.create() + block = block_cls() # Get source file try: @@ -520,7 +523,7 @@ def generate_overview_table(blocks: list[BlockDoc], block_dir_prefix: str = "") lines.append("") # Group blocks by category - by_category = defaultdict(list) + by_category = defaultdict[str, list[BlockDoc]](list) for block in blocks: primary_cat = block.categories[0] if block.categories else "BASIC" by_category[primary_cat].append(block) From 2fa166d83900b5bdd014989b231a52b710b63374 Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Thu, 12 Feb 2026 10:24:20 +0100 Subject: [PATCH 7/7] fix tests --- autogpt_platform/backend/backend/blocks/__init__.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/autogpt_platform/backend/backend/blocks/__init__.py b/autogpt_platform/backend/backend/blocks/__init__.py index d9446280a6..524e47c31d 100644 --- a/autogpt_platform/backend/backend/blocks/__init__.py +++ b/autogpt_platform/backend/backend/blocks/__init__.py @@ -3,16 +3,13 @@ import logging import os import re from pathlib import Path -from typing import TYPE_CHECKING, Sequence, Type, TypeVar +from typing import Sequence, Type, TypeVar +from backend.blocks._base import AnyBlockSchema, BlockType from backend.util.cache import cached logger = logging.getLogger(__name__) - -if TYPE_CHECKING: - from backend.blocks._base import AnyBlockSchema, BlockType - T = TypeVar("T")