Рабочий пайплайн: YouTube -> транскрипция -> выжимка -> Postgres
- новый api/: /pipeline/process (yt-dlp, субтитры или Vosk, LLM/экстрактивная суммаризация), /videos, /llm; старый код перенесён в api_legacy/ - web/: Flet UI (flet 0.28.3 + flet-web, порт 8550) - Dockerfile.api: ffmpeg слоем из mwader/static-ffmpeg, pip с кэш-маунтом - requirements/pyproject: убраны moviepy, imageio-ffmpeg, битый asyncio - README с инструкцией запуска и загрузкой Vosk-модели Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
95
api/route/llm.py
Normal file
95
api/route/llm.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Endpoints for inspecting and testing the external LLM hookup.
|
||||
|
||||
The LLM is not hosted here — the user supplies a base URL + model + key.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from api.lib.llm import (
|
||||
LLMConfig,
|
||||
LLMError,
|
||||
chat_complete,
|
||||
from_settings,
|
||||
list_models,
|
||||
ping,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/llm", tags=["llm"])
|
||||
|
||||
|
||||
class LLMConfigIn(BaseModel):
|
||||
base_url: str
|
||||
api_key: str = ""
|
||||
# model is optional here so /llm/models can be called before a model is picked
|
||||
model: str = ""
|
||||
timeout: int = 120
|
||||
max_tokens: int = 1000
|
||||
temperature: float = 0.3
|
||||
extra_headers: dict[str, str] = {}
|
||||
|
||||
|
||||
class LLMStatus(BaseModel):
|
||||
configured: bool
|
||||
base_url: str | None = None
|
||||
model: str | None = None
|
||||
|
||||
|
||||
class ChatIn(BaseModel):
|
||||
config: LLMConfigIn | None = None
|
||||
messages: list[dict]
|
||||
|
||||
|
||||
@router.get("/config", response_model=LLMStatus)
|
||||
async def get_status():
|
||||
cfg = from_settings()
|
||||
if cfg is None:
|
||||
return LLMStatus(configured=False)
|
||||
return LLMStatus(configured=True, base_url=cfg.base_url, model=cfg.model)
|
||||
|
||||
|
||||
@router.post("/test")
|
||||
async def test_connection(cfg: LLMConfigIn | None = None):
|
||||
"""Send a tiny ping to verify the endpoint accepts our requests."""
|
||||
config = LLMConfig(**cfg.model_dump()) if cfg else from_settings()
|
||||
if config is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No LLM config in body and none configured in environment",
|
||||
)
|
||||
try:
|
||||
reply = ping(config)
|
||||
except LLMError as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
return {"ok": True, "model": config.model, "reply": reply}
|
||||
|
||||
|
||||
@router.post("/models")
|
||||
async def models(cfg: LLMConfigIn | None = None):
|
||||
"""List available models from the configured (or supplied) endpoint."""
|
||||
config = LLMConfig(**cfg.model_dump()) if cfg else from_settings()
|
||||
if config is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No LLM config in body and none configured in environment",
|
||||
)
|
||||
try:
|
||||
items = list_models(config)
|
||||
except LLMError as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
return {"models": items, "count": len(items)}
|
||||
|
||||
|
||||
@router.post("/chat")
|
||||
async def chat(body: ChatIn):
|
||||
"""Pass-through chat endpoint — useful for sanity-testing prompts."""
|
||||
config = LLMConfig(**body.config.model_dump()) if body.config else from_settings()
|
||||
if config is None:
|
||||
raise HTTPException(status_code=400, detail="No LLM config supplied")
|
||||
try:
|
||||
reply = chat_complete(config, body.messages)
|
||||
except LLMError as e:
|
||||
raise HTTPException(status_code=502, detail=str(e))
|
||||
return {"reply": reply, "model": config.model}
|
||||
Reference in New Issue
Block a user