|
|
|
|
|
""" |
|
|
Dynamic Model API - REST endpoints for dynamic model loading |
|
|
API برای بارگذاری هوشمند مدلها |
|
|
""" |
|
|
|
|
|
from fastapi import APIRouter, HTTPException, Body |
|
|
from pydantic import BaseModel, Field |
|
|
from typing import Dict, Any, Optional, List |
|
|
from datetime import datetime |
|
|
|
|
|
from backend.services.dynamic_model_loader import dynamic_loader |
|
|
|
|
|
router = APIRouter(prefix="/api/dynamic-models", tags=["Dynamic Models"]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ModelConfig(BaseModel): |
|
|
"""تنظیمات مدل جدید""" |
|
|
model_id: str = Field(..., description="Unique identifier for the model") |
|
|
model_name: str = Field(..., description="Display name") |
|
|
base_url: str = Field(..., description="Base URL of the API") |
|
|
api_key: Optional[str] = Field(None, description="API key (if required)") |
|
|
api_type: Optional[str] = Field(None, description="API type (auto-detected if not provided)") |
|
|
endpoints: Optional[Dict[str, Any]] = Field(None, description="Custom endpoints (auto-discovered if not provided)") |
|
|
custom_config: Optional[Dict[str, Any]] = Field(None, description="Additional configuration") |
|
|
|
|
|
|
|
|
class PasteConfig(BaseModel): |
|
|
""" |
|
|
کپی/پیست تنظیمات از منابع مختلف |
|
|
Supports multiple formats |
|
|
""" |
|
|
config_text: str = Field(..., description="Pasted configuration (JSON, YAML, or key-value pairs)") |
|
|
auto_detect: bool = Field(True, description="Auto-detect format and API type") |
|
|
|
|
|
|
|
|
class ModelUsageRequest(BaseModel): |
|
|
"""درخواست استفاده از مدل""" |
|
|
endpoint: str = Field(..., description="Endpoint to call (e.g., '', '/predict', '/generate')") |
|
|
payload: Dict[str, Any] = Field(..., description="Request payload") |
|
|
|
|
|
|
|
|
class DetectionRequest(BaseModel): |
|
|
"""درخواست تشخیص نوع API""" |
|
|
config: Dict[str, Any] = Field(..., description="Configuration to analyze") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/register") |
|
|
async def register_model(config: ModelConfig): |
|
|
""" |
|
|
ثبت مدل جدید |
|
|
|
|
|
**Usage**: |
|
|
```json |
|
|
{ |
|
|
"model_id": "my-custom-model", |
|
|
"model_name": "My Custom Model", |
|
|
"base_url": "https://api.example.com/models/my-model", |
|
|
"api_key": "sk-xxxxx", |
|
|
"api_type": "huggingface" |
|
|
} |
|
|
``` |
|
|
|
|
|
**Auto-Detection**: |
|
|
- If `api_type` is not provided, it will be auto-detected |
|
|
- If `endpoints` are not provided, they will be auto-discovered |
|
|
""" |
|
|
try: |
|
|
result = await dynamic_loader.register_model(config.dict()) |
|
|
|
|
|
if not result['success']: |
|
|
raise HTTPException(status_code=400, detail=result.get('error', 'Registration failed')) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": "Model registered successfully", |
|
|
"data": result |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Registration failed: {str(e)}") |
|
|
|
|
|
|
|
|
@router.post("/paste-config") |
|
|
async def paste_configuration(paste: PasteConfig): |
|
|
""" |
|
|
کپی/پیست تنظیمات از هر منبعی |
|
|
|
|
|
**Supported Formats**: |
|
|
- JSON |
|
|
- YAML |
|
|
- Key-value pairs |
|
|
- HuggingFace model cards |
|
|
- OpenAI config |
|
|
- cURL commands |
|
|
|
|
|
**Example**: |
|
|
``` |
|
|
{ |
|
|
"config_text": "{\\"model_id\\": \\"gpt-4\\", \\"base_url\\": \\"https://api.openai.com\\", ...}", |
|
|
"auto_detect": true |
|
|
} |
|
|
``` |
|
|
""" |
|
|
try: |
|
|
import json |
|
|
import yaml |
|
|
|
|
|
config_text = paste.config_text.strip() |
|
|
parsed_config = None |
|
|
|
|
|
|
|
|
try: |
|
|
parsed_config = json.loads(config_text) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
if not parsed_config: |
|
|
try: |
|
|
parsed_config = yaml.safe_load(config_text) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
if not parsed_config: |
|
|
parsed_config = {} |
|
|
for line in config_text.split('\n'): |
|
|
if ':' in line or '=' in line: |
|
|
separator = ':' if ':' in line else '=' |
|
|
parts = line.split(separator, 1) |
|
|
if len(parts) == 2: |
|
|
key = parts[0].strip().lower().replace(' ', '_') |
|
|
value = parts[1].strip() |
|
|
parsed_config[key] = value |
|
|
|
|
|
if not parsed_config or not isinstance(parsed_config, dict): |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail="Could not parse configuration. Please provide valid JSON, YAML, or key-value pairs." |
|
|
) |
|
|
|
|
|
|
|
|
if 'model_id' not in parsed_config: |
|
|
parsed_config['model_id'] = f"pasted-model-{datetime.now().strftime('%Y%m%d%H%M%S')}" |
|
|
|
|
|
if 'model_name' not in parsed_config: |
|
|
parsed_config['model_name'] = parsed_config['model_id'] |
|
|
|
|
|
if 'base_url' not in parsed_config: |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail="'base_url' is required in configuration" |
|
|
) |
|
|
|
|
|
|
|
|
if paste.auto_detect and 'api_type' not in parsed_config: |
|
|
parsed_config['api_type'] = await dynamic_loader.detect_api_type(parsed_config) |
|
|
|
|
|
|
|
|
result = await dynamic_loader.register_model(parsed_config) |
|
|
|
|
|
if not result['success']: |
|
|
raise HTTPException(status_code=400, detail=result.get('error', 'Registration failed')) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": "Model registered from pasted configuration", |
|
|
"parsed_config": parsed_config, |
|
|
"data": result |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to process pasted config: {str(e)}") |
|
|
|
|
|
|
|
|
@router.post("/detect-api-type") |
|
|
async def detect_api_type(request: DetectionRequest): |
|
|
""" |
|
|
تشخیص خودکار نوع API |
|
|
|
|
|
**Example**: |
|
|
```json |
|
|
{ |
|
|
"config": { |
|
|
"base_url": "https://api-inference.huggingface.co/models/bert-base", |
|
|
"api_key": "hf_xxxxx" |
|
|
} |
|
|
} |
|
|
``` |
|
|
|
|
|
**Returns**: Detected API type (huggingface, openai, rest, graphql, etc.) |
|
|
""" |
|
|
try: |
|
|
api_type = await dynamic_loader.detect_api_type(request.config) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"api_type": api_type, |
|
|
"config": request.config |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Detection failed: {str(e)}") |
|
|
|
|
|
|
|
|
@router.post("/test-connection") |
|
|
async def test_connection(config: ModelConfig): |
|
|
""" |
|
|
تست اتصال به مدل بدون ثبت |
|
|
|
|
|
**Usage**: Test before registering |
|
|
""" |
|
|
try: |
|
|
result = await dynamic_loader.test_model_connection(config.dict()) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"test_result": result |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Test failed: {str(e)}") |
|
|
|
|
|
|
|
|
@router.get("/models") |
|
|
async def get_all_models(): |
|
|
""" |
|
|
دریافت لیست همه مدلهای ثبت شده |
|
|
|
|
|
**Returns**: List of all registered dynamic models |
|
|
""" |
|
|
try: |
|
|
models = dynamic_loader.get_all_models() |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"total": len(models), |
|
|
"models": models |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to get models: {str(e)}") |
|
|
|
|
|
|
|
|
@router.get("/models/{model_id}") |
|
|
async def get_model(model_id: str): |
|
|
""" |
|
|
دریافت اطلاعات یک مدل خاص |
|
|
""" |
|
|
try: |
|
|
model = dynamic_loader.get_model(model_id) |
|
|
|
|
|
if not model: |
|
|
raise HTTPException(status_code=404, detail=f"Model not found: {model_id}") |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"model": model |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to get model: {str(e)}") |
|
|
|
|
|
|
|
|
@router.post("/models/{model_id}/use") |
|
|
async def use_model(model_id: str, usage: ModelUsageRequest): |
|
|
""" |
|
|
استفاده از یک مدل ثبت شده |
|
|
|
|
|
**Example**: |
|
|
```json |
|
|
{ |
|
|
"endpoint": "", |
|
|
"payload": { |
|
|
"inputs": "Bitcoin is bullish!" |
|
|
} |
|
|
} |
|
|
``` |
|
|
""" |
|
|
try: |
|
|
result = await dynamic_loader.use_model( |
|
|
model_id, |
|
|
usage.endpoint, |
|
|
usage.payload |
|
|
) |
|
|
|
|
|
if not result['success']: |
|
|
raise HTTPException(status_code=400, detail=result.get('error', 'Model usage failed')) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"data": result |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to use model: {str(e)}") |
|
|
|
|
|
|
|
|
@router.delete("/models/{model_id}") |
|
|
async def delete_model(model_id: str): |
|
|
""" |
|
|
حذف یک مدل |
|
|
""" |
|
|
try: |
|
|
success = dynamic_loader.delete_model(model_id) |
|
|
|
|
|
if not success: |
|
|
raise HTTPException(status_code=404, detail=f"Model not found: {model_id}") |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": f"Model {model_id} deleted successfully" |
|
|
} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Failed to delete model: {str(e)}") |
|
|
|
|
|
|
|
|
@router.post("/auto-configure") |
|
|
async def auto_configure_from_url(url: str = Body(..., embed=True)): |
|
|
""" |
|
|
تنظیم خودکار کامل از URL |
|
|
|
|
|
**Usage**: Just provide a URL, everything else is auto-detected |
|
|
|
|
|
**Example**: |
|
|
```json |
|
|
{ |
|
|
"url": "https://api-inference.huggingface.co/models/bert-base-uncased" |
|
|
} |
|
|
``` |
|
|
|
|
|
**Process**: |
|
|
1. Auto-detect API type from URL |
|
|
2. Auto-discover endpoints |
|
|
3. Test connection |
|
|
4. Register if successful |
|
|
""" |
|
|
try: |
|
|
|
|
|
config = { |
|
|
'model_id': url.split('/')[-1] or f'auto-{datetime.now().strftime("%Y%m%d%H%M%S")}', |
|
|
'model_name': url.split('/')[-1] or 'Auto-configured Model', |
|
|
'base_url': url |
|
|
} |
|
|
|
|
|
|
|
|
api_type = await dynamic_loader.detect_api_type(config) |
|
|
config['api_type'] = api_type |
|
|
|
|
|
|
|
|
discovered = await dynamic_loader.auto_discover_endpoints(url) |
|
|
config['endpoints'] = discovered |
|
|
|
|
|
|
|
|
test_result = await dynamic_loader.test_model_connection(config) |
|
|
|
|
|
if not test_result['success']: |
|
|
return { |
|
|
"success": False, |
|
|
"error": "Connection test failed", |
|
|
"test_result": test_result, |
|
|
"config": config, |
|
|
"message": "Model configuration created but connection failed. You can still register it manually." |
|
|
} |
|
|
|
|
|
|
|
|
result = await dynamic_loader.register_model(config) |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": "Model auto-configured and registered successfully", |
|
|
"config": config, |
|
|
"test_result": test_result, |
|
|
"registration": result |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Auto-configuration failed: {str(e)}") |
|
|
|
|
|
|
|
|
@router.get("/health") |
|
|
async def health_check(): |
|
|
"""سلامت سیستم""" |
|
|
return { |
|
|
"status": "healthy", |
|
|
"timestamp": datetime.now().isoformat() |
|
|
} |
|
|
|
|
|
|