第一次提交

This commit is contained in:
wangjiming
2026-07-27 09:12:47 +08:00
commit b4ff5db17b
579 changed files with 48768 additions and 0 deletions

View File

@@ -0,0 +1 @@
"""Versioned API package."""

View File

@@ -0,0 +1 @@
"""API endpoint modules."""

View File

@@ -0,0 +1,14 @@
from fastapi import APIRouter
from app.core.logging import get_logger
from app.db.platform_store import get_platform_store
router = APIRouter()
logger = get_logger(__name__)
@router.get("/health")
async def health_check() -> dict[str, object]:
logger.info("health check requested")
return {"code": 0, "message": "ok", "data": get_platform_store().health_metrics()}

View File

@@ -0,0 +1,844 @@
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from typing import Any
import uuid
from fastapi import APIRouter, Body, File, HTTPException, Query, UploadFile
from fastapi.responses import PlainTextResponse, StreamingResponse
from app.db.platform_store import get_platform_store
from app.modules.fine_tune.service import apply_presets
from fastapi import Request as FastAPIRequest
router = APIRouter()
def _actor(request: FastAPIRequest) -> str | None:
auth = request.headers.get("Authorization", "")
token = auth.replace("Bearer ", "").strip()
return token or None
def ok(data: Any = None, message: str = "ok") -> dict[str, Any]:
return {"code": 0, "message": message, "data": data}
def fail(status_code: int, message: str) -> HTTPException:
return HTTPException(status_code=status_code, detail={"code": status_code, "message": message, "data": None})
@router.get("/dashboard/overview")
async def dashboard_overview() -> dict[str, Any]:
store = get_platform_store()
tasks = store.tasks()
return ok(
{
"models": len(store.models()),
"datasets": len(store.datasets()),
"fine_tune_tasks": len(tasks),
"running_tasks": len([t for t in tasks if t["status"] in {"syncing", "queued", "running"}]),
"compute_nodes": len(store.compute_nodes()),
"gpus": len(store.gpus()),
}
)
@router.get("/dashboard/stats")
async def dashboard_stats() -> dict[str, Any]:
"""看板聚合数据:基于平台真实数据;缺项做合理近似(见下)。"""
store = get_platform_store()
tasks = store.tasks()
users = store.users()
nodes = store.compute_nodes()
datasets = store.datasets()
running_statuses = {"syncing", "queued", "running"}
running_ft = [t for t in tasks if t.get("status") in running_statuses]
failed_ft = [t for t in tasks if t.get("status") == "failed"]
online_nodes = [n for n in nodes if n.get("scheduler_status") == "online"]
# 近 7 天训练统计(按创建日期分桶;准确率为 None因任务无该字段
now = datetime.now(timezone.utc)
train_by_day: dict[str, int] = {}
for t in tasks:
ct = t.get("create_time")
if ct:
train_by_day[ct[:10]] = train_by_day.get(ct[:10], 0) + 1
training_7d = []
for i in range(6, -1, -1):
day = (now - timedelta(days=i)).strftime("%Y-%m-%d")
training_7d.append(
{
"date": day[5:],
"train": train_by_day.get(day, 0),
"gpu": sum(len(t.get("gpus") or []) for t in running_ft),
"accuracy": None,
}
)
# 服务状态:模型推理用在线计算节点近似;模型评测暂无独立数据源,置 0
service_status = [
{
"type": "模型推理",
"status": "error" if (nodes and not online_nodes) else ("busy" if (nodes and len(online_nodes) < len(nodes)) else "normal"),
"count": len(online_nodes),
},
{
"type": "模型微调",
"status": "error" if failed_ft else ("busy" if running_ft else "normal"),
"count": len(running_ft),
},
{"type": "模型评测", "status": "normal", "count": 0},
{
"type": "数据处理",
"status": "normal" if not failed_ft else "busy",
"count": len(datasets),
},
]
# 训练任务状态归一化fine_tune 的 syncing/queued 等映射到前端已知状态)
status_map = {
"syncing": "running",
"queued": "running",
"running": "running",
"pending": "pending",
"paused": "pending",
"completed": "completed",
"failed": "failed",
"error": "failed",
"cancelled": "failed",
}
op_labels = [
("模型训练", lambda a: "fine_tune" in a or "train" in a),
("数据处理", lambda a: "data" in a or "dataset" in a),
("模型评测", lambda a: "eval" in a),
("模型推理", lambda a: "infer" in a or "serving" in a or "deploy" in a),
("系统设置", lambda a: True),
]
def _op_label(action: str) -> str:
for label, fn in op_labels:
if fn(action):
return label
return "系统设置"
training_tasks = [
{
"id": t.get("id"),
"name": t.get("name"),
"status": status_map.get(t.get("status"), "pending"),
"train_type": t.get("train_type"),
"train_method": t.get("train_method"),
"base_model": t.get("base_model"),
"progress": t.get("progress", 0),
"accuracy": t.get("accuracy"),
"started_at": (t.get("create_time") or "")[:16],
}
for t in tasks[:8]
]
# 用户操作分布(按 audit action 归类为中文分类)
audit = store.audit_logs(limit=500)
op_counter: dict[str, int] = {}
for log in audit.get("items", []):
act = log.get("action") or "unknown"
op_counter[_op_label(act)] = op_counter.get(_op_label(act), 0) + 1
operation_distribution = [{"name": k, "value": v} for k, v in op_counter.items()]
# 最近登录用户:后端有 last_login 字段,返回真实数据
recent = sorted(
[u for u in users if u.get("last_login")],
key=lambda u: u["last_login"],
reverse=True,
)[:5]
recent_login_users = [
{
"user": u.get("display_name") or u.get("username"),
"role": u.get("role"),
"last_login": (u.get("last_login") or "")[:16],
}
for u in recent
]
# 登录时长:后端暂无该数据源,先留空,待接入后补充
login_duration_rank: list = []
return ok(
{
"online_services": sum(s["count"] for s in service_status),
"running_tasks": len(running_ft),
"pending_alerts": 0, # 平台暂无独立告警数据源,先置 0待接入后补充
"training_7d": training_7d,
"service_status": service_status,
"training_tasks": training_tasks,
"operation_distribution": operation_distribution,
"login_duration_rank": login_duration_rank,
"recent_login_users": recent_login_users,
}
)
@router.get("/system-info")
async def system_info() -> dict[str, Any]:
return ok(get_platform_store().system_info())
@router.get("/users")
async def users() -> dict[str, Any]:
return ok(get_platform_store().users())
@router.post("/users")
async def create_user(payload: dict[str, Any] = Body(...), request: FastAPIRequest = None) -> dict[str, Any]:
store = get_platform_store()
user = store.create_user(payload)
store.record_audit(
action="user.create",
actor_id=_actor(request),
target_type="user",
target_id=user["id"],
detail=f"username={user.get('username')}",
)
return ok(user)
@router.put("/users/{user_id}")
async def update_user(user_id: str, payload: dict[str, Any] = Body(...), request: FastAPIRequest = None) -> dict[str, Any]:
store = get_platform_store()
try:
user = store.update_user(user_id, payload)
except KeyError:
raise fail(404, "user not found")
store.record_audit(
action="user.update",
actor_id=_actor(request),
target_type="user",
target_id=user_id,
detail=f"fields={','.join(payload.keys())}",
)
return ok(user)
@router.delete("/users/{user_id}")
async def delete_user(user_id: str, current_username: str | None = Query(default=None)) -> dict[str, Any]:
try:
get_platform_store().delete_user(user_id)
return ok({"deleted": user_id, "current_username": current_username})
except KeyError:
raise fail(404, "user not found")
except ValueError as exc:
raise fail(400, str(exc))
@router.post("/users/{user_id}/reset-password")
async def reset_password(
user_id: str,
payload: dict[str, Any] = Body(default={}),
request: FastAPIRequest = None,
) -> dict[str, Any]:
try:
user = get_platform_store().reset_password(user_id, payload.get("password") or "")
except KeyError:
raise fail(404, "user not found")
except ValueError as exc:
raise fail(400, str(exc))
get_platform_store().record_audit(
action="user.reset_password",
actor_id=_actor(request),
target_type="user",
target_id=user_id,
detail=f"username={user.get('username')}",
)
return ok({"id": user_id})
@router.get("/model-manage/local-models")
async def local_models() -> dict[str, Any]:
models = [{"path": item.get("path") or "", "name": item["name"]} for item in get_platform_store().models()]
return ok({"models": models})
@router.get("/model-manage/trained-models")
async def trained_models() -> dict[str, Any]:
return ok({"models": get_platform_store().trained_models()})
@router.delete("/model-manage/trained-models/{model_id}")
async def delete_trained_model(model_id: str, type: str = Query(default="merged")) -> dict[str, Any]:
return ok({"deleted": model_id, "type": type})
@router.get("/model-manage/name/{name}")
async def model_by_name(name: str) -> dict[str, Any]:
try:
return ok(get_platform_store().model_by_name(name))
except KeyError:
raise fail(404, "model not found")
@router.get("/model-manage")
async def model_list() -> dict[str, Any]:
return ok(get_platform_store().models())
@router.post("/model-manage")
async def create_model(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
return ok(get_platform_store().create_model(payload))
@router.get("/model-manage/{model_id}")
async def model_detail(model_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().model(model_id))
except KeyError:
raise fail(404, "model not found")
@router.put("/model-manage/{model_id}")
async def update_model(model_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().update_model(model_id, payload))
except KeyError:
raise fail(404, "model not found")
@router.put("/model-manage/{model_id}/purpose")
async def update_model_purpose(model_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().update_model(model_id, {"purpose": payload.get("purpose", "training")}))
except KeyError:
raise fail(404, "model not found")
@router.delete("/model-manage/{model_id}")
async def delete_model(model_id: str) -> dict[str, Any]:
get_platform_store().delete_model(model_id)
return ok({"deleted": model_id})
@router.post("/model-manage/merge")
async def merge_model(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
return ok({"job_id": f"merge_{uuid.uuid4().hex[:12]}", "status": "queued", **payload})
@router.get("/dataset-manage/preview/{file_id}")
async def dataset_preview(file_id: str) -> dict[str, Any]:
try:
row = get_platform_store().dataset_file(file_id)
return ok({"content": row["content"]})
except KeyError:
raise fail(404, "dataset file not found")
@router.get("/dataset-manage/versions/{file_id}")
async def dataset_versions(file_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().file_versions(file_id))
except KeyError:
raise fail(404, "dataset file not found")
@router.get("/dataset-manage/versions/{file_id}/{version_id}")
async def dataset_version_content(file_id: str, version_id: str) -> dict[str, Any]:
try:
row = get_platform_store().dataset_file(file_id)
versions = get_platform_store().file_versions(file_id)["versions"]
version = next((item for item in versions if item["id"] == version_id), None)
if not version:
raise KeyError(version_id)
return ok({"version": version, "content": row["content"]})
except KeyError:
raise fail(404, "dataset version not found")
@router.post("/dataset-manage/versions/{file_id}")
async def create_dataset_version(file_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().create_file_version(file_id, payload))
except KeyError:
raise fail(404, "dataset file not found")
@router.put("/dataset-manage/versions/{file_id}/active")
async def activate_dataset_version(file_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().activate_file_version(file_id, payload["version_id"]))
except KeyError:
raise fail(404, "dataset version not found")
@router.delete("/dataset-manage/versions/{file_id}/{version_id}")
async def delete_dataset_version(file_id: str, version_id: str) -> dict[str, Any]:
return ok(get_platform_store().file_versions(file_id))
@router.post("/dataset-manage/upload/{dataset_id}")
async def upload_dataset_files(dataset_id: str, files: list[UploadFile] = File(default=[])) -> dict[str, Any]:
created: list[dict[str, Any]] = []
store = get_platform_store()
try:
store.dataset(dataset_id)
except KeyError:
raise fail(404, "dataset not found")
with store.connect() as conn:
for file in files:
raw = await file.read()
content = raw.decode("utf-8", errors="replace")
created.append(store.add_dataset_file(conn, dataset_id, file.filename or "upload.jsonl", content))
return ok({"files": created})
@router.get("/dataset-manage/download/{dataset_id}")
async def download_dataset(dataset_id: str) -> PlainTextResponse:
dataset = get_platform_store().dataset(dataset_id)
content = "\n".join([f"{file['name']}" for file in dataset.get("files", [])])
return PlainTextResponse(content, media_type="text/plain")
@router.get("/dataset-manage/download/{dataset_id}/{file_id}")
async def download_dataset_file(dataset_id: str, file_id: str, version_id: str | None = Query(default=None)) -> PlainTextResponse:
row = get_platform_store().dataset_file(file_id)
return PlainTextResponse(row["content"], media_type="text/plain")
@router.get("/dataset-manage")
async def dataset_list() -> dict[str, Any]:
return ok(get_platform_store().datasets())
@router.post("/dataset-manage")
async def create_dataset(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
dataset = get_platform_store().create_dataset(payload)
return ok({"id": dataset["id"]})
@router.get("/dataset-manage/{dataset_id}")
async def dataset_detail(dataset_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().dataset(dataset_id))
except KeyError:
raise fail(404, "dataset not found")
@router.put("/dataset-manage/{dataset_id}")
async def update_dataset(dataset_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().update_dataset(dataset_id, payload))
except KeyError:
raise fail(404, "dataset not found")
@router.delete("/dataset-manage/{dataset_id}")
async def delete_dataset(dataset_id: str) -> dict[str, Any]:
get_platform_store().delete_dataset(dataset_id)
return ok({"deleted": dataset_id})
@router.get("/fine-tune/check-name")
async def check_fine_tune_name(name: str = Query(...)) -> dict[str, Any]:
exists = any(task["name"] == name for task in get_platform_store().tasks())
return ok({"exists": exists})
@router.get("/fine-tune/progress/{task_id}")
async def fine_tune_progress(task_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().progress(task_id))
except KeyError:
raise fail(404, "fine tune task not found")
@router.post("/fine-tune/tensorboard/start")
async def tensorboard_start() -> dict[str, Any]:
return ok({"status": "running", "url": "http://localhost:6006"})
@router.get("/fine-tune")
async def fine_tune_list() -> dict[str, Any]:
return ok(get_platform_store().tasks())
@router.post("/fine-tune")
async def create_fine_tune(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
task = get_platform_store().create_task(apply_presets(payload))
return ok({"id": task["id"]})
except ValueError as exc:
raise fail(400, str(exc))
@router.post("/fine-tune/start")
async def start_fine_tune(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().start_task(payload))
except KeyError:
raise fail(404, "fine tune task not found")
except RuntimeError as exc:
raise fail(409, str(exc))
@router.get("/fine-tune/{task_id}")
async def fine_tune_detail(task_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().task(task_id))
except KeyError:
raise fail(404, "fine tune task not found")
@router.put("/fine-tune/{task_id}")
async def update_fine_tune(task_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().update_task(task_id, payload))
except KeyError:
raise fail(404, "fine tune task not found")
@router.post("/fine-tune/stop/{task_id}")
async def stop_fine_tune(task_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().stop_task(task_id))
except KeyError:
raise fail(404, "fine tune task not found")
@router.post("/fine-tune/{task_id}/stop")
async def stop_fine_tune_alt(task_id: str) -> dict[str, Any]:
return await stop_fine_tune(task_id)
@router.post("/fine-tune/pause/{task_id}")
async def pause_fine_tune(task_id: str) -> dict[str, Any]:
if not get_platform_store().pause_task(task_id):
raise fail(409, "当前没有可暂停的训练进程")
return ok({"paused": task_id})
@router.post("/fine-tune/resume/{task_id}")
async def resume_fine_tune(task_id: str) -> dict[str, Any]:
if not get_platform_store().resume_task_engine(task_id):
raise fail(409, "当前没有可恢复的训练进程")
return ok({"resumed": task_id})
@router.post("/fine-tune/cancel/{task_id}")
async def cancel_fine_tune(task_id: str) -> dict[str, Any]:
get_platform_store().cancel_task_engine(task_id)
return ok({"canceled": task_id})
@router.delete("/fine-tune/{task_id}")
async def delete_fine_tune(task_id: str) -> dict[str, Any]:
get_platform_store().delete_task(task_id)
return ok({"deleted": task_id})
@router.get("/fine-tune/{task_id}/overview")
async def fine_tune_overview(task_id: str) -> dict[str, Any]:
task = get_platform_store().task(task_id)
return ok({"task": task, "progress": get_platform_store().progress(task_id)})
@router.get("/fine-tune/{task_id}/checkpoints")
async def fine_tune_checkpoints(task_id: str) -> dict[str, Any]:
task = get_platform_store().task(task_id)
checkpoints = []
for step in [50, 100, 150]:
if task.get("progress", 0) >= min(100, step // 2):
checkpoints.append({"step": step, "path": f"/data/yg-ft/outputs/{task['name']}/checkpoint-{step}"})
return ok(checkpoints)
@router.get("/compute/nodes")
async def compute_nodes() -> dict[str, Any]:
return ok(get_platform_store().compute_nodes())
@router.post("/compute/nodes")
async def create_compute_node(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().create_compute_node(payload))
except KeyError as exc:
raise fail(400, f"missing field: {exc}")
@router.put("/compute/nodes/{node_id}")
async def update_compute_node(node_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().update_compute_node(node_id, payload))
except KeyError:
raise fail(404, "compute node not found")
@router.post("/compute/nodes/{node_id}/test-connection")
async def test_compute_node(node_id: str) -> dict[str, Any]:
return ok({"node_id": node_id, "success": True, "latency_ms": 12})
@router.post("/compute/nodes/{node_id}/enable")
async def enable_compute_node(node_id: str) -> dict[str, Any]:
return ok(get_platform_store().update_compute_node(node_id, {"enabled": True, "scheduler_status": "online"}))
@router.post("/compute/nodes/{node_id}/disable")
async def disable_compute_node(node_id: str) -> dict[str, Any]:
return ok(get_platform_store().update_compute_node(node_id, {"enabled": False, "scheduler_status": "offline"}))
@router.post("/compute/nodes/{node_id}/drain")
async def drain_compute_node(node_id: str) -> dict[str, Any]:
return ok(get_platform_store().update_compute_node(node_id, {"scheduler_status": "draining"}))
@router.get("/compute/nodes/{node_id}/replicas")
async def compute_node_replicas(node_id: str) -> dict[str, Any]:
return ok(get_platform_store().replicas(node_id))
@router.get("/compute/gpus")
async def compute_gpus() -> dict[str, Any]:
return ok(get_platform_store().gpus())
@router.get("/compute/queue")
async def compute_queue() -> dict[str, Any]:
return ok(get_platform_store().queue())
@router.post("/internal/compute-sync/resources")
async def create_compute_sync(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
sync_id = get_platform_store().create_sync_job(payload.get("target_node_id", "node_01"), payload)
return ok(get_platform_store().sync_job(sync_id))
@router.get("/internal/compute-sync/resources/{sync_id}")
async def compute_sync_detail(sync_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().sync_job(sync_id))
except KeyError:
raise fail(404, "sync job not found")
@router.get("/training-log-files")
async def training_log_files() -> dict[str, Any]:
return ok(get_platform_store().training_log_files())
@router.get("/training-log-content")
async def training_log_content(file: str = Query(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().training_log_content(file))
except KeyError:
raise fail(404, "training log not found")
@router.get("/log-files")
async def log_files(date: str | None = Query(default=None)) -> dict[str, Any]:
return ok(get_platform_store().log_files(date))
@router.get("/log-content")
async def log_content(file: str = Query(...)) -> dict[str, Any]:
return ok(get_platform_store().log_content(file))
@router.post("/web-log")
async def web_log(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
return ok({"received": True, **payload})
# ===================== Project Management (§13.2) =====================
@router.get("/projects")
async def project_list(
tenant_id: str = Query(default="default"),
status: str | None = Query(default=None),
keyword: str | None = Query(default=None),
) -> dict[str, Any]:
return ok(get_platform_store().projects(tenant_id, status, keyword))
@router.post("/projects")
async def create_project(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
project = get_platform_store().create_project(payload)
return ok({"id": project["id"]})
except KeyError as exc:
raise fail(400, f"missing required field: {exc}")
@router.get("/projects/{project_id}")
async def project_detail(project_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().project(project_id))
except KeyError:
raise fail(404, "project not found")
@router.put("/projects/{project_id}")
async def update_project(project_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().update_project(project_id, payload))
except KeyError:
raise fail(404, "project not found")
@router.post("/projects/{project_id}/activate")
async def activate_project(project_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().activate_project(project_id))
except KeyError:
raise fail(404, "project not found")
@router.get("/projects/{project_id}/members")
async def project_members(project_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().project_members(project_id))
except KeyError:
raise fail(404, "project not found")
@router.post("/projects/{project_id}/members")
async def add_project_member(project_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
user_id = payload.get("user_id")
if not user_id:
raise fail(400, "user_id is required")
try:
return ok(get_platform_store().add_project_member(project_id, user_id, payload.get("role", "member")))
except KeyError as exc:
raise fail(404, str(exc))
@router.put("/projects/{project_id}/members/{user_id}")
async def update_project_member_role(project_id: str, user_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().update_project_member_role(project_id, user_id, payload.get("role", "member")))
except KeyError as exc:
raise fail(404, str(exc))
@router.delete("/projects/{project_id}/members/{user_id}")
async def remove_project_member(project_id: str, user_id: str) -> dict[str, Any]:
try:
get_platform_store().remove_project_member(project_id, user_id)
return ok({"deleted": user_id})
except KeyError as exc:
raise fail(404, str(exc))
# ===================== Fine-tune Events (§7.1) =====================
@router.get("/fine-tune/{task_id}/events")
async def fine_tune_events(task_id: str) -> StreamingResponse:
import json as _json
async def event_stream():
store = get_platform_store()
try:
events = store.task_events(task_id)
for event in events:
yield f"data: {_json.dumps(event, default=str)}\n\n"
yield f"data: {_json.dumps({'type': 'done', 'data': {}}, default=str)}\n\n"
except KeyError:
yield f"data: {_json.dumps({'type': 'error', 'data': {'message': 'task not found'}}, default=str)}\n\n"
return StreamingResponse(event_stream(), media_type="text/event-stream")
# ===================== Fine-tune Retry & Resume (§13.9) =====================
@router.post("/fine-tune/{task_id}/retry")
async def retry_fine_tune(task_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().retry_task(task_id))
except KeyError:
raise fail(404, "fine tune task not found")
except ValueError as exc:
raise fail(400, str(exc))
@router.post("/fine-tune/{task_id}/resume")
async def resume_fine_tune(task_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
checkpoint_id = payload.get("checkpoint_id")
if not checkpoint_id:
raise fail(400, "checkpoint_id is required")
try:
return ok(get_platform_store().resume_task(task_id, checkpoint_id))
except KeyError as exc:
raise fail(404, str(exc))
except ValueError as exc:
raise fail(400, str(exc))
# ===================== Checkpoint Management (§13.9) =====================
@router.delete("/fine-tune/{task_id}/checkpoints/{checkpoint_id}")
async def delete_checkpoint(task_id: str, checkpoint_id: str) -> dict[str, Any]:
try:
get_platform_store().delete_checkpoint(checkpoint_id)
return ok({"deleted": checkpoint_id})
except KeyError:
raise fail(404, "checkpoint not found")
@router.put("/fine-tune/{task_id}/checkpoint-retention")
async def set_checkpoint_retention(task_id: str, payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
return ok(get_platform_store().set_checkpoint_retention(task_id, payload))
except KeyError:
raise fail(404, "fine tune task not found")
@router.get("/fine-tune/{task_id}/checkpoint-retention")
async def get_checkpoint_retention(task_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().get_checkpoint_retention(task_id))
except KeyError:
raise fail(404, "fine tune task not found")
# ===================== Compute Jobs (§13.6) =====================
@router.post("/compute/jobs")
async def create_compute_job(payload: dict[str, Any] = Body(...)) -> dict[str, Any]:
try:
job = get_platform_store().create_compute_job(payload)
return ok({"id": job["id"]})
except KeyError as exc:
raise fail(400, f"missing required field: {exc}")
@router.get("/compute/jobs/{job_id}")
async def compute_job_detail(job_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().compute_job(job_id))
except KeyError:
raise fail(404, "compute job not found")
@router.get("/compute/jobs")
async def compute_jobs(task_id: str = Query(default=None)) -> dict[str, Any]:
if task_id:
return ok(get_platform_store().compute_jobs_by_task(task_id))
return ok([])
@router.post("/compute/jobs/{job_id}/stop")
async def stop_compute_job(job_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().stop_compute_job(job_id))
except KeyError:
raise fail(404, "compute job not found")
@router.get("/compute/jobs/{job_id}/logs")
async def compute_job_logs(job_id: str) -> dict[str, Any]:
try:
return ok(get_platform_store().compute_job_logs(job_id))
except KeyError as exc:
raise fail(404, str(exc))

View File

@@ -0,0 +1,21 @@
from fastapi import APIRouter
from app.api.v1.endpoints.platform import router as platform_router
from app.api.v1.endpoints.health import router as health_router
from app.modules.auth import router as auth_router
from app.modules.system import router as system_router
from app.modules.tenant import router as tenant_router
from app.modules.project import router as project_router
from app.modules.resource import router as resource_router
from app.modules.approval import router as approval_router
api_router = APIRouter()
api_router.include_router(health_router, tags=["health"])
api_router.include_router(platform_router, tags=["platform"])
api_router.include_router(auth_router, tags=["auth"])
api_router.include_router(system_router, tags=["system"])
api_router.include_router(tenant_router, tags=["tenant"])
api_router.include_router(project_router, tags=["project"])
api_router.include_router(resource_router, tags=["resource"])
api_router.include_router(approval_router, tags=["approval"])