분석 서비스
분석 서비스
분석 서비스(analysis-svc)는 OpenAI GPT-4o-mini를 활용하여 시험 전체 해설과 학생 개별 피드백을 한국어로 자동 생성합니다. RabbitMQ 소비자 없이 HTTP API만 제공하며, lumie-backend 및 joossameng FE가 직접 호출합니다. 영어 교육 전문가 관점의 프롬프트 설계로 고품질의 교육적 피드백을 제공합니다.
기술 스택
| 라이브러리 | 버전 | 용도 |
|---|---|---|
| OpenAI SDK | 1.82.0 | GPT-4o-mini API 호출 |
| Pydantic | 2.10.0 | 요청/응답 스키마 검증 |
| FastAPI | 0.115.0 | HTTP API 서버 |
| Uvicorn | 0.30.0 | ASGI 서버 |
LLM 설정
| 설정 항목 | 환경변수 | 기본값 | 설명 |
|---|---|---|---|
| API 키 | LLM_API_KEY | — | OpenAI API 키 |
| Base URL | LLM_BASE_URL | https://api.openai.com/v1 | API 엔드포인트 |
| 모델명 | LLM_MODEL | gpt-4o-mini | 사용할 LLM 모델 |
출력 설정
| API | max_tokens | 목표 길이 | 설명 |
|---|---|---|---|
| 시험 해설 | 1024 | 200–400자 | 전체 시험에 대한 종합 분석 |
| 학생 피드백 | 2048 | 500–800자 | 개인별 맞춤 학습 가이드 |
모든 응답은 한국어로 생성되며, 수능 영어 전문가 톤으로 작성됩니다.
API 엔드포인트
POST /api/analysis/exam-commentary — 시험 해설 생성
시험 전체에 대한 종합 해설을 생성합니다. 출력 길이는 200–400자 한국어 텍스트입니다.
요청 (JSON)
{
"examName": "2024년 3월 고1 영어 모의고사",
"totalParticipants": 150,
"averageScore": 68.4,
"highestScore": 95,
"lowestScore": 25,
"gradeDistribution": [
{"grade": 1, "count": 8, "percentage": 5.3},
{"grade": 2, "count": 15, "percentage": 10.0},
{"grade": 3, "count": 25, "percentage": 16.7}
],
"questionStatistics": [
{
"questionNumber": 18,
"correctRate": 0.72,
"incorrectRate": 0.28,
"questionType": "주제",
"actualScore": 2
},
{
"questionNumber": 31,
"correctRate": 0.45,
"incorrectRate": 0.55,
"questionType": "빈칸추론",
"actualScore": 3
}
]
}
응답 (JSON)
{
"content": "이번 시험은 전반적으로 빈칸 추론과 순서 배열 문항에서 오답률이 높게 나타났습니다. 특히 31번~34번 빈칸 추론 문항의 평균 정답률이 45%로 낮았으며, 학생들이 문맥 파악에 어려움 을 겪은 것으로 보입니다..."
}
POST /api/analysis/student-feedback — 학생 개별 피드백 생성
학생의 문항별 정오 데이터를 분석하여 맞춤형 학습 피드백을 생성합니다. 출력 길이는 500–800자 한국어 텍스트입니다.
요청 (JSON)
{
"examName": "2024년 3월 고1 영어 모의고사",
"studentName": "김민준",
"totalScore": 74,
"grade": 3,
"averageScore": 68.4,
"incorrectQuestions": [
{
"questionNumber": 31,
"questionType": "빈칸추론",
"selectedChoice": "2",
"correctAnswer": "4",
"questionCorrectRate": 45.0
},
{
"questionNumber": 34,
"questionType": "순서배열",
"selectedChoice": "1",
"correctAnswer": "3",
"questionCorrectRate": 38.2
}
],
"questionTypeAchievement": [
{
"type": "어휘",
"correctCount": 8,
"totalCount": 10,
"correctRate": 80.0
},
{
"type": "빈칸추론",
"correctCount": 2,
"totalCount": 4,
"correctRate": 50.0
}
]
}
응답 (JSON)
{
"content": "민준 학생은 이번 시험에서 평균보다 5.6점 높은 74점을 획득하여 양호한 성과를 보였습니다. 어휘 문항에서 80%의 높은 정답률을 보인 것은 기초 실력이 탄탄함을 의미합니다. 다만 빈칸 추론 유형에서 50%의 정답률을 보여 개선이 필요합니다..."
}
프롬프트 설계
전문가 페르소나
모든 API는 한국 영어 교육 전문가 관점의 시스템 프롬프트를 사용합니다:
SYSTEM_PROMPT_COMMENTARY = """당신은 수능 영어 전문 강사입니다. 모의고사 데이터를 분석하여 학생과 학부모에게 전달할 '모의고사 총평'을 작성합니다.
작성 가이드라인:
1. 전체적인 난이도와 출제 경향 분석
2. 등급별 분포와 평균 점수 해석
3. 오답률이 높은 문항 유형 분석
4. 향후 학습 방향 제시
5. 200-400자 내외의 간결하고 전문적인 톤
반드시 한국어로 작성하며, 교육적이고 건설적인 내용으로 구성하세요."""
문항 유형별 분석
시험 해설 생성 시 문항 유형별 정답률을 자동으로 집계하여 분석에 포함합니다:
def _build_exam_commentary_prompt(req: ExamCommentaryRequest) -> str:
# 유형별 정답률 집계
type_stats = {}
for q in req.questionStatistics:
qtype = q.questionType or "기타"
if qtype not in type_stats:
type_stats[qtype] = {"correct_sum": 0, "count": 0}
type_stats[qtype]["correct_sum"] += q.correctRate
type_stats[qtype]["count"] += 1
# 평균 정답률 계산
type_lines = []
for qtype, tdata in type_stats.items():
avg_rate = (tdata["correct_sum"] / tdata["count"]) * 100
type_lines.append(f" - {qtype}: {avg_rate:.1f}%")
난이도 자동 분류
평균 점수를 기준으로 시험 난이도를 자동 분류합니다:
def _classify_difficulty(average: float) -> str:
if average >= 75:
return "쉬움"
elif average >= 60:
return "보통"
else:
return "어려움"
학생 피드백 톤 결정
학생의 성취 수준에 따라 피드백 톤이 자동으로 결정됩니다:
def _build_student_feedback_prompt(req: StudentFeedbackRequest) -> str:
# 성취 수준별 톤 결정
if req.totalScore < 50:
tone = "기초 보강과 시간 관리 강조"
elif req.totalScore >= 80:
tone = "기초는 탄탄하며 고난도 전략 강조"
else:
tone = "균형적 분석과 유형별 보완 강조"
오답 문항 분류
학생 피드백에서는 오답 문항을 난이도별로 분류하여 분석합니다:
# 오답 문항을 난이도별로 분류
easy_wrong = [q for q in req.incorrectQuestions if q.questionCorrectRate >= 70] # 쉬운 문제 오답
hard_wrong = [q for q in req.incorrectQuestions if q.questionCorrectRate < 40] # 어려운 문제 오답
| 정답률 구간 | 난이도 분류 | 피드백 방향 |
|---|---|---|
| 70% 이상 | 쉬움 | 기초 개념 재점검 필요 |
| 40~70% | 보통 | 문제 해결 전략 개선 |
| 40% 미만 | 어려움 | 고난도 문항, 정상적인 오답 |
데이터 모델
시험 해설 요청
class ExamCommentaryRequest(BaseModel):
examName: str
totalParticipants: int
averageScore: float
highestScore: int
lowestScore: int
gradeDistribution: List[GradeDistributionItem]
questionStatistics: List[QuestionStatItem]
class GradeDistributionItem(BaseModel):
grade: int
count: int
percentage: float
class QuestionStatItem(BaseModel):
questionNumber: int
correctRate: float # 0~1
incorrectRate: float # 0~1
questionType: Optional[str] = None
actualScore: Optional[int] = None
학생 피드백 요청
class StudentFeedbackRequest(BaseModel):
examName: str
studentName: str
totalScore: int
grade: int
averageScore: float
incorrectQuestions: list[IncorrectQuestionItem]
questionTypeAchievement: list[QuestionTypeAchievement]
class IncorrectQuestionItem(BaseModel):
questionNumber: int
questionType: str
selectedChoice: str
correctAnswer: str
questionCorrectRate: float # 해당 문항 전체 정답률 (0~100)
class QuestionTypeAchievement(BaseModel):
type: str
correctCount: int
totalCount: int
correctRate: float # 0~100
OpenAI 클라이언트 관리
OpenAI 클라이언트는 지연 초기화(lazy initialization) 패턴을 사용합니다:
client: Optional[OpenAI] = None
def get_client() -> OpenAI:
global client
if client is None:
client = OpenAI(api_key=LLM_API_KEY, base_url=LLM_BASE_URL)
return client
이를 통해 서비스 시작 시 API 키 검증 없이 빠른 부팅이 가능하며, 실제 요청 시에만 클라이언트를 초기화합니다.
배포 현황
분석 서비스는 현재 Kubernetes에 배포되어 joossameng FE에서 직접 호출됩니다. CORS 허용 오리진은 joossameng.vercel.app, joossameng.com, lumie-edu.com 계열입니다. LLM_API_KEY는 HashiCorp Vault에서 관리됩니다.
환경 변수
| 변수 | 기본값 | 설명 |
|---|---|---|
LLM_API_KEY | — (필수) | OpenAI API 키 |
LLM_BASE_URL | https://api.openai.com/v1 | API 엔드포인트 |
LLM_MODEL | gpt-4o-mini | 사용할 LLM 모델 |
헬스체크
GET /health
{ "status": "ok" }