Tool Node Input Validation#
Input validation for workflow tool nodes spans two layers: a frontend layer that classifies each parameter and renders the right control, and a backend layer that converts, type-checks, and resolves values before passing them to a tool. Understanding both layers is necessary when debugging unexpected validation failures or adding new parameter types.
Frontend: FormInputItem and helpers#
FormInputItem is the React component that renders a single tool parameter row in the workflow canvas. It drives all UI-side validation decisions.
State classification β getFormInputState() in form-input-item.helpers.ts derives a FormInputState object from the raw CredentialFormSchema and the current stored value. Key flags it computes:
| Flag | Meaning |
|---|---|
isFile / isFiles | FormTypeEnum.file or FormTypeEnum.files |
isNumber | FormTypeEnum.textNumber |
isDate | FormTypeEnum.date |
isDateRange | FormTypeEnum.dateRange |
isMultipleSelect | schema.multiple === true for select or dynamic-select types |
isConstant | varInput.type === VarKindType.constant or no type set |
showVariableSelector | true when isFile or when input kind is variable |
showTypeSwitch | true for number, boolean, object, array, select, date β the types that can be toggled between constant and variable |
Input kind β The three possible kinds are constant, variable, and mixed (from the VarKindType enum). Files are always variable kind; strings are always mixed; numbers, booleans, arrays, objects, dates, and date-ranges default to constant .
Variable filtering β getFilterVar() returns a per-type filter function used by VarReferencePicker to restrict which upstream variables can be selected. For example, a number input only accepts VarType.number; a file input accepts both VarType.file and VarType.arrayFile; a multi-select select or dynamic-select input accepts VarType.array and VarType.arrayString.
Variable clearing on type switch β handleTypeChange() in FormInputItem clears the current value when the user toggles between constant and variable kind: switching to variable sets value: ''; switching to constant resets to the schema's defaultValue.
Numeric inputs β getNumberInputValue() normalizes the stored value for the HTML number input: NaN is coerced to '', strings are passed through, and any other type becomes ''. On change, handleValueChange() calls Number.parseFloat() before storing.
Placeholder disambiguation β Select dropdowns show the schema placeholder when no option is selected; for dynamic selects during loading, the trigger shows 'Loading...' instead of the placeholder .
Variable selector normalization β normalizeVariableSelectorValue() coerces falsy variable selector values (e.g., null, undefined) to '' before storing, preventing the picker from rendering stale references.
Variable type mapping β getTargetVarType() returns the VarType that a parameter expects when in variable mode. Multi-select select or dynamic-select parameters return VarType.arrayString, enabling variable pickers to only show compatible upstream array outputs.
Backend: _convert_tool_parameters_type() and _transform_tool_parameters_type()#
Runtime parameter resolution β _convert_tool_parameters_type() in api/core/tools/tool_manager.py iterates over all FORM-type ToolParameter objects and builds a runtime_parameters dict from workflow node inputs.
Three input modes (workflow context):
tool_input.type | Resolution |
|---|---|
"variable" | Looks up variable_pool.get(selector) β raises ToolParameterError if selector is not a list of strings or variable is absent |
"constant" | Uses tool_input.value directly |
"mixed" | Calls variable_pool.convert_template(str(tool_input.value)) and takes .text |
ToolNodeData.ToolInput (from the external graphon package) is validated via model_validate() before type dispatch.
Skipping unconfigured parameters β If config is missing or has no "value" key, the parameter is silently skipped (continue) . This is the mechanism that makes optional parameters truly optional at runtime.
Non-variable-pool (simple) path β Without a variable pool (e.g., direct app invocations), init_frontend_parameter() is called . When use_default_for_missing_form_parameters=True, a missing value falls back to parameter.default, then to the first option for SELECT types, then continue .
File parameters in agent mode β Required FILE, FILES, or SYSTEM_FILES parameters raise ValueError when called with typ="agent" and allow_file_parameters=False .
Type casting β After value extraction, cast_parameter_value() in api/core/plugin/entities/parameters.py coerces values to their declared types:
NUMBER: acceptsint/floatdirectly; parses non-empty strings; empty strings returnNonevia fallbackFILE: unwraps single-item lists; raises if multiple files providedFILES/SYSTEM_FILES: wraps scalars into a listOBJECT: attemptsjson.loads()on strings before returning{}as fallbackDATE: validates that the value is a string inYYYY-MM-DDformat; empty strings return''DATE_RANGE: accepts a dict or JSON string with optionalstartandendkeys; validates both dates are inYYYY-MM-DDformat andstartβ€end; empty values return{}
Multi-select normalization β _transform_tool_parameters_type() in api/core/tools/__base/tool.py normalizes tool parameter values before invocation. When parameter.multiple is True for SELECT or DYNAMIC_SELECT types, it calls parameter.init_frontend_parameter() to validate that the value is a list of strings. When parameter.multiple is False, it uses the original parameter.type.cast_value() behavior:
for parameter in self.entity.parameters or []:
if parameter.name in tool_parameters:
if parameter.multiple:
result[parameter.name] = parameter.init_frontend_parameter(result.get(parameter.name))
else:
result[parameter.name] = parameter.type.cast_value(tool_parameters[parameter.name])
Tool parameter validation (ToolParameter)#
Multi-select declaration β ToolParameter includes a multiple boolean field (default: False) that enables multi-select behavior for SELECT and DYNAMIC_SELECT parameter types. When multiple=True, the parameter accepts an array of strings instead of a single value [validate_multiple()]:
- The
multiplefield is only valid forSELECTandDYNAMIC_SELECTparameter types; using it on other types raises aValueError. - When
multiple=True, thedefaultvalue must be a list; otherwise aValueErroris raised. - When
multiple=False, thedefaultvalue must not be a list.
Multi-select runtime validation β When parameter.multiple=True, init_frontend_parameter() enforces:
- The value must be a list of strings (raises
ValueErrorif not). - Required parameters reject empty lists (raises
ValueErrorwith message"tool parameter {name} not found in tool config"). - For
SELECTtypes, all list items must be valid options (raisesValueErrorif not). - If
valueisNone, it defaults toparameter.defaultor[].
LLM schema generation β get_llm_parameters_json_schema() generates different JSON schemas for multi-select parameters:
When multiple=True for SELECT or DYNAMIC_SELECT:
{
"type": "array",
"items": {
"type": "string",
"enum": ["option1", "option2"]
}
}
When multiple=False:
{
"type": "string",
"enum": ["option1", "option2"]
}
Application-level file input validation (BaseAppGenerator)#
For workflow/chat app inputs (not tool parameters), _validate_inputs() in api/core/app/apps/base_app_generator.py applies an additional layer:
- Optional file placeholder fix (PR #28948): If a
FILEorFILE_LISTvariable is optional and its value is an empty string (the frontend default), the method returnsNonerather than failing type validation . This prevents the frontend's universaldefault: ''from causing false validation failures. - Number empty string β An empty or whitespace-only string for a
NUMBERvariable returnsNoneinstead of raising . - Required guard β If
value is Noneandvariable_entity.required, aValueErroris raised immediately before any type-specific checks .
Key source files#
| File | Purpose |
|---|---|
form-input-item.tsx | Renders tool parameter inputs; handles type switching, value changes, variable selection |
form-input-item.helpers.ts | Pure helper functions: state derivation, type filtering, value normalization |
var-reference-picker.tsx | Variable selector popup; respects filterVar and isFilterFileVar props |
tool/node.tsx | Tool node canvas display; renders multi-select values as comma-separated strings |
api/core/tools/tool_manager.py | _convert_tool_parameters_type() β runtime parameter resolution for tool nodes |
api/core/tools/__base/tool.py | _transform_tool_parameters_type() / get_llm_parameters_json_schema() β multi-select handling |
api/core/tools/entities/tool_entities.py | ToolParameter β multi-select field declaration and validation |
api/core/plugin/entities/parameters.py | cast_parameter_value() / init_frontend_parameter() β type coercion |
api/core/app/apps/base_app_generator.py | _validate_inputs() β app-level input validation including file placeholder fix |
Related PRs: