교회에서 중고등부 교사를 하면서 비효율적으로 출석 체크를 하는 현상을 확인했다...
매주 학생 리스트에 엑셀로 1,0 을 입력하며 출석을 하고 있던 것이였다..
그래서 간단하게 AI로 프론트 개발하고
백엔드는 직접 개발하며 출석관리체계를 만들어 1년간 사용했다.

부끄러운 프론트
그냥 간단하게 출석, 지각, 결석 여부를 체크하는 방식이다.
뭐 숨겨진 여러 기능이 있긴하다만..
2026년이 되면서 2.0으로 업그레이드 할 겸
지금까지 쌓은 출석 데이터를 바탕으로 AI 에이전트를 만들어보고자 한다.
"이번 달 출석 위험 학생 알려줘”
“지각이 잦은 순서로 10명 보여줘”
“지난 학기 대비 출석 개선된 학생은?”
“이번 달 한 번도 안 나온 아이들 알려줘”
“3주 연속 결석한 학생 있어?”
이런식의 자연어 질문을 AI가 해석하고 응답하게끔 하는 것이 목표다.
먼저 우리 시스템이 어떻게 사용자의 질문을 이해하고 답변하는지 큰 그림은 다음과 같다.
1. 사용자: "김민준 학생 결석 몇 번 했어?" 라고 질문을 던지면
2. 백엔드 서버 (우리의 Spring Boot 앱): 이 질문을 받아서 처리한다
3. LLM (AI 언어 모델): 백엔드 서버가 자연어 질문을 이해하고 답변을 생성할 수 있도록 도와주는 두뇌 역할을 한다. (예: OpenAI의 GPT, Google의 Gemini)
4. 데이터베이스 (우리의 MySQL DB): 실제 출석 데이터가 저장된 곳
핵심은 "LLM이 우리 데이터베이스 내용을 어떻게 알까?" 이다.
LLM은 세상의 일반적인 지식은 알아도, 우리가 쌓은 '출석 데이터'는 전혀 모르기 때문이다.
이 문제를 해결하는 기술이 바로 RAG (Retrieval-Augmented Generation, 검색 증강 생성) 이다.
핵심 기술: RAG 쉽게 이해하기
RAG는 3단계로 이루어진다. "민우 학생 결석 몇 번 했어?" 라는 질문을 예시로 설명하자면
1. Retrieval (검색):
* AI가 먼저 "아, 이건 '특정 학생의 결석 횟수'를 묻는 거구나!" 라고 질문의 의도를 파악하고
* 그 다음, 이 의도에 맞춰 우리 데이터베이스에서 실제 정보를 검색한다.
* SELECT COUNT(*) FROM attendance WHERE student_name = '민우' AND status = 'ABSENT'; 와 같은 쿼리를 실행해서 "민우, 결석, 5회" 라는 데이터를 찾아낸다.
2. Augmentation (정보 보강):
* 이제 LLM에게 보낼 질문지(프롬프트)에, 우리가 데이터베이스에서 찾아낸 정보를 추가(보강)한다.
> [LLM에게 보낼 프롬프트 예시]
> 너는 친절한 출석 관리 조교야. 아래 "정보"를 바탕으로 "질문"에 대답해줘
> # 정보:
> * 학생 이름: 민우
> * 결석 횟수: 5회
> # 질문:
> 민우 학생 결석 몇 번 했어?
3. Generation (답변 생성):
* 이렇게 정보가 보강된 프롬프트를 LLM API에 전달한다.
* LLM은 주어진 정보를 보고 아주 자연스러운 문장으로 답변을 생성하고
* "네, 민우 학생은 총 5번 결석했습니다." 와 같은 답변이 만들어지고, 우리는 이걸 사용자에게 보여주면 된다.
AI 에이전트 구축 과정 (총 4단계)
1단계: 기능 정의 및 DB 쿼리 메서드 구현
AI가 답변할 수 있는 질문의 종류를 명확하게 정의하는 것이 가장 중요하다. 이 단계가 잘 되어야 뒤따르는 개발이 수월해진다.
// 예시: AttendanceService.java 에 추가될 메서드들
public int getAbsenceCountForStudent(String studentName) {
// ... JPA Repository를 호출하여 결석 횟수 조회 로직
}
public List<Student> findStudentsWithExcessiveLateness(int count, LocalDate startDate, LocalDate endDate) {
// ... 지각 3회 이상 학생 목록 조회 로직
}
AI의 도움을 받아 답변을 구할 질문 5가지를 선정했다.
AI 에이전트가 답변할 핵심 질문 5가지
---
1. 장기 결석자 찾기
* 사용자 질문: "이번 달 한 번도 안 나온 아이들 알려줘"
* AI의 해석 (의도): LIST_FULL_ABSENCE_STUDENTS
* 필요한 정보:
* 기간: "이번 달" (예: 2025-12-01 ~ 2025-12-31)
* 구현 방향:
1. 해당 기간의 모든 출석/지각 기록을 조회합니다.
2. 전체 학생 명단에서, 위 출석 기록에 한 번도 등장하지 않은 학생을 찾아냅니다.
3. 찾아낸 학생들의 명단을 반환합니다.
* 선정 이유: 가장 기본적이고 필수적인 기능입니다. '관리의 시작'이라고 할 수 있는 질문이라 활용도가 높습니다.
---
2. 연속 결석 패턴 감지
* 사용자 질문: "3주 연속 결석한 학생 있어?"
* AI의 해석 (의도): FIND_CONSECUTIVE_ABSENCE_STUDENTS
* 필요한 정보:
* 주(Week) 수: 3
* 구현 방향:
1. 최근 3주간의 날짜 범위를 계산합니다. (예: 3주 전 일요일 ~ 지난주 토요일)
2. 각 주(Week)마다 결석한 학생들을 각각 조회합니다.
3. 3주 모두 결석 명단에 포함된 학생을 찾아냅니다.
* 선정 이유: 단순 조회가 아닌 '패턴'을 분석하는 질문입니다. 기술적으로 한 단계 더 깊이가 있어, 개발자의 문제 해결 능력을 보여주기 좋은 예시입니다.
---
3. 신입생 정착 현황 파악
* 사용자 질문: "신입인데 2주 연속 나온 학생"
* AI의 해석 (의도): FIND_NEW_CONSECUTIVE_ATTENDEES
* 필요한 정보:
* 신입생 기준: "최근 3개월 내 등록"
* 연속 출석 주(Week) 수: 2
* 구현 방향:
1. 먼저 '신입생' 그룹을 정의하고, 최근 3개월 내 등록한 학생 목록을 조회합니다.
2. 이 학생들을 대상으로, '2번 질문'과 유사하게 최근 2주간 연속으로 출석했는지 패턴을 분석합니다.
* 선정 이유: 학생 정보(신입생 여부)와 출석 정보를 결합해야 하는 질문입니다. 여러 도메인의 데이터를 조합하는 능력을 보여줄 수 있습니다.
---
4. 학년별 데이터 요약 및 비교
* 사용자 질문: "학년별 평균 출석률 보여줘"
* AI의 해석 (의도): GET_AVERAGE_ATTENDANCE_RATE_BY_GRADE
* 필요한 정보:
* 기간: "이번 학기" 또는 "이번 달"
* 구현 방향:
1. 해당 기간 동안 각 학년의 전체 학생 수와 실제 출석 수를 각각 계산합니다. (데이터베이스의 GROUP BY 기능 활용)
2. (실제 출석 수 / 전체 학생 수) * 100 공식을 사용하여 학년별 평균 출석률을 계산합니다.
* 선정 이유: 개별 학생 조회가 아닌, 데이터를 집계하고 통계를 내는 능력을 보여줍니다. 백엔드 개발자의 중요한 역량 중 하나입니다.
---
5. 복합 조건 분석 및 요약 (AI의 강점)
* 사용자 질문: "이번 분기 관리가 필요한 학생 요약해줘"
* AI의 해석 (의도): SUMMARIZE_STUDENTS_NEEDING_CARE
* 필요한 정보:
* '관리가 필요한'의 정의: (예시) "결석 2회 이상" 또는 "지각 3회 이상" (이 기준은 우리가 직접 정해야 합니다)
* 기간: "이번 분기"
* 구현 방향:
1. 해당 분기 동안, 결석 횟수가 2회 이상인 학생 목록을 조회합니다.
2. 지각 횟수가 3회 이상인 학생 목록도 조회합니다.
3. 두 목록을 합친 후 중복을 제거하여 최종 "관리가 필요한 학생" 목록을 만듭니다.
* 선정 이유: "관리가 필요하다"는 다소 추상적인 질문을 명확한 데이터 기준으로 정의하고, 여러 조건을 조합하여 해결하는 과정을 보여줍니다. AI 에이전트의 가장 큰 가치를 보여줄 수 있는 질문입니다.
---
* DB 쿼리 메서드 구현:
* 위에서 정의한 각 질문 유형에 맞춰, AttendanceService 또는 새로 만들 AIService에 데이터를 조회하는 메서드를 미리 만들어둔다. JPA Repository에 필요한 쿼리 메서드를 추가해야 한다.
2단계: LLM 연동 및 프롬프트 엔지니어링
Java에서 LLM을 쉽게 사용하도록 도와주는 Spring AI 프로젝트를 활용하는 것을 추천한다.
* 의존성 추가 (`build.gradle`):
// Spring AI는 아직 정식 버전이 아닐 수 있어, repository 추가가 필요할 수 있습니다.
repositories {
maven { url 'https://repo.spring.io/milestone' }
}
dependencies {
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter:1.0.0-M1' // OpenAI 연동용
}
* API 키 설정 (`application.yml`):
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY} // GitHub Secret 또는 환경 변수로 관리
* 프롬프트 작성 및 API 호출:
* 1단계에서 만든 서비스 메서드를 호출하여 얻은 데이터와 사용자의 질문을 조합하여 LLM에게 보낼 프롬프트를 만든다.
// 예시: AIService.java
@Service
@RequiredArgsConstructor
public class AiChatService {
private final OpenAiChatClient chatClient; // Spring AI가 제공
private final AttendanceService attendanceService;
public String getAttendanceAnswer(String userQuestion) {
// TODO: 사용자의 질문 의도 파악 (초기에는 '학생 결석 횟수' 질문만 처리한다고 가정)
// 1. 데이터베이스에서 정보 검색 (Retrieval)
int absenceCount = attendanceService.getAbsenceCountForStudent("민우"); // 이름은 질문에서 파싱해야 함
// 2. 프롬프트 생성 (Augmentation)
String promptTemplate = """
너는 친절한 출석 관리 조교야.
주어진 정보를 바탕으로 사용자의 질문에 한국어로 대답해줘.
# 정보:
- 학생 이름: 민우
- 결석 횟수: {count}회
# 질문:
{question}
""";
Prompt prompt = new PromptTemplate(promptTemplate)
.create(Map.of("count", absenceCount, "question", userQuestion));
// 3. LLM API 호출 및 답변 생성 (Generation)
ChatResponse response = chatClient.call(prompt);
return response.getResult().getOutput().getContent();
}
}
3단계: API 엔드포인트 생성
사용자의 자연어 질문을 받고 답변을 반환하는 API를 만든다
* 컨트롤러 생성 (`AiController.java`):
@RestController
@RequestMapping("/api/ai")
@RequiredArgsConstructor
public class AiController {
private final AiChatService aiChatService;
@PostMapping("/chat")
public ResponseEntity<String> chatWithAgent(@RequestBody String question) {
String answer = aiChatService.getAttendanceAnswer(question);
return ResponseEntity.ok(answer);
}
}
4단계: 간단한 UI 구현
'Spring' 카테고리의 다른 글
| Audit Log 조회 API 성능 개선기 - 3 (1) | 2025.08.08 |
|---|---|
| Audit Log 조회 API 성능 개선기 - 2 (8) | 2025.08.08 |
| Audit Log 조회 API 성능 개선기 - 1 (4) | 2025.07.19 |
| Github Action 환경에서 모든 테스트코드가 실패한다. (0) | 2025.07.19 |
| 테스트 코드는 왜 짜야할까.. (0) | 2025.07.17 |