vignette/apps/api/app/main.py
Yun Chan 24b1b7a6e1 feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG
web (Vite+React19+TS, Cloudflare Pages 배포):
- 디자인토큰(세이지틸/테라코타 SSOT), 앱셸, 공통 UI 프리미티브
- 7화면: 로그인/학습자홈/상담세션/회기리뷰/교수자/관리자/설정
- ClientAvatar: SVG 반구상 흉상 4상태 + RMS 립싱크 + 6파라미터 정서
- 회기리뷰는 외부 레퍼런스 디자인을 Vignette 토큰으로 리스킨

api (FastAPI):
- 게이트웨이 /v1/generate·/v1/stream 어댑터(상주풀/EngineSession 보존)
- services: 페르소나 L0~L6 빌더 / 결정론 상태머신 / 가드레일 /
  턴 오케스트레이터 / 회기간 메모리 / 평가AI / 음성 / RAG
- store: DB off 폴백(in-memory), sessions 실구현

검증:
- web: node22 tsc+vite build 통과(node23 segfault 회피), Pages 배포 200
- api: app.main import 통과
- 핫픽스: Topbar initials undefined-safe (undefined.trim 크래시)
- E2E: 서연(P1) 상담 1턴 — 좋은/나쁜 상담에 차등 반응 실증
2026-06-25 23:37:22 +09:00

76 lines
2.5 KiB
Python

"""Vignette FastAPI 앱 엔트리포인트.
Dockerfile CMD: uvicorn app.main:app --host 0.0.0.0 --port 8000
소유: 상태머신·가드레일·엔진 어댑터 호출부·SSE 스트리머·RBAC (마스터플랜 §5).
엔진 게이트웨이(engine_gateway/)는 람다 직접 소유 — 여기선 engine_client 로 호출만.
"""
from __future__ import annotations
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from . import __version__
from .config import settings
from .db import close_pool, healthcheck, init_pool
from .engine_client import engine_client
from .routes import auth as auth_routes
from .routes import eval as eval_routes
from .routes import kb as kb_routes
from .routes import sessions as session_routes
from .routes import voice as voice_routes
@asynccontextmanager
async def lifespan(app: FastAPI):
"""startup: DB 풀 + 엔진 클라이언트 / shutdown: 정리."""
await init_pool()
await engine_client.startup()
try:
yield
finally:
await engine_client.shutdown()
await close_pool()
app = FastAPI(
title="Vignette API",
version=__version__,
description="상담 시뮬레이션 훈련 플랫폼 백엔드 (FastAPI · 상태머신 · 가드레일 · RBAC)",
lifespan=lifespan,
)
# CORS — 정적 프론트(chanpaca.net) + SSE 분리경로(stream.chanpaca.net)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True, # __Host- HttpOnly 쿠키 전송
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
)
# TODO: Presidio PII 마스킹 미들웨어 (외부 LLM 경로 진입 전 하드 게이트, R7/F-03)
app.include_router(auth_routes.router)
app.include_router(session_routes.router)
# Features 트랙 스텁 라우터(evaluator/voice/rag 가 채움). 등록만 — import 가능 보장.
app.include_router(eval_routes.router)
app.include_router(voice_routes.router)
app.include_router(kb_routes.router)
@app.get("/health", tags=["meta"])
async def health() -> dict[str, object]:
"""liveness + DB + 엔진 게이트웨이 readiness."""
db_ok = await healthcheck()
engine_ok = await engine_client.health()
return {
"status": "ok" if db_ok else "degraded",
"version": __version__,
"environment": settings.environment,
"db": db_ok,
"engine": engine_ok,
"engine_mode": settings.engine_mode,
}