Repository: Significant-Gravitas/Auto-GPT
File: autogpt_platform/backend/backend/data/graph.py (Python)

Code before:

    @computed_field
    @property
    def input_schema(self) -> dict[str, Any]:
        webhook_input_block = next(
            (
                block
                for node in self.nodes
                if (block := get_block(node.block_id))
                and block.block_type in (BlockType.WEBHOOK, BlockType.WEBHOOK_MANUAL)
            ),
            None,
        )
        return self._generate_schema(
            AgentInputBlock.Input,
            (
                [
                    node.input_default
                    for node in self.nodes
                    if (b := get_block(node.block_id))
                    and b.block_type == BlockType.INPUT
                    and "name" in node.input_default
                ]
                if not webhook_input_block
                else [
                    {"name": "payload", "title": f"{webhook_input_block.name} payload"}
                ]
            ),
        )

    @computed_field
    @property
    def output_schema(self) -> dict[str, Any]:
        return self._generate_schema(
            AgentOutputBlock.Input,
            [
                node.input_default
                for node in self.nodes
                if (b := get_block(node.block_id))
                and b.block_type == BlockType.OUTPUT
                and "name" in node.input_default
            ],
        )

    @staticmethod
    def _generate_schema(
        type_class: Type[AgentInputBlock.Input] | Type[AgentOutputBlock.Input],
        data: list[dict],
    ) -> dict[str, Any]:
        props = []
        for p in data:

Review comment (suggestion):
The current logic completely replaces the schema derived from `AgentInputBlock` nodes if *any* webhook block (`WEBHOOK` or `WEBHOOK_MANUAL`) exists in the graph. Is this the intended behavior? It seems this could prevent defining graphs that can be triggered *both* manually (using inputs defined by `AgentInputBlock`s) *and* via a webhook (using the `payload`). If a webhook block is present, the schema will *only* show the `payload`, potentially hiding the necessary inputs for manual runs. Could we clarify the expected behavior for such mixed-input graphs?

Also, the PR checklist notes that testing hasn't been performed yet. Given the potential impact on the graph's input contract, adding tests covering different scenarios (no webhook, webhook only, webhook + input blocks) would be crucial before merging.

Code after:

    @computed_field
    @property
    def input_schema(self) -> dict[str, Any]:
        return self._generate_schema(
            *(
                (block.input_schema, node.input_default)
                for node in self.nodes
                if (block := node.block).block_type == BlockType.INPUT
                and issubclass(block.input_schema, AgentInputBlock.Input)
            )
        )

    @computed_field
    @property
    def output_schema(self) -> dict[str, Any]:
        return self._generate_schema(
            *(
                (block.input_schema, node.input_default)
                for node in self.nodes
                if (block := node.block).block_type == BlockType.OUTPUT
                and issubclass(block.input_schema, AgentOutputBlock.Input)
            )
        )

    @staticmethod
    def _generate_schema(
        *props: tuple[Type[AgentInputBlock.Input] | Type[AgentOutputBlock.Input], dict],
    ) -> dict[str, Any]:
        schema = []
        for type_class, input_default in props:
            try:
                schema.append(type_class(**input_default))
            except Exception as e:
                logger.warning(f"Invalid {type_class}: {input_default}, {e}")

        return {
            "type": "object",
            "properties": {
                p.name: {
                    **{
                        k: v
                        for k, v in p.generate_schema().items()
                        if k not in ["description", "default"]
                    },
                    "secret": p.secret,
                    # Default value has to be set for advanced fields.
                    "advanced": p.advanced and p.value is not None,
                    "title": p.title or p.name,
                    **({"description": p.description} if p.description else {}),
                    **({"default": p.value} if p.value is not None else {}),