ELI5 · 쉬운 설명

ppi-issue-classifier — 신고 한 줄이
"어디서 × 무엇이" 상위 3개가 되기까지

마지막 업데이트 2026-09-23

"진행자 측에서 아동 소리 안 들림" 같은 신고 한 건과 그 수업의 로그 4종을 넣으면, 원인이 어디(아동·진행자·서버·네트워크)에서 무엇(마이크·권한·오디오…)인지 확률 순으로 3개를 냅니다. 데이터가 흘러가는 길을 그림 8장으로 설명합니다.

2026-09-23저장소 Dobraindev/ppi-issue-classifier (1cbd1ac)하루치 일괄 트리아지는 lesson-log-triage 계열

한 장 요약. 로그 수만 줄을 그대로 모델에 주지 않습니다. 코드가 먼저 세고, 줄이고, 단어로 바꾼 뒤 모델(TypeSafe Jev)에게는 "이건 마이크 문제야? 예/아니오 확률만 줘" 식의 짧은 질문 19개를 한 번에 던집니다. 둘을 합쳐 순위를 매깁니다.

그림 1전체 그림 — 왼쪽에서 오른쪽으로 한 번

다섯 단계. 파란 칸은 코드, 보라 칸은 모델, 초록 칸은 결과물입니다.

이슈 분류 전체 데이터 흐름 증상 신고와 로그 파일 4개가 정리 단계로 들어가 하나의 이벤트 목록이 되고, 규칙 채점과 압축을 거쳐 Jev 질문 19개로 보내진 뒤, 규칙 점수와 Jev 확률을 합쳐 위치 × 유형 상위 3개와 JSON, ppi-docs HTML이 나온다. 증상 신고 한 줄 "진행자 측 소리 안 들림" Grafana 로그 4개 guest (아동) monitor (진행자) ppi-livekit ppi-socket ① 정리 4가지 모양을 한 줄 모양으로 events.py ② 채점 · 압축 규칙으로 점수 수천 줄 → 수십 줄 features.py ③ Jev에게 질문 질문 19개를 한 번에 · 확률만 jev.py 규칙 점수는 따로 들고 간다 ④ 합치기 규칙 + Jev → 순위 classify.py ⑤ 결과 상위 3개 JSON ppi-docs HTML

모델은 가운데 한 칸뿐입니다. 나머지는 전부 코드라 같은 입력이면 같은 결과가 나옵니다.

그림 2정리 — 네 가지 모양을 한 줄 모양으로

로그마다 생김새가 다릅니다. Grafana에서 받으면 한 번 더 포장돼 있기도 합니다. 포장을 벗기고 같은 모양으로 맞춥니다.

로그 네 종류를 공통 이벤트로 정리 guest와 monitor 세션 로그, ppi-socket, ppi-livekit 로그가 각자 다른 필드 이름을 쓰고, Grafana 포장이나 gzip이 씌워져 있을 수 있다. 정리 단계가 이를 벗기고 시각, 출처, 수준, 문맥, 메시지로 된 한 줄 모양으로 맞추며, 출처에 따라 아동측, 진행자측, 서버로 위치 꼬리표를 붙인다. guest · monitor 세션 로그 {ts, level, ctx, msg, data} ppi-socket {timestamp, level, msg, prefix} ppi-livekit {timestamp, logger, message} · 평문 포장 (있을 수도 없을 수도) Loki {"line": "…"} · JSON 배열 · gzip 벗기고 맞춤 한 줄 모양 (Event) 시각 · 출처 · 수준 문맥 · 메시지 · 데이터 시각순으로 한 줄로 섞음 꼬리표 guest → 아동측 child monitor → 진행자측 host livekit · socket → 서버 server

파일 확장자는 믿지 않습니다. 안을 열어 모양을 보고 판별하고, 애매하면 파일명(guest·monitor·아동·진행자…)을 봅니다.

그림 3압축 — 모델이 읽을 만큼만 남기기

Jev가 한 번에 읽는 양은 32k 토큰까지입니다. 게다가 상관없는 줄이 많을수록 틀립니다. 네 번 거릅니다.

로그 압축 깔때기 수천 줄의 원본 로그가 노이즈 제거, 같은 줄 접기, 숫자를 단어로 바꾸기, 32k 토큰 조각 나누기를 거쳐 모델에 보낼 짧은 state가 된다. 원본 로그 — 수천 ~ 수만 줄 ① 노이즈 제거: [MFCC] 등 언제나 나오는 줄 (denylist) ② 같은 줄 접기 — 25번 나온 줄은 1줄 + "x25" ③ 숫자 → 단어: 0 none · 1 once · 2~4 few · 5~19 many · 20+ very many ④ 넘치면 시간순 조각으로 나눔 (최대 6조각) 조각마다 20k 토큰 이하 · 합성 예시는 1조각 약 590토큰

③이 핵심입니다. Jev는 개수 세기와 시간 비교가 약해서 "25번"보다 "very many"를 더 정확히 읽습니다. 셈은 코드가 합니다.

이름 가리기. --child 홍길동으로 준 실명은 모델에 보내기 전에 <name>으로 바뀌고, 문서 제목·파일명에는 --label 아동070 같은 표시 이름만 씁니다. 이메일·전화번호·토큰도 자동으로 가립니다.

그림 4규칙 채점 — 이 줄은 무슨 문제의 흔적인가

로그 문구마다 "이 문구가 보이면 이 유형일 가능성이 이만큼"을 적어 둔 표(rules.json)가 있습니다. 약 40개.

🔒 권한
NotAllowedError · Permission denied
가중치 0.9
🎙️ 마이크
Failed to produce mic audio · 트랙 muted
가중치 0.85
🔇 오디오(수신)
Consumer suspected silent
22번 넘어야 만점
🌐 연결
Send transport failed · 외부 연결 경고 합계 5+
양쪽이면 네트워크
🎬 자동전환
영상 끝 → 다음 스텝 체인이 끊김 → 진행자 수동 전환
체인 판정 이식
🚪 입장
Guest force kicked
3번 넘어야 만점
왜 "몇 번 넘어야"가 있나. Guest force kicked는 이름은 무섭지만 정상 종료 때도 나옵니다(206세션 중 139세션). 이름만 보면 모든 수업이 이슈가 되므로, lesson-log-triage가 실측한 p99 횟수를 넘어야 만점을 줍니다.

그림 5두 축 — "어디서"와 "무엇이"를 따로 묻는다

결과는 4 × 11 격자의 한 칸입니다. 짙을수록 확률이 높습니다. 아래는 합성 예시 "아동 권한 거부 → 마이크 안 나감".

유형 11개: 오디오(수신) · 볼륨·출력 · 마이크 · 카메라 · 권한 · 브라우저 · 연결 · 영상 재생 · 자동전환 · STT·전사 · 입장

신고한 쪽 ≠ 원인 쪽. "진행자 측에서 아동 소리 안 들림"을 신고한 사람은 진행자지만, 원인은 아동의 마이크 권한일 수 있습니다. 그래서 "누가 신고했나(reported_by)"와 "어디가 원인인가"를 분리해서 모델에 넘깁니다.

그림 6Jev에게 묻기 — 편지 한 통에 질문 19개

Jev는 글을 써 주는 모델이 아니라 정해진 보기 중 확률을 돌려주는 모델입니다. 한 요청에 질문을 여러 개 실으면 한꺼번에 평가합니다.

Jev 요청과 응답 구조 압축된 state 하나와 질문 19개가 한 번의 요청으로 Jev에 간다. 질문은 가장 그럴듯한 유형과 위치를 고르는 Choice 2개, 증상과 맞는지 묻는 Noul 1개, 유형별 Noul 11개, 위치별 Noul 4개다. 응답은 보기별 확률과 예 확률이다. 조각이 여러 개면 조각마다 동시에 보낸다. 요청 1통 state 증상 · 신고한 쪽 · 신호 · 근거 줄 Choice 2 가장 그럴듯한 유형 · 위치 Noul 1 로그가 증상을 설명하나? Noul 11 마이크 문제? 권한 문제? … Noul 4 아동측? 진행자측? 서버? 네트워크? POST Jev 한 번 읽고 19개 동시 평가 답 = 숫자만 유형: 권한 0.80 · 마이크 0.10 · … 위치: 아동측 0.85 · 진행자측 0.05 · … 마이크 문제? 예 0.80 권한 문제? 예 0.90 증상 설명? 예 0.90 (테스트용 가짜 응답 예시 · 실제 호출 전)

조각이 여러 개면 조각마다 요청을 동시에 보냅니다. 이것이 "병렬 분류"입니다. 과금은 입력 토큰만(100만 토큰당 $0.042), 답은 무료.

그림 7합치기 — 규칙과 모델을 섞어 순위를

규칙은 확실하지만 문구가 있어야만 잡고, 모델은 문구가 달라도 뜻을 읽습니다. 둘을 섞습니다.

점수 결합 방식 유형 점수는 규칙 점수에 0.35, Jev 점수에 0.65를 곱해 더한다. 위치는 그 유형 신호가 찍힌 쪽과 Jev 위치 확률의 평균이다. 두 값을 곱한 것이 한 칸의 확률이고, 높은 순으로 3개를 고른다. 가장 높은 값이 0.35보다 낮거나 로그가 증상을 설명하지 못하면 사람 확인 필요로 표시한다. 규칙 점수 × 0.35 Jev 확률 × 0.65 유형 확률 "권한일 확률" × 위치 확률 "권한이면 아동측일 확률" 칸 확률 → 상위 3개 1. 아동측 × 권한 2. 아동측 × 마이크 · 3. … 1위가 35% 미만이거나 "로그가 증상을 설명하나?"가 30% 미만 → 사람 확인 필요

0.35 / 0.65 비율은 첫 값입니다. 원인이 확정된 과거 이슈 30~50건으로 맞춰 볼 예정입니다.

그림 8결과물 — 터미널 한 화면 + ppi-docs 한 장

같은 결과를 두 가지로 남깁니다.

🖥️ 터미널
상위 3개 · 위치 분포 · 호출 시간·토큰
바로 보기
🧾 JSON
점수 전부 · 근거 신호 · 근거 줄 — 나중에 평가용
out/
📄 ppi-docs HTML
확률 막대 · 근거 로그 · 관련 기존 문서 자동 링크
--ppi-docs → issues/
관련 기존 문서. 결과가 "오디오"면 사운드 이슈 종합 분석처럼, incident navigator의 증상 그룹에서 대표 문서를 찾아 붙입니다. 이미 분석된 이슈를 다시 조사하지 않게 하려는 것입니다.

지금어디까지 왔나 — 2026-09-23

4읽을 수 있는 로그 종류
4 × 11위치 × 유형
19요청당 질문
17테스트 통과
0실제 Jev 호출
0실제 로그로 검증
✅ 만든 것
정리 · 규칙 채점 · 압축 · Jev 클라이언트(재시도·캐시·동시 호출) · 합치기 · HTML
커밋 1cbd1ac
⏳ 기다리는 것
TypeSafe API 키 · 원인 확정된 이슈 1건의 실제 Grafana 로그 4개
다음 단계
🎯 그다음
과거 이슈 30~50건으로 정답률 측정 · 비율 보정 · 볼륨·권한·브라우저 문구 보강
평가

지금의 그림 속 숫자는 합성 로그와 가짜 응답으로 만든 예시입니다. 실제 정확도는 아직 모릅니다.

관련 문서