| |
| """ |
| Dynamic Model Loader - Intelligent Model Detection & Registration |
| سیستم هوشمند بارگذاری و تشخیص مدلهای AI |
| |
| Features: |
| - Auto-detect API type (HuggingFace, OpenAI, REST, GraphQL, etc.) |
| - Intelligent endpoint detection |
| - Automatic initialization |
| - Persistent storage in database |
| - Cross-page availability |
| """ |
|
|
| import httpx |
| import json |
| import re |
| import logging |
| from typing import Dict, Any, Optional, List |
| from datetime import datetime |
| import sqlite3 |
| from pathlib import Path |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class DynamicModelLoader: |
| """ |
| هوشمند: تشخیص خودکار نوع API و مدل |
| """ |
| |
| def __init__(self, db_path: str = "data/dynamic_models.db"): |
| self.db_path = db_path |
| Path(db_path).parent.mkdir(parents=True, exist_ok=True) |
| self.init_database() |
| |
| |
| self.api_patterns = { |
| 'huggingface': [ |
| r'huggingface\.co', |
| r'api-inference\.huggingface\.co', |
| r'hf\.co', |
| r'hf_[a-zA-Z0-9]+', |
| ], |
| 'openai': [ |
| r'openai\.com', |
| r'api\.openai\.com', |
| r'sk-[a-zA-Z0-9]+', |
| ], |
| 'anthropic': [ |
| r'anthropic\.com', |
| r'claude', |
| r'sk-ant-', |
| ], |
| 'rest': [ |
| r'/api/v\d+/', |
| r'/rest/', |
| r'application/json', |
| ], |
| 'graphql': [ |
| r'/graphql', |
| r'query.*\{', |
| r'mutation.*\{', |
| ], |
| 'websocket': [ |
| r'ws://', |
| r'wss://', |
| ] |
| } |
| |
| def init_database(self): |
| """ایجاد جداول دیتابیس""" |
| conn = sqlite3.connect(self.db_path) |
| cursor = conn.cursor() |
| |
| |
| cursor.execute(''' |
| CREATE TABLE IF NOT EXISTS dynamic_models ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| model_id TEXT UNIQUE NOT NULL, |
| model_name TEXT, |
| api_type TEXT, |
| base_url TEXT, |
| api_key TEXT, |
| config JSON, |
| endpoints JSON, |
| is_active BOOLEAN DEFAULT 1, |
| auto_detected BOOLEAN DEFAULT 1, |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| last_used_at TIMESTAMP, |
| use_count INTEGER DEFAULT 0 |
| ) |
| ''') |
| |
| |
| cursor.execute(''' |
| CREATE TABLE IF NOT EXISTS model_usage_history ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| model_id TEXT NOT NULL, |
| endpoint_used TEXT, |
| response_time_ms REAL, |
| success BOOLEAN, |
| error_message TEXT, |
| used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| FOREIGN KEY (model_id) REFERENCES dynamic_models(model_id) |
| ) |
| ''') |
| |
| conn.commit() |
| conn.close() |
| logger.info(f"✅ Dynamic Models database initialized: {self.db_path}") |
| |
| async def detect_api_type(self, config: Dict[str, Any]) -> str: |
| """ |
| تشخیص هوشمند نوع API |
| |
| Args: |
| config: تنظیمات ورودی (url, key, headers, etc.) |
| |
| Returns: |
| نوع API (huggingface, openai, rest, graphql, etc.) |
| """ |
| config_str = json.dumps(config).lower() |
| |
| |
| scores = {} |
| for api_type, patterns in self.api_patterns.items(): |
| score = 0 |
| for pattern in patterns: |
| if re.search(pattern, config_str, re.IGNORECASE): |
| score += 1 |
| scores[api_type] = score |
| |
| |
| if max(scores.values()) > 0: |
| detected_type = max(scores, key=scores.get) |
| logger.info(f"🔍 Detected API type: {detected_type} (score: {scores[detected_type]})") |
| return detected_type |
| |
| |
| logger.info("🔍 No specific type detected, defaulting to REST") |
| return 'rest' |
| |
| async def auto_discover_endpoints(self, base_url: str, api_key: Optional[str] = None) -> Dict[str, Any]: |
| """ |
| کشف خودکار endpoints |
| |
| Args: |
| base_url: URL پایه |
| api_key: کلید API (اختیاری) |
| |
| Returns: |
| لیست endpoints کشف شده |
| """ |
| discovered = { |
| 'endpoints': [], |
| 'methods': [], |
| 'schemas': {} |
| } |
| |
| |
| common_paths = [ |
| '', |
| '/docs', |
| '/openapi.json', |
| '/swagger.json', |
| '/api-docs', |
| '/health', |
| '/status', |
| '/models', |
| '/v1/models', |
| '/api/v1', |
| ] |
| |
| headers = {} |
| if api_key: |
| |
| headers['Authorization'] = f'Bearer {api_key}' |
| |
| async with httpx.AsyncClient(timeout=10.0) as client: |
| for path in common_paths: |
| try: |
| url = f"{base_url.rstrip('/')}{path}" |
| response = await client.get(url, headers=headers) |
| |
| if response.status_code == 200: |
| discovered['endpoints'].append({ |
| 'path': path, |
| 'url': url, |
| 'status': 200, |
| 'content_type': response.headers.get('content-type', '') |
| }) |
| |
| |
| if 'json' in response.headers.get('content-type', ''): |
| try: |
| data = response.json() |
| discovered['schemas'][path] = data |
| except: |
| pass |
| |
| except Exception as e: |
| logger.debug(f"Failed to discover {path}: {e}") |
| continue |
| |
| logger.info(f"🔍 Discovered {len(discovered['endpoints'])} endpoints") |
| return discovered |
| |
| async def test_model_connection(self, config: Dict[str, Any]) -> Dict[str, Any]: |
| """ |
| تست اتصال به مدل |
| |
| Args: |
| config: تنظیمات مدل |
| |
| Returns: |
| نتیجه تست |
| """ |
| api_type = config.get('api_type', 'rest') |
| base_url = config.get('base_url', '') |
| api_key = config.get('api_key') |
| |
| result = { |
| 'success': False, |
| 'api_type': api_type, |
| 'response_time_ms': 0, |
| 'error': None, |
| 'detected_capabilities': [] |
| } |
| |
| start_time = datetime.now() |
| |
| try: |
| |
| if api_type == 'huggingface': |
| result = await self._test_huggingface(base_url, api_key) |
| elif api_type == 'openai': |
| result = await self._test_openai(base_url, api_key) |
| elif api_type == 'rest': |
| result = await self._test_rest(base_url, api_key) |
| elif api_type == 'graphql': |
| result = await self._test_graphql(base_url, api_key) |
| else: |
| result = await self._test_generic(base_url, api_key) |
| |
| end_time = datetime.now() |
| result['response_time_ms'] = (end_time - start_time).total_seconds() * 1000 |
| |
| except Exception as e: |
| result['error'] = str(e) |
| logger.error(f"❌ Test failed: {e}") |
| |
| return result |
| |
| async def _test_huggingface(self, url: str, api_key: Optional[str]) -> Dict[str, Any]: |
| """تست مدل HuggingFace""" |
| headers = {'Content-Type': 'application/json'} |
| if api_key: |
| headers['Authorization'] = f'Bearer {api_key}' |
| |
| |
| test_payload = {'inputs': 'Test'} |
| |
| async with httpx.AsyncClient(timeout=30.0) as client: |
| response = await client.post(url, headers=headers, json=test_payload) |
| |
| return { |
| 'success': response.status_code in [200, 503], |
| 'status_code': response.status_code, |
| 'detected_capabilities': ['text-classification', 'sentiment-analysis'] |
| if response.status_code == 200 else ['loading'] |
| } |
| |
| async def _test_openai(self, url: str, api_key: Optional[str]) -> Dict[str, Any]: |
| """تست API سازگار با OpenAI""" |
| headers = {'Content-Type': 'application/json'} |
| if api_key: |
| headers['Authorization'] = f'Bearer {api_key}' |
| |
| |
| test_payload = { |
| 'model': 'gpt-3.5-turbo', |
| 'messages': [{'role': 'user', 'content': 'Test'}], |
| 'max_tokens': 5 |
| } |
| |
| async with httpx.AsyncClient(timeout=30.0) as client: |
| response = await client.post( |
| f"{url.rstrip('/')}/v1/chat/completions", |
| headers=headers, |
| json=test_payload |
| ) |
| |
| return { |
| 'success': response.status_code == 200, |
| 'status_code': response.status_code, |
| 'detected_capabilities': ['chat', 'completion', 'embeddings'] |
| } |
| |
| async def _test_rest(self, url: str, api_key: Optional[str]) -> Dict[str, Any]: |
| """تست REST API عمومی""" |
| headers = {} |
| if api_key: |
| headers['Authorization'] = f'Bearer {api_key}' |
| |
| async with httpx.AsyncClient(timeout=10.0) as client: |
| response = await client.get(url, headers=headers) |
| |
| return { |
| 'success': response.status_code == 200, |
| 'status_code': response.status_code, |
| 'detected_capabilities': ['rest-api'] |
| } |
| |
| async def _test_graphql(self, url: str, api_key: Optional[str]) -> Dict[str, Any]: |
| """تست GraphQL API""" |
| headers = {'Content-Type': 'application/json'} |
| if api_key: |
| headers['Authorization'] = f'Bearer {api_key}' |
| |
| |
| query = {'query': '{ __schema { types { name } } }'} |
| |
| async with httpx.AsyncClient(timeout=10.0) as client: |
| response = await client.post(url, headers=headers, json=query) |
| |
| return { |
| 'success': response.status_code == 200, |
| 'status_code': response.status_code, |
| 'detected_capabilities': ['graphql'] |
| } |
| |
| async def _test_generic(self, url: str, api_key: Optional[str]) -> Dict[str, Any]: |
| """تست عمومی""" |
| async with httpx.AsyncClient(timeout=10.0) as client: |
| response = await client.get(url) |
| |
| return { |
| 'success': response.status_code == 200, |
| 'status_code': response.status_code, |
| 'detected_capabilities': ['unknown'] |
| } |
| |
| async def register_model(self, config: Dict[str, Any]) -> Dict[str, Any]: |
| """ |
| ثبت مدل جدید |
| |
| Args: |
| config: { |
| 'model_id': 'unique-id', |
| 'model_name': 'My Model', |
| 'base_url': 'https://...', |
| 'api_key': 'xxx', |
| 'api_type': 'huggingface' (optional, auto-detected), |
| 'endpoints': {...} (optional, auto-discovered), |
| 'custom_config': {...} (optional) |
| } |
| |
| Returns: |
| نتیجه ثبت |
| """ |
| |
| if 'api_type' not in config: |
| config['api_type'] = await self.detect_api_type(config) |
| |
| |
| if 'endpoints' not in config: |
| discovered = await self.auto_discover_endpoints( |
| config.get('base_url', ''), |
| config.get('api_key') |
| ) |
| config['endpoints'] = discovered |
| |
| |
| test_result = await self.test_model_connection(config) |
| |
| if not test_result['success']: |
| return { |
| 'success': False, |
| 'error': f"Connection test failed: {test_result.get('error', 'Unknown error')}", |
| 'test_result': test_result |
| } |
| |
| |
| conn = sqlite3.connect(self.db_path) |
| cursor = conn.cursor() |
| |
| try: |
| cursor.execute(''' |
| INSERT OR REPLACE INTO dynamic_models |
| (model_id, model_name, api_type, base_url, api_key, config, endpoints, auto_detected) |
| VALUES (?, ?, ?, ?, ?, ?, ?, ?) |
| ''', ( |
| config.get('model_id'), |
| config.get('model_name'), |
| config.get('api_type'), |
| config.get('base_url'), |
| config.get('api_key'), |
| json.dumps(config.get('custom_config', {})), |
| json.dumps(config.get('endpoints', {})), |
| True |
| )) |
| |
| conn.commit() |
| |
| logger.info(f"✅ Model registered: {config.get('model_id')}") |
| |
| return { |
| 'success': True, |
| 'model_id': config.get('model_id'), |
| 'api_type': config.get('api_type'), |
| 'test_result': test_result, |
| 'message': 'Model registered successfully' |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Failed to register model: {e}") |
| return { |
| 'success': False, |
| 'error': str(e) |
| } |
| |
| finally: |
| conn.close() |
| |
| def get_all_models(self) -> List[Dict[str, Any]]: |
| """دریافت همه مدلهای ثبت شده""" |
| conn = sqlite3.connect(self.db_path) |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
| |
| cursor.execute(''' |
| SELECT * FROM dynamic_models |
| WHERE is_active = 1 |
| ORDER BY use_count DESC, created_at DESC |
| ''') |
| |
| models = [dict(row) for row in cursor.fetchall()] |
| conn.close() |
| |
| |
| for model in models: |
| model['config'] = json.loads(model.get('config', '{}')) |
| model['endpoints'] = json.loads(model.get('endpoints', '{}')) |
| |
| return models |
| |
| def get_model(self, model_id: str) -> Optional[Dict[str, Any]]: |
| """دریافت یک مدل خاص""" |
| conn = sqlite3.connect(self.db_path) |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
| |
| cursor.execute(''' |
| SELECT * FROM dynamic_models |
| WHERE model_id = ? AND is_active = 1 |
| ''', (model_id,)) |
| |
| row = cursor.fetchone() |
| conn.close() |
| |
| if row: |
| model = dict(row) |
| model['config'] = json.loads(model.get('config', '{}')) |
| model['endpoints'] = json.loads(model.get('endpoints', '{}')) |
| return model |
| |
| return None |
| |
| async def use_model(self, model_id: str, endpoint: str, payload: Dict[str, Any]) -> Dict[str, Any]: |
| """ |
| استفاده از یک مدل ثبت شده |
| |
| Args: |
| model_id: شناسه مدل |
| endpoint: endpoint مورد نظر |
| payload: دادههای ورودی |
| |
| Returns: |
| خروجی مدل |
| """ |
| model = self.get_model(model_id) |
| |
| if not model: |
| return { |
| 'success': False, |
| 'error': f'Model not found: {model_id}' |
| } |
| |
| |
| conn = sqlite3.connect(self.db_path) |
| cursor = conn.cursor() |
| cursor.execute(''' |
| UPDATE dynamic_models |
| SET use_count = use_count + 1, last_used_at = CURRENT_TIMESTAMP |
| WHERE model_id = ? |
| ''', (model_id,)) |
| conn.commit() |
| conn.close() |
| |
| |
| api_type = model['api_type'] |
| base_url = model['base_url'] |
| api_key = model['api_key'] |
| |
| headers = {'Content-Type': 'application/json'} |
| if api_key: |
| if api_type == 'huggingface': |
| headers['Authorization'] = f'Bearer {api_key}' |
| elif api_type == 'openai': |
| headers['Authorization'] = f'Bearer {api_key}' |
| else: |
| headers['Authorization'] = api_key |
| |
| url = f"{base_url.rstrip('/')}/{endpoint.lstrip('/')}" |
| |
| start_time = datetime.now() |
| |
| try: |
| async with httpx.AsyncClient(timeout=30.0) as client: |
| response = await client.post(url, headers=headers, json=payload) |
| |
| end_time = datetime.now() |
| response_time = (end_time - start_time).total_seconds() * 1000 |
| |
| |
| conn = sqlite3.connect(self.db_path) |
| cursor = conn.cursor() |
| cursor.execute(''' |
| INSERT INTO model_usage_history |
| (model_id, endpoint_used, response_time_ms, success) |
| VALUES (?, ?, ?, ?) |
| ''', (model_id, endpoint, response_time, response.status_code == 200)) |
| conn.commit() |
| conn.close() |
| |
| if response.status_code == 200: |
| return { |
| 'success': True, |
| 'data': response.json(), |
| 'response_time_ms': response_time |
| } |
| else: |
| return { |
| 'success': False, |
| 'error': f'HTTP {response.status_code}: {response.text[:200]}' |
| } |
| |
| except Exception as e: |
| logger.error(f"❌ Model usage failed: {e}") |
| |
| |
| conn = sqlite3.connect(self.db_path) |
| cursor = conn.cursor() |
| cursor.execute(''' |
| INSERT INTO model_usage_history |
| (model_id, endpoint_used, success, error_message) |
| VALUES (?, ?, ?, ?) |
| ''', (model_id, endpoint, False, str(e))) |
| conn.commit() |
| conn.close() |
| |
| return { |
| 'success': False, |
| 'error': str(e) |
| } |
| |
| def delete_model(self, model_id: str) -> bool: |
| """حذف یک مدل""" |
| conn = sqlite3.connect(self.db_path) |
| cursor = conn.cursor() |
| |
| cursor.execute(''' |
| UPDATE dynamic_models |
| SET is_active = 0 |
| WHERE model_id = ? |
| ''', (model_id,)) |
| |
| conn.commit() |
| affected = cursor.rowcount |
| conn.close() |
| |
| return affected > 0 |
|
|
|
|
| |
| dynamic_loader = DynamicModelLoader() |
|
|
| __all__ = ['DynamicModelLoader', 'dynamic_loader'] |
|
|
|
|