AQIT 0.1.0
Loading...
Searching...
No Matches
schema.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2# This file is part of the Aquin Engine. Unauthorized copying, modification,
3# distribution, or use of this file, via any medium, is strictly prohibited.
4# Proprietary and confidential. See LICENSE for terms.
5
6"""Recipe loop objects: DataRevision, Recipe, RunRecord, Checkpoint, EvalGate."""
7
8from __future__ import annotations
9
10from dataclasses import asdict, dataclass, field
11from typing import Any
12
13
14FAMILIES = ("tabular", "llm")
15TABULAR_METHODS = ("linear", "boosting")
16LLM_METHODS = ("lora",)
17TABULAR_TASKS = ("classification", "regression")
20class RecipeError(ValueError):
21 """Invalid recipe or train inputs."""
22
23
24@dataclass
25class DataRevision:
26 id: str
27 path: str
28 sha256: str
29 format: str
30 n_rows: int | None = None
31 columns: list[str] | None = None
32 snapshot_dir: str | None = None
33
34 def to_dict(self) -> dict[str, Any]:
35 return asdict(self)
37 @classmethod
38 def from_dict(cls, data: dict[str, Any]) -> DataRevision:
39 return cls(
40 id=str(data["id"]),
41 path=str(data["path"]),
42 sha256=str(data["sha256"]),
43 format=str(data.get("format") or "file"),
44 n_rows=data.get("n_rows"),
45 columns=list(data["columns"]) if data.get("columns") else None,
46 snapshot_dir=data.get("snapshot_dir"),
47 )
48
49
50@dataclass
51class EvalGate:
52 passed: bool
53 metric: str
54 score: float | None
55 min_score: float | None = None
56 skipped: bool = False
57 details: dict[str, Any] = field(default_factory=dict)
59 def to_dict(self) -> dict[str, Any]:
60 return asdict(self)
62 @classmethod
63 def from_dict(cls, data: dict[str, Any]) -> EvalGate:
64 return cls(
65 passed=bool(data.get("passed")),
66 metric=str(data.get("metric") or ""),
67 score=data.get("score"),
68 min_score=data.get("min_score"),
69 skipped=bool(data.get("skipped")),
70 details=dict(data.get("details") or {}),
71 )
72
73
74@dataclass
75class Checkpoint:
76 path: str
77 kind: str
78 step: int | None = None
79 extra: dict[str, Any] = field(default_factory=dict)
80
81 def to_dict(self) -> dict[str, Any]:
82 return asdict(self)
84 @classmethod
85 def from_dict(cls, data: dict[str, Any]) -> Checkpoint:
86 return cls(
87 path=str(data["path"]),
88 kind=str(data.get("kind") or "file"),
89 step=data.get("step"),
90 extra=dict(data.get("extra") or {}),
91 )
92
93
94@dataclass
95class Recipe:
96 name: str
97 family: str
98 data: dict[str, Any]
99 train: dict[str, Any]
100 eval: dict[str, Any] = field(default_factory=dict)
101 source: str | None = None
102 extra: dict[str, Any] = field(default_factory=dict)
104 def to_dict(self) -> dict[str, Any]:
105 return {
106 "name": self.name,
107 "family": self.family,
108 "data": self.data,
109 "train": self.train,
110 "eval": self.eval,
111 "source": self.source,
112 "extra": self.extra,
113 }
114
115 @classmethod
116 def from_dict(cls, data: dict[str, Any], *, source: str | None = None) -> Recipe:
117 if not isinstance(data, dict):
118 raise RecipeError("Recipe must be a mapping (YAML/JSON object).")
119 name = str(data.get("name") or "").strip()
120 if not name:
121 raise RecipeError("Recipe needs a name.")
122 family = str(data.get("family") or "").strip().lower()
123 if family not in FAMILIES:
124 raise RecipeError(f"family must be one of {list(FAMILIES)}, got {family!r}.")
125 data_block = data.get("data")
126 if not isinstance(data_block, dict) or not data_block.get("path"):
127 raise RecipeError("Recipe data.path is required (CSV or jsonl).")
128 train = data.get("train")
129 if not isinstance(train, dict):
130 raise RecipeError("Recipe train: block is required.")
131 method = str(train.get("method") or "").strip().lower()
132 if family == "tabular" and method not in TABULAR_METHODS:
133 raise RecipeError(
134 f"tabular train.method must be one of {list(TABULAR_METHODS)}, got {method!r}."
135 )
136 if family == "llm" and method not in LLM_METHODS:
137 raise RecipeError(f"llm train.method must be one of {list(LLM_METHODS)}, got {method!r}.")
138 if family == "tabular" and not data_block.get("target"):
139 raise RecipeError("tabular recipes need data.target (label column).")
140 if family == "llm" and not train.get("base"):
141 raise RecipeError("llm recipes need train.base (HuggingFace model id).")
142 task = str(train.get("task") or "classification").lower()
143 if family == "tabular" and task not in TABULAR_TASKS:
144 raise RecipeError(f"train.task must be one of {list(TABULAR_TASKS)}.")
145 known = {"name", "family", "data", "train", "eval"}
146 extra = {k: v for k, v in data.items() if k not in known}
147 return cls(
148 name=name,
149 family=family,
150 data=dict(data_block),
151 train=dict(train),
152 eval=dict(data.get("eval") or {}),
153 source=source,
154 extra=extra,
155 )
156
157
158@dataclass
159class RunRecord:
160 run_id: str
161 name: str
162 family: str
163 status: str
164 run_dir: str
165 recipe: dict[str, Any]
166 revision: dict[str, Any]
167 checkpoint: dict[str, Any] | None = None
168 gate: dict[str, Any] | None = None
169 metrics: dict[str, Any] = field(default_factory=dict)
170 inspect: dict[str, Any] = field(default_factory=dict)
171 error: str | None = None
173 def to_dict(self) -> dict[str, Any]:
174 return asdict(self)
Checkpoint from_dict(cls, dict[str, Any] data)
Definition schema.py:89
dict[str, Any] to_dict(self)
Definition schema.py:85
DataRevision from_dict(cls, dict[str, Any] data)
Definition schema.py:42
dict[str, Any] to_dict(self)
Definition schema.py:38
dict[str, Any] to_dict(self)
Definition schema.py:63
EvalGate from_dict(cls, dict[str, Any] data)
Definition schema.py:67
Recipe from_dict(cls, dict[str, Any] data, *, str|None source=None)
Definition schema.py:120
dict[str, Any] to_dict(self)
Definition schema.py:108
dict[str, Any] to_dict(self)
Definition schema.py:177