diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index 88780349c347..7bdf29707b70 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -257,7 +257,7 @@ def sanitize(name): for link in self.links: input_links[link.sink_id].append(link) - # Nodes: required fields are filled or connected + # Nodes: required fields are filled or connected and dependencies are satisfied for node in self.nodes: block = get_block(node.block_id) if block is None: @@ -278,6 +278,38 @@ def sanitize(name): f"Node {block.name} #{node.id} required input missing: `{name}`" ) + # Get input schema properties and check dependencies + input_schema = block.input_schema.model_fields + required_fields = block.input_schema.get_required_fields() + + def has_value(name): + return ( + node is not None + and name in node.input_default + and node.input_default[name] is not None + and str(node.input_default[name]).strip() != "" + ) or (name in input_schema and input_schema[name].default is not None) + + # Validate dependencies between fields + for field_name, field_info in input_schema.items(): + + # Apply input dependency validation only on run & field with depends_on + json_schema_extra = field_info.json_schema_extra or {} + dependencies = json_schema_extra.get("depends_on", []) + if not for_run or not dependencies: + continue + + # Check if dependent field has value in input_default + field_has_value = has_value(field_name) + field_is_required = field_name in required_fields + + # Check for missing dependencies when dependent field is present + missing_deps = [dep for dep in dependencies if not has_value(dep)] + if missing_deps and (field_has_value or field_is_required): + raise ValueError( + f"Node {block.name} #{node.id}: Field `{field_name}` requires [{', '.join(missing_deps)}] to be set" + ) + node_map = {v.id: v for v in self.nodes} def is_static_output_block(nid: str) -> bool: diff --git a/autogpt_platform/backend/backend/data/model.py b/autogpt_platform/backend/backend/data/model.py index 933d2036efe3..898ef1040b0c 100644 --- a/autogpt_platform/backend/backend/data/model.py +++ b/autogpt_platform/backend/backend/data/model.py @@ -138,6 +138,7 @@ def SchemaField( secret: bool = False, exclude: bool = False, hidden: Optional[bool] = None, + depends_on: list[str] | None = None, **kwargs, ) -> T: json_extra = { @@ -147,6 +148,7 @@ def SchemaField( "secret": secret, "advanced": advanced, "hidden": hidden, + "depends_on": depends_on, }.items() if v is not None } diff --git a/autogpt_platform/frontend/src/hooks/useAgentGraph.ts b/autogpt_platform/frontend/src/hooks/useAgentGraph.ts index f24168960486..f0ce56bd2fdd 100644 --- a/autogpt_platform/frontend/src/hooks/useAgentGraph.ts +++ b/autogpt_platform/frontend/src/hooks/useAgentGraph.ts @@ -404,6 +404,54 @@ export default function useAgentGraph( } }); } + + Object.entries(node.data.inputSchema.properties || {}).forEach( + ([key, schema]) => { + if (schema.depends_on) { + const dependencies = schema.depends_on; + + // Check if dependent field has value + const hasValue = + inputData[key] != null || + ("default" in schema && schema.default != null); + + const mustHaveValue = node.data.inputSchema.required?.includes(key); + + // Check for missing dependencies when dependent field is present + const missingDependencies = dependencies.filter( + (dep) => + !inputData[dep as keyof typeof inputData] || + String(inputData[dep as keyof typeof inputData]).trim() === "", + ); + + if ((hasValue || mustHaveValue) && missingDependencies.length > 0) { + setNestedProperty( + errors, + key, + `Requires ${missingDependencies.join(", ")} to be set`, + ); + errorMessage = `Field ${key} requires ${missingDependencies.join(", ")} to be set`; + } + + // Check if field is required when dependencies are present + const hasAllDependencies = dependencies.every( + (dep) => + inputData[dep as keyof typeof inputData] && + String(inputData[dep as keyof typeof inputData]).trim() !== "", + ); + + if (hasAllDependencies && !hasValue) { + setNestedProperty( + errors, + key, + `${key} is required when ${dependencies.join(", ")} are set`, + ); + errorMessage = `${key} is required when ${dependencies.join(", ")} are set`; + } + } + }, + ); + // Set errors setNodes((nodes) => { return nodes.map((n) => { diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index af43687bd3fc..d2259cf72cad 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -56,6 +56,7 @@ export type BlockIOSubSchemaMeta = { description?: string; placeholder?: string; advanced?: boolean; + depends_on?: string[]; hidden?: boolean; };