Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/adapters/api/v1/admin/ai_models.py
2026-06-19 18:35:25 +00:00

182 lines
6.5 KiB
Python

from __future__ import annotations
from typing import Any, Dict, Optional
from fastapi import APIRouter, Body, Depends, Path, Request
from sqlalchemy.orm import Session
from adapters.db.models.ai_model import AIModel
from adapters.db.repositories.ai_model_repository import AIModelRepository
from adapters.db.session import get_db
from app.core.auth_dependency import AuthContext, get_current_user
from app.core.responses import ApiError, success_response
from app.services.ai.ai_model_service import serialize_model
from app.services.ai.ai_model_seed_service import seed_models_from_config
router = APIRouter(prefix="/admin/ai/models", tags=["admin-ai-models"])
def _require_admin(ctx: AuthContext) -> None:
if not ctx.has_any_permission("system_settings", "superadmin"):
raise ApiError("FORBIDDEN", "فقط مدیر سیستم می‌تواند مدل‌ها را مدیریت کند", http_status=403)
def _serialize(model: AIModel) -> Dict[str, Any]:
return serialize_model(model)
def _normalize_reasoning_effort(value: Any) -> Optional[str]:
"""اعتبارسنجی سطح تلاش استدلال؛ مقدار نامعتبر → None (خودکار)."""
from app.services.ai.ai_constants import REASONING_EFFORT_LEVELS
if value is None:
return None
normalized = str(value).strip().lower()
if not normalized:
return None
if normalized not in REASONING_EFFORT_LEVELS:
raise ApiError(
"INVALID_REASONING_EFFORT",
"سطح تلاش استدلال نامعتبر است (minimal/low/medium/high)",
http_status=400,
)
return normalized
@router.get("", summary="لیست مدل‌های AI")
async def list_ai_models(
request: Request,
only_active: Optional[bool] = None,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
_require_admin(ctx)
repo = AIModelRepository(db)
if only_active:
models = repo.get_active_models()
else:
models = repo.get_all()
return success_response([_serialize(m) for m in models], request)
@router.get("/{model_id}", summary="جزئیات مدل AI")
async def get_ai_model(
model_id: int = Path(...),
request: Request = None,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
_require_admin(ctx)
repo = AIModelRepository(db)
model = repo.get_by_id(model_id)
if not model:
raise ApiError("MODEL_NOT_FOUND", "مدل یافت نشد", http_status=404)
return success_response(_serialize(model), request)
@router.post("", summary="ایجاد مدل AI")
async def create_ai_model(
request: Request,
payload: Dict[str, Any] = Body(...),
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
_require_admin(ctx)
code = str(payload.get("code") or "").strip()
if not code:
raise ApiError("CODE_REQUIRED", "کد مدل الزامی است", http_status=400)
repo = AIModelRepository(db)
if repo.get_by_code(code):
raise ApiError("DUPLICATE_MODEL_CODE", "کد مدل تکراری است", http_status=400)
model = AIModel(
code=code,
display_name=str(payload.get("display_name") or code),
description=payload.get("description"),
provider=str(payload.get("provider") or "openai"),
model_id=str(payload.get("model_id") or code),
tier=payload.get("tier"),
supports_tools=bool(payload.get("supports_tools", True)),
max_tokens_default=int(payload.get("max_tokens_default") or 4000),
supports_reasoning=bool(payload.get("supports_reasoning", False)),
reasoning_effort=_normalize_reasoning_effort(payload.get("reasoning_effort")),
reference_input_cost_per_1k=payload.get("reference_input_cost_per_1k"),
reference_output_cost_per_1k=payload.get("reference_output_cost_per_1k"),
is_active=bool(payload.get("is_active", True)),
sort_order=int(payload.get("sort_order") or 0),
)
db.add(model)
db.commit()
db.refresh(model)
return success_response(_serialize(model), request, "مدل با موفقیت ایجاد شد")
@router.put("/{model_id}", summary="ویرایش مدل AI")
async def update_ai_model(
model_id: int = Path(...),
request: Request = None,
payload: Dict[str, Any] = Body(...),
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
_require_admin(ctx)
repo = AIModelRepository(db)
model = repo.get_by_id(model_id)
if not model:
raise ApiError("MODEL_NOT_FOUND", "مدل یافت نشد", http_status=404)
allowed = (
"display_name",
"description",
"provider",
"model_id",
"tier",
"supports_tools",
"max_tokens_default",
"supports_reasoning",
"reasoning_effort",
"reference_input_cost_per_1k",
"reference_output_cost_per_1k",
"is_active",
"sort_order",
)
for key in allowed:
if key in payload:
value = payload[key]
if key == "reasoning_effort":
value = _normalize_reasoning_effort(value)
setattr(model, key, value)
db.commit()
db.refresh(model)
return success_response(_serialize(model), request, "مدل با موفقیت به‌روزرسانی شد")
@router.delete("/{model_id}", summary="غیرفعال کردن مدل AI")
async def deactivate_ai_model(
model_id: int = Path(...),
request: Request = None,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
_require_admin(ctx)
repo = AIModelRepository(db)
model = repo.get_by_id(model_id)
if not model:
raise ApiError("MODEL_NOT_FOUND", "مدل یافت نشد", http_status=404)
model.is_active = False
db.commit()
return success_response({"id": model.id}, request, "مدل با موفقیت غیرفعال شد")
@router.post("/seed-from-config", summary="ایجاد خودکار مدل‌ها از تنظیمات")
async def seed_models_endpoint(
request: Request,
include_presets: bool = Body(True, embed=True),
force: bool = Body(False, embed=True),
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
_require_admin(ctx)
result = seed_models_from_config(db, include_presets=include_presets, force=force)
return success_response(result, request, "عملیات seed انجام شد")