# 한신대 실사용 피드백 개선 계획 레드팀 검토 작성일: 2026-07-03 대상: `docs/ops/hanshin-feedback-improvement-plan-2026-07-03.md` 상태: 1차 적대 검토 완료 / 구현 전 차단 조건 반영 필요 ## 1. 요약 계획의 방향은 맞다. 한신대 실사용 피드백을 단순 화면 문구 문제가 아니라 컨텍스트, 출력 품질, 리뷰 UX, 설명 가능성 문제로 나눈 판단은 타당하다. 다만 그대로 구현하면 위험한 지점이 있다. 특히 아래 네 가지는 구현 전 반드시 막아야 한다. 1. `persona.py` role mapping이 잘못된 상태에서 gateway history만 보존하면 품질이 악화될 수 있다. 2. `_split_messages()` 변경은 단순 보강이 아니라 gateway 계약 변경이다. 3. 리뷰 API의 마스킹 토큰을 자연어화하면 privacy proof와 기존 E2E 계약이 깨질 수 있다. 4. 품질 fallback을 실제 내담자 발화처럼 저장하면 평가·워크시트·carry-over가 오염된다. ## 2. BLOCKER ### B1. P1은 원자적으로 처리해야 한다 `persona.build_turn_messages()`의 recent turn role mapping 수정과 gateway `_split_messages()` history 소비 변경은 같은 패치에서 처리해야 한다. 현재 매핑이 잘못된 상태에서 history만 보존하면 상담자 발화를 내담자 과거 발화처럼 소비할 수 있다. 반영 문장: > P1-1 role mapping 수정과 P1-2 gateway history 소비는 같은 패치에서 원자적으로 처리한다. history 보존만 먼저 배포하지 않는다. ### B2. Gateway history 보존은 계약 변경이다 현재 gateway 테스트는 “이전 assistant/user는 버리고 마지막 user만 보낸다”를 명시적으로 고정하고 있다. 따라서 `_split_messages()` 변경은 테스트 추가가 아니라 기존 계약과 골든 변경이다. 필수 조치: - 기존 gateway split 테스트 기대값 갱신 - 호출부가 어떤 `ai_role`에서 history를 소비하는지 명시 - evaluator/structured output 요청에서 client history가 섞이지 않는지 회귀 테스트 ### B3. Resident session은 1차 범위에서 꺼야 한다 gateway `_resolve_session()`은 같은 `session_id`가 살아 있으면 새 system prompt를 무시하고 재사용한다. evaluator도 같은 app session id를 보낼 수 있으므로 resident session을 성급히 켜면 client 컨텍스트와 evaluator 컨텍스트가 섞일 수 있다. 반영 문장: > 이번 1차 구현은 stateless history injection으로 한정하며, resident session 재사용/prompt caching 복구는 별도 설계 없이는 켜지 않는다. ### B4. Fallback은 실제 임상 반응이 아니다 품질 게이트 실패 후 “말을 고르며 잠시 멈춘다” 같은 fallback을 실제 내담자 발화로 저장하면 deep-loop, 회기 요약, 다음 회기 carry-over, `clientFeedback`, worksheet evidence가 오염된다. 필수 조치: - fallback 저장 시 `synthetic`/`fallback` 메타데이터 부여 - 평가·워크시트·carry-over 기본 입력에서 제외 - 학습자 UI에는 내부 reason 대신 안전한 표시 문구 제공 ### B5. 리뷰 API 마스킹 토큰은 보존해야 한다 리뷰/상세 API는 privacy proof로 `[NAME]`, `[ORG]`, `[PHONE]` 같은 마스킹 토큰을 유지해야 한다. 사람용 UI에서 자연어화가 필요하더라도 API 축어록·근거 quote·worksheet evidence·export는 마스킹 불변식을 보존해야 한다. 반영 문장: > 리뷰 API의 축어록·근거 quote는 privacy proof를 위해 `[NAME]` 등 마스킹 토큰을 보존한다. 자연어 치환은 UI 표시 전용 필드 또는 컴포넌트 렌더링에서만 적용한다. ### B6. 교수자 설명은 임상 확정처럼 쓰면 안 된다 DSM/CBT/약물·심리평가/루브릭/골든셋 설명은 “현재 구현됨”과 “임상팀 검수 필요”를 같은 톤으로 쓰면 안 된다. 특히 CBT는 현재 `theory_mode`와 evaluator prompt 연결 수준이지, 임상팀 확정 CBT 단계 chain/citation/rubric 구조가 아니다. 반영 문장: > DSM/CBT/약물·검사/루브릭/골든셋 설명은 임상팀 검수 상태와 source/citation이 연결된 범위에서만 확정 표현을 쓴다. ## 3. MAJOR - gateway history 직렬화는 `ai_role=client` 요청에만 적용해야 한다. - `recent_turns(k=6)`은 레코드 수 기준이라 상담자/내담자 pair를 보장하지 않는다. 최근 완성 K쌍 기준으로 자른다. - stream 버퍼링을 넣으면 사용자가 본 텍스트, 저장된 텍스트, 평가된 텍스트가 달라질 수 있다. - voice 경로는 stream이 아니므로 generate 지연, TTS 시작, persistence 실패 시 learner-only 기록 방지를 따로 검증해야 한다. - fast-loop 라벨만으로 “후속 해명을 반영하지 않았다”는 혼란이 닫히지 않을 수 있다. deep-loop/교수자 검토와 구분을 같이 보여줘야 한다. - `clientFeedback`는 실제 피드백 산출물이 아니라 마지막 client turn이므로 라벨과 포함 조건을 재검토해야 한다. - 오류 문구 개선은 단순 문자열 치환이 아니라 `errorKind`, role별 display copy, operator raw error 계약 분리여야 한다. - 공유 payload에도 원문 축어록과 학습자 식별 정보가 들어가지 않는지 확인해야 한다. ## 4. MINOR - 검증 명령은 Windows PowerShell 5.1 기준으로 `Push-Location ...; ...; Pop-Location` 또는 `py -3.11 -X utf8` 형태로 적는다. - “컨텍스트 보존이 반복/문맥오해를 줄인다”는 표현은 필요조건으로 낮춘다. 출력 게이트와 평가 라벨까지 같이 닫아야 한다. - 개방도는 “점수”보다 “시뮬레이션 상태값”으로 설명한다. - seed persona 기준인지 DB catalog 기준인지 교수자 설명에서 구분한다. - 학습자용 오류 문구는 “평가 AI 응답 형식이 맞지 않아”보다 “자동 평가를 완료하지 못했어”처럼 내부 구현을 덜 드러내는 표현이 낫다. ## 5. 구현 전 테스트 제안 Backend: - `Push-Location apps/api; py -3.11 -X utf8 -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py app/test_evaluator_model_routing.py app/test_voice_ws.py -q; Pop-Location` - 신규: `persona.build_turn_messages()`가 `counselor -> user`, `client -> assistant`를 보장하는 단위 테스트. - 신규: gateway `_split_messages()`가 client history를 라벨 포함 payload로 정확히 1회만 넣고 evaluator 요청에는 넣지 않는 테스트. - 신규: REST/stream/voice fake engine 캡처 테스트에서 직전 상담자 질문과 내담자 답변이 실제 gateway user payload까지 도달하는지 검증. - 신규: evaluator fast/deep이 client resident/history를 재사용하지 않는 회귀 테스트. - 신규: quality fallback turn이 synthetic으로 표시되고 `clientFeedback`, worksheet evidence, deep eval에 무표시로 섞이지 않는지 검증. Review/privacy: - 신규: `/review` JSON은 raw PII 미포함 + masked token 보존, UI 표시만 humanized 되는지 검증. - 신규: `no_structured_output`/`engine_error`/`parse_error`가 role별 display copy로 매핑되고 raw error는 learner UI에 안 나오는지 검증. - 신규: 리뷰 API/detail/eval/export 경로에서 `[NAME]` 마스킹 불변식 보존. - 신규: 공유 payload에 원문 축어록과 학습자 식별 정보가 실리지 않는지 검증. Frontend: - `Push-Location apps/web; npm run typecheck; Pop-Location` - `Push-Location apps/web; npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1; Pop-Location` - DB/engine 가능 시 `Push-Location apps/web; npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1 --grep "Korean PII|manual AI evaluation retry|case worksheet|selected CBT theory mode"; Pop-Location` SSOT: - `py -3.11 -X utf8 scripts\check-dev-dashboard-ssot.py --json` ## 6. 판정 계획은 진행 가능하다. 단, P1 내부는 표면 개선으로 쪼개면 위험하다. 대레드팀 검토 후 1차 구현 단위는 다음처럼 조정한다. - P1: role mapping + client-only stateless history injection + evaluator 분리 focused test - P2 1차: fallback 저장 없이 명백한 출력 결함 감지 + 1회 재생성 + 실패 시 client turn 미저장 - P3 1차: API 마스킹 불변식 + 리뷰 UI 렌더링 자연어화 + raw error display mapper + fast/deep 라벨 - P4 1차: 현재 구현과 임상팀 확정 필요 범위를 분리한 교수자 설명 패널 교수자 설명 패널은 제품 신뢰를 높일 수 있지만, 임상팀 확정 전 표현은 보수적으로 써야 한다. 후속 균형 검토: `docs/redteam/hanshin-feedback-plan-counter-redteam-2026-07-03.md`