NCERT/CBSE를 위한 무료 AI 레이어 구축하기: Ollama, LangChain, React 프론트엔드를 활용하여 인도 교실을 위한
요약
인도 공립학교의 NCERT PDF 교과서를 활용하여 인터넷 연결 없이 로컬에서 작동하는 무료 AI 교육 레이어를 구축하는 방법을 다룹니다. Ollama, LangChain, ChromaDB를 사용하여 RAG 파이프라인을 구현함으로써 비용 문제와 속도 제한을 해결합니다.
핵심 포인트
- Ollama와 LangChain을 활용한 로컬 RAG 파이프라인 구축
- ChromaDB를 이용한 NCERT PDF 데이터 임베딩 및 저장
- 인터넷 연결과 유료 API 비용 없이 오프라인 환경 구현
- 섹션 단위 청킹을 통한 검색 정확도 향상 및 환각 방지
요약 (TL;DR): Rajasthan이나 Bihar의 공립학교 교사는 펜드라이브에 40개의 NCERT PDF 파일을 가지고 있습니다. 그녀는 파일을 열고, ctrl+F로 검색하고, 몇 페이지를 인쇄할 수는 있습니다.
📖 읽기 시간: 약 36분
이 글의 내용
- 실제 문제: NCERT PDF는 검색 불가능한 지식의 무덤이다
- 내가 선택한 기술 스택 (세 가지 다른 시도 끝에)
- 1단계 — Ollama 설치 및 RAM에 실제로 들어가는 모델 가져오기 (Pull)
- 2단계 — ChromaDB에 NCERT PDF 주입하기 (Ingesting)
- 3단계 — LangChain에서 RAG 체인 연결하기
- 4단계 — 프론트엔드: Open WebUI vs 직접 구축하기
- 디버깅에 몇 시간이 걸렸던 주의 사항 (Gotchas)
- 실제 운영 비용
실제 문제: NCERT PDF는 검색 불가능한 지식의 무덤이다
Rajasthan이나 Bihar의 공립학교 교사는 펜드라이브에 40개의 NCERT PDF 파일을 가지고 있습니다. 그녀는 파일을 열고, ctrl+F로 검색하고, 몇 페이지를 인쇄할 수는 있습니다. 하지만 그녀가 할 수 없는 것은 그 파일들에게 무엇인가를 물어보는 것입니다. 학생이 다가와서 "광합성을 12살 아이 수준으로 설명해 주세요"라고 말하면, PDF는 그저 쳐다만 볼 뿐입니다. 맥락(Context)도 없고, 적응(Adaptation)도 없으며, 인내심도 없습니다. 콘텐츠는 존재하지만, 지능 레이어(Intelligence layer)가 없습니다.
이 격차를 메울 수 있는 유료 도구들은 사실상 사용이 불가능합니다. OpenAI의 API는 토큰당 비용이 발생합니다. ChatGPT Plus는 월 20달러인데, 이는 학교 IT 예산이 존재하지 않는 상황에서는 실제 큰 돈입니다. Google Gemini의 무료 티어는 40명의 학생이 동시에 질문을 던지는 교실 환경에서는 금방 사라져 버리는 속도 제한(Rate limits)이 있습니다. 우리에게 실제로 필요한 것은 이미 있는 하드웨어에서 작동하는 것입니다: 중간 사양의 노트북, GPU 불필요, 인터넷 의존성 없음, 반복적인 비용 제로.
이 격차는 당혹스러울 정도로 구체적입니다. NCERT는 지구상에서 가장 철저하게 구조화된 무료 커리큘럼 중 하나입니다. 장(chapter) 번호가 매겨져 있고, 개념들이 서로를 바탕으로 쌓아 올려지며, 도표에는 라벨이 붙어 있고, 연습 문제는 난이도별로 분류되어 있습니다. 인도는 이 책들에 수십 년간의 교육학적 노력을 쏟아부었으며, 이를 ncert.nic.in에서 무료로 다운로드할 수 있게 만들었습니다. 하지만 이 말뭉치(corpus)를 지식 베이스(knowledge base)로 취급하도록 특별히 설계된 오픈 소스 AI 도구는 단 하나도 없습니다. 에듀테크(edtech) 스타트업에서 나오는 교육용 AI는 모두 영어권 사립학교에 집중되어 있거나, 유료 API 위에 얇은 래퍼(wrapper)를 씌운 형태뿐입니다. 아무도 실제로 NCERT 말뭉치를 로컬(local)에 수집하여 그 주변에 검색 레이어(retrieval layer)를 구축하지 않았습니다.
우리가 구축하고 있는 것은 바로 이 작업을 수행하는 로컬 RAG 파이프라인 — 검색 증강 생성 (Retrieval-Augmented Generation) — 입니다. 아키텍처는 간단합니다. PDF를 수집하고, 텍스트를 지능적으로 청킹(chunking, 임의의 토큰 창이 아닌 섹션 단위로)하며, CPU에서 실행되는 모델을 사용하여 해당 청크를 임베딩(embedding)하고, 임베딩을 로컬에 저장한 다음, 관련 NCERT 컨텍스트를 먼저 가져와 질문에 답하는 작은 로컬 LLM을 연결하는 방식입니다. 모델은 답변이 실제로 검색된 구절에 근거하기 때문에 "NCERT가 무엇이라고 말하는지"에 대해 환각(hallucination)을 일으키지 않습니다. 이 모든 과정은 오프라인으로 작동합니다. 교사는 한 번만 설정해 두면 WiFi가 없는 학교에서도 사용할 수 있습니다.
이 프로토타입을 처음 시도했을 때 저를 당혹스럽게 했던 점은 NCERT PDF가 깨끗하지 않다는 것이었습니다. 어떤 판본은 스캔된 형태이고, 다른 판본은 비표준 글꼴을 사용하며, 특히 힌디어 매체 PDF는 단순한 pdftotext를 실행하면 거의 쓰레기 수준의 결과물이 나옵니다. 따라서 "PDF 수집" 단계가 실제로 가장 어려운 부분입니다. 모델도, 검색도, UI도 아닙니다. 흥미로운 AI 작업이 시작되기도 전에, 여러분은 OCR 분류(triage) 작업을 수행해야 합니다. 이 가이드에서 우리가 실제로 많은 시간을 할애할 부분이 바로 여기입니다.
내가 선택한 스택 (세 가지 다른 대안을 시도한 끝에)
저를 당황하게 만든 것은 LLM(Large Language Models) 사이에서의 선택이 아니었습니다. 바로 2019년 이후로 업그레이드되지 않은 학교 하드웨어에서 실행할 때 _서빙 레이어 (serving layer)_가 얼마나 중요한가였습니다. 저는 Ollama로 전환하기 전 llama.cpp를 직접 사용하는 데 2주를 허비했는데, 사용성 측면에서의 격차는 결코 미미하지 않았습니다.
llama.cpp 직접 사용 및 GPT4All보다 Ollama를 선택한 이유
lama.cpp는 원시적인 제어권을 제공하지만, 프로세스 생명 주기(process lifecycle), 모델 로딩 플래그(model loading flags), 그리고 별도로 부착해야 하는 불안정한 HTTP 서버를 직접 관리해야 합니다. GPT4All은 괜찮은 GUI를 갖추고 있지만, API 인터페이스가 계속 변경되었고 이중 언어(힌디어/영어) 콘텐츠를 위해 조정할 수 있는 생성 파라미터(generation parameters)를 충분히 노출하지 않았습니다. Ollama는 이 모든 것을 깔끔한 REST 인터페이스로 감싸주며, 이를 기반으로 구축하기에 충분할 만큼 안정적이었습니다. 모델을 가져오는(pull) 명령어 하나, 서빙하는 명령어 하나면 충분합니다:
# 16GB RAM에 실제로 들어가는 양자화된 모델 가져오기
ollama pull mistral:7b-instruct-q4_K_M
...
q4_K_M 양자화(quantization)는 제가 찾아낸 최적의 지점입니다. 가장 작은 크기는 아니지만, 혼합 스크립트(mixed-script) 콘텐츠에 대해 q4_0보다 유의미하게 더 나은 출력 품질을 보여주며, 여전히 10GB VRAM(또는 CPU 전용인 경우 RAM) 이내에 여유롭게 들어옵니다. 이에 대해서는 잠시 후에 더 자세히 다루겠습니다.
Weaviate가 아닌 ChromaDB
생태계 관련 기사들이 모두 추천하기 때문에 처음에는 Weaviate를 살펴보았습니다. 그러고 나서 학교의 리퍼비시(refurb) 컴퓨터에서 실행해 보았는데, 유휴 상태(idling)임에도 즉시 4GB를 할당하는 것을 목격했습니다. Weaviate는 단일 노드(single-node)의 탈을 쓴 분산 시스템(distributed system)입니다. 즉, 클러스터(cluster)를 위해 설계되었습니다. 반면 ChromaDB는 모든 것을 SQLite와 원시 numpy 배열(raw numpy arrays) 형태로 디스크에 저장하는 지속 가능한 로컬 모드(persistent local mode)를 가지고 있습니다. 별도의 프로세스도, Docker-in-Docker 같은 번거로운 작업도 필요 없이 다음과 같이 사용합니다:
pip install chromadb
# 코드 내에서 — 로컬 디렉토리에 지속 가능한 저장소 설정
...
ChromaDB의 총 유휴 메모리 점유율(idle memory footprint)은 200MB 미만입니다. 이는 사용자의 기기에서 Open WebUI와 Ollama를 동시에 실행해야 할 때 매우 중요한 요소입니다.
LangChain Python — 버전을 고정(Pin)하지 않으면 후회하게 될 것입니다
LangChain의 0.1에서 0.2로의 마이그레이션(migration)은 제가 사용하던 거의 모든 임포트 경로(import path)를 깨뜨려 놓았습니다. from langchain.vectorstores import Chroma가 from langchain_chroma import Chroma로 변경되었습니다. RetrievalQA 체인은 LCEL (LangChain Expression Language)을 위해 지원이 중단(deprecated)되었습니다. 불평을 하려는 것은 아닙니다. 실제로 LCEL이 더 깔끔하니까요. 하지만 버전을 고정(pin)하지 않으면, 3개월 뒤에 실행한 pip install --upgrade 명령어가 시험 기간 직전 새벽 2시에 당신의 RAG 체인을 조용히 망가뜨릴 것입니다. 저의 requirements 파일은 다음과 같습니다:
# requirements.txt — 버전을 방치하지 마세요
langchain==0.2.16
langchain-community==0.2.16
...
제가 JS 대신 Python을 선택한 이유는 구체적으로 문서 로더(document loaders) 때문입니다. pypdf 백엔드를 사용하는 LangChain의 PyPDFLoader는 데바나가리(Devanagari), 음차된 힌디어(transliterated Hindi), 영어가 섞여 있는 NCERT PDF를 현재 JS 생태계의 그 어떤 것보다 훨씬 더 잘 처리합니다. JS 로더들은 10학년 과학 교과서 1장의 유니코드(Unicode)를 계속해서 망가뜨렸고, 저는 런타임을 완전히 바꾸기 전까지 하루 종일 디버깅(debugging)을 해야 했습니다.
프론트엔드(Frontend)로서의 Open WebUI
Open WebUI를 발견하기 전까지는 React 프론트엔드를 구축할 계획이었습니다. Open WebUI는 Ollama의 API 엔드포인트(endpoint)에 직접 연결되며, 모델 전환 기능이 내장되어 있고, 대화 기록(conversation history)을 지원하며, 설정 방법이 말 그대로 Docker 명령어 한 줄 또는 pip install open-webui로 끝납니다. 학생들은 ChatGPT 스타일의 인터페이스를 사용할 수 있고, 저는 프론트엔드 코드를 단 한 줄도 작성하지 않았습니다. OLLAMA_BASE_URL=http://localhost:11434를 설정하여 구성하면, 당신이 내려받은(pulled) 모델들을 자동으로 찾아냅니다. 솔직한 한 가지 한계점은 UI가 힌디어로 현지화(localized)되어 있지 않다는 것입니다. 따라서 저학년 학생들(6~7학년)의 경우 교사의 중재가 필요합니다.
왜 LlamaIndex가 아닌가
LlamaIndex는 진정으로 탄탄합니다. 제가 이를 비하하려는 것이 아닙니다. 하지만 NCERT의 PDF(그중 일부는 2단 레이아웃, 캡션 텍스트가 포함된 삽입 다이어그램, 굵은 글씨의 데바나가리(Devanagari) 장 제목 등을 포함함)를 대상으로 테스트했을 때, LangChain의 로더 파이프라인(loader pipeline)이 별도의 설정 없이도 더 깔끔한 텍스트 청크(text chunks)를 제공했습니다. LlamaIndex는 이러한 예외 상황을 처리하기 위해 더 많은 커스텀 노드 파싱(custom node parsing)이 필요합니다. 만약 여러분의 코퍼스(corpus)가 깨끗한 영어 PDF라면 어느 쪽을 선택해도 좋습니다. 하지만 명백히 스캔되었거나, PageMaker로 조판된 후 위원회에 의해 PDF로 내보내진 정부 교과서라면 LangChain의 로더(loaders)를 고수하십시오.
하드웨어의 현실
이 전체 스택은 Core i5-8500, 16GB DDR4, 512GB SSD를 탑재한 35,000루피(₹) 상당의 중고 HP ProDesk에서 실행됩니다. GPU는 없습니다. mistral:7b-instruct-q4_K_M을 사용한 CPU 추론(inference)은 해당 하드웨어에서 초당 약 3~5개의 토큰(tokens/second)을 생성합니다. 이는 사용자에게 기대치(
# 영어 비중이 높은 NCERT 콘텐츠에 적합, 6~12학년 범위
ollama pull mistral:7b-instruct
만약 8GB RAM(구형 학교 서버 하드웨어에서 흔히 볼 수 있는 사양)을 사용 중이라면, 대신 Gemma 2B를 내려받으세요:
# 8GB RAM에 적합하며, 사실 기반의 CBSE 질의응답(Q&A)에서도 여전히 일관성을 유지함
ollama pull gemma2:2b
GPU가 없는 머신에서는 llama3.1:8b를 내려받지 마세요. 제가 저지른 실수입니다. CPU 전용 하드웨어에서는 — 제법 괜찮은 8코어 머신이라 할지라도 — 쿼리(query)당 응답 시간이 40~50초 정도 소요됩니다. 이는 추측이 아닙니다. 제가 16GB RAM을 탑und한 Core i5 머신에서 47초를 직접 측정했습니다. 동일한 머신에서 Gemma2:2b는 12초 미만이었습니다. 9학년 과학 설명을 위한 모델 품질의 차이는 진정으로 그 기다림을 감수할 만큼의 가치가 없습니다.
내려받은 직후 바로 테스트하세요. 학생들이 실제로 물어볼 법한 내용을 대표하는 다음 쿼리를 정확히 실행해 보세요:
ollama run mistral:7b-instruct 'Explain Newton second law for Class 9'
시간을 측정하세요. 만약 20초 이내에 전체 응답을 받는다면 교실에서 사용하기에 좋은 상태입니다. 만약 30초 이상 걸린다면 gemma2:2b로 낮추세요. NCERT 수준의 콘텐츠에 대한 Mistral 7B instruct의 응답 품질은 탄탄합니다. 충분한 교과서 스타일의 영어로 학습되었기 때문에, 추가적인 프롬프팅(prompting) 없이도 설명이 적절한 독해 수준에 맞춰 제공됩니다.
이제 README에는 없는 주의사항(gotcha)을 알려드립니다: Ollama는 기본적으로 127.0.0.1:11434에 바인딩(bind)됩니다. 모든 학생이 동일한 머신을 사용한다면 괜찮겠지만, 실제로는 그렇지 않습니다. 학생 기기에서 LAN(근거리 통신망) 접속을 허용하려면 systemd 환경 설정을 재정의(override)해야 합니다. 메인 서비스 파일을 직접 수정하지 마세요. 업데이트 시 설정이 덮어씌워지는 것을 방지하기 위해 드롭인 오버라이드(drop-in override) 방식을 사용하세요:
# 오버라이드 디렉토리와 파일 생성
sudo mkdir -p /etc/systemd/system/ollama.service.d/
sudo nano /etc/systemd/system/ollama.service.d/override.conf
파일에 다음 내용을 붙여넣으세요:
[Service]
Environment="OLLAMA_HOST=0.0.0.0"
그 다음 다시 로드하고 재시작하세요:
sudo systemctl daemon-reload
sudo systemctl restart ollama
...
한 가지 더 — Ollama는 기본적으로 인증 (auth) 기능이 없습니다. 0.0.0.0에 바인딩(bind)하고 나면, LAN 내의 누구나 API에 접근할 수 있습니다. 학교 인트라넷(intranet) 환경이라면 보통 허용 가능한 수준이지만, 11434 포트를 인터넷에 직접 노출하지는 마세요. 기본 인증 (basic auth)이나 속도 제한 (rate limiting)을 추가해야 한다면 Nginx를 앞에 두세요. 이 부분은 나중에 다루겠습니다.
2단계 — ChromaDB로 NCERT PDF 데이터 주입하기
저를 가장 놀라게 했던 점은 소스 자료가 실제로 매우 깔끔하다는 것이었습니다. NCERT는 ncert.nic.in에서 모든 교과서를 무료 PDF로 공개하고 있습니다. 이는 퍼블릭 도메인 (public domain)이며, 라이선스 문제나 스크래핑 (scraping)의 회색 지대 문제도 없습니다. 구조 또한 예측 가능합니다. 각 학년별로 과목당 하나의 폴더가 있습니다. 다음과 같이 전체 학년을 일괄 다운로드할 수 있습니다:
# 10학년의 모든 PDF를 다운로드합니다 — 학년에 따라 경로 패턴을 조정하세요
wget -r -A.pdf -nd -P ./ncert_pdfs/class10 \
https://ncert.nic.in/textbook/pdf/
그렇긴 하지만, 서버의 폴더 구조가 학년마다 완벽하게 일관되지는 않으므로, 몇 번의 수동 점검 (spot-checks)이 필요할 것입니다. 612학년의 수학 및 과학 자료는 구조가 잘 잡혀 있습니다. 68학년의 역사 및 시민 의식 (civics) PDF는 다소 복잡한데, 이에 대해서는 잠시 후에 더 자세히 설명하겠습니다.
버전을 고정한 상태로 환경을 설정하세요. 저는 의도적으로 langchain==0.2.16을 지정하고 있습니다. 0.3.x 시리즈는 커뮤니티 로더 (community loaders)를 조용히 고장 내는 방식으로 구조를 변경했기 때문에, 프로젝트 중간에 이를 디버깅(debug)하고 싶지는 않을 것입니다.
python3 -m venv ncert-env
source ncert-env/bin/activate
...
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기