105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from collections.abc import Iterator
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Query, Request
|
|
from fastapi.responses import StreamingResponse
|
|
|
|
from backend.data.gateway import MarketDataUnavailable
|
|
from backend.features.accounts.auth import (
|
|
AuthenticatedPrincipal,
|
|
SmartAccessPrincipal,
|
|
SmartWritePrincipal,
|
|
)
|
|
from backend.features.mentor.schemas import (
|
|
CountResponse,
|
|
MentorChatInput,
|
|
MentorPreferencesInput,
|
|
)
|
|
from backend.features.mentor.service import MentorError
|
|
from backend.http.errors import AppError
|
|
from backend.llm.gateway import LLMGatewayError
|
|
|
|
router = APIRouter(prefix="/mentors", tags=["mentors"])
|
|
|
|
|
|
@router.get("/setup", response_model=dict)
|
|
def setup(
|
|
request: Request,
|
|
principal: AuthenticatedPrincipal,
|
|
trade_date: Annotated[str, Query(alias="date", min_length=10, max_length=10)],
|
|
) -> dict:
|
|
return _call(request, "setup", principal, trade_date)
|
|
|
|
|
|
@router.put("/preferences", response_model=dict)
|
|
def save_preferences(
|
|
payload: MentorPreferencesInput,
|
|
request: Request,
|
|
principal: SmartWritePrincipal,
|
|
) -> dict:
|
|
return _call(request, "save_preferences", principal, payload.order, payload.pinned)
|
|
|
|
|
|
@router.get("/messages", response_model=list[dict])
|
|
def messages(
|
|
request: Request,
|
|
principal: SmartAccessPrincipal,
|
|
mentor_id: Annotated[str, Query(min_length=1, max_length=100)],
|
|
trade_date: Annotated[str, Query(alias="date", min_length=10, max_length=10)],
|
|
) -> list[dict]:
|
|
return _call(request, "messages", principal, mentor_id, trade_date)
|
|
|
|
|
|
@router.delete("/messages", response_model=CountResponse)
|
|
def clear_messages(
|
|
request: Request,
|
|
principal: SmartWritePrincipal,
|
|
mentor_id: Annotated[str, Query(min_length=1, max_length=100)],
|
|
trade_date: Annotated[str, Query(alias="date", min_length=10, max_length=10)],
|
|
) -> CountResponse:
|
|
deleted = _call(request, "clear", principal, mentor_id, trade_date)
|
|
return CountResponse(deleted=deleted)
|
|
|
|
|
|
@router.post("/chat")
|
|
def chat(
|
|
payload: MentorChatInput,
|
|
request: Request,
|
|
principal: SmartWritePrincipal,
|
|
) -> StreamingResponse:
|
|
prepared = _call(
|
|
request,
|
|
"prepare_chat",
|
|
principal,
|
|
payload.mentor_id,
|
|
payload.trade_date,
|
|
payload.question,
|
|
)
|
|
|
|
def body() -> Iterator[bytes]:
|
|
for event in request.app.state.container.mentor.stream_chat(prepared):
|
|
yield (json.dumps(event, ensure_ascii=False, separators=(",", ":")) + "\n").encode(
|
|
"utf-8"
|
|
)
|
|
|
|
return StreamingResponse(
|
|
body(),
|
|
media_type="application/x-ndjson",
|
|
headers={"Cache-Control": "no-cache, no-transform", "X-Accel-Buffering": "no"},
|
|
)
|
|
|
|
|
|
def _call(request: Request, method: str, *args):
|
|
try:
|
|
return getattr(request.app.state.container.mentor, method)(*args)
|
|
except MentorError as exc:
|
|
raise AppError("mentor_unavailable", str(exc), 409) from exc
|
|
except MarketDataUnavailable as exc:
|
|
raise AppError("market_data_unavailable", str(exc), 503) from exc
|
|
except LLMGatewayError as exc:
|
|
status = 403 if exc.code in {"membership_required", "quota_exhausted"} else 503
|
|
raise AppError(exc.code, str(exc), status) from exc
|