- 프로젝트 요약
- 배경과 문제 정의
- 무엇을 평가하는가
- 핵심 기능
- 시스템 아키텍처
- AI 평가 파이프라인 (LangGraph)
- ⭐ Multi-Agent Debate (MAD)
- 점수 산정 로직
- 기술 스택
- 기술적 난관과 해결
- 레포지토리 구성
- 팀원 구성
VibeCodeEval 은 응시자가 AI 튜터와 대화하며 코딩 문제를 풀어가는 과정을 그대로 기록하고, ① 제출한 코드의 정확성·성능 과 ② AI에게 던진 프롬프트의 품질 을 함께 채점하는 AI 코딩 테스트 플랫폼입니다.
| 한 줄 소개 | AI 협업 역량(프롬프트)까지 정량 평가하는 코딩 테스트 |
| 평가 축 | 프롬프트 활용 40% · 코드 정확성 40% · 코드 성능 20% |
| 차별점 | 단일 LLM 채점의 편향을 다중 에이전트 토론(MAD) 으로 보정 |
| 구성 | Frontend(Next.js) · Backend(Spring Boot) · AI Worker(FastAPI + LangGraph) |
| 실시간 | STOMP WebSocket(시험 상태) · SSE(채점 진행) · WS 토큰 스트리밍(AI 응답) |
AI 코딩 도구가 표준이 된 시대, 개발자의 실력은 더 이상 "혼자 정답 코드를 짜는 능력"만으로 설명되지 않습니다. 이제는 AI에게 무엇을, 어떻게 질문하는가 — 즉 프롬프트를 설계하고 AI의 출력을 검증·발전시키는 역량 이 생산성을 가릅니다.
그러나 기존 코딩 테스트는 여전히 최종 결과물(코드) 만 채점합니다.
기존 코딩 테스트 → "정답 코드를 제출했는가?" (결과만 평가)
VibeCodeEval → "AI를 얼마나 잘 활용해 도달했는가?" (과정 + 결과 평가)
이로 인해 우리가 풀어야 했던 핵심 질문은 다음과 같았습니다.
- AI와의 대화 과정을 어떻게 시험 환경에서 통제·기록할 것인가? → 직접 답을 주지 않고 힌트만 주는 소크라테스식 AI 튜터 + 가드레일
- "좋은 프롬프트"라는 주관적 가치를 어떻게 공정하게 점수화할 것인가? → 단일 LLM 채점의 편향·비일관성을 3인 심사관 토론(MAD) 으로 보정
- 무거운 AI 평가를 어떻게 실시간 시험 흐름에 녹일 것인가? → 채팅 중엔 평가하지 않고, 제출 시점에 전체 턴을 일괄 평가 + 비동기 파이프라인
응시자의 한 세션은 여러 턴의 대화 + 1회 코드 제출 로 구성됩니다. 제출 순간, 시스템은 두 갈래를 동시에 채점합니다.
flowchart LR
SUB([📨 코드 제출]) --> P[🧠 프롬프트 트랙]
SUB --> C[💻 코드 트랙]
P --> P1["턴별 프롬프트 품질<br/>R1 논리·효율 / R2 명확성<br/>R3 구조·예시 / R4 맥락유지"]
P --> P2["세션 전체 흐름<br/>Multi-Agent Debate"]
C --> C1["정확성<br/>Judge0 테스트 통과율"]
C --> C2["성능<br/>실행시간·메모리"]
C --> C3["품질<br/>복잡도·AST·LLM 리뷰"]
P1 & P2 & C1 & C2 & C3 --> F([🏆 최종 점수 · 등급 A~F])
style SUB fill:#4338CA,color:#fff
style F fill:#7C3AED,color:#fff
style P fill:#312E81,color:#fff
style C fill:#0F766E,color:#fff
|
|
|
|
flowchart TB
user([👤 참가자]):::ext
admin([🧑💼 관리자]):::ext
user & admin -->|HTTPS / WSS| CF[🌐 Cloudflare<br/>DNS · CDN · WAF · SSL]
CF --> NGINX[Nginx :443<br/>Reverse Proxy]
subgraph SERVER [🖥️ VPS / EC2 · Docker]
direction TB
NGINX --> FE
subgraph FE [▲ Vercel · Next.js 16 / React 19]
FEU[User 시험 화면]
FEA[Admin / Master 대시보드]
end
NGINX -->|:8080| BE
NGINX -->|:8001| AI
subgraph BE [🍃 Spring Boot · :8080]
BEAPI[REST API · STOMP · SSE]
BEDOM["Clean Architecture<br/>auth · exam · submission<br/>chat · problem · admin"]
BEAPI --- BEDOM
end
subgraph AI [🤖 FastAPI AI Worker · :8001]
AIAPI[REST · WebSocket]
LG["LangGraph Pipeline<br/>의도분석 → 튜터 → 평가"]
AIAPI --- LG
end
subgraph DATA [💾 Data Layer]
PG[(PostgreSQL<br/>시험·제출·채팅·점수)]
RD[(Redis<br/>세션·턴로그·Outbox)]
end
end
JUDGE[Judge0<br/>코드 실행]:::ext
GEM[Google Gemini<br/>LLM]:::ext
BEDOM <-->|JPA| PG
BEDOM <-->|Lettuce| RD
BEDOM -->|"POST /chat (제출 이벤트)"| AIAPI
AIAPI -->|"POST /callbacks (채점 결과)"| BEAPI
AIAPI <-->|asyncpg| PG
LG <-->|state| RD
LG -->|코드 실행| JUDGE
LG -->|추론| GEM
classDef ext fill:#1E293B,color:#E2E8F0,stroke:#475569;
📐 풀해상도 인프라 구성도:
docs/architecture.md
AI Worker는 하나의 LangGraph 안에서 일반 채팅과 제출 평가를 분기 처리합니다.
flowchart TB
START([제출]) --> N1[N1 · 상태 로드<br/>Redis graph_state]
N1 --> N2[N2 · 의도 분석 + 가드레일]
N2 --> N4[N4 · 턴 프롬프트 일괄 평가<br/>R1~R4 · 의도별 게이트]
N4 --> N5[N5 · Judge0 코드 실행<br/>정확성 · 성능]
N5 --> N6[N6 · 정적 분석<br/>Radon CC · ΔCC · AST]
N6 --> N7[N7 · LLM 코드 리뷰<br/>가독성 · 효율 · 예외처리]
N7 --> N8{{N8 · Multi-Agent Debate<br/>3인 심사관 토론}}
N8 --> N9[N9 · 최종 집계<br/>가중합 · 등급 · DB 저장]
N9 --> END([결과 콜백 → BE → SSE])
style N8 fill:#4338CA,color:#fff,stroke:#A78BFA
style N9 fill:#7C3AED,color:#fff
| 노드 | 역할 | 산출물 |
|---|---|---|
| N4 | 제출 직전까지의 모든 대화 턴을 사용자 프롬프트 기준으로 채점 | turn_scores, aggregate_turn_score |
| N5 | Judge0로 코드 실행 → 테스트 통과율·실행시간·메모리 측정 | correctness_score, performance_score |
| N6 | Radon 순환복잡도(CC), 초기 코드 대비 변화량(ΔCC), AST 패턴 분석 | code_quality_metrics |
| N7 | 단일 LLM이 코드를 정성 리뷰 (효율/가독성/에러처리) | code_eval_report |
| N8 | 다중 에이전트 토론으로 프롬프트·세션 흐름 종합 평가 | holistic_flow_score, r4_score |
| N9 | 모든 신호를 가중 집계 → 최종 점수·등급 산출 후 PostgreSQL 저장 | final_scores, rubric_json |
💬 설계 포인트 — 채팅 중에는 평가를 돌리지 않고(LLM 비용·지연 절감), 제출 시점에 N4가 전체 턴을 한 번에 동기 평가합니다.
"좋은 프롬프트"는 주관적입니다. 단일 LLM 채점은 모델의 그날의 편향에 휘둘립니다. VibeCodeEval은 이 문제를 서로 다른 입장·모델·온도를 가진 3명의 심사관이 2라운드 토론하게 하고, 수석 심사관이 토론 전체를 종합해 점수를 확정하는 구조로 해결했습니다.
| 심사관 | 입장 | 모델 | Temp | 역할 |
|---|---|---|---|---|
| ⚖️ Strict | 검사 | Gemini 2.5 Pro | 0.1 |
프롬프트의 결함·논리적 허점을 집중 비판 |
| 🛡️ Advocate | 변호인 | Gemini 2.0 Flash | 0.3 |
사용자의 의도·성취·맥락을 적극 변호 |
| ⚓ Neutral | 중재자 | Gemini 1.5 Pro | 0.2 |
양측을 균형 있게 조율 |
| 👨⚖️ Verdict | 수석 심사관 | Gemini 2.5 Pro | 0.0 |
토론 종합 → 최종 점수 확정 |
flowchart TB
subgraph R1 [Round 1 · 독립 의견 · 병렬]
direction LR
S1[⚖️ Strict<br/>opinion]
A1[🛡️ Advocate<br/>opinion]
N1[⚓ Neutral<br/>opinion]
end
R1 --> SYNC[/sync_opinions · 의견 팬인/]
SYNC --> R2
subgraph R2 [Round 2 · 반론·재평가 · 순차]
direction LR
S2[⚖️ Strict<br/>rebuttal] --> A2[🛡️ Advocate<br/>rebuttal] --> N2[⚓ Neutral<br/>rebuttal]
end
R2 --> V[👨⚖️ Final Verdict<br/>holistic_flow_score 0~100<br/>r4_context_maintenance_score]
V --> OUT([→ N9 최종 집계])
style V fill:#7C3AED,color:#fff,stroke:#C4B5FD
style SYNC fill:#312E81,color:#fff
style R1 fill:#1E1B4B,color:#fff
style R2 fill:#1E1B4B,color:#fff
세 심사관은 각자 다른 직감이 아니라, 동일한 4종 평가 신호를 모두 보고 토론합니다.
N4 턴별 루브릭 + 대화 요약 ┐
N5 Judge0 테스트 결과 ├──▶ _build_base_context() ──▶ 3-Agent Debate
N6 복잡도(CC)·AST 패턴 │
N7 LLM 코드 리뷰 리포트 ┘
가드레일 위반 턴은
filter_turn_logs_for_debate로 토론 입력에서 제외되어, 부적절한 대화가 점수에 개입하지 못합니다.
pie showData
title 최종 점수 가중치
"프롬프트 활용" : 40
"코드 정확성" : 40
"코드 성능" : 20
prompt_score = holistic_flow_score(N8) × 0.60 + aggregate_turn_score(N4) × 0.40
( r4_context_maintenance_score 존재 시 prompt_score 에 20% 가중 보정 )
total_score = prompt_score × 0.40
+ correctness_score × 0.40 ← N5 Judge0 통과율
+ performance_score × 0.20 ← N5 실행시간·메모리
grade = A(≥90) · B(≥80) · C(≥70) · D(≥60) · F(<60)
실제 산정 예시 (TSP 문제):
| 신호 | 값 | |
|---|---|---|
holistic_flow_score (N8) |
88.0 | 다중 토론 결과 |
aggregate_turn_score (N4) |
85.0 | 턴 루브릭 평균 |
correctness_score (N5) |
100.0 | 테스트 100% 통과 |
performance_score (N5) |
100.0 | 성능 만점 |
| → prompt_score | 86.8 | 88×0.6 + 85×0.4 |
| → total_score | 94.72 | 86.8×0.4 + 100×0.4 + 100×0.2 → A |
| 영역 | 기술 |
|---|---|
| Frontend | Next.js 16 · React 19 · TypeScript · Tailwind CSS · shadcn/ui · Zustand · STOMP/SockJS |
| Backend | Java 17 · Spring Boot 3.2 · Spring Security · JPA/Hibernate · STOMP WebSocket · SSE · JWT |
| AI Worker | Python 3.12 · FastAPI · LangGraph · LangChain · SQLAlchemy(async) · Pydantic |
| LLM / 실행 | Google Gemini (2.5 Pro / 2.0 Flash / 1.5 Pro) · Vertex AI · Judge0 |
| Data | PostgreSQL 15 (BE·AI 공유) · Redis 7 (세션·턴로그·Outbox) |
| Infra | Docker Compose · Nginx · Cloudflare · Vercel · Prometheus / Micrometer |
| Tooling | Gradle · uv · Jasypt · Springdoc OpenAPI |
1. 프롬프트 평가의 주관성·편향 — 단일 LLM의 한계
- 문제: "좋은 프롬프트"는 정답이 없어, 단일 LLM 채점은 호출마다 점수가 흔들리고 모델 편향에 취약.
- 해결: 서로 다른 모델·온도·입장을 가진 3인 심사관 2라운드 토론(MAD) + 수석 심사관 종합 verdict. 독립 의견(R1) → 반론·재평가(R2) → 종합 판정으로 편향을 상쇄하고 재현성을 확보.
- 부가 설계: 가드레일 위반 턴을 토론 입력에서 필터링해 오염 차단.
2. 무거운 AI 평가를 실시간 시험 흐름에 통합
- 문제: 제출 채점은 Judge0 + 다수 LLM 호출로 수 초~수십 초 소요 → 동기 응답 시 타임아웃·UX 저하.
- 해결:
- 제출 API는
202 Accepted즉시 응답 후 비동기 처리 - BE는 Outbox 패턴(Redis) 으로 이벤트를 AI Worker에 안정 전달 (
OutboxPoller) - 채점 결과는 AI → BE 콜백 수신 후 SSE 스트림으로 관리자 화면에 실시간 push
- 제출 API는
- 추가: 채팅 중에는 평가를 돌리지 않고 제출 시 전체 턴 일괄 평가로 LLM 비용·지연 최소화.
3. STOMP WebSocket 인증 — HttpOnly 쿠키의 딜레마
- 문제: 보안을 위해 JWT를 HttpOnly 쿠키로 저장 → JavaScript가 읽을 수 없어 STOMP
connectHeaders에 토큰을 실을 수 없음. - 해결: 입장 응답에서 access token을 쿠키(HttpOnly) + 응답 body 양쪽으로 전달.
FE는 body 토큰을 메모리(Zustand)에만 보관해 STOMP 연결에 사용하고, REST는 쿠키로 인증.
서버는
StompPrincipalInterceptor에서 CONNECT 헤더의 토큰을 검증.
4. 시험 자동 시작 — 분산 환경의 타임존 불일치
- 문제:
startsAt도래 시 WAITING→RUNNING 자동 전환 스케줄러가 로컬(KST)에선 정상, 배포 서버(Docker 기본 UTC)에선 9시간 동안 미동작. - 원인:
LocalDateTime.now()가 JVM 기본 타임존을 따름 → 컨테이너 UTC와 KST 설정 시각의 불일치. - 해결: 컨테이너가 아닌 JVM 레벨에서 타임존 고정 →
ENTRYPOINT ["java","-Duser.timezone=Asia/Seoul", ...]. 로그 타임스탬프가+09:00로 정렬되고 스케줄러가 정시에 시험을 시작.
5. LLM 비용·지연 최적화
- 모델 티어 분리: 비판/최종 판정은 정밀한 Pro(2.5), 변호는 빠른 Flash(2.0) 로 역할별 배치.
- 평가 지연 배치: 매 턴 평가 대신 제출 시 일괄 평가 → 불필요한 LLM 호출 제거.
- 상태 캐싱: Redis
graph_state/turn_logs로 세션 상태를 유지해 재계산 방지.
| 레포 | 설명 | 스택 |
|---|---|---|
| BE-VibeCodeEval | 메인 백엔드 — 시험·인증·제출·실시간 통신 | Spring Boot · PostgreSQL · Redis |
| AI-VibeCodeEval | AI 평가 워커 — LangGraph 파이프라인·MAD | FastAPI · LangGraph · Gemini · Judge0 |
| FE-VibeCodeEval | 프론트엔드 — User·Admin·Master UI | Next.js · React · Tailwind · shadcn/ui |
VibeCodeEval/
├── FE-VibeCodeEval/ # Next.js 프론트엔드
├── BE-VibeCodeEval/ # Spring Boot 백엔드
├── AI-VibeCodeEval/ # FastAPI + LangGraph AI 워커
└── docs/ # 통합 아키텍처 문서
|
박영두 PM · 백엔드 Spring Boot · 실시간 통신 · 인증 · 인프라 |
유시현 AI LangGraph · Multi-Agent Debate · 점수 설계 |
이찬욱 프론트엔드 Next.js UI · 시험·대시보드 · 실시간 연동 |
VibeCodeEval — AI 시대의 진짜 개발 역량을 측정합니다.
코드의 정답 + AI를 다루는 프롬프트 역량 + 합의 기반 공정 채점