마지막 업데이트 2026-09-08
사후에 원인을 판별할 수 없던 두 신고 — 토큰 초과 뒤 빈 프롬프트로 AI 세션이 시작되는 경우와 활동 템플릿 삭제 뒤 회기 생성이 실패하는 경우 — 에 대해 기록만 추가한 PR이다. 동작 변경은 없다.
가장 먼저 볼 지점은 agent.py의 session.on("error") 구독이다. 지금까지 이 이벤트에 구독자가 아무도 없어 SDK 안에서 오류가 소멸하고 있었다. 그다음은 삭제 감사 로그를 try 밖으로 뺀 이유다.
S3 진단 로깅 개선 적용 계획의 P0-1(LiveKit 오류·프롬프트 상태) / P0-2(삭제·생성 감사) 범위만 구현했다. 새 전달 경로를 만들지 않고 기존 것을 재사용한다.
agent-event-log data channel → 브라우저 DataReceived → 세션 로그 → S3. 신규 API·신규 S3 prefix 없음.member.name을 뺀다. 셋 다 테스트로 고정.ppi-web-prod. 계획서의 P0-3(서버 → S3 수집 경로)을 하지 않아 교차 조회는 아직 안 된다.지침 스냅샷 저장, 토큰엔 configRef만
lib/voice-agent/livekit-session-config-store.ts
지침 원문은 브라우저를 거치지 않는다. Valkey에 저장하고 짧은 키만 토큰에 넣는다.
agent가 Valkey에서 본문 조회
livekit_model_config_store.py
기존에는 modelConfig만 반환하며 같은 스냅샷의 diagnostics를 버렸다.
오류·프롬프트 상태를 data channel로 발행
agent.py agent_session_error_events.py
원문 없이 code·status·크기·sha256만.
브라우저가 받아 세션 로그 → S3
lib/voice-agent/livekit-agent-diagnostic-fields.ts
두 이벤트 타입에서만 필드를 추출한다.
vendored SDK는 agent_activity.py:2429~2438에서 4가지 오류를 session.emit("error", ...)로 올리지만, PPI 쪽에 리스너가 하나도 없었다. 아래가 그 첫 구독자다.
초기 구현은 provider 메시지를 200자로 잘라 담았다가, lifecycle_trace.py의 기존 계약(error_message는 "private provider response"가 섞일 수 있어 안전 로그에서 제외)과 그것을 지키는 기존 테스트에 걸려 되돌렸다. 결과적으로 계획서가 요구한 "provider code 허용목록 정규화"와 같아졌다.
build_safe_model_config_log_payload는 만들어져 있고 테스트도 있었지만 어디에서도 호출되지 않았다. 원인은 조회 함수가 modelConfig만 반환하며 같은 스냅샷의 diagnostics를 버렸기 때문.
처음에는 logger.info가 try 안에 있었다. 그러면 로깅 예외가 catch로 떨어져 이미 지워진 템플릿에 대해 500을 반환하고, 클라이언트는 실패로 보고 재시도해 404를 받는다. 코드리뷰에서 잡아 try 밖으로 옮겼다.
DataReceived는 모든 agent 이벤트마다 돈다. 여기에 진단 필드를 무조건 붙이면 전사 delta 같은 이벤트 로그까지 넓어진다. 두 타입에서만 붙인다.
S3 세션 로그 (아동 브라우저 경유) — 실행해 뽑은 형태:
Loki ppi-web-prod — 두 줄이 공통 templateId로 이어진다:
서버 로그는 S3로 가지 않는다. apps/web/logger.js의 서버 분기가 console 출력 후 return하고, S3 싱크는 브라우저 분기에만 있다. 그래서 P0-2는 Loki 전용이다.
조사 과정에서 원본 계획 문서의 전제 3개가 실제 코드와 달랐다. 후속 작업 시 계획서보다 이쪽을 신뢰한다.
| 계획서 전제 | 실제 |
|---|---|
마스킹이 [REDACTED] 노이즈 원인 |
dbb120fc(PPI-1273, 9/4)로 이미 자격증명 key만 가리도록 축소됨. 9/3~9/6 Daily Report 시그니처는 그 커밋 전 로그. 범위에서 제외했다. |
| 프롬프트 길이·fingerprint 신규 수집 필요 | build_safe_model_config_log_payload와 생산측 livekit-session-config-store.ts에 이미 있었고 호출만 안 되고 있었다. 신규 설계가 아니라 배선. |
대표 시그니처 AUTO_TRANSITION|stepUpdate |
web 소스에 존재하지 않는 문자열. 분석기 쪽 라벨이다. 실제 경로는 video-end-watchdog.ts → guest-step-publisher.ts이고, 후자에는 revision·ack·retry가 이미 있다. |
| 레이어 | 파일 | 핵심 변경 |
|---|---|---|
| agent | agent_session_error_events.py 신규 | ErrorEvent → 진단 payload 정규화, 프롬프트 상태 builder. 원문 미포함 |
| agent | agent.py | session.on("error") 첫 구독, ready 후 프롬프트 상태 1회 발행, _fetch_model_config 반환 dict→tuple |
| agent | livekit_model_config_store.py | fetch_model_config_snapshot_from_valkey 신설. 기존 함수는 래퍼로 축소 |
| agent | lifecycle_trace.py | 안전 로그 허용목록에 신규 필드 12개. error_message는 기존대로 제외 유지 |
| web · API | activity-templates/[id]/route.ts | 삭제 성공 감사 기록. try 밖에서 남겨 응답 뒤집힘 차단 |
| web · API | lessons/[userId]/[index]/route.ts | 로그 없이 400만 반환하던 ActivityPlanError 경로에 구조화 로그 |
| web · lib | lesson-utils.ts | ActivityPlanError.details에 lessonTemplateId·누락 ID 목록 |
| web · lib | activity-template-audit.ts 신규 | 감사 레코드 builder. member.name 미포함 |
| web · voice-agent | livekit-agent-diagnostic-fields.ts 신규 | 두 이벤트 타입에서만 필드 추출 |
| web · voice-agent | livekit-event-fields.ts 신규 | stringField/numberField/booleanField 공용화 (중복 제거) |
| 테스트 | 5개 파일 | agent pytest +8, web vitest +8. 원문 미유출 3건을 테스트로 고정 |
실 세션에서 두 이벤트가 실제로 S3에 남는지는 검증되지 않았다. 수업 1건 진행 후 세션 로그에서 eventType:agent_prompt_state를, 지침을 한도 초과로 만들어 agent_session_error의 errorCode를 확인해야 한다.
ctx.connect()가 session.start() 뒤(agent.py:4530→4533)라, config fetch나 session.start 자체의 실패는 data channel이 없어 python 로그로만 남는다. 순서 변경은 로깅 범위를 넘는 동작 변경이라 손대지 않았고, 계획서 P0-3의 몫이다.
이벤트 타입 문자열(agent_session_error·agent_prompt_state)이 Python·TS 양쪽에 리터럴로 있다. 한쪽만 바꾸면 필드가 조용히 사라진다. 기존 AGENT_EVENT_TOPIC과 같은 패턴이라 그대로 뒀다.
fetch_model_config_from_valkey는 이제 프로덕션 호출자가 없고 테스트 2곳에서만 참조된다. 계약 유지용으로 남겼으나 정리 후보다.
동작 변경 없음. 세션 생성·시작·greeting 순서와 API 응답·상태코드가 모두 그대로다. 공유 계약 2건(_fetch_model_config 반환 타입, ActivityPlanError 파라미터)은 각각 호출자 동시 수정과 기본값 부여로 처리해 기존 호출 12곳은 무변경.
계획서 4장의 반복 로그 축소는 하지 않았다. baseline 없이 지우지 않기로 했다. 이번 PR은 순수 추가라 로그량이 늘기만 한다(세션당 프롬프트 상태 1건 + 오류 시 1건).
| 대상 | 결과 |
|---|---|
apps/livekit-agent pytest | 342 passed (baseline 334 + 신규 8) |
apps/web vitest | 14 passed (lesson-utils 7 / audit 3 / diagnostic-fields 4) |
livekit-client-session.test.ts | passed |
tsc --noEmit · prettier | 통과 |
사전 존재 실패 2건은 HEAD d235a6e0에서 임시 worktree로 재현해 이 변경과 무관함을 확인했다 — lesson-session.service.disconnect-race.test.ts(sessionDao.createSession mock 누락), host-disconnected.tsx TS2307(next-env.d.ts 미생성). next lint는 이 환경에서 CLI 인자 오류로 실행하지 못했다.