vignette/docs/guides/local-development.md
Yun Chan 085460b5e0 대시보드 폴드아웃/드릴다운 정리 + 페르소나 역린·misconduct 반응 + 게이트웨이 격리·RAG 비차단 수정
SSOT 대시보드:
- 한신대 기술분석 PDF(19쪽) 정합성 분석 + 이번 세션 발견 섹션 추가
- 섹션 폴드아웃(접기)·상단 목차(드릴다운)·모두 펼치기/접기 — 내용 보존, 레이아웃만 정리

페르소나 반응 강화('저항·반응 조절' 핵심 차별):
- PersonaCard.triggers(역린) 필드 + CCD 핵심상처 파생 역린 블록
- L0에 무례·모욕·조롱 시 현실적 동맹 균열 반응 지침

버그·성능 수정(라이브/E2E로 포착):
- 게이트웨이 페르소나 격리: --append-system-prompt를 --system-prompt(교체)로 + --exclude-dynamic-system-prompt-sections (내담자 캐릭터 붕괴·개발맥락 누출 차단)
- RAG: 임베더 동기 로드(약 7-13초)를 _warm_rag_caches 백그라운드 warm으로(세션 생성 블로킹 회귀 수정)
- voice TTS RMS 데드힌트 제거, init_state OpennessParams 파라미터객체화
- 한국어 PII(날짜·금액·주소) 마스킹 보강
- 레이아웃 시각 게이트: 폼 컨트롤 값 스크롤 오탐 제외(7/7)

검증: 백엔드 84/84, E2E 42(데스크톱 27·모바일 11·아바타 4), 시각 게이트 7/7
2026-06-27 02:30:46 +09:00

15 KiB

로컬 개발 실행 가이드

Vignette(AI 심리상담 시뮬레이션 훈련 플랫폼)를 로컬에서 띄워서 테스트하기 위한 단계별 가이드다. 주 환경은 Windows 11 + PowerShell을 기준으로 하되, 셸 차이가 중요한 곳은 별도로 표시한다.

  • 작업 디렉터리(저장소 루트): D:\workspace\vignette
  • 모노레포 구성
    • apps/api — FastAPI 백엔드 (Python)
    • apps/api/engine_gateway — AI 턴 생성용 엔진 게이트웨이(별도 프로세스, 포트 9099)
    • apps/web — React 19 + Vite 프런트엔드
    • infra — Docker Compose 스택(pgvector pg16 + api + web + rag + proxy)
    • scripts — 운영/점검 스크립트

핵심 구성요소: 학습자(상담수련생)가 AI 내담자 페르소나와 회기를 진행하고, 종료 후 회기 리뷰 피드백을 받는다. 백엔드는 페르소나 엔진 · 오케스트레이터 · 저항엔진 · 마스킹 게이트 · 음성 캐스케이드 · 회기 리뷰를 소유하고, 실제 AI 발화 생성은 engine_gateway(ENGINE_MODE 라우팅)가 담당한다. 단일 SoR은 Postgres16+pgvector이며, DB가 없으면 인메모리 degraded 폴백으로 기동한다.


0. 가장 빠른 경로 — 한 커맨드 (권장)

게이트웨이(9099)+API(8000)+웹(5173)을 한 번에 깔끔히 (재)기동한다. 기존/고아 프로세스를 커맨드라인 기준으로 정리한 뒤 결정론적으로 띄운다(--reload 워처 불안정 회피).

powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-up.ps1
# 종료: powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-down.ps1
  • 진입점 http://localhost:5173 → 로그인 페이지에서 dev-login(아무 @hs.ac.kr, role learner/teacher/admin).
  • DB 없이 in-memory degraded로 뜨고, seed 페르소나(P1~P3)·AI 턴 생성까지 동작.
  • 로그는 .devlogs/(gitignore). 코드 수정 후에는 dev-up을 다시 실행해 재기동(reload 미사용).
  • 옵션: -NoGateway(UI만), -NoWeb(API만).

스크립트는 uvicorn이 설치된 python을 자동 해석한다(시스템에 복수 python 공존 시 'python' 별칭이 uvicorn 없는 인터프리터를 가리킬 수 있음 — 이 함정 때문에 명시 해석함).

수동 기동(대안)

DB·엔진 없이도 UI/로그인/페르소나/세션 생성까지는 동작한다(턴 생성만 불가).

  1. APIapps/api에서 python -m uvicorn app.main:app --host 127.0.0.1 --port 8000
  2. apps/web에서 npm run devhttp://localhost:5173
  3. (선택) 엔진 게이트웨이apps/api에서 python -m uvicorn engine_gateway.gateway:app --host 127.0.0.1 --port 9099 (실제 AI 턴 생성을 하려면 필요. claude CLI 설치+로그인 전제)

아래에서 각 단계를 자세히 설명한다.


1. 사전 준비

  • Python 3.11 (운영 스크립트가 Python 3.11 기준). 가상환경 권장.
  • Node.js(최신 LTS) + npm — apps/web용.
  • (선택) Docker Desktopinfra/docker-compose.yml 전체 스택을 띄울 때만.
  • (선택) claude CLIENGINE_MODE=claude_cli로 실제 턴 생성을 할 때. 설치 후 로그인되어 있어야 한다.

Python 의존성 설치 (apps/api)

# 저장소 루트에서
python -m venv .venv
.\.venv\Scripts\Activate.ps1          # PowerShell 활성화
python -m pip install -r apps\api\requirements.txt

apps/api/requirements.txtfastapi, uvicorn[standard], asyncpg, pydantic, pydantic-settings, python-multipart, httpx, sse-starlette를 포함한다. 엔진 게이트웨이도 fastapi+uvicorn만 쓰므로 위 설치로 함께 충족된다. (Presidio PII 마스킹은 선택 의존성이라 기본 제외 — 없으면 정규식 폴백으로 자동 degrade.)

웹 의존성 설치 (apps/web)

cd apps\web
npm install

2. API 서버 실행 (FastAPI)

2.1 환경설정(.env 자동 로드)

설정은 전부 env 주입이며 pydantic-settings작업 디렉터리의 .env를 자동 로드한다 (apps/api/app/config.pySettingsConfigDict(env_file=".env", env_file_encoding="utf-8")). 즉 apps/api 디렉터리에서 uvicorn을 실행하면 apps/api/.env가 자동 반영된다.

로컬 dev에서 중요한 키(이미 apps/api/.env에 셋업되어 있거나, 없으면 .env.example 참고):

로컬 dev 값 의미
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 라우팅
AUTH_DEV_LOGIN_ENABLED true dev-login 엔드포인트 활성화
AUTH_ALLOWED_EMAIL_DOMAINS ["hs.ac.kr","twentyoz.kr"] 로그인 허용 이메일 도메인(dev-login 포함 검증)

참고: 프로세스 환경변수($env:KEY)는 .env보다 우선한다. 일회성 오버라이드에 쓸 수 있다.

2.2 실행

cd apps\api
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload

기동 로그에 Application startup complete.가 뜨면 정상이다. 프록시(아래 웹)는 /api 프리픽스를 제거해 이 8000 포트로 붙는다.

2.3 seed 페르소나로 띄우기 (선택)

기본값은 seed 미적재(AUTO_SEED_PERSONAS=false)다. 내장 페르소나(P1~P3)를 메모리/DB에 올려서 바로 회기를 만들고 싶으면 실행 전에 두 플래그를 켠다.

cd apps\api
$env:AUTO_SEED_PERSONAS = "true"
$env:ALLOW_SEED_PERSONA_FALLBACK = "true"
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload

main.py lifespan은 settings.auto_seed_personas가 켜져 있을 때만 materialize_seed_personas()를 호출한다. (또는 apps/api/.env에 두 키를 true로 적어도 된다.)

2.4 DB 없이 degraded 기동 (정상 동작)

DB 연결이 안 되어도 dev에서는 그대로 기동한다. main.py lifespan이 풀 초기화 예외를 잡고 store 인메모리 폴백으로 degraded 기동하며, 다음 경고를 남긴다.

DB 풀 초기화 실패 — store 인메모리 폴백으로 degraded 기동: ...

이때 /health는 다음과 같이 응답한다(DB·엔진 미가용이면 status: degraded).

{ "status": "degraded", "db": false, "engine": false, "environment": "dev", "engine_mode": "claude_cli", ... }

중요: dev에서 db: false버그가 아니라 의도된 폴백이다. 실제 DB가 필요하면 Postgres16+pgvector를 DATABASE_URL이 가리키는 곳(로컬 dev는 127.0.0.1:55432)에 띄우면 된다. ENVIRONMENT가 dev가 아니면 폴백하지 않고 그대로 실패한다.

헬스 체크:

Invoke-RestMethod http://127.0.0.1:8000/health
# 또는
curl.exe http://127.0.0.1:8000/health

3. dev 로그인 (로컬/E2E용 서버 로그인)

프로덕션 경로는 Google OIDC지만, 로컬에서는 dev-login으로 쿠키 세션을 바로 발급받을 수 있다. 조건: ENVIRONMENT=dev 그리고 AUTH_DEV_LOGIN_ENABLED=true, 그리고 요청 Origin/Host가 로컬이어야 한다 (routes/auth.py_dev_login_available). 충족 못 하면 404 dev login is disabled.

  • 엔드포인트: POST /auth/dev-login
  • 본문: { "email": ..., "role": "learner|teacher|admin", "display_name": ... }
  • role 기본값은 learner.
  • 성공 시 __Host-vignette_sid 쿠키(및 dev 전용 vignette_sid 쿠키)를 세팅한다.

주의(이메일 도메인): dev-login도 validate_google_identity_domain을 거치므로 이메일 도메인이 AUTH_ALLOWED_EMAIL_DOMAINS에 있어야 한다. 예: learner@hs.ac.kr. 다른 도메인이면 403 email domain is not allowed.

3.1 PowerShell(권장) — Invoke-RestMethod + 세션 쿠키

PowerShell의 curlInvoke-WebRequest 별칭이라 JSON 본문·쿠키 다루기가 번거롭다. PowerShell에서는 Invoke-RestMethod가 가장 깔끔하다.

$body = '{"email":"learner@hs.ac.kr","role":"learner","display_name":"테스트 학습자"}'
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8000/auth/dev-login `
  -ContentType 'application/json' -Body $body -SessionVariable s

# 발급된 쿠키로 현재 사용자 확인
Invoke-RestMethod -Uri http://127.0.0.1:8000/auth/me -WebSession $s

3.2 curl.exe(셸 무관) — JSON 본문은 파일로

Windows에서 따옴표 이스케이프 사고를 피하려면 본문을 파일에 넣고 --data-binary @file로 보낸다. (PowerShell에서는 반드시 curl.exe라고 적어 별칭이 아닌 실제 curl을 호출한다.)

# body.json 작성
'{"email":"learner@hs.ac.kr","role":"learner","display_name":"테스트 학습자"}' |
  Out-File -Encoding ascii body.json

# 쿠키 저장(-c) + 본문은 파일(@)로
curl.exe -i -X POST http://127.0.0.1:8000/auth/dev-login `
  -H "Content-Type: application/json" --data-binary "@body.json" -c cookies.txt

# 저장한 쿠키(-b)로 재호출
curl.exe http://127.0.0.1:8000/auth/me -b cookies.txt

3.3 웹 UI

apps/web의 로그인 화면(Login.tsx)에 dev-login 경로가 있다. 웹을 띄운 상태(5173)에서 프록시를 통해 /api/auth/dev-login으로 동일하게 동작한다.


4. 웹(프런트엔드) 실행

cd apps\web
npm run dev
  • 접속: http://localhost:5173
  • 프록시: apps/web/vite.config.ts/apihttp://127.0.0.1:8000로 보내며 /api 프리픽스를 제거한다. (changeOrigin: true, ws: true로 쿠키·WebSocket 전달.) 따라서 브라우저의 /api/auth/me는 백엔드의 /auth/me에 도달한다.
  • API가 8000에서 떠 있어야 프록시가 의미가 있다(2장 먼저 실행).

기타 웹 스크립트(apps/web):

npm run typecheck   # tsc -b
npm run build       # tsc -b && vite build
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개로 컨텍스트·프롬프트 캐시를 재사용한다.

5.1 실행

cd apps\api
python -m uvicorn engine_gateway.gateway:app --host 127.0.0.1 --port 9099

관련 환경변수(gateway.py):

  • CLAUDE_BIN — claude 실행 파일 경로(기본 claude). PATH에 없으면 절대경로 지정.
  • 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). 설치는 됐지만 로그인 안 된 상태를 여기서 잡는다.
Invoke-RestMethod http://127.0.0.1:9099/health
Invoke-RestMethod http://127.0.0.1:9099/ready

API의 /health는 게이트웨이의 /ready를 호출(404면 /health로 폴백)해 engine 필드를 채운다 (engine_client.health_detail). 즉 게이트웨이가 떠 있어도 claude가 인증 안 됐으면 /ready가 503이라 API /healthengine: false가 된다.

5.3 프로빙 스크립트 (선택)

이미 떠 있는 게이트웨이의 스트리밍 TTFT/지연/세션 재사용을 측정한다.

python scripts\probe-engine-gateway.py --base-url http://127.0.0.1:9099
python scripts\probe-engine-gateway.py --json     # 기계 판독용

기본 base-url은 ENGINE_URL 또는 http://127.0.0.1:9099. 이 스크립트는 실제 세션을 만들어 생성 호출을 하므로(예산 소모) 자격증명은 넘기지 않고 이미 떠 있는 게이트웨이에만 붙는다.

게이트웨이가 없을 때: API /healthengine: false이고 턴 생성은 실패한다. 다만 UI/네비게이션/로그인/페르소나 조회/세션 생성(in-memory)은 정상 동작한다.


6. (선택) Docker Compose 전체 스택

DB까지 포함한 통합 실행은 infra/docker-compose.yml(db pgvector pg16 + api + web + rag + proxy)을 쓴다. Docker Desktop이 필요하고, infra/.env(템플릿: infra/.env.example)에 POSTGRES_PASSWORD, APP_DB_PASSWORD, SESSION_SECRET, OAuth/OpenAI 키 등을 채워야 한다.

cd infra
docker compose up -d db        # DB만
docker compose up -d           # 전체

엔진 게이트웨이(9099)는 컴포즈 밖 호스트에서 돌리고, api 컨테이너는 ENGINE_URL=http://host.docker.internal:9099로 호출한다(compose 기본값).


7. 테스트

# 백엔드 (apps/api)
cd apps\api
python -m pytest app\ -q              # 현재 77 pass
python -m pytest engine_gateway\ -q   # 7 pass

# 웹 (apps/web)
cd apps\web
npm run typecheck
npm run build
npm run e2e                           # Playwright — web+api+DB 스택 필요

8. 트러블슈팅

  • 포트 충돌(8000/5173/9099): 점유 프로세스 확인 후 종료.

    Get-NetTCPConnection -LocalPort 8000 | Select-Object OwningProcess
    Stop-Process -Id <PID> -Force
    

    또는 uvicorn --port를 바꾼다(웹 프록시는 8000 가정이므로 바꾸면 vite.config.ts target도 함께 조정).

  • /healthdb: false: dev에서는 정상 degraded 폴백(인메모리 store). 실제 DB가 필요하면 DATABASE_URL 대상(로컬 dev 127.0.0.1:55432)에 Postgres16+pgvector를 띄운다. ENVIRONMENT가 dev가 아니면 폴백하지 않고 기동이 실패한다.

  • /healthengine: false / 턴 생성 실패: 게이트웨이(9099)가 안 떠 있거나 claude CLI가 인증 안 됨. 게이트웨이 GET /readydetail을 보고 원인을 확인한다. 게이트웨이를 먼저 실행하라.

  • dev-login이 404 (dev login is disabled): ENVIRONMENT=dev + AUTH_DEV_LOGIN_ENABLED=true인지, 요청이 로컬 Origin/Host인지 확인. apps/api에서 uvicorn을 실행해 .env가 로드됐는지도 확인.

  • dev-login이 403 (email domain is not allowed): 이메일 도메인이 AUTH_ALLOWED_EMAIL_DOMAINS에 없음. learner@hs.ac.kr 같은 허용 도메인을 쓴다.

  • /auth/me가 401: 쿠키가 전달되지 않음. curl은 -c/-b로 쿠키를 저장·재사용하고, PowerShell은 -SessionVariable/-WebSession을 쓴다. 브라우저는 프록시(5173) 경유로 호출해야 __Host- 쿠키가 동일 출처로 붙는다.

  • PowerShell에서 curl이 이상하게 동작: PowerShell curlInvoke-WebRequest 별칭이다. 실제 curl을 쓰려면 curl.exe로 호출하고, JSON 본문은 --data-binary "@body.json"처럼 파일로 넘긴다.

  • 한글/공백 경로: 경로에 공백·한글이 있으면 큰따옴표로 감싼다. .env/JSON 파일은 UTF-8(또는 ascii)로 저장.