-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Summary
Add Pydantic validators to support ergonomic dict-style syntax in YAML for defining tool inputs/outputs/parameters and flow variables, while maintaining the internal list[Variable] representation for consistency.
Motivation
Currently, QType requires verbose list syntax for defining variables in both tools and flows:
tools:
- id: weather_api
name: Weather API
inputs:
- id: location
type: text
optional: false
- id: units
type: text
optional: true
outputs:
- id: temperature
type: float
optional: false
flows:
- id: process_weather
variables:
- id: location
type: text
- id: result
type: floatThis is consistent across the DSL, but significantly more verbose than necessary. Users must repeat the id field as both a key and value, making the YAML harder to write and read.
Proposed Solution
Add Pydantic @field_validator(mode="before") decorators that automatically convert dict-style YAML to list[Variable] during deserialization:
tools:
- id: weather_api
name: Weather API
inputs:
location: text
units: text?
outputs:
temperature: float
flows:
- id: process_weather
variables:
location: text
result: floatBenefits
- Reduced verbosity - No ID repetition (30-40% less YAML)
- Better readability - Natural YAML mapping structure
- Familiar pattern - Matches OpenAPI, JSON Schema, and other DSLs
- Backward compatible - List syntax still works
- No internal changes -
list[Variable]representation unchanged
Design Trade-off
We previously chose consistency over convenience, requiring list syntax everywhere. This proposal reverses that decision based on:
- Ergonomics matter - Users write YAML much more than they interact with Python internals
- Consistency can be internal - Python models remain consistent (
list[Variable]), YAML can be ergonomic - Industry standard - Dict syntax for parameters is ubiquitous in API specs and config DSLs
Implementation
1. Tool Classes
Add validator to Tool base class (applies to all tool types):
class Tool(StrictBaseModel, ABC):
inputs: list[Variable] = Field(default_factory=list)
outputs: list[Variable] = Field(default_factory=list)
@field_validator("inputs", "outputs", mode="before")
@classmethod
def convert_dict_to_list(cls, value):
"""Convert dict-style parameters to list[Variable] for YAML ergonomics."""
if isinstance(value, dict):
result = []
for name, params in value.items():
if isinstance(params, dict):
result.append(Variable(id=name, **params))
elif isinstance(params, Variable):
result.append(params)
else:
# params is just a type string
result.append(Variable(id=name, type=params))
return result
return value2. APITool Subclass
Override validator to also handle parameters field:
class APITool(Tool):
parameters: list[Variable] = Field(default_factory=list)
@field_validator("inputs", "outputs", "parameters", mode="before")
@classmethod
def convert_dict_to_list(cls, value):
# Same implementation as base class
...3. Flow and Step Classes
Add similar validators to:
Flow.variablesStep.inputs(if we want to support dict syntax for step input references - TBD)Step.outputs(if we want to support dict syntax for step output references - TBD)
Note: Step inputs/outputs are typically Reference[Variable] | str not Variable, so the validator would need to be adapted or skipped.
Examples
Tool Definition
Before (current):
tools:
- id: calculate_distance
type: PythonFunctionTool
name: Calculate Distance
function_name: calculate_distance
module_path: geo.utils
inputs:
- id: lat1
type: float
optional: false
- id: lon1
type: float
optional: false
- id: lat2
type: float
optional: false
- id: lon2
type: float
optional: false
outputs:
- id: distance_km
type: float
optional: falseAfter (proposed):
tools:
- id: calculate_distance
type: PythonFunctionTool
name: Calculate Distance
function_name: calculate_distance
module_path: geo.utils
inputs:
lat1: float
lon1: float
lat2: float
lon2: float
outputs:
distance_km: floatTesting Requirements
- Parity tests - Verify dict syntax produces identical internal models as list syntax
- Round-trip tests - Load dict YAML → serialize → verify list format in output
- Validator coverage - Test all parameter value types:
- Full dict with
type,optional,ui - Type-only string shorthand
- Already-parsed
Variableobjects
- Full dict with
- Backward compatibility - Existing list-syntax YAML still works
- Update examples - Convert all
examples/andtests/document-specs/to dict syntax
Migration Path
- Phase 1: Implement validators (backward compatible)
- Phase 2: Update documentation and examples to show dict syntax as preferred
- Phase 3: Add linter/formatter to auto-convert list to dict syntax (optional)
- Phase 4: (Future) Consider deprecating list syntax in YAML (but not in Python API)
Scope Decisions
In Scope
- Tool
inputs,outputs,parameters - Flow
variables
Out of Scope (for now)
- Step
inputs/outputs- These are References, not Variable definitions - Nested variables in CustomType properties (already string-based)
Open Questions
- Should we support dict syntax for Flow
inputs/outputs(which areReference[Variable] | str)? - Should serialization preserve user's original format (dict vs list) or normalize to one?
- Recommendation: Serialize to list format for consistency in generated files
Related Work
- This proposal was initially implemented in PR #XXX but reverted to prioritize consistency
- We chose to unify Tool parameters with Flow variables using
list[Variable] - This proposal maintains that internal consistency while improving YAML ergonomics
References
- Original discussion: Tool refactoring to remove
ToolParameterclass - Design decision: "consistency is worth the added yaml"
- Implementation:
qtype/dsl/model.py- Tool and Flow classes