File size: 9,390 Bytes
594ed40 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
"""
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エンドポイント ---
@router.get("/models", response_model=List[PydanticModelConfig])
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
@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()
# 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
@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) # Clear apprentice
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.")
# ModelRouterにもアクティブドメインの変更を通知
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) # 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}") |