κ΅μ‘μ μΌκ΅΄ νλ μκ³Ό μμ
ν
μ€νΈλ₯Ό λ°νμΌλ‘ μ΄ν΄ μ ν μ νΈλ₯Ό λΆμνκ³ , μμ½κ³Ό μ½μΉ κ°μ΄λλ₯Ό μμ±νλ FastAPI μλΉμ€μ
λλ€.
νμ¬ κ΅¬νμ FER + MediaPipe + OpenAI μ‘°ν©μ μ¬μ©νλ©°, λΆμ APIλ μΌκ΅΄ νμ νΉμ§κ³Ό GPT νλ¨μ ν¨κ» μ¬μ©ν©λλ€.
| νλͺ© | λ΄μ© |
|---|---|
| λ°νμ | Python 3.11 |
| νλ μμν¬ | FastAPI 0.111.0, Uvicorn 0.29.0 |
| API Prefix | /ai-api |
| μ£Όμ κΈ°λ₯ | μΌκ΅΄ νλ μ λΆμ, κ°μ ν μ€νΈ μμ½, λμ보λ κΈ°λ° AI μ½μΉ |
| μΌκ΅΄ κ°μ λΆμ | fer==22.5.1 |
| μΌκ΅΄ λλλ§ν¬ | mediapipe==0.10.33 |
| LLM | OpenAI Chat Completions |
| κΈ°λ³Έ λͺ¨λΈ | gpt-5.4-mini |
| κΈ°λ³Έ ν¬νΈ | 8000 |
flowchart LR
A[Student Frame] --> B[FastAPI /analyze]
B --> C[OpenCV Decode]
C --> D[FER Emotion Features]
C --> E[MediaPipe Landmark Features]
D --> F[Feature Merge]
E --> F
F --> G[OpenAI Judge]
G --> H[Signal Classification]
H --> I[AnalyzeResponse]
J[Lecture Transcript] --> K[FastAPI /summarize]
K --> L[OpenAI Summary]
L --> M[SummarizeResponse]
N[Dashboard Data] --> O[FastAPI /coaching]
O --> P[OpenAI Coaching]
P --> Q[Fallback Coaching]
P --> R[CoachingResponse]
Q --> R
flowchart TD
A[Upload Image] --> B{Content-Type Valid?}
B -- No --> X[400 Error]
B -- Yes --> C{Empty or > 10MB?}
C -- Yes --> X
C -- No --> D[cv2.imdecode]
D --> E[FER Emotion Detection]
D --> F[MediaPipe Landmark Detection]
E --> G[Feature Extraction]
F --> G
G --> H[OpenAI JSON Judgment]
H --> I[Rule-based Signal Classification]
I --> J[AnalyzeResponse]
flowchart TD
A[GPT confused?] -->|No| B[FACIAL_INSTABILITY / LOW_SIGNAL]
A -->|Yes| C{Face Detected?}
C -->|No| D[GAZE_AWAY / FACE_MISSING]
C -->|Yes| E{head_tilt_deg >= 20}
E -->|Yes| F[GAZE_AWAY / HEAD_TURNED]
E -->|No| G{ear < 0.18}
G -->|Yes| H[GAZE_AWAY / EYES_CLOSED]
G -->|No| I{top_emotion in fear/sad/surprise/angry/disgust}
I -->|Yes| J[FACIAL_INSTABILITY / emotion subtype]
I -->|No| K[FACIAL_INSTABILITY / CONFUSED_PATTERN]
κ°μ λ³ signalSubtype λ§€νμ λ€μκ³Ό κ°μ΅λλ€.
| top_emotion | signal_subtype |
|---|---|
fear |
FEAR_DOMINANT |
sad |
SAD_DOMINANT |
surprise |
SURPRISE_DOMINANT |
angry |
ANGRY_TENSION |
disgust |
DISGUST_TENSION |
| Method | Path | μ€λͺ |
|---|---|---|
POST |
/ai-api/analyze/{student_id} |
κ΅μ‘μ μΌκ΅΄ νλ μ 1μ₯μ λΆμ |
POST |
/ai-api/summarize |
κ°μ μ μ¬ ν μ€νΈ μμ½ |
POST |
/ai-api/coaching |
λμ보λ μ§κ³ λ°μ΄ν° κΈ°λ° μ½μΉ μμ± |
GET |
/ai-api/health |
μλ² λ° λΆμκΈ° μ€λΉ μν νμΈ |
κ΅μ‘μ μΌκ΅΄ νλ μ 1μ₯μ λ°μ μ΄ν΄ μ ν μ¬λΆλ₯Ό λΆμν©λλ€.
Request
Content-Type:multipart/form-data- Path parameter:
student_id - Form field:
file
Validation
- νμ© MIME:
image/jpeg,image/png,image/webp - λΉ νμΌμ΄λ©΄
400 - 10MB μ΄κ³Ό μ
400
Response fields
studentIdconfusedconfidenceemotiongptReasonsignalTypesignalSubtypesignalLabelfaceFeatures
confidenceλ FER κ°μ confidenceκ° μλλΌ, GPTκ° νλ¨ν "μ΄ν΄νμ§ λͺ»ν μ λ"μ
λλ€.
{
"studentId": "student-42",
"confused": true,
"confidence": 0.82,
"emotion": "fear",
"gptReason": "νμμ΄ νμ¬ λ΄μ©μ μ΄ν΄νμ§ λͺ»ν κ²μΌλ‘ 보μ
λλ€.",
"signalType": "FACIAL_INSTABILITY",
"signalSubtype": "FEAR_DOMINANT",
"signalLabel": "νμ κΈ°λ° λΆμμ ",
"faceFeatures": {
"face_detected": true,
"emotions": {
"angry": 0.053,
"disgust": 0.071,
"fear": 0.712,
"happy": 0.011,
"sad": 0.084,
"surprise": 0.049,
"neutral": 0.02
},
"top_emotion": "fear",
"confidence": 0.712,
"brow_eye_ratio": 0.0382,
"ear": 0.2113,
"head_tilt_deg": 4.26
}
}faceFeatures.confidenceλ FER κΈ°μ€ μ΅κ³ κ°μ μ μμ΄κ³ , μ΅μμ confidenceμ μλ―Έκ° λ€λ¦
λλ€.
κ°μ μ μ¬ ν μ€νΈλ₯Ό μμ½νκ³ , 보좩 μ€λͺ μ΄ νμν κ°λ κ³Ό ν΅μ¬ ν€μλλ₯Ό μμ±ν©λλ€.
Request
{
"audioText": "ν¨μλ μ
λ ₯μ λ°μ μΆλ ₯μ λ°ννλ ꡬ쑰μ
λλ€. νκ· λ³νμ¨κ³Ό λΉκ΅νλ©΄..."
}Response
summary: νκ΅μ΄ 2λ¬Έμ₯ μμ½recommendedConcept: μΆκ° μ€λͺ μ΄ νμν κ°λ 1κ°keywords: μ΅λ 5κ°, κ° νλͺ© 3λ¨μ΄ μ΄ν
{
"summary": "μ΄λ² μ€λͺ
μ ν¨μμ μλ―Έλ₯Ό μ
λ ₯κ³Ό μΆλ ₯ κ΄κ³ μ€μ¬μΌλ‘ μ 리νμ΅λλ€. νκ· λ³νμ¨κ³Όμ μ°¨μ΄λ₯Ό μ΄ν΄νλ κ²μ΄ λ€μ λ¨κ³μ
λλ€.",
"recommendedConcept": "νκ· λ³νμ¨κ³Ό ν¨μ κ΄κ³",
"keywords": ["ν¨μ", "μ
λ ₯ μΆλ ₯", "νκ· λ³νμ¨", "λμ κ΄κ³", "κ°λ
λΉκ΅"]
}λμ보λμμ μ§κ³ν μμ λ°μ λ°μ΄ν°λ₯Ό λ°νμΌλ‘, κ°μ¬κ° λ°λ‘ νμ©ν μ μλ μ½μΉ λ©μμ§λ₯Ό μμ±ν©λλ€.
Request μ£Όμ νλ
datecurriculumclassIdclassIdsparticipantCountalertCountavgConfusionPercenttopKeywordstopTopicssignalBreakdownrecentAlerts
Response fields
summarypriorityLevelcoachingTipsreExplainTopicsstudentSignalsrecommendedActionNowsampleMentions
GPT μλ΅ νμ±μ΄ μ€ν¨νλ©΄ _fallback_coaching()μ΄ μ
λ ₯ λ°μ΄ν° κΈ°λ° κΈ°λ³Έ μ½μΉ 문ꡬλ₯Ό μμ±ν©λλ€.
{
"status": "ok",
"analyzer_ready": true
}erDiagram
ANALYZE_RESPONSE {
string studentId
boolean confused
float confidence
string emotion
string gptReason
string signalType
string signalSubtype
string signalLabel
json faceFeatures
}
SUMMARIZE_REQUEST {
string audioText
}
SUMMARIZE_RESPONSE {
string summary
string recommendedConcept
string_list keywords
}
COACHING_REQUEST {
string date
string curriculum
string classId
string_list classIds
int participantCount
int alertCount
int avgConfusionPercent
string_list topKeywords
string_list topTopics
}
COACHING_SIGNAL_ITEM {
string signalType
string label
int count
}
COACHING_RECENT_ALERT_ITEM {
string classId
string capturedAt
string topic
string reason
int confusionPercent
}
COACHING_RESPONSE {
string summary
string priorityLevel
string_list coachingTips
string_list reExplainTopics
string_list studentSignals
string recommendedActionNow
string_list sampleMentions
}
COACHING_REQUEST ||--o{ COACHING_SIGNAL_ITEM : contains
COACHING_REQUEST ||--o{ COACHING_RECENT_ALERT_ITEM : contains
fastapi/
βββ main.py
βββ analyzer.py
βββ models.py
βββ requirements.txt
βββ Dockerfile
βββ Dockerfile.base
βββ docker-compose.yml
βββ build-base.sh
main.py: FastAPI μ±, λΌμ°ν°, CORS, μμ² κ²μ¦, μλ΅ λ³νanalyzer.py: μ΄λ―Έμ§ λμ½λ©, νΉμ§ μΆμΆ, GPT νλ¨, μμ½/μ½μΉ, fallback μ²λ¦¬models.py: Pydantic μμ²/μλ΅ μ€ν€λ§
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txtνκ²½ λ³μ:
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-5.4-miniμ€ν:
uvicorn main:app --host 0.0.0.0 --port 8000 --reloadλ¬Έμ:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc
Base μ΄λ―Έμ§ μμ±:
bash ./build-base.shμ ν리μΌμ΄μ μ€ν:
docker compose up --build -d
docker compose logs -fμ€μ§:
docker compose down- μλ² μμ μ
lifespan()μμFaceAnalyzerλ₯Ό 1ν μ΄κΈ°νν©λλ€. - Windowsμμλ
face_landmarker.taskλ₯Ό μ¬μ©μ ν λλ ν°λ¦¬μ μ μ₯νκ³ , κ·Έ μΈ νκ²½μμλ νλ‘μ νΈ λλ ν°λ¦¬ κΈ°μ€μΌλ‘ μ¬μ©ν©λλ€. OPENAI_API_KEYκ° μμΌλ©΄ μ± μμ λ¨κ³μμFaceAnalyzerμ΄κΈ°νκ° μ€ν¨ν©λλ€.- λΆμ μ€ν¨ fallback μλ΅μμλ
confused=false,confidence=0.0μΌλ‘ λ°νλ©λλ€.