ZANI
Fullstack학생 집중도를 실시간으로 분석하고 맞춤 리포트를 제공하는 AI 강의 플랫폼
SSAFY 우수상(1등)
실시간 음성 전사와 다중 강의 RAG를 설계하고, 리포트 API·플레이어와 프론트엔드를 구현했습니다.
대외비 정책에 따라 저장소와 상세 코드는 공개하지 않습니다.

핵심 성과
- 근거 Recall합성 평가 · 기존 85.2%
- 93.7%
- 목록 페이지 LCP썸네일 최적화 · 기존 3.5초
- 1.4초
- 커밋 규칙 준수율1,300개 중 규칙 위반 5건
- 99.62%
시스템 아키텍처
실시간 강의 · 코칭 · 다중 강의 RAG
미디어는 LiveKit, 업무 API와 코칭은 Spring, 강의 검색·답변 검증은 LangGraph가 담당합니다.
프로젝트 개요
목표
- 실시간 수업 참여도를 파악하는 학습 환경 구현
- 강의 음성·전사 데이터를 활용한 놓친 내용 복습 지원
- 현재·이전 강의에 대한 검색·질의응답 지원
배경
- 긴 온라인 강의에서 놓친 구간을 찾고 이전 강의와 연결하는 데 어려움 발생
- 현재 강의 전체 전사 입력으로 질문과 무관한 내용까지 LLM에 전달
- 다른 강의의 근거를 찾고 필요한 원문을 선별하는 검색 라우팅 필요
주요 기능
- 브라우저 참여도 분석과 학생 응답을 집계해 강사에게 실시간 코칭 제공
- 녹화·전사·참여도를 연결한 리포트와 복습 클립·퀴즈 제공
- 다중 강의 검색·비교와 실제 전사 인용, 근거 영상 구간 이동 지원
담당 역할
Fullstack Developer · Frontend / AI
강의 화면부터 리포트 API와 AI 입력 데이터 처리까지 연결해 전사·복습·질의응답을 하나의 학습 흐름으로 구현했습니다.
팀 협업 자동화 구축
- 브랜치명 기반 커밋 자동화 훅
브랜치명을 읽어 커밋에 티켓 번호를 자동으로 넣고 컨벤션을 검사하는 훅 구축
- 지라 자동화 봇
지라 이슈 상태 자동화와 JQL로 매일 아침 작업자별 할 일을 공지하는 봇 구현
실시간 오디오 파이프라인 구현
- 오디오 링버퍼 수집
강사 음성을 LiveKit Egress WebSocket으로 받아 세션마다 최근 300초를 링버퍼에 저장
- 다운샘플·전사 연동
헷갈림·놓침 유형의 코칭에서 확보된 최근 최대 300초 음성을 복사하고, 락 밖에서 16kHz 다운샘플·MP3 인코딩 후 Whisper 전사. 무응답·자리비움 유형은 STT·LLM 없이 고정 팁으로 처리
다중 강의 검색 라우팅 챗봇 구현
- 강의 범위 판단·원문 검색
질문·이력·강의 제목·목차로 필요한 강의와 강의별 검색 질문을 결정하는 LangGraph 구성. 강의별 목차 최대 3구간의 원문과 원래 질문의 BM25 상위 최대 3발화를 병합해 답변 근거 선별
- LLM Wiki·지식 그래프 보완
원문 참조를 가진 개념 페이지와 선수·대조·관련 관계를 사전 생성하는 파일 기반 인덱스 구현. 원문이 부족하거나 재검색할 때 활용하고, 원문 해시 대조·강의별 개념 ID 분리·최대 2홉 탐색 적용
- 답변 검증·재시도
질문의 필수 항목과 답변 문장을 원문 ID로 대조하고, 근거 부족은 재검색·내용 누락은 답변 수정으로 분기. 검증된 주장과 인용을 보존하며 최대 2회 재시도하고 최종 인용은 서버 원문에서 구성
- 다중 강의 API·영상 이동 UI
현재 강의 외 최대 4개 강의 선택, 강의별 참여·리포트 게시 권한 검사와 익명화·빈도 제한 구현. 범위 변경 시 요청 취소·대화 초기화, 다른 강의 인용 시 해당 영상 시각을 새 탭으로 여는 UI 연결
- 품질 평가·코드 책임 분리
합성 3강의 48문항·추가 24문항으로 검색 근거, 초안·최종 답변, 검증 피드백, 토큰·시간을 기록하고 gpt-4o로 익명 비교 채점. 그래프 연결·검색·검증·통신을 분리해 graph.py를 기존 저장 버전 478줄에서 현재 108줄로 정리하고 Python 관련 테스트 75개 통과
리포트 API·플레이어 구현
- 리포트 API·플레이어
학생·강사 리포트 API와 리포트 플레이어(화자별 전사 타임라인, 복습 클립, 구간 이동) 구현
- 영상 접근 제어
녹화 영상은 권한을 확인한 뒤 짧게 유효한 URL로 서빙해 무단 접근 차단
프론트엔드 초기 세팅 및 퍼블리싱
- 프론트엔드 아키텍처 세팅
Next.js에 DDD 4계층 구조를 잡고 Vitest, TanStack Query·Zustand, OpenAPI 타입 자동 생성 구성
- 화면 퍼블리싱
12개 전체 화면 퍼블리싱과 공통 UI 컴포넌트 담당
기술 선택과 구현
Next.js · React · TypeScript
- 선택 이유
- 강의 플레이어, 전사, 채팅처럼 상호작용이 많은 화면을 모듈로 나누고 API 데이터 구조를 타입으로 관리하기 위해 사용했습니다.
- 구현 내용
- DDD 4계층 구조, 공통 UI, 리포트 플레이어와 채팅 드로어를 구성하고 OpenAPI 기반 타입 생성을 연결했습니다.
TanStack Query · Zustand
- 선택 이유
- 강의·리포트 서버 데이터의 수명과 화면 내부 상태의 수명을 분리하기 위해 선택했습니다.
- 구현 내용
- 서버 데이터 조회·동기화와 UI 상태 관리를 구분해 학습 화면에 연동했습니다.
LiveKit · Whisper
- 선택 이유
- 강의 트랙 수집·녹화와 시점별 음성 전사가 필요했습니다.
- 구현 내용
- 최근 300초 음성을 링버퍼에 저장하고 필요한 구간을 16kHz로 다운샘플·MP3 변환해 전사했습니다. 음소거 중에는 무음을 보충했습니다.
Python · LangGraph
- 선택 이유
- 현재 강의 전체 전사를 입력하던 방식에서 필요한 강의를 먼저 고르고, 검색·생성·검증 결과에 따라 다음 동작을 달리해야 했습니다.
- 구현 내용
- 강의 범위 판단 → 강의별 검색 → 답변 생성 → 검증을 노드로 구성했습니다. 근거 부족은 재검색, 답변 누락은 재생성으로 분기하고 캐시·원문 출처를 확인했습니다.
RAG · BM25
- 선택 이유
- 질문과 무관한 전사를 줄이면서, 목차 요약이나 재작성한 검색 질문에서 빠진 코드·실습 조건도 찾아야 했습니다.
- 구현 내용
- 목차·요약으로 고른 구간 원문에 원래 질문의 BM25 검색 결과를 보완했습니다. 후보 강의 안에서 검색하고 중복 제거·강의별 배분 후 근거를 최대 24,000바이트·80발화로 제한했습니다.
LLM Wiki · 지식 그래프
- 선택 이유
- 원문 검색이 부족할 때 개념별 설명과 선수 관계를 통해 추가 근거를 찾기 위한 보완 수단으로 연결했습니다.
- 구현 내용
- 개념 페이지와 prerequisite_of·contrasts_with·related_to 관계를 원문 참조와 함께 사전 생성했습니다. 실제 선수 간선이 있을 때만 선수 관계를 탐색하고, Wiki·그래프에서 연결한 원문으로 답변을 뒷받침하도록 했습니다.
문제 해결 사례
사례 1다중 강의 RAG 검색 개선으로 합성 평가 정답률 81.25% 확보
1. 오류 분석
- 문제 현상
- 기존 챗봇은 현재 강의의 전체 전사를 LLM에 전달해 답변을 생성했습니다. 질문과 무관한 설명·실습 안내까지 함께 입력됐고, 다른 강의의 내용을 묻거나 여러 강의를 비교하는 질문에는 필요한 자료를 가져올 수 없었습니다.
- 원인
- 입력 자료가 현재 강의에 한정되어 있었고, 질문에 필요한 강의·구간을 먼저 고르는 과정이 부족했습니다. 여러 강의를 지원하려면 자료 접근 범위를 확장하는 동시에, 모든 전사를 답변 모델에 넣지 않도록 근거를 선별해야 했습니다.
- 추가 확인 사항
- 현재 강의·다른 강의·강의 간 비교 질문을 구분하고, 필요한 각 강의의 원문을 확보할 수 있는지 확인했습니다.
- 선택한 강의의 접근 권한, 서로 다른 강의의 동일 구간 번호, 캐시의 원문 버전과 인용 출처를 함께 검사했습니다.
- 합성 Java·Spring 3강의 48문항과 추가 24문항에서 원문·질문·정답 근거를 고정하고, 검색 결과·답변·검증 피드백·토큰·시간을 기록했습니다.
- Python 평가 실행기로 실제 LangGraph와 gpt-4o-mini 생성·검증을 실행하고 gpt-4o로 후보 순서를 섞어 채점했습니다. 저장 응답으로 결과를 재현하고 pytest 75개와 Ruff·strict mypy 검사도 통과했습니다.
- 측정 기준
- 수치는 라우팅 도입 후 BM25 보완 전후의 합성 자료·LLM 채점 결과로, 최초 전체 전사 입력 방식과의 직접 비교나 실서비스 정답률은 아닙니다. 48문항의 평균 처리 시간은 8.86초에서 9.84초, 토큰은 556,552에서 620,040으로 늘었고 기존 답변 4건의 채점 변동도 있었습니다. Spring → LangGraph에는 전체 원문을 전달하며 답변 생성·검증 모델에 제공할 근거를 선별합니다.
2. 개선 과정
- 1.전체 전사 입력의 한계 확인
- 2.다중 강의 선택·권한 검사
- 3.질문에 필요한 강의 결정
- 4.목차·BM25 원문 검색
- 5.LLM Wiki·지식 그래프 조건부 보완
- 6.답변 검증·재시도 분기
- 7.강의별 인용·영상 이동 연결
- 8.합성 질문으로 검색·답변 평가
LangGraph 검색 라우팅을 도입해 질문·이력·제목·목차로 필요한 강의를 먼저 고르고 강의별 검색 질문을 만들도록 구성했습니다. 각 강의에서 목차 최대 3구간의 실제 전사 원문을 읽고, 원래 질문의 BM25 검색으로 최대 3발화를 보완해 중복·용량을 정리했습니다. 원문이 없거나 재검색할 때는 사전 구축된 LLM Wiki의 개념 페이지와 지식 그래프의 관계·연결 원문으로 근거를 보완합니다. 질문의 필수 내용·주장·인용을 검증해 근거 부족은 재검색, 답변 누락은 수정으로 분기했습니다. 사용자는 현재 강의 외 최대 4개 강의를 선택하고 답변 인용에서 해당 영상 시각으로 이동할 수 있습니다.
변경 전 · 변경 후
주황색: 추가·변경한 부분
3. Before & After 비교
| 항목 | Before | After |
|---|---|---|
| 답변 모델 입력 | 현재 강의 전체 전사 | 질문에 필요한 강의·구간·발화를 선별한 근거 |
| 검색 범위 | 현재 강의에 한정 | 현재 강의 + 선택한 다른 강의 최대 4개 |
| 정답률 · BM25 보완 전후 | 72.92% · 35/48문항 | 81.25% · 39/48문항 · +8.33%p |
| 근거 Recall · BM25 보완 전후 | 85.2% | 93.7% · +8.5%p |
| 인용 탐색 | 현재 강의 안에서 확인 | 강의 ID·원문 시각을 보존해 다른 강의 영상으로 이동 |
해결 결과
다중 강의 라우팅 도입 후 BM25 검색 보완의 합성 48문항 비교에서 정답률을 72.92%(35/48)에서 81.25%(39/48)로 8.33%p 높였습니다. 근거 Recall도 85.2%에서 93.7%로 8.5%p 개선했습니다. 현재 강의 외 최대 4개 강의의 검색·비교와 출처 영상 이동을 지원하며, 추가 24문항은 Recall 100%·정답 19/24로 생성·검증의 개선 과제도 확인했습니다.
사례 2썸네일 용량 약 94% 감소로 목록 페이지 LCP 60% 단축
1. 오류 분석
- 문제 현상
- 내 강의실 페이지의 LCP가 3.5초, Performance 점수가 81점에 머물렀습니다. 555×312 카드에 녹화 영상에서 추출한 1280×720 PNG 썸네일을 그대로 넣고 있어 이미지 전송량이 컸습니다.
- 원인
- 555×312 카드에 1280×720 PNG를 그대로 사용했고, 요청마다 달라지는 URL 때문에 이미지 캐시 재사용이 어려웠습니다.
- 추가 확인 사항
- 원본 변경 시 캐시 갱신과 첫 화면 밖 이미지의 로딩 시점을 함께 점검했습니다.
- JDK에 WebP 인코더가 없어 네이티브 라이브러리에 의존하게 되므로, 인코딩이 실패하더라도 썸네일이 사라지지 않도록 대체 경로를 설계했습니다.
- 포맷을 바꾸면서 화질이 떨어지지 않는지 동일한 축소 결과를 기준으로 비교했습니다.
2. 개선 과정
- 1.LCP·이미지 요청 분석
- 2.카드 크기로 리사이즈
- 3.WebP 변환·캐시(JPEG 대체 경로)
- 4.화면 밖 이미지 지연 로딩
- 5.용량·화질 비교 측정
- 6.Lighthouse 재측정
썸네일 URL이 요청마다 달라 Next.js 이미지 캐시를 쓰기 어려웠습니다. 그래서 서버에서 원본을 카드 크기에 맞게 줄이고 WebP로 변환했습니다. JDK 이미지 라이브러리에는 WebP 인코더가 없어 네이티브 라이브러리를 추가해야 했기 때문에, 인코더를 불러오지 못하는 환경에서는 JPEG로, 변환 자체가 실패하면 원본으로 단계적으로 대체하도록 설계했습니다. 변환한 이미지는 캐시해 다시 쓰고, 원본이 바뀌면 캐시도 함께 갱신했습니다. 첫 화면 밖 썸네일은 지연 로딩으로 바꿨습니다.
변경 전 · 변경 후
주황색: 추가·변경한 부분
3. Before & After 비교
| 항목 | Before | After |
|---|---|---|
| LCP | 3.5초 | 1.4초 · 2.1초(60%) 단축 |
| Lighthouse Performance | 81점 | 96점 |
| 썸네일 전송량 | 0.5~1MB | 수십 KB · 약 94% 감소 |
| 썸네일 포맷 | PNG 원본 | WebP · 동일 원본에서 JPEG 대비 약 28% 추가 감소 |
| 포맷 전환 시 화질 | JPEG SSIM 0.9171 | WebP SSIM 0.9300 |
해결 결과
목록 페이지 LCP를 3.5초에서 1.4초로 2.1초(60%) 단축하고, Lighthouse Performance 점수를 81점에서 96점으로 높였습니다. 썸네일 전송량은 0.5~1MB에서 수십 KB로 약 94% 줄였습니다. 동일 원본의 JPEG 대비 WebP 용량도 90.4KB에서 64.9KB로 약 28% 줄이면서 화질 지표를 개선했습니다.
사례 3오디오 버퍼 개선으로 락 점유 약 800ms → 3ms 단축
1. 오류 분석
- 문제 현상
- 실시간 코칭을 위해 강사의 최근 5분 음성을 링버퍼에 저장했습니다. 하지만 LiveKit에서는 음소거 중 오디오 프레임이 오지 않아, 실제 최근 5분이 아니라 음성이 들어온 시간만 따진 최근 5분이 조회됐습니다.
- 원인
- 음소거 중 오디오 프레임이 오지 않아 버퍼가 실제 경과 시간 대신 수신한 음성 길이만 누적했습니다.
- 추가 확인 사항
- 짧은 네트워크 지연을 음소거로 오인하지 않도록 0.5초 기준을 적용했습니다.
- MP3 인코딩 중 락이 오디오 수집을 막는지도 확인했습니다.
2. 개선 과정
- 1.음소거 재현
- 2.수신 시간·실제 시간 비교
- 3.긴 공백에 무음 삽입
- 4.인코딩을 락 밖으로 분리
- 5.시간 오차·락 점유 확인
버퍼 시간이 실제 시간과 맞도록 0.5초 이상 오디오가 비면 서버에서 무음 데이터를 채웠습니다. 짧은 네트워크 지연은 기다리고, 긴 공백만 보정해 불필요한 무음 삽입을 줄였습니다. MP3 변환은 필요한 음성만 먼저 복사한 뒤 락 밖에서 진행했습니다.
변경 전 · 변경 후
주황색: 추가·변경한 부분
3. Before & After 비교
| 항목 | Before | After |
|---|---|---|
| 버퍼 시간 기준 | 수신한 음성 길이 | 실제 시간 · 오차 최대 0.5초 |
| 락 점유 시간 | 약 800ms | 약 3ms |
해결 결과
버퍼 락 점유 시간을 약 800ms에서 3ms로 약 99.6% 단축하고, 음소거 구간을 보정해 버퍼 시간 오차를 최대 0.5초 이내로 줄였습니다. MP3 변환 중에도 새 오디오를 수집할 수 있도록 했습니다.
사례 4커밋 규칙 준수율 99.62%를 만든 팀 협업 자동화
1. 오류 분석
- 문제 현상
- 이전 팀 프로젝트에서 Jira 티켓 누락, 서로 다른 커밋 형식, 작업 상태 갱신 지연으로 진행 상황을 파악하기 어려웠습니다. 기록만으로 확인할 수 없어 팀원들에게 진행 상황을 다시 물어야 했습니다.
- 원인
- 협업 기록과 상태 갱신이 팀원들의 수작업에 의존했습니다. 개발 중에도 기록이 남도록 규칙을 작업 흐름에 연결하고, 반복되는 관리 작업을 줄일 필요가 있었습니다.
- 추가 확인 사항
- 브랜치 검사 → 이슈 키 자동 삽입 → 커밋 메시지 검사가 순서대로 실행되는지 확인했습니다.
- 이미 이슈 키가 있는 메시지는 중복 삽입하지 않고, 자동 생성 커밋과 보호·릴리스 브랜치에는 단계별 예외를 적용했습니다.
- 총 1,300개 커밋 중 메시지 규칙 위반 5건을 집계해 준수율을 계산했습니다.
2. 개선 과정
- 1.티켓·담당자·스크럼 규칙 합의
- 2.이슈 키 자동 삽입·형식 검사
- 3.Jira 상태·아침 공지 자동화
- 4.커밋 규칙 준수율 집계
다음 5인 프로젝트에서는 기능별 티켓 생성·담당자 지정·매일 스크럼을 팀과 합의했습니다. 제가 구축한 Husky 훅은 브랜치명에서 티켓 번호를 읽어 커밋 메시지에 자동으로 넣고, 정해진 메시지 형식을 검사하도록 했습니다. Jira 작업 상태 변경도 자동화하고, JQL로 필요한 티켓을 조회해 매일 아침 담당자별 할 일을 공지하는 봇을 만들었습니다. 스크럼 전에 각자의 작업을 확인할 수 있도록 기록과 공지를 연결했습니다.
변경 전 · 변경 후
주황색: 추가·변경한 부분
3. Before & After 비교
| 항목 | Before | After |
|---|---|---|
| 이슈 연결 | Jira 티켓 누락·수동 이슈 연결 | 브랜치에서 자동 추출·삽입, 중복 방지 |
| 협업 규칙 확인 | 팀원마다 다른 커밋 형식 | 브랜치·커밋 훅에서 자동 검사 |
| 작업 현황 공유 | 작업 상태 갱신 지연·진행 상황 재확인 | 상태 변경·아침 공지 자동화, 스크럼 전 작업 확인 |
해결 결과
총 1,300개 커밋 중 메시지 규칙 위반은 5건으로, 1,295건이 규칙을 준수해 준수율 99.62%를 달성했습니다. 협업 규칙을 합의하는 데서 나아가 이를 지킬 수 있는 자동화까지 마련하고, 팀이 같은 작업 기록을 확인하며 협업하도록 했습니다.
프로젝트 회고
기술적 한계와 개선 방안
- 한계·아쉬운 점
- 추가 합성 24문항에서 근거 Recall은 100%, 정답 판정은 19/24로 답변 생성·검증의 개선 과제를 확인했습니다.
- 개선 방향
- 실제 강의 STT와 새 질문으로 평가 범위를 넓히고, 질문의 필수 조건·답변 누락·인용 정확성을 각각 검증하겠습니다.
- 한계·아쉬운 점
- 검색 보완 후 합성 48문항의 평균 처리 시간이 8.86초에서 9.84초로 늘었고, Spring → LangGraph에는 여전히 선택한 강의의 전체 전사를 전달합니다.
- 개선 방향
- 단계별 호출·입력량·재시도를 계측하고, 원문 버전 기반 캐시와 필요한 구간만 조회하는 방식으로 지연·전송량을 줄이겠습니다.