Using PydanticTaskTemplate for AIKP Development
Introduction
In the OCLI framework, the PydanticTaskTemplate provides a declarative way to define Task and Recipe structures using Pydantic models. This approach offers strong type safety and automated validation.
The Template Object
The only mandatory requirement for an AIKP to be recognized by OCLI is the presence of a Template class within the module path specified when creating a task (e.g., task create --template ocli.aikp.your_module).
When using the Pydantic-based approach, your Template class inherits from PydanticTaskTemplate and must bind a TaskSchema and a RecipeSchema.
from ocli.classes.pydantic_task_template import PydanticTaskTemplate
class Template(PydanticTaskTemplate[YourTaskSchema, YourRecipeSchema]):
TaskSchema = YourTaskSchema
RecipeSchema = YourRecipeSchema
# Optional: override hooks like update_recipe or cli_task_mount
There are no strict requirements for where these schemas or your CLI commands are defined; they can be defined in the same file as the Template class, or imported from any other module.
1. Defining Schemas
Schemas are the core of the Pydantic approach. They define the data structure and validation rules for your AIKP.
TaskSchema
The TaskSchema must inherit from ocli.classes.task.TaskHeadSchema. It defines the fields configurable via task set.
from pydantic import ConfigDict, Field
from ocli.classes.task import TaskHeadSchema
class YourTaskSchema(TaskHeadSchema):
model_config = ConfigDict(validate_assignment=True, extra='allow')
template: Literal['your.template.path'] = 'your.template.path'
custom_parameter: int = Field(10, ge=0)
# Add other fields as needed
RecipeSchema
The RecipeSchema must inherit from ocli.ai.recipe_head_schema.RecipeHeadSchema. This model defines the structure of the final recipe JSON.
from ocli.ai.recipe_head_schema import RecipeHeadSchema, RecipeType, RecipeKind
class YourRecipeSchema(RecipeHeadSchema):
type: RecipeType = RecipeType.YourType
kind: RecipeKind = RecipeKind.YourKind
custom_parameter: int | None = None
2. Implementing the Template
The Template class orchestrates the interaction between the Task config and the generated Recipe. The primary hook for this is update_recipe.
from ocli.pro.helpers.task_helpers import updates_metadata_preset
class Template(PydanticTaskTemplate[YourTaskSchema, YourRecipeSchema]):
TaskSchema = YourTaskSchema
RecipeSchema = YourRecipeSchema
@classmethod
@updates_metadata_preset
def update_recipe(cls, task: Task, recipe: dict, roi: dict | None = None) -> list[str]:
# Populate standard fields (DATADIR, OUTDIR) and validate
errors = super().update_recipe(task, recipe, roi)
try:
# Type-safe access to task configuration
task_config = YourTaskSchema.model_validate(task.config)
# Map task configuration to recipe properties type-safely
recipe_model = YourRecipeSchema(**recipe, custom_parameter=task_config.custom_parameter)
# Update the original dictionary with validated/formatted data
recipe.update(recipe_model.model_dump(mode="json", exclude_unset=True, by_alias=True))
except Exception as e:
errors.append(str(e))
return errors
Key Benefits
Declarative Validation: Use Pydantic’s native features (types,
Fieldconstraints,@validator) to enforce rules instead of manualif/elsechecks.Automated Recipe Sync:
PydanticTaskTemplateautomatically handles schema validation for recipes, eliminating the need for a separaterecipe_schema.json.Flexibility: You are free to structure your code across one or many files; OCLI only cares about finding the
Templateobject at the module path.Builder Pattern & Resilience:
PydanticTaskTemplatetreats yourTaskSchemaas a builder. This means tasks can be created and updated incrementally viatask seteven if they are in an “invalid” or incomplete state. The framework only enforces strict schema compliance when it’s time to generate a recipe, allowing for a flexible, interactive configuration workflow.