분석
목적
analysis-svc는 구조화된 통계로부터 시험 코멘터리와 학생별 피드백을 생성하는 워커입니다. grading과 report와 달리 큐 기반이 아닙니다. 호출자가 HTTP 요청을 보내면 생성된 텍스트 응답을 즉시 받습니다.
리포지토리 전반의 worker 모델은 Workers Overview를 참조하세요.
소스 경로
| 경로 | 역할 |
|---|---|
lumie-worker/services/analysis/main.py | FastAPI 앱, lifespan, route handler, metrics mount |
lumie-worker/services/analysis/src/schema.py | wire 요청 및 응답 모델 |
lumie-worker/services/analysis/src/usecase.py | LLM 호출 오케스트레이션과 metric |
lumie-worker/services/analysis/src/domain/prompts.py | 한국어 프롬프트 템플릿과 프롬프트 빌더 |
lumie-worker/services/analysis/src/adapters/llm.py | AsyncOpenAI 클라이언트 adapter |
lumie-worker/services/analysis/src/config.py | LLM_*와 OTel 설정 |
lumie-worker/services/analysis/tests/test_analysis_usecase.py | use case 동작 테스트 |
lumie-worker/services/analysis/tests/test_observability.py | metric 및 tracing smoke 테스트 |
공개 표면
라우트:
GET /GET /healthGET /metricsPOST /api/analysis/exam-commentaryPOST /api/analysis/student-feedback
두 생성 라우트는 모두 다음을 반환합니다.
{
"content": "..."
}
요청 모델
/api/analysis/exam-commentary는 다음과 같은 집계 수준 시험 데이터를 받습니다.
- 시험 이름
- 응시자 수
- 평균, 최고, 최저 점수
- 등급 분포
- 문제별 정답률 통계
/api/analysis/student-feedback는 다음과 같은 학생 수준 데이터를 받습니다.
- 학생 이름과 시험 이름
- 총점, 등급, 시험 평균
- 선택 답안과 정답이 포함된 오답 문제
- 문제 유형별 성취도
요청 및 응답 계약은 services/analysis/src/schema.py에 있으며, wire 형식에 대해 직접 Pydantic 검증을 사용합니다.
이 스키마는 백엔드와 프론트엔드 호출자가 이미 camelCase JSON을 사용하므로 alias 없이 의도적으로 camelCase 필드를 사용합니다. 모든 호출자를 같은 변경에서 함께 마이그레이션하지 않는 한 이 모델을 snake_case로 바꾸지 마세요.
시험 코멘터리 요청
{
"examName": "June Mock Exam",
"totalParticipants": 120,
"averageScore": 71.4,
"highestScore": 98,
"lowestScore": 22,
"gradeDistribution": [
{ "grade": 1, "count": 7, "percentage": 5.8 }
],
"questionStatistics": [
{
"questionNumber": 31,
"correctRate": 0.42,
"incorrectRate": 0.58,
"questionType": "빈칸추론",
"actualScore": 3
}
]
}
학생 피드백 요청
{
"examName": "June Mock Exam",
"studentName": "Student A",
"totalScore": 84,
"grade": 2,
"averageScore": 71.4,
"incorrectQuestions": [
{
"questionNumber": 31,
"questionType": "빈칸추론",
"selectedChoice": "2",
"correctAnswer": "4",
"questionCorrectRate": 42.0
}
],
"questionTypeAchievement": [
{ "type": "빈칸추론", "correctCount": 2, "totalCount": 4, "correctRate": 50.0 }
]
}
생성 플로우
- FastAPI가 들어온 JSON을 타입이 지정된 요청 모델로 검증합니다.
AnalysisUseCase가src/domain/prompts.py의 순수 함수를 사용해 구조화된 입력에서 프롬프트를 만듭니다.- 서비스가
openai.AsyncOpenAI를 통해 OpenAI 호환 chat completion API를 호출합니다. - 반환된 메시지 내용을
GenerationResponse로 감싸서 반환합니다.
두 생성 경로는 주로 프롬프트 구성 방식에서 차이가 납니다.
- 시험 코멘터리는 분포 수준 경향과 어려운 문제 패턴에 집중합니다
- 학생 피드백은 한 학생의 실수, 문제 유형별 수행, 학습 가이드에 집중합니다
현재 프롬프트 템플릿은 교사 스타일의 일반 텍스트 출력과 한국어 응답에 맞춰져 있지만, 서비스 계약 자체는 텍스트 입력과 텍스트 출력으로 단순합니다.
프롬프트 계약
프롬프트 빌더는 순수 함수입니다. 시험 난이도를 분류하고, 등급 분포를 요약하고, 오답률이 높은 문제를 고르고, 문제 유형을 묶은 뒤, 한국어 강사 스타일 프롬프트를 렌더링합니다. use case는 요청을 변경하거나 추가 데이터를 가져오지 않습니다.
생성 결과는 다음을 만족해야 합니다.
- Markdown이 아닌 일반 텍스트
- 한국어 교사 스타일 문장
- 시험 코멘터리는
max_tokens=1024제한 - 학생 피드백은
max_tokens=2048제한
제품 요구사항이 다국어 출력으로 바뀐다면 프롬프트 계약도 명시적으로 변경되어야 합니다. 현재 서비스는 요청 필드에서 출력 언어를 추론하지 않습니다.
설정 및 의존성
주요 설정:
LLM_API_KEYLLM_BASE_URLLLM_MODELOTEL_ENABLEDOTEL_ENDPOINTOTEL_SERVICE_NAME
코드상의 기본 모델 설정은 다음과 같습니다.
- base URL:
https://api.openai.com/v1 - model:
gpt-4o-mini
이 서비스는 lifespan hook에서 AsyncOpenAI 클라이언트 수명주기를 관리하고, 종료 시 해당 클라이언트를 닫습니다.
관측성과 실패 처리
analysis worker는 다음을 내보냅니다.
analysis_llm_requests_totalanalysis_llm_duration_secondsanalysis_llm_inflight
metric은 작업 단위로 라벨링됩니다.
exam_commentarystudent_feedback
실패는 다음과 같이 나뉩니다.
- 상위 OpenAI 호환 API 실패용
api_error - 로컬 버그 또는 예기치 못한 런타임 오류용
handler_failure
Tracing은 공유 worker observability 헬퍼를 통해 활성화됩니다. FastAPI와 httpx가 계측되므로, 별도의 클라이언트 코드 없이 외부 LLM 트래픽이 추적됩니다.
실패 의미
| 실패 | 결과 |
|---|---|
| 잘못된 JSON 또는 필수 필드 누락 | FastAPI/Pydantic 검증 오류 |
OpenAI 호환 API가 APIError 발생 | metric 결과 api_error, 예외 재발생 |
| 로컬 버그 또는 예기치 못한 런타임 오류 | metric 결과 handler_failure, 예외 재발생 |
| 비어 있는 LLM 메시지 내용 | 빈 content를 가진 유효한 GenerationResponse |
이 서비스는 api_error와 handler_failure를 의도적으로 분리하여, 대시보드가 상위 LLM 문제와 로컬 worker 결함을 구분할 수 있게 합니다.
운영 메모
GET /health는{"status":"ok"}를 반환합니다.GET /는 기본 확인용 간단한 liveness payload를 반환합니다.- 일부 환경에서 웹 클라이언트가 직접 호출하도록 설계되어 있으므로 CORS가 활성화되어 있습니다.
LLM_API_KEY에는 기본값이 없으며, 누락 시 시작 실패가 나야 합니다.- 단위 테스트는 LLM port를 mock하고 네트워크 호출 없이 오케스트레이션 동작을 검증합니다.
검증
cd lumie-worker
uv run pytest services/analysis/tests
cd /path/to/Lumie
rg -n "exam-commentary|student-feedback|analysis_llm" lumie-worker/services/analysis
예상 성공 신호:
pytest가0으로 종료services/analysis/tests/test_analysis_usecase.py가 코멘터리, 학생 피드백, 빈 콘텐츠 케이스를 통과services/analysis/tests/test_observability.py가 tracing과/metricssmoke 테스트를 통과- grep이
services/analysis/main.py의 두 HTTP 라우트와services/analysis/src/아래의analysis_llm_*metric 이름을 보여줌