이 글에서 다루는 내용
AirClassGrading은 수업 이후의 평가 업무를 줄이기 위해 만든 채점 보조 시스템이다. 이번 6편에서는 그중 첫 번째 축인 Teams 과제 자동 채점을 다룬다.
Teams 과제 자동 채점의 목표는 단순히 AI에게 점수를 물어보는 것이 아니다. 실제 교사가 반복해서 하던 일을 시스템이 단계별로 대신 처리하게 만드는 것이다.
- Teams 과제 목록과 제출물을 가져온다.
- 학생별 첨부 파일을 내려받는다.
- PDF, 사진, 코드 파일을 AI가 읽을 수 있게 정리한다.
- 과제 안내문과 루브릭을 snapshot으로 보존한다.
- AI가 제출물을 1차 리뷰한다.
- 교사가 리뷰 웹에서 최종 판단을 확인한다.
- 필요한 경우 Teams로 피드백을 발송한다.
전체 흐름은 다음과 같다.
서술형 답안 이미지 채점 흐름은 성격이 다르기 때문에 다음 편에서 따로 다룬다. 이번 글은 Teams 과제 자동 채점 구조에 집중한다.
왜 Teams 과제 자동 채점이 필요했나
Teams 과제 기능 자체는 편하다. 학생은 파일을 올리고, 교사는 제출 여부를 확인할 수 있다. 하지만 실제 평가 업무에서는 Teams 화면을 열어 보는 것만으로 끝나지 않는다.
교사는 매번 다음 일을 반복한다.
- 누가 제출했고 누가 아직 안 냈는지 확인한다.
- 제출물이 PDF인지, 사진인지, 코드 파일인지 확인한다.
- 파일이 정상적으로 열리는지 확인한다.
- 학생이 지난주 파일이나 엉뚱한 파일을 올리지 않았는지 본다.
- 과제 설명에 적어 둔 조건을 다시 읽는다.
- 제출물과 기준을 대조한다.
- 학생에게 줄 피드백을 쓴다.
특히 포트폴리오나 수행평가처럼 제출물이 여러 장의 사진, PDF, 코드, 설명문으로 섞이면 이 과정이 길어진다. AI 채점보다 먼저 필요한 것은 제출물을 채점 가능한 상태로 정리하는 일이었다.
그래서 AirClassGrading의 첫 목표는 “AI가 점수를 자동 확정하는 시스템”이 아니라, 다음을 자동화하는 것이었다.
Teams에 흩어진 제출물과 과제 기준을 모아, AI와 교사가 같은 자료를 보고 검토할 수 있는 상태로 만드는 것.
코드 구조
Teams 과제 자동 채점 흐름은 legacy_airclass_engine 아래에 정리되어 있다.
legacy_airclass_engine/
core/
engine_api.py
review_runner.py
review_store.py
assignment_context.py
assignment_loader.py
grade_prepare_service.py
prompt_builder.py
rubric_loader.py
second_pass_review_prompt_builder.py
slim_prompt_builder.py
web_review_app.py
scripts/
prepare_portfolio.py
prepare_quiz.py
finalize_portfolio.py
finalize_quiz.py
generate_grades_csv.py
generate_quiz_review_csv.py
run_worker.py
auto_feedback_worker.py
teams/
teams_graph.py
teams_feedback_writer.py
teams_account_sender.py
teams_bot_sender.py
teams_assignment_live.py
역할을 나누면 다음과 같다.
| 영역 | 역할 |
|---|---|
core |
API, 리뷰 실행, DB 저장, 프롬프트 생성, 과제 컨텍스트 관리 |
scripts |
제출물 수집, 전처리, 채점 실행, CSV 생성, worker 실행 |
teams |
Microsoft Teams/Graph API 연동, 피드백 전송 |
docs, k8s, argocd |
운영 문서와 배포 설정 |
루트의 docs/ENGINE_API_README.md, docs/WEB_REVIEW_README.md, docs/TEAMS_GRAPH_API.md는 이 흐름을 설명하는 운영 문서다.
engine_api.py: API와 리뷰 웹을 묶는 중심
engine_api.py는 Teams 과제 자동 채점 흐름의 통합 FastAPI 앱이다. 이 앱은 단순 API 서버가 아니라, prepare API, 교사용 리뷰 웹, snapshot 관리, 피드백 큐 등록까지 함께 담당한다.
주요 기능은 다음과 같다.
- Teams 제출물 전처리 API
- 과제 컨텍스트 snapshot 생성/비교
- AI 리뷰 실행 진입점
- 교사용 리뷰 목록/상세/수정 화면
- 리뷰 row 동기화
- 피드백 전송 큐 등록
- 제출물 이미지/PDF 렌더링 산출물 제공
실행 형태는 다음과 같다.
python3 -m uvicorn engine_api:app --host 0.0.0.0 --port 8092
Docker나 k3s 배포에서는 컨테이너 내부 /app 기준으로 실행하고, /app/out은 PVC나 host volume으로 유지한다. PDF 렌더링 이미지, manifest, debug JSON 같은 산출물이 컨테이너 재시작으로 사라지지 않게 하기 위해서다.
실제 과제 지침은 프롬프트 묶음으로 바뀐다
AirClassGrading 1에서 중요한 점은 Teams 과제 지침을 그대로 사람이 다시 읽는 데서 끝내지 않고, AI가 단계별로 판단할 수 있는 프롬프트 묶음으로 바꾼다는 것이다.
실제 1주차 포트폴리오 과제에서는 하나의 과제 지침을 다음 다섯 역할로 나누었다.
| 프롬프트 | 역할 |
|---|---|
fitPrompt |
현재 과제 제출물로 볼 수 있는지 판정 |
structurePrompt |
구성력만 평가 |
diligencePrompt |
성실도만 평가 |
formatPrompt |
형식 준수 여부만 확인 |
finalReviewPrompt |
앞 단계 결과를 종합해 최종 리뷰 생성 |
예를 들어 fitPrompt는 점수를 매기지 않는다. 오직 제출물이 현재 과제에 맞는지만 본다.
과제명: (수행-포트폴리오) 포트폴리오 수행 안내 및 1주차 내용정리
역할: 제출 적합성(content fit)만 판정한다.
이 프롬프트에서는 구성력/성실도/형식 점수를 매기지 말고,
오직 현재 과제 제출물로 볼 수 있는지만 판단하라.
반드시 JSON만 출력:
{
"content_fit": "fit|mismatch|uncertain",
"submission_verdict": "valid|ineligible|uncertain",
"reasons": ["string"],
"uncertainties": ["string"]
}
이 단계를 분리한 이유는 과제와 무관한 제출물을 먼저 걸러내기 위해서다. 현재 과제와 맞지 않는 자료를 두고 구성력이나 성실도를 평가하면, AI가 그럴듯한 근거를 만들어 버릴 수 있다.
구성력과 성실도는 다른 프롬프트로 평가한다
포트폴리오 평가는 크게 구성력과 성실도로 나뉜다. 둘을 한 번에 묻지 않고 분리한 이유는, AI가 한 영역의 근거를 다른 영역 점수에 끌고 가는 문제를 줄이기 위해서였다.
실제 structurePrompt는 구성력만 보게 했다.
역할: 구성력만 평가한다.
구성력은 개념 정리의 체계, 구분, 흐름, 가독성을 평가하는 영역이다.
봐야 할 것:
- 개념 정리 섹션이 실제로 구분되어 보이는가
- 핵심 개념이 항목별로 정리되어 있는가
- 제목, 구획, 순서가 있어 읽는 흐름이 명확한가
- 개념 설명이 너무 비어 있거나 뒤섞여 가독성이 떨어지는가
판정 원칙:
- 구성력 근거는 개념 정리의 체계·구분·가독성에서만 잡아라
- 문제풀이 분량, 문제 개수, 풀이 페이지 수, 비율은 구성력 근거로 쓰지 않는다
반대로 diligencePrompt는 성실도만 보게 했다.
역할: 성실도만 평가한다.
성실도는 문제 번호, 문제 개수, 풀이 과정, 작성 충실도를 평가하는 영역이다.
봐야 할 것:
- 문제 번호나 문항 구분이 실제로 보이는가
- 풀이 과정이 단계적으로 적혀 있는가
- 작성 분량과 밀도가 충분한가
- 요구된 문제 범위가 대체로 반영되어 보이는가
판정 원칙:
- 성실도 근거는 문제 수, 풀이 과정, 작성 충실도에서만 잡아라
- 개념 정리 분량, 비율, 체계성은 성실도 근거로 쓰지 않는다
이 분리는 실제 운영에서 중요했다. 예쁘게 정리된 개념 노트가 반드시 문제 풀이 성실도를 보장하지 않고, 풀이를 많이 했다고 해서 개념 정리의 체계성이 높아지는 것도 아니기 때문이다.
먼저 페이지를 관찰하고, 그 다음 최종 리뷰를 만든다
AI에게 처음부터 등급을 묻지 않는 단계도 있다. 한 페이지씩 보면서 실제로 보이는 사실만 구조화하게 하는 관찰 프롬프트다.
{
"page_type": "concept_summary|problem_solving|retry_or_correction|mixed|unclear",
"readability": "high|medium|low",
"observations": [
"보이는 사실 1",
"보이는 사실 2"
],
"signals": {
"has_concept_heading": false,
"has_structured_flow": false,
"has_restarted_problem_numbers": false,
"has_problem_source": false,
"has_step_by_step_solution": false,
"has_answer_only_pattern": false,
"has_retry_or_correction": false,
"shows_diligent_organization": false,
"shows_sparse_or_low_effort_notes": false
},
"uncertainties": [
"판독 한계 또는 확신 없는 점"
]
}
이 단계의 목표는 채점이 아니라 증거 수집이다. AI가 A/B/C/D를 바로 정하는 대신, 개념 제목이 보이는지, 풀이 과정이 단계적인지, 답만 적은 패턴이 있는지 같은 신호를 먼저 남긴다.
마지막 finalReviewPrompt는 앞 단계 결과를 종합한다.
역할: 적합성/구성력/성실도/형식 결과를 종합 검토한다.
당신은 최종 검토자이자 최종 재채점자다.
1차 결과를 참고하되, 최종 등급과 학생 피드백은 당신이 다시 일관되게 확정한다.
최종 검토 순서:
1) fit 결과로 현재 과제 제출물인지 먼저 확인
2) 구성력은 개념 정리의 체계·구분·가독성만 다시 본다
3) 성실도는 문제 수·풀이 과정·작성 충실도만 다시 본다
4) 형식 위반이 실제 감점 요소인지 마지막에 확인한다
핵심 원칙:
- 구성력과 성실도는 독립 영역이다.
- 한 영역의 근거를 다른 영역 점수 근거로 옮겨 쓰지 마라.
- 부적합 판단은 fit 결과와 실제 이미지의 주된 내용 근거로만 한다.
이렇게 나누면 AI 채점 결과가 단일 응답 하나로 끝나지 않는다. 제출 적합성, 구성력, 성실도, 형식, 최종 종합의 판단 흔적이 각각 남는다.
prepare: Teams 제출물을 채점 가능한 자료로 바꾸기
자동 채점의 첫 단계는 prepare다. prepare는 단순 다운로드가 아니다. Teams 제출물을 AI가 안정적으로 읽고, 교사가 나중에 추적할 수 있는 자료 구조로 바꾸는 과정이다.
처리 흐름은 다음과 같다.
- 과제 유형에 따라 포트폴리오 또는 퀴즈 prepare 흐름을 선택한다.
- Teams Graph API로 과제 제출물과 첨부 파일을 가져온다.
- PDF는 페이지별 이미지로 렌더링한다.
- 사진 파일은 방향과 파일명을 정리한다.
manifest.json과analysisTargets를 만든다.- Teams 과제 안내문을 기반으로 assignment context snapshot을 생성하거나 기존 snapshot을 연결한다.
- prompt bundle, parsed instructions, grading prompt 후보를 함께 반환한다.
Teams 화면에서는 한 학생의 제출물이 한 덩어리처럼 보인다. 하지만 실제 파일은 더 복잡하다.
학생 A의 제출물
report.pdf
page 1
page 2
page 3
photo_1.jpg
photo_2.jpg
code.py
이 상태 그대로 AI에게 넘기면 순서와 출처가 흔들릴 수 있다. 그래서 prepare 단계에서는 제출물을 작은 분석 단위로 정리한다.
Teams submission
→ resources / submittedResources
→ local files
→ normalized artifacts
→ analysisTargets
→ AI review
analysisTargets는 “이 자료가 어느 학생의 어느 제출물에서 나온 몇 번째 페이지 또는 파일인가”를 추적하는 목록이다. AI 리뷰와 교사 검토가 같은 자료 순서를 보게 만드는 핵심 장치다.
과제 기준 snapshot
자동 채점에서 제출물만큼 중요한 것이 기준이다. Teams 과제 설명은 운영 중에 수정될 수 있고, 과제마다 제출 형식, 마감, 지각 정책, 루브릭이 다르다.
AirClassGrading은 이 기준을 snapshot으로 남긴다.
| 항목 | 의미 |
|---|---|
| live assignment | Graph API로 현재 조회한 Teams 과제 원문 |
| instruction digest | 과제 안내문의 핵심 요약 |
| parsed instructions | 마감, 제출 형식, 지각 정책 등 구조화된 지시사항 |
| prompt bundle | AI 채점에 사용할 프롬프트 묶음 |
| assignment context snapshot | 특정 시점의 과제 기준 저장본 |
snapshot이 필요한 이유는 명확하다. 나중에 “그때 어떤 기준으로 이 제출물을 봤는가?”를 확인할 수 있어야 하기 때문이다.
교사용 웹에서는 현재 live assignment와 저장된 snapshot을 비교하거나, snapshot끼리 비교할 수 있다. 과제 지시문이 중간에 바뀌었을 때 특히 중요하다.
AI 리뷰 실행
AI 리뷰는 단순히 점수 하나를 뱉는 방식이 아니다. 과제 유형에 따라 여러 관점으로 나누어 검토한다.
포트폴리오 과제에서는 보통 다음을 본다.
- 과제 적합성
- 개념 이해
- 풀이 또는 구성의 구조
- 성실도
- 제출 형식
- 지각 정책 적용 여부
- 학생용 피드백 초안
퀴즈 과제에서는 체크리스트 기반으로 Pass/NP/uncertain 같은 판정을 할 수 있다. 과제와 무관한 제출물은 NP 또는 ineligible에 가깝게 처리한다.
리뷰 실행은 review_runner.py 흐름과 연결된다. prepare 결과를 학생/제출 단위로 묶고, 과제 기준과 루브릭을 반영해 AI 리뷰를 실행한다.
여기서 중요한 것은 “AI가 근거 없이 최종 점수만 쓰는 구조”를 피하는 것이다. 교사가 나중에 확인할 수 있도록 판단 근거와 피드백 초안을 함께 남긴다.
DB 저장과 교사용 리뷰 웹
AirClassGrading은 결과를 파일 하나로 끝내지 않는다. 운영에서는 리뷰, 기준, 알림 상태가 분리되어 저장된다.
| 데이터 | 역할 |
|---|---|
reviews |
학생별 AI 리뷰 결과, 교사 확정값, 상태 |
assignment_context_snapshots |
과제 지시문과 채점 기준 snapshot |
review_notifications |
피드백 발송 큐와 전송 상태 |
out/ 산출물 |
PDF 렌더링 이미지, manifest, debug JSON |
교사용 리뷰 웹에서는 다음 화면을 제공한다.
| 화면 | 역할 |
|---|---|
/ |
최근 리뷰 목록, AI 예측, 교사 확정 여부, 피드백 상태 확인 |
/assignments |
Teams 과제 목록 조회 |
/assignments/{assignment_id} |
과제 상세, 제출 상태, snapshot, prompt 후보 확인 |
/reviews/{review_id} |
제출물 미리보기, AI 근거, 교사 최종 점수·코멘트 입력 |
교사는 리뷰 상세 화면에서 제출물 렌더링 이미지, AI 판단 근거, 학생용 피드백 초안을 확인한다. 필요하면 최종 점수와 코멘트를 수정하고 저장한다.
피드백 발송은 큐와 worker로 분리
학생에게 피드백을 보내는 일은 채점과 분리되어 있다. 리뷰가 만들어졌다고 바로 Teams로 보내지 않고, notification queue에 넣은 뒤 worker가 처리한다.
지원할 수 있는 방식은 운영 설정에 따라 다르다.
- Teams assignment feedback
- Teams bot DM
- delegated Teams account DM
- DB placeholder
- dry-run / none
이 분리가 중요한 이유는 실패 지점이 다르기 때문이다. AI 리뷰는 성공했지만 Teams API 전송만 실패할 수 있다. 이런 경우 리뷰를 다시 돌릴 필요 없이 피드백 발송만 재시도하면 된다.
worker와 배포 구조
루트에는 k8s, Argo CD, Jenkins 관련 파일이 있다.
k8s/
deployment.yaml
grading-worker-deployment.yaml
feedback-worker-deployment.yaml
retry-worker-deployment.yaml
regrade-job.yaml
retry-late-job.yaml
retry-pending-job.yaml
service.yaml
pvc.yaml
argocd/
airclass-grading-application.yaml
jenkins/
job-config.xml
Jenkinsfile
운영 구조를 역할별로 보면 다음과 같다.
| 구성 | 역할 |
|---|---|
| API 서버 | prepare 요청, 리뷰 웹, artifact 제공 |
| grading worker | 제출물 AI 리뷰 실행 |
| feedback worker | 학생 피드백 발송 |
| retry worker/job | 실패·지연 작업 재시도 |
| PVC/volume | out/ 산출물 보존 |
이 구조는 AirClassGrading이 단순한 로컬 스크립트가 아니라, API 서버와 worker를 나누어 운영할 수 있는 자동 채점 시스템이라는 점을 보여 준다.
실제 운영 산출물로 본 규모
학생 이름, 계정, 원본 파일명, 제출 이미지는 글에 싣지 않았다. 대신 로컬 산출물에 남아 있는 비식별 집계만 보면, 이 흐름이 단순 구상에 머문 것은 아니었다.
out/ 아래의 포트폴리오 수행평가 산출물을 기준으로 확인한 값은 다음과 같았다.
| 항목 | 확인된 수치 | 의미 |
|---|---|---|
| 포트폴리오 주차 산출 폴더 | 4개 | 1~4주차 수행 포트폴리오 흐름이 생성됨 |
review_debug.json |
221건 | AI 리뷰가 실제 제출물 단위로 실행되고 근거가 저장됨 |
prepare manifest.json |
16개 | 제출물 수집·전처리 실행 기록이 반복적으로 남음 |
top-level manifest의 analysisTargets |
42개 | PDF 페이지·사진 등 AI가 읽을 분석 단위로 분해된 자료 수 |
주차별 review_debug.json 분포는 다음과 같았다.
| 구분 | 리뷰 디버그 기록 |
|---|---|
| 포트폴리오 1주차 | 47건 |
| 포트폴리오 2주차 | 76건 |
| 포트폴리오 3주차 | 95건 |
| 포트폴리오 4주차 | 3건 |
| 합계 | 221건 |
여기서 중요한 것은 “AI가 몇 점을 줬다”가 아니다. 실제 효과는 다음에 가깝다.
- 제출물을 열어 보고 정리하는 작업이
manifest와analysisTargets로 남는다. - AI가 어떤 근거로 리뷰했는지
review_debug.json으로 추적할 수 있다. - 주차별로 리뷰 기록이 쌓이기 때문에, 특정 과제에서만 실패했는지 전체 흐름의 문제인지 구분할 수 있다.
- 피드백 발송이나 재시도 worker를 붙일 때, 이미 만들어진 리뷰 산출물을 다시 활용할 수 있다.
즉 AirClassGrading 1의 효과는 “채점 시간이 몇 분 줄었다” 같은 단일 숫자보다, 제출물 수집 → 전처리 → AI 리뷰 → 교사 확인이 재현 가능한 데이터 흐름으로 남았다는 데 있다.
이 흐름에서 얻은 것
Teams 과제 자동 채점에서 가장 큰 수확은 “AI 채점” 자체보다 그 앞뒤 구조였다.
- 제출물을 안정적으로 모으는 구조
- 파일을 AI가 읽을 수 있게 정리하는 prepare 단계
- 과제 기준을 snapshot으로 남기는 방식
- AI 리뷰와 교사 최종 판단을 분리하는 DB 구조
- 피드백 발송을 큐와 worker로 분리하는 운영 방식
AI 모델을 바꾸는 것은 비교적 쉽다. 하지만 제출물, 기준, 결과, 피드백의 흐름이 정리되어 있지 않으면 실제 수업에서는 쓸 수 없다. AirClassGrading 1은 이 흐름을 Teams 과제 중심으로 정리한 시도였다.
다음 편으로 이어지는 문제
Teams 과제 자동 채점은 파일 제출형 과제에는 잘 맞는다. 하지만 학교 평가에는 또 다른 유형이 있다. 바로 서술형 답안지다.
서술형 답안지는 Teams 제출물과 다르다.
- 한 PDF 안에 여러 학생 답안이 들어 있다.
- 한 학생 답안 안에 여러 문항이 들어 있다.
- 문항마다 부분점수 기준이 다르다.
- AI 초검이 흔들리는지 반복 확인해야 한다.
- 최종 점수는 문항별로 교사가 확정해야 한다.
그래서 다음 편에서는 AirClassGrading의 두 번째 흐름인 서술형 채점 AI 적용을 다룬다. written_response_grader에서 PDF 전처리, 문항별 이미지 crop, 부분점수 기준 JSON, AI 초검 반복, 교사 최종 점수 확정까지 어떻게 구성했는지 정리한다.
💬 댓글
이 글에 대한 의견을 남겨주세요