|
|
""" |
|
|
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 |
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/models", response_model=List[PydanticModelConfig]) |
|
|
async def get_models_config(): |
|
|
""" |
|
|
現在利用可能なモデルの設定一覧を取得する。 |
|
|
""" |
|
|
models = list(app_config_manager.models.values()) |
|
|
return models |
|
|
|
|
|
class DomainConfigResponse(BaseModel): |
|
|
domain_id: str |
|
|
name: str |
|
|
|
|
|
|
|
|
class EngineStatusResponse(PydanticModelConfig): |
|
|
status: str |
|
|
unique_id: Optional[str] = None |
|
|
|
|
|
class EngineSwapRequest(BaseModel): |
|
|
apprentice_model_id: str |
|
|
|
|
|
class EnginePromoteRequest(BaseModel): |
|
|
apprentice_model_id: str |
|
|
|
|
|
class NewApprenticeResponse(BaseModel): |
|
|
new_apprentice_id: str |
|
|
message: str |
|
|
|
|
|
@router.get("/domains", response_model=List[DomainConfigResponse]) |
|
|
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] |
|
|
|
|
|
@router.post("/models", response_model=PydanticModelConfig) |
|
|
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 |
|
|
|
|
|
@router.get("/engines", response_model=List[EngineStatusResponse]) |
|
|
async def get_all_engines(): |
|
|
""" |
|
|
システムに登録されている全てのエンジン(モデル)とそのステータスを取得する。 |
|
|
""" |
|
|
all_models = [] |
|
|
current_master = app_model_router.get_master_model() |
|
|
current_apprentice = app_model_router.get_apprentice_model() |
|
|
|
|
|
|
|
|
|
|
|
all_managed_engines = app_model_router.get_all_managed_engines() |
|
|
|
|
|
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' |
|
|
|
|
|
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'] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
all_models.append( |
|
|
EngineStatusResponse( |
|
|
**model_config.dict(), |
|
|
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 |
|
|
|
|
|
@router.get("/engines/active", response_model=ActiveEnginesResponse) |
|
|
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 |
|
|
) |
|
|
|
|
|
@router.post("/engines/active") |
|
|
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) |
|
|
|
|
|
return {"status": "success", "message": f"Active engines set to {engines.master_engine_id} (Master) and {engines.apprentice_engine_id or 'None'} (Apprentice)."} |
|
|
|
|
|
@router.post("/engines/swap", response_model=ActiveEnginesResponse) |
|
|
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 |
|
|
) |
|
|
|
|
|
@router.post("/engines/promote", response_model=ActiveEnginesResponse) |
|
|
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 |
|
|
) |
|
|
|
|
|
@router.post("/engines/apprentice/new", response_model=NewApprenticeResponse) |
|
|
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 |
|
|
|
|
|
@router.post("/domains/active") |
|
|
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.") |
|
|
|
|
|
|
|
|
app_model_router.set_active_domain_id(domain.domain_id) |
|
|
return {"status": "success", "message": f"Active domain set to {domain.domain_id}."} |
|
|
|
|
|
@router.post("/upload-gguf") |
|
|
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) |
|
|
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}") |