Портал технической службы: наряды и чек-листы, задачи, отказы и неисправности, анализ надёжности, карты, склады, приложение для Android
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""AI-помощник: статус, чат (любой вошедший пользователь, если включено), настройки и проверка (администратор)."""
|
||||
from typing import Literal, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .. import assistant as A, settings_store as ss
|
||||
from ..db import get_db
|
||||
from ..models import User
|
||||
from ..security import current_user, require_portal_admin as require_admin
|
||||
from .catalog import commit
|
||||
|
||||
router = APIRouter(prefix="/assistant", tags=["assistant"])
|
||||
|
||||
|
||||
class Msg(BaseModel):
|
||||
role: Literal["user", "assistant"]
|
||||
content: str = Field(max_length=4000)
|
||||
|
||||
|
||||
class ChatIn(BaseModel):
|
||||
messages: list[Msg] = Field(min_length=1, max_length=40)
|
||||
|
||||
|
||||
class ConfigIn(BaseModel):
|
||||
enabled: Optional[bool] = None
|
||||
provider: Optional[Literal["openai", "anthropic", "gigachat"]] = None
|
||||
base_url: Optional[str] = Field(default=None, max_length=300)
|
||||
model: Optional[str] = Field(default=None, max_length=200)
|
||||
api_key: Optional[str] = Field(default=None, max_length=2000) # пустая строка — удалить ключ; null — не менять
|
||||
scope: Optional[str] = Field(default=None, max_length=2000) # доп. указания помощнику от администратора
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
def status(db: Session = Depends(get_db), _: User = Depends(current_user)):
|
||||
return {"enabled": A.ready(db)}
|
||||
|
||||
|
||||
@router.post("/chat")
|
||||
async def chat(data: ChatIn, request: Request, db: Session = Depends(get_db), u: User = Depends(current_user)):
|
||||
token = request.headers.get("authorization", "")[7:]
|
||||
try:
|
||||
return await A.chat(request.app, db, u, token, [m.model_dump() for m in data.messages])
|
||||
except A.AssistantError as e:
|
||||
raise HTTPException(429 if "Слишком много" in str(e) else 503 if "выключен" in str(e) or "провайдер" in str(e).lower() else 422, str(e))
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
def get_config(db: Session = Depends(get_db), _: User = Depends(require_admin)):
|
||||
return A.public_cfg(db)
|
||||
|
||||
|
||||
@router.put("/config")
|
||||
def put_config(data: ConfigIn, db: Session = Depends(get_db), _: User = Depends(require_admin)):
|
||||
d = data.model_dump(exclude_unset=True)
|
||||
if d.get("base_url") and not d["base_url"].strip().lower().startswith(("https://", "http://")):
|
||||
raise HTTPException(422, "Адрес провайдера должен начинаться с https:// (или http:// для локальной модели)")
|
||||
for k, v in d.items():
|
||||
if v is None:
|
||||
continue
|
||||
ss.set_(db, "assistant_" + k, ("1" if v else "0") if isinstance(v, bool) else str(v).strip())
|
||||
commit(db)
|
||||
c = A.cfg(db)
|
||||
if c["enabled"] and (not c["model"] or not (c["api_key"] or "localhost" in c["base_url"] or "127.0.0.1" in c["base_url"])):
|
||||
ss.set_(db, "assistant_enabled", "0")
|
||||
commit(db)
|
||||
raise HTTPException(422, "Для включения укажите модель и ключ API (без ключа — только локальная модель)")
|
||||
return A.public_cfg(db)
|
||||
|
||||
|
||||
@router.post("/test")
|
||||
async def test(request: Request, db: Session = Depends(get_db), u: User = Depends(require_admin)):
|
||||
"""Проверка подключения: короткий вопрос по сервису и вопрос не по теме (должен быть отклонён)."""
|
||||
c = A.cfg(db)
|
||||
if not c["model"]:
|
||||
raise HTTPException(422, "Укажите модель")
|
||||
try:
|
||||
token = request.headers.get("authorization", "")[7:]
|
||||
if not A.ready(db): # проверяем и до включения: временно считаем помощника включённым
|
||||
c["enabled"] = True
|
||||
ok = await A.chat(request.app, db, u, token, [{"role": "user", "content": "Какие у меня задания на сегодня и где их посмотреть?"}], cfg_override=c)
|
||||
off = await A.complete(c, A.system_prompt(u, c["scope"]), [{"role": "user", "content": "Напиши стихотворение про весну"}])
|
||||
except A.AssistantError as e:
|
||||
raise HTTPException(502, str(e))
|
||||
return {"ok": True, "answer": ok["reply"][:600], "tools": ok["tools"], "offtopic_filtered": off.strip().upper().startswith("OFFTOPIC")}
|
||||
Reference in New Issue
Block a user