포트폴리오에 이력서 기반 AI 챗봇 붙이기
AI2026.09.02

포트폴리오에 이력서 기반 AI 챗봇 붙이기

#AI#LLM#RAG#FastAPI#Supabase

들어가며

최근 LLMRAG를 공부하면서, 배운 내용을 직접 프로젝트로 구현해보고 싶었습니다. 마침 포트폴리오를 정리하고 있던 터라, 이력서를 기반으로 질문에 답하는 챗봇을 만들어보면 어떨까 하는 아이디어가 떠올랐습니다.
개발은 이론에 머물기보다 빠른 실행으로 배우는 것이 가장 효과적이라 생각해, 곧바로 로컬 환경에 Ollama를 설치하고 PoC로 검증한 후 실제 포트폴리오에 서비스로 구현했습니다.

전체 구조

먼저 챗봇 구조를 도식화하면 다음과 같습니다.
[포트폴리오 프론트] │ SSE ▼ [chatbot-api] ──── 생성 ──────▶ [Groq] 무료 FastAPI ──── 임베딩 ────▶ [NVIDIA Build] │ (Groq 장애 시 생성도 NVIDIA로 폴백) └──── 문서·벡터 ────▶ [Supabase + pgvector]
 
핵심은 GPU가 필요한 부분을 전부 외부 무료 API에 맡긴 것입니다. 챗봇 서버는 문서를 관리하고 프롬프트를 조립해 전달하는 역할만 합니다. 따라서 서버 스펙이 거의 필요 없고, 저렴한(혹은 무료인) 컨테이너 호스팅으로도 운영이 가능합니다.

기술 스택

Groq + NVIDIA Build- LLM 추론 및 임베딩 API 활용

LLM 추론을 담당하는 핵심 기술입니다. GroqNVIDIA Build에서 발급받은 키로 다양한 오픈소스 모델을 호출할 수 있으며, 둘 다 무료로 시작할 수 있습니다.
무료 LLM API 선택지 중에서는 Groq을 메인으로 골랐습니다. 추론 속도가 압도적이라 대화형 서비스에 잘 맞고, 무료 티어만으로도 포트폴리오 수준의 트래픽을 감당할 수 있습니다.
빠른 추론 속도를 유지하기 위해 요청마다 전달하는 컨텍스트의 크기도 최소화했습니다. 답변에 필요한 내용은 벡터 검색을 통해 관련 청크만 선별해 전달하고, 문서와 무관한 질문에는 불필요한 컨텍스트를 전달하지 않습니다. 또한 전체 목록처럼 많은 정보가 필요한 경우에는 특정 페이지로 안내해 LLM의 불필요한 호출을 줄였습니다.
 
Groq과 NVIDIA를 함께 사용하면서 알게 된 점입니다.
  1. 임베딩은 NVIDIA를 사용했습니다 - RAG 구현에는 생성 모델과 문서를 벡터로 변환하는 임베딩 모델이 모두 필요합니다. Groq은 임베딩 API를 제공하지 않기 때문에, 생성은 Groq에서, 임베딩은 NVIDIA에서 담당하도록 구성했습니다.
  1. OpenAI 규격을 그대로 활용할 수 있습니다 - 두 API 모두 OpenAI 호환 규격을 지원해 기존 코드나 라이브러리를 큰 수정 없이 재사용할 수 있습니다. 덕분에 API를 교체하거나 조합하는 과정에서도 개발 생산성을 유지할 수 있었습니다.
현재 사용 중인 주요 모델 조합은 다음과 같습니다.
용도
모델
답변 생성 (주)
openai/gpt-oss-120b — Groq
임베딩
nvidia/nemotron-3-embed-1b (2048차원) — NVIDIA
답변 생성 (예비)
nvidia/nemotron-3-super-120b-a12b — NVIDIA

Ollama - 로컬 개발용

개발 중에는 로컬에서 Ollama를 띄워서 테스트합니다. 무료 크레딧이라도 한도가 있으니, 반복 테스트를 하면서 불필요하게 소모하고 싶지 않았습니다.
환경
사용 제공자
이점
로컬 개발
Ollama (qwen2.5:7b)
크레딧 소모 없음
배포
Groq (생성) + NVIDIA (임베딩)
GPU 불필요
 
전환은 환경변수 교체로 수정합니다. 개발 중엔 Ollama로 마음껏 테스트하고, 배포된 서버만 실제 크레딧을 쓰는 구조입니다.

FastAPI - 백엔드

백엔드는 FastAPI를 사용했습니다. Python 기반이라 LLM API, 임베딩, 벡터 검색 등 AI 생태계의 라이브러리를 자연스럽게 활용할 수 있었고, RAG 파이프라인을 빠르게 구현하기에 적합했습니다. 또한 비동기 처리가 기본이라 외부 LLM 응답이나 벡터 DB 검색처럼 대기 시간이 발생하는 작업을 효율적으로 처리할 수 있습니다.
특히 이 프로젝트에서는 답변을 실시간으로 보여주기 위한 SSE 스트리밍 구현이 중요했는데, FastAPI는 이를 비교적 간결하게 구성할 수 있었습니다. 요청·응답 형식은 Pydantic 모델로 검증하고 Swagger 문서도 자동으로 생성할 수 있어, 프론트엔드 연동과 개발 중 API 테스트까지 편하게 진행했습니다.

Supabase + pgvector - 벡터 저장소

이력서를 조각(청크)으로 나눠 벡터와 함께 저장합니다. 전용 벡터 DB(Chroma DB, Pinecone등)도 있지만 Supabase Postgres + pgvector를 택했습니다.
무료 티어로 시작할 수 있고, 익숙한 Postgres 위에서 SQL로 다룰 수 있다는 점이 좋았습니다. 벡터 검색을 위해 새로운 쿼리 문법을 배울 필요또한 없었습니다.
 
-- 코사인 거리 순으로 유사한 청크 찾기 select content, 1 - (embedding <=> $1::vector) as score from chunks order by embedding <=> $1::vector limit 10
 

Railway - 호스팅

컨테이너 호스팅으로 Railway를 사용하고 있습니다. GitHub 레포를 연결하면 Dockerfile을 감지해 자동으로 빌드·배포합니다.
가입 시 트라이얼 크레딧이 제공돼 한 달가량은 무료로 운영할 수 있습니다. 이후에는 사용량 기반 과금으로 넘어가는데, 포트폴리오 수준의 트래픽이라면 부담이 크지 않습니다.
Railway 외에도 Render 같은 컨테이너 호스팅 서비스를 사용할 수 있고, AWS EC2나 Lightsail처럼 직접 서버를 구축하는 방법도 있습니다. 이 프로젝트는 상주 프로세스를 유지할 수 있는 컨테이너 기반 호스팅이 더 적합하다고 판단했고, 그중에서도 Dockerfile 기반으로 간편하게 배포할 수 있는 Railway를 선택했습니다. 본인에게 맞는 비용과 운영 방식 등을 고려해 배포환경을 선택하면 좋을 것 같습니다.

비용 정리

구성요소
서비스
비용
LLM 추론
Groq
무료 (한도 내)
임베딩
NVIDIA API Catalog
무료 (크레딧)
벡터 DB
Supabase
무료 티어
호스팅
Railway
무료 티어
 
현재는 월 0원으로 운영 중입니다. GPU 서버를 직접 띄웠다면 가장 큰 비용이 됐을 추론을 무료 API로 대체한 게 비용절감의 핵심입니다.

LLM을 유연하게 교차하기

무료 서비스로 구성하면 성능이나 한도보다 더 신경 써야 할 것이 있습니다. 정책이 언제 바뀔지 모른다는 점입니다.
실제로 이번 프로젝트에서도 예비로 둔 NVIDIA Build쪽 모델이 어느 날 EOL(End of Life) 처리돼 410 Gone을 반환하기 시작했습니다. 크레딧이 소진될 수도 있고, 무료 티어가 사라질 수도 있고, 이렇게 쓰던 모델이 예고 없이 내려가기도 합니다.
따라서 처음부터 LLM 모델을 자유롭게 바꿀 수 있는 구조로 만들었습니다.
 
def _build() -> ChatClient: if settings.llm_provider == "ollama": return OllamaChat() # 로컬 개발용 primary = OpenAICompatChat(...) # 지금은 Groq if not settings.llm_fallback_api_key: return primary fallback = OpenAICompatChat(...) # 지금은 NVIDIA return FallbackChat(primary, fallback) # 한도 초과·EOL이면 예비로
 
NVIDIA, Groq, OpenRouter, Cerebras 등은 OpenAI 호환 API 규격을 지원합니다. 따라서 동일한 인터페이스로 여러 서비스를 사용할 수 있으며, LLM 서비스를 변경하더라도 코드 수정 없이 환경변수만 변경하면 됩니다.
 
LLM_BASE_URL=https://api.groq.com/openai/v1 LLM_MODEL=openai/gpt-oss-120b LLM_FALLBACK_BASE_URL=https://integrate.api.nvidia.com/v1 LLM_FALLBACK_MODEL=nvidia/nemotron-3-super-120b-a12b
 
예비 모델을 추가하고, 내려간 모델을 다른 모델로 교체하고, 주·예비 모델의 순서를 조정하는 동안 코드는 한 줄도 수정하지 않았습니다. 모두 환경변수만 변경했습니다.

기다림을 설계하기

RAG 답변은 빠르지 않습니다. 문서를 검색하고, 컨텍스트를 구성하고, LLM이 답변을 생성하는 데 시간이 걸립니다. 로컬 Ollama로 테스트할 때는 첫 토큰이 나오기까지 10초 이상 걸리기도 했습니다.
빈 화면에서 10초를 기다리면 사용자는 보통 에러가 발생했다고 생각합니다. 그래서 답변을 기다리는 동안 현재 처리 상태를 trace message로 함께 스트리밍하도록 했습니다.
다만 모든 요청에 trace message를 보여주지는 않았습니다. 1.5초 이상 걸리는 경우에만 trace를 노출하도록 했습니다. 1초 만에 끝나는 답변에 "검색 중..." 같은 메시지가 잠깐 나타났다 사라지면 오히려 화면이 산만해지기 때문입니다.
처리가 길어질수록 trace message도 현재 상황에 맞게 단계적으로 변경했습니다.
 
0~7초 참고 문서 3개를 읽고 있어요 7~20초 답변을 정리하고 있어요 20~40초 조금만 더 기다려 주세요 40초~이후 평소보다 오래 걸리고 있어요. 잠시만요
 
실제 배포 환경에서 측정한 타임라인입니다. Groq의 빠른 추론 속도와 검색으로 추린 작은 컨텍스트 덕분에 대부분의 요청이 2초 안에 완료되어 trace message가 표시되지 않습니다. 공들여 만든 trace가 정작 자주 보이지 않는 셈이지만, 콜드 스타트나 LLM 폴백처럼 응답이 지연되는 상황에서는 사용자가 현재 처리 상태를 알 수 있도록 제 역할을 합니다.
 
1.39s sources 이벤트 (근거 2개) 1.67s 첫 토큰 2.16s 답변 완료 (268토큰) * trace 없음 — 1.5초 이내에 완료되어 trace message가 표시되지 않았습니다

마치며

"내 이력서를 학습한 챗봇"이라고 하면 흔히 파인튜닝을 떠올리지만, 이 경우엔 RAG가 정답이었습니다. 모델을 재학습하지 않고 질문이 들어올 때마다 문서를 근거로 넣어주는 방식이라, PDF만 교체하면 즉시 반영됩니다. 이력서처럼 자주 수정되는 문서에는 이 방향이 훨씬 유리했습니다.
무엇보다 사이드 프로젝트를 오래 유지하기 위해서는 비용 절감이 절대적이라고 생각했습니다. 매달 고정 비용이 발생하면 프로젝트를 지속하기 어렵기 때문입니다. 다행히 찾아보니 무료 API 생태계가 생각보다 잘 갖춰져 있었고, GPU 서버를 고민할 필요 없이 무료 크레딧과 API만으로 서버 비용 없이 구조를 완성할 수 있었습니다.
프론트엔드 개발자이지만 챗봇 구현에는 단 몇 시간밖에 걸리지 않았습니다. 새삼 AI 기술의 가파른 발전을 실감하는 계기가 되었고, 그동안 이론으로만 접하던 LLMRAG 기술을 직접 코드로 구현해 보며 그 구조와 흐름을 체득할 수 있는 좋은 경험이었습니다.