Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/adapters/api/v1/admin/ai_settings.py
2026-08-21 14:14:21 +00:00

209 lines
7.9 KiB
Python
Executable file

from __future__ import annotations
import logging
from typing import Dict, Any, Optional
from fastapi import APIRouter, Depends, Request, Body
from sqlalchemy.orm import Session
from adapters.db.session import get_db
from app.core.auth_dependency import get_current_user, AuthContext
from app.core.responses import success_response, ApiError
from adapters.db.repositories.ai_config_repository import AIConfigRepository
from adapters.db.models.ai_config import AIConfig, AIProvider
from pydantic import BaseModel
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/admin/ai", tags=["admin-ai"])
class AIConfigRequest(BaseModel):
provider: str
model_name: str
api_base_url: str | None = None
api_key: str | None = None
max_tokens: int = 4000
temperature: float = 0.7
is_active: bool = True
# اگر null باشد (کلاینت قدیمی) هنگام به‌روز رکورد موجود، مقدار DB عوض نمی‌شود
function_calling_enabled: Optional[bool] = None
@router.get("/config", summary="دریافت تنظیمات AI")
async def get_ai_config(
request: Request,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
"""دریافت تنظیمات AI (فقط مدیر سیستم)"""
if not ctx.has_any_permission("system_settings", "superadmin"):
raise ApiError("FORBIDDEN", "فقط مدیر سیستم می‌تواند تنظیمات AI را مشاهده کند", http_status=403)
repo = AIConfigRepository(db)
config = repo.get_active_config()
if not config:
return success_response({
"provider": "openai",
"model_name": "gpt-4",
"api_base_url": None,
"api_key": None,
"max_tokens": 4000,
"temperature": 0.7,
"is_active": False,
"function_calling_enabled": True,
}, request)
return success_response({
"id": config.id,
"provider": config.provider,
"model_name": config.model_name,
"api_base_url": config.api_base_url,
"api_key": "***" if config.api_key else None, # مخفی کردن API key
"max_tokens": config.max_tokens,
"temperature": float(config.temperature),
"is_active": config.is_active,
"function_calling_enabled": bool(getattr(config, "function_calling_enabled", True)),
}, request)
@router.put("/config", summary="به‌روزرسانی تنظیمات AI")
async def update_ai_config(
request: Request,
config_data: AIConfigRequest = Body(...),
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
"""به‌روزرسانی تنظیمات AI (فقط مدیر سیستم)"""
if not ctx.has_any_permission("system_settings", "superadmin"):
raise ApiError("FORBIDDEN", "فقط مدیر سیستم می‌تواند تنظیمات AI را تغییر دهد", http_status=403)
repo = AIConfigRepository(db)
config = repo.get_active_config()
from app.services.ai.encryption import encrypt_api_key
if config:
config.provider = config_data.provider
config.model_name = config_data.model_name
config.api_base_url = config_data.api_base_url
if config_data.api_key:
# رمزگذاری API key
config.api_key = encrypt_api_key(config_data.api_key)
config.max_tokens = config_data.max_tokens
config.temperature = config_data.temperature
config.is_active = config_data.is_active
if config_data.function_calling_enabled is not None:
config.function_calling_enabled = config_data.function_calling_enabled
else:
from app.services.ai.encryption import encrypt_api_key
encrypted_key = encrypt_api_key(config_data.api_key) if config_data.api_key else None
config = AIConfig(
provider=config_data.provider,
model_name=config_data.model_name,
api_base_url=config_data.api_base_url,
api_key=encrypted_key,
max_tokens=config_data.max_tokens,
temperature=config_data.temperature,
is_active=config_data.is_active,
function_calling_enabled=config_data.function_calling_enabled
if config_data.function_calling_enabled is not None
else True,
)
db.add(config)
db.commit()
db.refresh(config)
from app.services.ai.ai_provider_service import upsert_credential_from_legacy_config
upsert_credential_from_legacy_config(db, config)
db.commit()
return success_response({
"id": config.id,
"provider": config.provider,
"model_name": config.model_name,
"api_base_url": config.api_base_url,
"api_key": "***" if config.api_key else None,
"max_tokens": config.max_tokens,
"temperature": float(config.temperature),
"is_active": config.is_active,
"function_calling_enabled": bool(getattr(config, "function_calling_enabled", True)),
}, request)
@router.post("/config/test-connection", summary="تست اتصال به AI Provider")
async def test_ai_connection(
request: Request,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
"""تست اتصال به AI Provider (فقط مدیر سیستم)"""
if not ctx.has_any_permission("system_settings", "superadmin"):
raise ApiError("FORBIDDEN", "فقط مدیر سیستم می‌تواند اتصال را تست کند", http_status=403)
repo = AIConfigRepository(db)
config = repo.get_active_config()
if not config or not config.is_active:
raise ApiError("CONFIG_NOT_FOUND", "تنظیمات AI فعالی وجود ندارد", http_status=400)
# رمزگشایی API Key
from app.services.ai.encryption import decrypt_api_key
api_key = decrypt_api_key(config.api_key) if config.api_key else None
if not api_key:
raise ApiError("API_KEY_NOT_SET", "API Key تنظیم نشده است", http_status=400)
# تست اتصال
try:
from app.services.ai.ai_provider import create_provider
provider = create_provider(
provider_type=config.provider,
api_key=api_key,
api_base_url=config.api_base_url
)
# ارسال یک درخواست تست
test_response = provider.chat_completion(
messages=[{"role": "user", "content": "سلام"}],
model=config.model_name,
max_tokens=10,
temperature=0.7
)
return success_response({
"success": True,
"message": "اتصال با موفقیت برقرار شد",
"model": config.model_name,
"provider": config.provider
}, request)
except Exception as e:
logger.error(f"AI connection test failed: {e}", exc_info=True)
raise ApiError("CONNECTION_FAILED", f"خطا در اتصال: {str(e)}", http_status=400)
@router.get("/ops-metrics", summary="شمارندهٔ خطا و زبان تفکر AI این فرآیند")
async def get_ai_ops_metrics(
request: Request,
ctx: AuthContext = Depends(get_current_user),
) -> Dict[str, Any]:
"""CHAT-14: reasoning_language_mismatch و tool_error بدون خواندن جلسه."""
if not ctx.has_any_permission("system_settings", "superadmin"):
raise ApiError("FORBIDDEN", "فقط مدیر سیستم می‌تواند متریک AI را ببیند", http_status=403)
from app.services.ai.ai_ops_metrics import metric_snapshot
counters = metric_snapshot()
return success_response(
{
"counters": counters,
"reasoning_language_mismatch": int(
counters.get("reasoning_language_mismatch", 0)
),
"tool_error": int(counters.get("tool_error", 0)),
},
request,
)