Spaces:
Sleeping
Sleeping
| """ | |
| NullAI システム設定API | |
| モデル、ドメイン、DBなどの設定をUIから動的に変更するためのAPI。 | |
| """ | |
| from fastapi import APIRouter, HTTPException, Depends, File, UploadFile | |
| from pydantic import BaseModel | |
| from typing import List, Dict, Any, Optional | |
| import os # For file operations | |
| import logging | |
| from backend.app.config import app_config_manager, app_model_router, ModelConfig as PydanticModelConfig | |
| router = APIRouter() | |
| logger = logging.getLogger(__name__) | |
| # モデル保存ディレクトリ (設定可能にするべきだが、一旦ハードコード) | |
| GGUF_MODELS_DIR = os.path.join(app_config_manager.base_dir, "models", "gguf") | |
| os.makedirs(GGUF_MODELS_DIR, exist_ok=True) # ディレクトリが存在しない場合は作成 | |
| # --- APIエンドポイント --- | |
| async def get_models_config(): | |
| """ | |
| 現在利用可能なモデルの設定一覧を取得する。 | |
| """ | |
| models = list(app_config_manager.models.values()) | |
| return models | |
| class DomainConfigResponse(BaseModel): # Renamed to avoid conflict | |
| domain_id: str | |
| name: str | |
| # Pydantic models for the new engine management endpoints | |
| class EngineStatusResponse(PydanticModelConfig): | |
| status: str # 'master' | 'apprentice' | 'available' | 'retired' | |
| unique_id: Optional[str] = None # For apprentice engines | |
| class EngineSwapRequest(BaseModel): | |
| apprentice_model_id: str | |
| class EnginePromoteRequest(BaseModel): | |
| apprentice_model_id: str | |
| class NewApprenticeResponse(BaseModel): | |
| new_apprentice_id: str | |
| message: str | |
| async def get_domains_config(): | |
| """ | |
| 現在利用可能なドメイン(知識DB)の設定一覧を取得する。 | |
| """ | |
| domains = list(app_config_manager.domains.values()) | |
| return [DomainConfigResponse(domain_id=d.get("domain_id"), name=d.get("name")) for d in domains] | |
| async def register_new_model(model_data: Dict[str, Any]): | |
| """ | |
| 新しいモデルを登録する。 | |
| """ | |
| new_model = app_config_manager.add_model(model_data) | |
| if not new_model: | |
| raise HTTPException(status_code=400, detail="Failed to add new model. Check data format.") | |
| return new_model | |
| async def get_all_engines(): | |
| """ | |
| システムに登録されている全てのエンジン(モデル)とそのステータスを取得する。 | |
| """ | |
| all_models = [] | |
| current_master = app_model_router.get_master_model() | |
| current_apprentice = app_model_router.get_apprentice_model() | |
| # Assuming app_model_router can provide info about all apprentices (even inactive ones) | |
| # This might need a new method in app_model_router if not available | |
| all_managed_engines = app_model_router.get_all_managed_engines() # This method needs to be implemented in ModelRouter | |
| for model_id, model_config in app_config_manager.models.items(): | |
| status = 'available' | |
| unique_id = None | |
| if current_master and model_id == current_master.model_id: | |
| status = 'master' | |
| elif current_apprentice and model_id == current_apprentice.model_id: | |
| status = 'apprentice' | |
| # Assuming app_model_router keeps track of unique_id for apprentices | |
| apprentice_info = next((e for e in all_managed_engines if e['config']['model_id'] == model_id and e['unique_id']), None) | |
| if apprentice_info: | |
| unique_id = apprentice_info['unique_id'] | |
| # else: default to 'available' or 'retired' if explicitly managed by router but not active | |
| # Consider models that were once master and now retired | |
| # This logic needs to be enhanced if app_model_router tracks retired models | |
| all_models.append( | |
| EngineStatusResponse( | |
| **model_config.dict(), # Unpack PydanticModelConfig fields | |
| status=status, | |
| unique_id=unique_id | |
| ) | |
| ) | |
| return all_models | |
| class ActiveEngines(BaseModel): | |
| master_engine_id: str | |
| apprentice_engine_id: Optional[str] = None | |
| class ActiveEnginesResponse(BaseModel): | |
| master_engine_id: str | |
| apprentice_engine_id: Optional[str] = None | |
| async def get_active_engines(): | |
| """ | |
| 現在設定されているマスターエンジンとアプレンティスエンジンのIDを取得する。 | |
| """ | |
| master_model = app_model_router.get_master_model() | |
| apprentice_model = app_model_router.get_apprentice_model() | |
| return ActiveEnginesResponse( | |
| master_engine_id=master_model.model_id if master_model else None, | |
| apprentice_engine_id=apprentice_model.model_id if apprentice_model else None | |
| ) | |
| async def set_active_engines(engines: ActiveEngines): | |
| """ | |
| マスターエンジンとアプレンティスエンジンを設定する。 | |
| """ | |
| if not app_model_router.set_master_model(engines.master_engine_id): | |
| raise HTTPException(status_code=400, detail=f"Master model '{engines.master_engine_id}' not found.") | |
| if engines.apprentice_engine_id: | |
| if not app_model_router.set_apprentice_model(engines.apprentice_engine_id): | |
| raise HTTPException(status_code=400, detail=f"Apprentice model '{engines.apprentice_engine_id}' not found.") | |
| else: | |
| app_model_router.set_apprentice_model(None) # Clear apprentice | |
| return {"status": "success", "message": f"Active engines set to {engines.master_engine_id} (Master) and {engines.apprentice_engine_id or 'None'} (Apprentice)."} | |
| async def swap_engines(request: EngineSwapRequest): | |
| """ | |
| 現在の師匠と指定した弟子を入れ替える。 | |
| """ | |
| if not app_model_router.swap_engines(request.apprentice_model_id): | |
| raise HTTPException(status_code=400, detail=f"Failed to swap engines with apprentice '{request.apprentice_model_id}'. Check logs.") | |
| master_model = app_model_router.get_master_model() | |
| apprentice_model = app_model_router.get_apprentice_model() | |
| return ActiveEnginesResponse( | |
| master_engine_id=master_model.model_id if master_model else None, | |
| apprentice_engine_id=apprentice_model.model_id if apprentice_model else None | |
| ) | |
| async def promote_apprentice(request: EnginePromoteRequest): | |
| """ | |
| 指定した弟子を師匠に昇格させ、現在の師匠を引退させる。 | |
| """ | |
| if not app_model_router.promote_apprentice(request.apprentice_model_id): | |
| raise HTTPException(status_code=400, detail=f"Failed to promote apprentice '{request.apprentice_model_id}'. Check logs.") | |
| master_model = app_model_router.get_master_model() | |
| apprentice_model = app_model_router.get_apprentice_model() | |
| return ActiveEnginesResponse( | |
| master_engine_id=master_model.model_id if master_model else None, | |
| apprentice_engine_id=apprentice_model.model_id if apprentice_model else None | |
| ) | |
| async def create_new_apprentice_endpoint(): | |
| """ | |
| 新しい「空っぽの弟子」推論エンジンを生成し、登録する。 | |
| """ | |
| new_apprentice_data = app_model_router.create_new_apprentice() | |
| if not new_apprentice_data: | |
| raise HTTPException(status_code=500, detail="Failed to create new apprentice engine.") | |
| return NewApprenticeResponse( | |
| new_apprentice_id=new_apprentice_data["config"]["model_id"], | |
| message=f"New apprentice '{new_apprentice_data['config']['display_name']}' created successfully." | |
| ) | |
| class ActiveDomain(BaseModel): | |
| domain_id: str | |
| async def set_active_domain(domain: ActiveDomain): | |
| """ | |
| 推論に使用するアクティブなドメイン(知識DB)を設定する。 | |
| """ | |
| if not app_config_manager.set_active_domain(domain.domain_id): | |
| raise HTTPException(status_code=400, detail=f"Domain '{domain.domain_id}' not found or failed to set.") | |
| # ModelRouterにもアクティブドメインの変更を通知 | |
| app_model_router.set_active_domain_id(domain.domain_id) | |
| return {"status": "success", "message": f"Active domain set to {domain.domain_id}."} | |
| async def upload_gguf_model(file: UploadFile = File(...)): | |
| """ | |
| GGUFモデルファイルをアップロードする。 | |
| """ | |
| file_path = os.path.join(GGUF_MODELS_DIR, file.filename) | |
| try: | |
| with open(file_path, "wb") as buffer: | |
| while True: | |
| chunk = await file.read(1024 * 1024) # 1MBずつ読み込み | |
| if not chunk: | |
| break | |
| buffer.write(chunk) | |
| logger.info(f"Uploaded GGUF file saved to: {file_path}") | |
| return {"filename": file.filename, "path": file_path, "message": "GGUF model uploaded successfully."} | |
| except Exception as e: | |
| logger.error(f"Failed to upload GGUF file: {e}") | |
| raise HTTPException(status_code=500, detail=f"Failed to upload file: {e}") |