Spaces:
Running on Zero
Running on Zero
File size: 9,901 Bytes
9936912 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | """Generate multi-turn tool-calling agent SFT dataset with verified SciPy execution."""
from __future__ import annotations
import json
import random
import sys
from pathlib import Path
from typing import Any
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np
from scipy import linalg, signal
from controlai_agent.registry import registry
import controlai_agent.tools # Register tools
OUTPUT_DIR = Path("data/training/agent_sft_v1")
RNG_SEED = 20260817
def format_matrix_latex(mat: np.ndarray) -> str:
if mat.ndim == 1:
return "[" + ", ".join(f"{x:.4g}" for x in mat) + "]"
rows = []
for r in mat:
rows.append(" & ".join(f"{x:.4g}" for x in r))
return "\\begin{bmatrix} " + " \\\\ ".join(rows) + " \\end{bmatrix}"
def generate_zoh_trajectory(rng: random.Random) -> dict[str, Any]:
n = rng.choice([2, 3])
A = np.diag([-rng.uniform(0.5, 3.0) for _ in range(n)])
for i in range(n - 1):
A[i, i + 1] = rng.uniform(0.5, 2.0)
B = np.zeros((n, 1))
B[-1, 0] = rng.uniform(1.0, 3.0)
Ts = rng.choice([0.01, 0.02, 0.05, 0.1, 0.2])
A_list = A.tolist()
B_list = B.tolist()
prompt = f"Discretize the continuous-time state-space system with A={A_list} and B={B_list} using exact zero-order hold at sampling period Ts={Ts} s."
tool_call_text = f"""<tool_call>
{{"name": "exact_zoh", "arguments": {{"A": {A_list}, "B": {B_list}, "Ts": {Ts}}}}}
</tool_call>"""
result = registry.execute("exact_zoh", {"A": A_list, "B": B_list, "Ts": Ts})
Ad = np.array(result["Ad"])
Bd = np.array(result["Bd"])
assistant_final = f"""The continuous-time system has been discretized under exact Zero-Order Hold (ZOH) at sample time $T_s = {Ts}$ s.
The resulting discrete-time state-space matrices are:
$$
A_d = {format_matrix_latex(Ad)}, \\quad B_d = {format_matrix_latex(Bd)}
$$
The discretization was cross-verified using the block matrix exponential method with a residual tolerance below $10^{{-7}}$. The system is ready for discrete-time state estimation or controller synthesis."""
return {
"messages": [
{"role": "user", "content": prompt},
{"role": "assistant", "content": tool_call_text},
{"role": "tool", "name": "exact_zoh", "content": json.dumps(result)},
{"role": "assistant", "content": assistant_final},
],
"metadata": {"task_type": "tool_calling", "family": "exact_zoh", "domain": "linear_systems"},
}
def generate_lqr_trajectory(rng: random.Random) -> dict[str, Any]:
A = [[0.0, 1.0], [-rng.uniform(1.0, 5.0), -rng.uniform(1.0, 4.0)]]
B = [[0.0], [rng.uniform(1.0, 3.0)]]
q1 = rng.choice([1.0, 5.0, 10.0, 20.0])
q2 = rng.choice([0.1, 1.0, 2.0])
Q = [[q1, 0.0], [0.0, q2]]
r_val = rng.choice([0.1, 1.0, 2.0])
R = [[r_val]]
prompt = f"Design a continuous-time LQR state feedback controller for system A={A}, B={B} with state penalty Q={Q} and control penalty R={R}. Compute the algebraic Riccati solution P, feedback gain K, and closed-loop poles."
tool_call_text = f"""<tool_call>
{{"name": "continuous_lqr", "arguments": {{"A": {A}, "B": {B}, "Q": {Q}, "R": {R}}}}}
</tool_call>"""
result = registry.execute("continuous_lqr", {"A": A, "B": B, "Q": Q, "R": R})
P_mat = np.array(result["P"])
K_mat = np.array(result["K"])
assistant_final = f"""The continuous-time Linear Quadratic Regulator (LQR) has been synthesized by solving the Algebraic Riccati Equation (CARE):
1. **Riccati Solution Matrix $P$**:
$$
P = {format_matrix_latex(P_mat)}
$$
2. **Optimal State Feedback Gain $K$**:
$$
K = {format_matrix_latex(K_mat)}
$$
3. **Closed-Loop Poles**:
$$
{result['closed_loop_poles']}
$$
The Riccati residual is verified at {result['verification']['riccati_residual_max']:.2e}, and all closed-loop eigenvalues lie strictly in the open left-half plane, certifying closed-loop asymptotic stability."""
return {
"messages": [
{"role": "user", "content": prompt},
{"role": "assistant", "content": tool_call_text},
{"role": "tool", "name": "continuous_lqr", "content": json.dumps(result)},
{"role": "assistant", "content": assistant_final},
],
"metadata": {"task_type": "tool_calling", "family": "continuous_lqr", "domain": "optimal_control"},
}
def generate_cbf_trajectory(rng: random.Random) -> dict[str, Any]:
x_min = -rng.uniform(0.5, 2.0)
x = x_min + rng.uniform(0.1, 0.5)
u_nom = -rng.uniform(1.5, 4.0)
alpha = rng.choice([0.5, 1.0, 1.2, 1.5, 2.0])
prompt = f"For a scalar single-integrator system x_dot = u with state safety boundary x >= {x_min:.2f}, apply a Control Barrier Function safety filter with class-K gain alpha = {alpha:.1f}. Given nominal control u_nom = {u_nom:.2f} at state x = {x:.2f}, compute the safe control input u."
tool_call_text = f"""<tool_call>
{{"name": "cbf_safety_filter", "arguments": {{"x": {x:.4f}, "u_nom": {u_nom:.4f}, "alpha": {alpha:.1f}, "x_min": {x_min:.4f}}}}}
</tool_call>"""
result = registry.execute("cbf_safety_filter", {"x": x, "u_nom": u_nom, "alpha": alpha, "x_min": x_min})
assistant_final = f"""The Control Barrier Function (CBF) quadratic program safety filter yields:
- **Barrier Function Value**: $h(x) = x - x_{{min}} = {result['h']:.4f}$
- **Lower Admissible Bound**: $\\dot{{h}} + \\alpha h \\ge 0 \\implies u \\ge -\\alpha h = {result['cbf_lower_bound']:.4f}$
- **Filtered Safe Control Input $u^*$**: ${result['u_safe']:.4f}$
The safety constraint is {'active' if result['is_constraint_active'] else 'inactive'}. The filtered control $u^*$ guarantees forward invariance of the safe set $\\mathcal{{C}} = \\{{x \\mid x \\ge {x_min:.2f}\\}}$, minimally perturbing the nominal control $u_{{nom}} = {u_nom:.2f}$."""
return {
"messages": [
{"role": "user", "content": prompt},
{"role": "assistant", "content": tool_call_text},
{"role": "tool", "name": "cbf_safety_filter", "content": json.dumps(result)},
{"role": "assistant", "content": assistant_final},
],
"metadata": {"task_type": "tool_calling", "family": "cbf_safety_filter", "domain": "safety_critical"},
}
def generate_underspecified_trajectory(rng: random.Random) -> dict[str, Any]:
cases = [
(
"Design a discrete state-feedback controller for the continuous plant with A=[[0, 1], [-4, -2]], B=[[0], [1]].",
"To design a discrete state-feedback controller, the following critical specifications are missing:\n1. **Sampling Period ($T_s$)**: Required to discretize the continuous-time plant matrices $(A, B)$ via Zero-Order Hold.\n2. **Performance Specifications**: Desired closed-loop pole locations $z_i$ or state/input penalty weights $(Q, R)$ for discrete LQR synthesis.\n\nPlease provide the sampling period $T_s$ and your desired closed-loop pole locations or LQR weighting matrices.",
),
(
"Tune a PID controller for an unknown motor.",
"To tune a PID controller, the mathematical model or empirical response of the motor is required:\n1. **Plant Transfer Function** $G(s)$ or State-Space Realization $(A, B, C, D)$.\n2. **Or Empirical FOPDT Parameters**: Process static gain $K$, time constant $\\tau$, and apparent dead time $L$.\n3. **Design Criteria**: Target phase margin, settling time, or overshoot tolerance.\n\nPlease provide the plant transfer function or experimental step response parameters.",
),
(
"Solve the MPC quadratic program for initial state x0=[1.0, 0.0].",
"To formulate and solve the Model Predictive Control (MPC) quadratic program, the following parameters are required:\n1. **Discrete System Dynamics**: State matrix $A$ and input matrix $B$.\n2. **Penalty Matrices**: State weighting matrix $Q$ and control weighting matrix $R$.\n3. **Prediction Horizon**: Horizon length $N$.\n4. **Actuator/State Constraints**: Bounds on inputs $|u_k| \\le u_{max}$ and states $x_{min} \\le x_k \\le x_{max}$.\n\nPlease provide the plant matrices $(A, B)$, weights $(Q, R)$, prediction horizon $N$, and any state/input constraints.",
),
]
prompt, response = rng.choice(cases)
return {
"messages": [
{"role": "user", "content": prompt},
{"role": "assistant", "content": response},
],
"metadata": {"task_type": "underspecified", "family": "refusal", "domain": "specification_gating"},
}
def main() -> int:
rng = random.Random(RNG_SEED)
generators = [
generate_zoh_trajectory,
generate_lqr_trajectory,
generate_cbf_trajectory,
]
all_records = []
# 1. Tool calling trajectories
for _ in range(1800):
gen = rng.choice(generators)
all_records.append(gen(rng))
# 2. Underspecified refusal trajectories
for _ in range(300):
all_records.append(generate_underspecified_trajectory(rng))
rng.shuffle(all_records)
split_idx = int(0.85 * len(all_records))
train_records = all_records[:split_idx]
valid_records = all_records[split_idx:]
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
with (OUTPUT_DIR / "train.jsonl").open("w", encoding="utf-8") as f:
for r in train_records:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
with (OUTPUT_DIR / "valid.jsonl").open("w", encoding="utf-8") as f:
for r in valid_records:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"Generated Agent SFT dataset in {OUTPUT_DIR}:")
print(f" - Train: {len(train_records):,} records")
print(f" - Valid: {len(valid_records):,} records")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|