본문으로 건너뛰기

분석 서비스

분석 서비스

분석 서비스(analysis-svc)는 OpenAI GPT-4o-mini를 활용하여 시험 전체 해설과 학생 개별 피드백을 한국어로 자동 생성합니다. RabbitMQ 소비자 없이 HTTP API만 제공하며, lumie-backend 및 joossameng FE가 직접 호출합니다. 영어 교육 전문가 관점의 프롬프트 설계로 고품질의 교육적 피드백을 제공합니다.

기술 스택

라이브러리버전용도
OpenAI SDK1.82.0GPT-4o-mini API 호출
Pydantic2.10.0요청/응답 스키마 검증
FastAPI0.115.0HTTP API 서버
Uvicorn0.30.0ASGI 서버

LLM 설정

설정 항목환경변수기본값설명
API 키LLM_API_KEYOpenAI API 키
Base URLLLM_BASE_URLhttps://api.openai.com/v1API 엔드포인트
모델명LLM_MODELgpt-4o-mini사용할 LLM 모델

출력 설정

APImax_tokens목표 길이설명
시험 해설1024200–400자전체 시험에 대한 종합 분석
학생 피드백2048500–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_URLhttps://api.openai.com/v1API 엔드포인트
LLM_MODELgpt-4o-mini사용할 LLM 모델

헬스체크

GET /health
{ "status": "ok" }

관련 문서