AI 에이전트 팀과 함께 나이지리아 대학을 위한 학습 플랫폼을 구축하며 배운 것
요약
나이지리아 대학생을 위한 학습 플랫폼 UniUI 구축 경험을 공유하며, AI 에이전트와 복잡한 기술 스택 관리의 어려움을 다룹니다. 핵심은 AI 자체보다 데이터 수집 및 구조화 파이프라인 구축에 가장 많은 시간을 할애했다는 점입니다.
핵심 포인트
- AI 에이전트를 활용하여 엔지니어링 작업을 병렬 처리함.
- 가장 큰 난관은 AI 기술 자체가 아닌, 비정형 데이터 관리와 시스템 운영임.
- Next.js 15, FastAPI, PostgreSQL 등 광범위한 스택을 사용했음.
- 데이터 수집 및 구조화 파이프라인 구축에 가장 많은 노력을 기울였음.
저는 Federal University of Technology, Owerri (FUTO)의 학생이며, 지난 5개월 동안 UniUI라는 플랫폼을 구축해 왔습니다. 이 플랫폼은 나이지리아 대학생이 자신의 학기 전체 자료(강좌, 노트, 기출문제, 마감일, 그룹 등)를 업로드하면 이를 운영하는 데 도움을 받는 곳입니다.
현재 FUTO에서 152명의 학생들과 함께 서비스가 운영 중이며, 대중 공개는 50일 후에 예정되어 있습니다. 저는 대부분의 엔지니어링 작업을 여러 AI 에이전트들이 병렬로 작동하게 하면서 진행하고 있으며, 그 과정에서 배운 것들을 기록하고자 합니다. 왜냐하면 실제로 경험한 내용들은 제가 예상했던 것들과 많이 달랐기 때문입니다.
간단히 요약하자면, 가장 어려웠던 부분은 AI 자체가 아니었습니다. 데이터, 자금(돈), 그리고 빠르게 성장하는 코드베이스가 스스로를 집어삼키지 않도록 관리하는 것이었습니다.
기술 스택 (The stack)
다음은 이 플랫폼을 구성하는 각 계층별 기술 목록입니다.
| Layer | What I use |
|---|---|
| Web | Next.js 15 (App Router), TypeScript everywhere, Tailwind, shadcn/ui, TanStack Query, Zustand, react-hook-form with zod |
| Mobile | React Native with Expo and Expo Router, SQLCipher for encrypted on-device storage |
| API | Python 3.12, FastAPI (2,000+ routes), Pydantic v2, asyncpg, Alembic, Celery |
| Data | PostgreSQL 18 with pgvector, pg_cron, pg_trgm and pgcrypto; Supabase for identity; Redis; MinIO; MeiliSearch |
| AI | Voyage AI embeddings, OpenAI models in three tiers, Whisper, Tesseract, FFmpeg |
| Payments | A primary provider with Paystack and Monnify as fallbacks, NIBSS for bank transfers |
| Ops | Docker Compose, Caddy, Cloudflare, Sentry, Prometheus, Grafana, Loki, UptimeRobot |
이 모든 것들, 즉 20개 이상의 컨테이너가 단일 Oracle Cloud VPS의 RAM 7.8GB에서 구동됩니다. 데이터베이스는 다섯 개의 스키마에 걸쳐 237개의 테이블을 가지고 있습니다. 대중 마케팅 사이트는 별도로 Vercel에 위치합니다.
진짜 문제는 데이터 문제다 (The real problem is a data problem)
제품 아이디어 자체는 말하기 쉽습니다. 학생이 질문을 하면, 인터넷의 일반적인 답변이 아니라 자신의 강좌 자료를 기반으로 출처와 함께 답변을 얻는 것입니다. 하지만 이 모든 것이 작동하기 전에, 기계가 사용할 수 있는 형태의 강좌 자료가 필요합니다. 그리고 나이지리아 대학의 자료는 그 정반대입니다.
노트들은 WhatsApp PDF, 휴대폰 사진, 손글씨 스캔본에 흩어져 있습니다. 과거 기출문제는 종종 과정 코드, 연도, 또는 강사 이름 없이 선배를 통해 전수됩니다. 어떤 버전이 최신인지 아무도 모릅니다.
그래서 제 시간 대부분은 콘텐츠 파이프라인 구축에 할애되었습니다. 현재 시스템에는 다음과 같은 것들이 있습니다:
• 4,936개 과정 카탈로그화
• 11,071개의 구조화된 학습 노트 (11,098개의 풍부한 섹션에서 생성)
• 질문 은행에 있는 21,607개의 기출문제
• 구조화되고 인덱싱된 3,708개의 공식
• 64,747개의 콘텐츠 청크
이 두 개의 파이프라인이 이 코퍼스를 채웁니다.
업로드 파이프라인은 학생이 제출하는 모든 것을 열 단계로 처리합니다: 클라이언트 측 유효성 검사(client-side validation), 청크 업로드(chunked upload), 바이러스 스캔, 텍스트 추출 (이미지는 Tesseract, 오디오는 Whisper, 미디어는 FFmpeg, pdftotext 사용), 분류(classification), 중복 제거(deduplication) (SHA256, MinHash 및 TF-IDF), 6가지 요소로 구성된 0점에서 100점의 루브릭을 통한 품질 점수 측정, 필요 시 과정 대표에 의한 수동 검토(human review), 풍부화(enrichment), 그리고 업로더에게 크레딧을 제공하기 위한 지갑 원장(wallet ledger)을 통한 출처 표기(attribution).
콘텐츠 파이프라인은 원자재를 학습 노트로 6단계에 걸쳐 변환합니다: 청킹(chunking), 섹션으로 처리(processing into sections), 풍부화(enrichment), 풍부한 노트(rich notes), 파생 콘텐츠 (연습 문제, 공식, 정의), 그리고 인덱싱(indexing).
두 가지가 저를 놀라게 했습니다. 처리량보다 품질 게이트가 더 중요하다는 점입니다: 1,951개의 청크가 낮은 품질로 격리되었고, 저는 그것들을 제공하기보다는 잃어버리는 편이 낫다고 생각합니다. 그리고 모든 것을 임베딩하는 것이 올바른 목표가 아니었습니다. 64,747개 원본 청크 중 약 15%만 임베딩되었지만, 11,128개의 검색 섹션은 모두 임베딩되어 있으며, AI가 실제로 검색하는 것은 이들입니다.
다시 할 수 있는 것이 있다면, 쓰기(write) 측면에서 정규화하는 것입니다. 섹션 풍부화와 풍부한 노트는 여전히 JSONB로 저장되는데, 이는 생성하기는 빠르지만 쿼리하기에는 고통스럽습니다. 그래서 저는 이 데이터를 추출하여 정규화된 테이블로 가져오는 ETL 스크립트를 작성하고 있습니다.
이 부분에서 조언을 하나만 하자면, 처음부터 모든 것에 출처(provenance)를 첨부하는 것입니다. 모든 청크는 과정 코드, 레벨 및 출처를 지니고 있어야 합니다.
그것은 회계 장부 정리처럼 들리지만, 모든 다운스트림(downstream) 작업이 가능하게 만드는 핵심입니다. 왜냐하면 제품의 전체 약속이 '이 답변은 당신의 강좌에서 나온다'이기 때문입니다. 만약 어떤 조각(chunk)이 어디서 왔는지 증명할 수 없다면, 그 약속을 할 수 없습니다.
학생 자신의 자료를 기반으로 답변하기
일반적인 답변은 열역학에 대해서는 정확할 수 있지만, 특정 강사가 가르치고 시험 볼 내용은 틀릴 수 있습니다. 따라서 검색(retrieval)은 먼저 강좌별로 범위가 지정된 다음 순위가 매겨지며, 모든 답변에는 출처가 표시됩니다.
작동 방식은 다음과 같습니다. 섹션과 조각들은 Voyage AI를 사용하여 1,024 차원으로 임베딩되어 pgvector에 HNSW 인덱스와 함께 저장됩니다. 학생이 질문을 하면, 오케스트레이터(orchestrator)는 먼저 학생 그래프(Student Graph, 25개의 노드 유형과 30개의 엣지 유형으로 구성되며, enrolled_in, mastered, struggles_with와 같은 엣지를 가짐)에서 컨텍스트를 불러옵니다. 그런 다음 해당 강좌 내에서 검색하여 상위 다섯 개의 섹션을 가져오고, 모델이 이 자료들을 기반으로 답변을 작성하게 합니다. 신뢰도 엔진(confidence engine)이 모든 답변에 점수를 매기고, 출처가 함께 반환됩니다. [신뢰도가 임계값보다 낮을 때 발생하는 상황 추가].
그래프 덕분에 '이것을 공부하세요, 이유가 여기 있습니다'라는 것이 가능해집니다. 단순 검색은 관련성 있는 단락을 찾을 수 있지만, 이 특정 학생이 한 주제에 대해 계속해서 질문을 놓치고 있다는 것을 아는 것이 시스템이 정확한 곳을 가리킬 수 있게 합니다.
비용 측면에서는 모델을 세 가지 계층으로 나누었습니다. 저렴한 분류 및 노이즈 필터링을 위한 나노(nano) 모델, 일상 업무를 위한 미니(mini) 모델, 그리고 에스컬레이션(escalation)에 할당된 더 큰 모델입니다. 반복적인 쿼리는 LLM 캐시를 거칩니다. 제가 전하고 싶은 교훈은 벤치마크가 아닌 자체 데이터로 모델을 테스트하는 것입니다. 제 작업량에서는 가장 저렴한 모델이 비싼 모델보다 성능이 좋았고, 이는 두 모델 모두에 실제 질문을 실행해 봐야 알 수 있었습니다.
제가 만족하는 결정 중 하나는 월별 청구 방식 대신 할당량(quota)으로 사용량을 판매하는 것입니다.
플랜은 다음과 같습니다:
• Quick Ask: 24시간 동안 ₦500, 질문 10개
• Sprint: 14일 동안 ₦2,500, 질문 100개
• Scholar: 30일 동안 ₦2,500, 질문 120개
• Scholar Plus: 30일 동안 ₦3,500, 질문 250개
• Deep Study: 30일 동안 ₦10,000, 무제한, 오프라인 접속 가능
쿼터(Quotas)는 학생 한 명을 서비스하는 비용을 예측 가능하게 만듭니다. 제 모델은 AI 추론(AI inference) 비용이 수익의 약 3%가 될 것으로 예상합니다. 이는 제가 자체적으로 가정한 것이므로 측정된 수치가 아니니 참고만 해주세요.
학생 간 돈 이동하기
저를 가장 긴장하게 만든 부분은 지갑 기능이었습니다. UniUI는 마켓플레이스, 노트 스토어, 과제 게시판, 튜터링 등 다양한 기능을 갖추고 있어, 서로 모르는 학생들 사이에서 실제 나이라(naira)가 오갑니다. 이는 에스크로(escrow), 수수료(fees), 분쟁(disputes)을 의미합니다.
과제 게시판을 예로 들어보겠습니다. 한 학생이 과제와 보상을 올립니다. 게시자는 보상에 5%를 더한 금액을 지불하고, 작업자는 보상에서 5%를 뺀 금액을 받으며, 플랫폼은 그 차액인 보상의 10%를 가져갑니다. 돈은 작업이 완료될 때까지 에스크로에 머무릅니다.
// 간소화 및 예시용: 실제 프로덕션 코드가 아님
function settleTask(reward: number) {
const posterPays = reward * 1.05; // 추가 5% 수수료
const workerGets = reward * 0.95; // 추가 5% 공제
const platformKeeps = posterPays - workerGets; // 보상의 10%
return { posterPays, workerGets, platformKeeps };
}
// settleTask(2000) -> { posterPays: 2100, workerGets: 1900, platformKeeps: 200 }
튜터링은 이해관계가 더 높고 작업 검증이 더 어렵기 때문에 흐름이 더 복잡합니다:
PAID (에스크로에 보관된 자금)
-> 세션 진행
-> 튜터가 세션 노트 제출
-> 양측이 출석 확인
-> 7일 분쟁 기간
-> 지급 완료 (튜터가 80-90% 보유)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기