feat: 운영 안정성과 세션 음성 경험 개선

This commit is contained in:
Yun Chan 2026-07-31 00:13:08 +09:00
parent facc4ad2d9
commit c788343467
95 changed files with 8431 additions and 1785 deletions

View file

@ -26,6 +26,8 @@ DB가 없으면 인메모리 degraded 폴백으로 기동한다.
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-up.ps1
# 로컬 Higgs Audio v3 P1 음성까지 함께 연결
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-up.ps1 -UseHiggsVoice
# 종료: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-down.ps1
# DB 컨테이너까지 멈출 때만: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-down.ps1 -Db
```
@ -33,7 +35,8 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-up.ps1
- 진입점 **http://localhost:5173** → 로그인 페이지에서 **dev-login**(아무 `@hs.ac.kr`, role learner/teacher/admin).
- Docker가 있으면 기본적으로 `127.0.0.1:55432` DB 컨테이너를 사용한다. 새 컨테이너 생성 시 `POSTGRES_USER=vignette_owner`, API용 앱 role은 `DATABASE_URL` 사용자로 분리해 RLS 검증 기반을 보존한다. Docker가 없거나 `-NoDb`를 쓰면 in-memory degraded로 뜬다.
- 로그는 `.devlogs/`(gitignore). 코드 수정 후에는 dev-up을 다시 실행해 재기동(reload 미사용).
- 옵션: `-NoGateway`(기존 gateway 보존), `-NoWeb`(기존 web 보존, API만 재기동), `-NoDb`(DB 컨테이너 보장 건너뜀).
- 옵션: `-NoGateway`(기존 gateway 보존), `-NoWeb`(기존 web 보존, API만 재기동), `-NoDb`(DB 컨테이너 보장 건너뜀),
`-UseHiggsVoice`(설치된 `higgs-audio-v3-tts-4b`를 127.0.0.1:9881에 상주시켜 P1 TTS로 연결).
- `-NoGateway`/`-NoWeb`를 쓰면 해당 컴포넌트의 stale 정리도 건너뛰고, API 정리는 지정한 `-ApiPort` listener만 대상으로 한다. public `8001`, Tailnet `8010`, local `8000`을 나눠 띄운 상태에서 API-only 재기동할 때 다른 포트를 건드리지 않는다.
- `dev-down.ps1`은 기본적으로 DB 컨테이너를 보존한다. 컨테이너도 멈추려면 `-Db`를 명시한다.
@ -90,7 +93,10 @@ DB·엔진 없이도 UI/로그인/페르소나/세션 생성까지는 동작한
- **Python 3.11** (운영 스크립트가 Python 3.11 기준). 가상환경 권장.
- **Node.js 22 + npm** — CI와 같은 기준. newer LTS는 별도 재검증 전까지 기준선으로 쓰지 않는다.
- (선택) **Docker Desktop**`infra/docker-compose.yml` 전체 스택을 띄울 때만.
- (선택) **`claude` CLI** — `ENGINE_MODE=claude_cli`로 실제 턴 생성을 할 때. 설치 후 로그인되어 있어야 한다.
- (선택) **`claude` CLI** — `claude_cli` 실행. 설치 후 로그인되어 있어야 한다.
- (선택) **Codex CLI**`codex_cli` 실행과 `model/list` 탐색. 기본 선택은 `gpt-5.6-terra` / Medium.
- (선택) **Agy CLI**`agy_cli` 실행과 `agy models` 탐색. 기본 선택은 `gemini-3.6-flash-high` / High.
- (선택) **`ANTHROPIC_API_KEY`** — `claude_api`의 모델 목록과 Messages API 실행.
### Python 의존성 설치 (`apps/api`)
@ -130,7 +136,8 @@ npm install
| `ENVIRONMENT` | `dev` | dev여야 DB 폴백·dev-login·seed 폴백이 허용됨 |
| `DATABASE_URL` | `postgresql://...@127.0.0.1:55432/vignette` | 미연결 시 degraded 폴백(dev 한정) |
| `ENGINE_URL` | `http://127.0.0.1:9099` | 엔진 게이트웨이 베이스 URL |
| `ENGINE_MODE` | `claude_cli` | 엔진 provider 라우팅 |
| `ENGINE_MODE` | `claude_cli` | `claude_cli` / `claude_api` / `codex_cli` / `agy_cli` 공급자 라우팅. 실제 운영 변경은 관리자 드롭다운이 DB에 저장 |
| `VIGNETTE_LIVE_CLIENT_PROVIDER` | `claude_cli` | 실시간 내담자 AI 전용 lane. 관리자에서 선택한 evaluator/review 공급자와 분리해 회기별 Claude 상주 세션을 재사용 |
| `AUTH_DEV_LOGIN_ENABLED` | `true` | dev-login 엔드포인트 활성화 |
| `AUTH_ALLOWED_EMAIL_DOMAINS` | `["hs.ac.kr","twentyoz.kr"]` | 기본 로그인 허용 이메일 도메인(dev-login 포함 검증). `/admin/users`에 미리 등록된 정확한 이메일은 도메인 밖이어도 예외로 로그인 가능 |
| `AUTH_SUPER_ADMIN_EMAILS` | `["yunchan@twentyoz.kr","hoonjungkoo@hs.ac.kr"]` | 학습자·교수자·관리자 공간 접근과 승인 상태를 부여할 슈퍼 관리자 이메일 |
@ -139,6 +146,8 @@ npm install
| `AUTH_EMAIL_COHORT_MAP` | `{}` | 특정 이메일을 cohort id로 매핑한다. 값은 comma-separated 문자열도 허용 |
| `AUTH_DOMAIN_COHORT_MAP` | `{}` | 이메일/Google hosted domain을 cohort id로 매핑한다. Google/SAML/dev-login 세션 `cohort_ids`에 반영 |
| `VIGNETTE_VOICE_POC_SAMPLE_TTS` | `false` | P1 무참조 샘플 음성을 `/voice/ws` TTS에 연결하는 개발 전용 플래그. 마이크/STT는 `OPENAI_API_KEY` 필요, 프로덕션 금지 |
| `VIGNETTE_VOICE_TTS_PROVIDER` | `openai` 또는 `higgs` | TTS 공급자. `higgs`는 dev + P1에서만 허용하며 다른 환경은 설정 검증에서 차단 |
| `VIGNETTE_HIGGS_TTS_URL` | `http://127.0.0.1:9881` | 로컬 Higgs 상주 서버. 저장소의 무참조 synthetic seed만 화자 참조로 사용 |
| `EVALUATOR_SEMANTIC_CACHE_ENABLED` | `true` | fast/deep evaluator structured 결과 인메모리 캐시 활성화. 원문 prompt/completion은 저장하지 않음 |
| `EVALUATOR_SEMANTIC_CACHE_TTL_SECONDS` | `900` | evaluator cache TTL(초). 0 이하면 비활성 |
| `EVALUATOR_SEMANTIC_CACHE_MAX_ENTRIES` | `256` | evaluator cache LRU 최대 엔트리 수. 0 이하면 비활성 |
@ -407,9 +416,15 @@ npm run e2e # Playwright (web + api + DB 스택 필요)
## 5. 엔진 게이트웨이 (AI 턴 생성)
`ENGINE_MODE=claude_cli`에서는 실제 발화 생성을 `engine_gateway`가 담당한다.
이 게이트웨이는 로컬 `claude -p`(Opus 4.8) 상주 프로세스 풀로, **컨테이너 밖(호스트)에서** 9099 포트로 실행한다.
회기 1개 = `claude -p` 프로세스 1개로 컨텍스트·프롬프트 캐시를 재사용한다.
실제 발화 생성은 `ENGINE_MODE`와 무관하게 `engine_gateway`가 담당한다. 게이트웨이는
**컨테이너 밖(호스트)** 9099 포트에서 Claude CLI 상주 풀, Anthropic Messages API, Codex CLI,
Agy CLI를 공통 계약으로 라우팅한다. 관리자는 `/admin/ai` 또는 `/settings`의 AI 운영 섹션에서 공급자를
고르고, 게이트웨이가 반환한 모델·추론 강도만 드롭다운으로 저장할 수 있다.
실시간 내담자 발화는 `VIGNETTE_LIVE_CLIENT_PROVIDER=claude_cli`일 때 관리자 기본 공급자와 별도의
회기별 상주 Claude lane을 쓴다. Agy를 evaluator/review 기본값으로 유지해도 내담자 답변이 Agy CLI의
매 호출 도구 스키마 부팅 비용을 기다리지 않는다. 스트림의 learner/client turn과 상태는 먼저 저장하고
`done`을 보낸 뒤 fast-loop 평가는 백그라운드에서 같은 learner turn에 durable하게 붙인다.
### 5.1 실행
@ -420,24 +435,29 @@ python -m uvicorn engine_gateway.gateway:app --host 127.0.0.1 --port 9099
관련 환경변수(`gateway.py`):
- `CLAUDE_BIN` — claude 실행 파일 경로(기본 `claude`). PATH에 없으면 절대경로 지정.
- `CODEX_BIN`, `AGY_BIN` — 각 CLI 경로. Windows Codex는 npm shim 아래 native exe를 자동 탐색한다.
- `ANTHROPIC_API_KEY`, `ANTHROPIC_API_BASE` — Anthropic 모델 목록·Messages API.
- `ENGINE_CLI_CWD` — Codex/Agy를 저장소 밖에서 실행할 격리 cwd.
- `ENGINE_CAPABILITY_CACHE_TTL_SECONDS`(기본 60), `ENGINE_CLI_TIMEOUT_SECONDS`(기본 300).
- `ENGINE_MODEL` — 비우면 CLI 기본(Opus 4.8), `ENGINE_FALLBACK_MODEL`, `SESSION_BUDGET_USD` 등.
### 5.2 헬스/레디 확인
게이트웨이는 가지 점검 엔드포인트를 제공한다.
게이트웨이는 가지 점검 엔드포인트를 제공한다.
- `GET /health` — 얕은 프로세스 liveness. `{"ok": true, "engine": "claude_p", ...}`
- `GET /ready` — **실제로 1회 생성**을 돌려 `claude -p` 인증/동작까지 증명(성공 200, 실패 503).
설치는 됐지만 로그인 안 된 상태를 여기서 잡는다.
- `GET /health` — 얕은 게이트웨이 프로세스 liveness. `{"ok": true, "engine": "claude_cli", ...}`
- `GET /ready?provider=&model=&reasoning_effort=` — 선택 조합으로 **실제 1회 생성**을 돌려 인증·동작까지 증명.
- `GET /v1/capabilities?provider=&force=` — 현재 계정에서 선택 가능한 모델·추론 강도를 반환.
```powershell
Invoke-RestMethod http://127.0.0.1:9099/health
Invoke-RestMethod http://127.0.0.1:9099/ready
Invoke-RestMethod 'http://127.0.0.1:9099/v1/capabilities?provider=codex_cli&force=true'
Invoke-RestMethod 'http://127.0.0.1:9099/ready?provider=codex_cli&model=gpt-5.6-terra&reasoning_effort=medium'
```
API의 `/health`게이트웨이의 `/ready`를 호출(404면 `/health`로 폴백)해 `engine` 필드를 채운다
(`engine_client.health_detail`). 즉 게이트웨이가 떠 있어도 `claude`가 인증 안 됐으면 `/ready`가 503이라
API `/health``engine: false`가 된다.
API의 `/health`현재 DB 설정의 provider/model/reasoning_effort를 게이트웨이 `/ready`에 전달하고
(구형 게이트웨이는 `/health`로 폴백) `engine` 필드를 채운다. 따라서 프로세스만 떠 있고 선택한 CLI/API가
미인증이거나 모델 조합을 실행할 수 없으면 API `/health``engine: false`가 된다.
### 5.3 프로빙 스크립트 (선택)
@ -488,8 +508,8 @@ RAG 임베딩/리랭커 의존성은 기본 이미지에 설치하지 않는다.
```powershell
# 백엔드 (apps/api)
cd apps\api
python -m pytest app/ -q # 백엔드 기준선 400 pass
python -m pytest engine_gateway\ -q # 현재 29 pass
python -m pytest app/ -q # 백엔드 기준선 432 pass
python -m pytest engine_gateway\ -q # 현재 44 pass
# 웹 (apps/web)
cd apps\web