Download src/api.py from Frankenstein-Labs/Cortex-0: direct link, hf CLI and curl.
- Browser
- Download file 5.03 kB
-
https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/api.py
- Command line
-
hf download hf://Frankenstein-Labs/Cortex-0/src/api.py
-
curl -L -o api.py https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/api.py
5.03 kB
| """Cortex-0 — API FastAPI (Phase 12). | |
| Expose le moteur multimédia maison (`src/inference.CortexEngine`) : | |
| * ``GET /health`` : état du service + si un checkpoint entraîné est chargé ; | |
| * ``GET /capabilities`` : tailles / modalités supportées ; | |
| * ``POST /chat`` : texte -> texte (JSON) ; | |
| * ``POST /vision`` : image (upload) -> légende ; | |
| * ``POST /audio`` : WAV (upload) -> transcription ; | |
| * ``POST /video`` : frames + audio optionnel -> description ; | |
| * ``POST /memory/ingest`` : ingérer un cours (RAG, mode A) ; | |
| * ``POST /memory/query`` : interroger la mémoire d'un cours (réponse extractive). | |
| Aucun secret en dur ; le checkpoint est choisi via ``CORTEX_CHECKPOINT``. Aucun poids | |
| pré-entraîné n'est téléchargé : seuls nos propres checkpoints sont chargés. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| from fastapi import FastAPI, File, Form, HTTPException, UploadFile | |
| from pydantic import BaseModel, Field | |
| from src.inference import CortexEngine | |
| from src.memory.rag import LessonMemory | |
| app = FastAPI(title="Cortex-0", version="0.1.0", | |
| description="IA multimodale from scratch (texte/image/audio/vidéo).") | |
| _engine: CortexEngine | None = None | |
| _memory = LessonMemory(dim=512, max_words=40) | |
| def get_engine() -> CortexEngine: | |
| global _engine | |
| if _engine is None: | |
| ckpt = os.environ.get("CORTEX_CHECKPOINT") or None | |
| _engine = CortexEngine(checkpoint=ckpt) | |
| return _engine | |
| # --------------------------------------------------------------------------- | |
| # Schémas | |
| # --------------------------------------------------------------------------- | |
| class ChatRequest(BaseModel): | |
| prompt: str = Field(..., min_length=1) | |
| max_new_tokens: int = Field(40, ge=1, le=512) | |
| class IngestRequest(BaseModel): | |
| text: str = Field(..., min_length=1) | |
| source: str = "manual" | |
| class QueryRequest(BaseModel): | |
| question: str = Field(..., min_length=1) | |
| k: int = Field(3, ge=1, le=10) | |
| # --------------------------------------------------------------------------- | |
| # Routes | |
| # --------------------------------------------------------------------------- | |
| def health() -> dict: | |
| eng = get_engine() | |
| return {"status": "ok", "trained": eng.trained, "device": str(eng.device), | |
| "vocab_size": eng.tokenizer.vocab_size} | |
| def capabilities() -> dict: | |
| eng = get_engine() | |
| return { | |
| "modalities": ["text", "image", "audio", "video"], | |
| "image_tokens": eng.n_image_tokens, | |
| "audio_tokens": eng.n_audio_tokens, | |
| "video_tokens": eng.n_video_tokens, | |
| "from_scratch": True, | |
| "external_weights": False, | |
| } | |
| def chat(req: ChatRequest) -> dict: | |
| return get_engine().chat(req.prompt, max_new_tokens=req.max_new_tokens) | |
| async def vision(file: UploadFile = File(...), | |
| max_new_tokens: int = Form(20)) -> dict: | |
| data = await file.read() | |
| if not data: | |
| raise HTTPException(status_code=400, detail="image vide") | |
| try: | |
| return get_engine().describe_image(data, max_new_tokens=max_new_tokens) | |
| except TypeError as exc: | |
| raise HTTPException(status_code=415, detail=str(exc)) from exc | |
| async def audio(file: UploadFile = File(...), | |
| max_new_tokens: int = Form(20)) -> dict: | |
| data = await file.read() | |
| if not data: | |
| raise HTTPException(status_code=400, detail="audio vide") | |
| try: | |
| return get_engine().transcribe_audio(data, max_new_tokens=max_new_tokens) | |
| except TypeError as exc: | |
| raise HTTPException(status_code=415, detail=str(exc)) from exc | |
| async def video(frames: UploadFile = File(...), | |
| audio_file: UploadFile | None = File(None), | |
| max_new_tokens: int = Form(24)) -> dict: | |
| """``frames`` = archive numpy ``.npy`` de forme ``(T, H, W, 3)``.""" | |
| import io | |
| import numpy as np | |
| raw = await frames.read() | |
| try: | |
| arr = np.load(io.BytesIO(raw), allow_pickle=False) | |
| except Exception as exc: # fichier invalide | |
| raise HTTPException(status_code=400, detail=f"frames .npy invalides : {exc}") from exc | |
| aud = await audio_file.read() if audio_file is not None else None | |
| return get_engine().describe_video(arr, audio=aud, max_new_tokens=max_new_tokens) | |
| def memory_ingest(req: IngestRequest) -> dict: | |
| _memory.add_text(req.text, source=req.source) | |
| return {"passages": len(_memory.passages), "source": req.source} | |
| def memory_query(req: QueryRequest) -> dict: | |
| return _memory.answer(req.question, k=req.k) | |
| def main() -> None: | |
| import uvicorn | |
| ckpt = os.environ.get("CORTEX_CHECKPOINT") | |
| if ckpt and Path(ckpt).is_file(): | |
| get_engine() | |
| uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8000"))) | |
| if __name__ == "__main__": | |
| main() | |