0파운드의 추론 예산으로 내 이력서 기반 AI 채팅 구축하기
요약
추론 예산 0원을 목표로 여러 무료 AI API 제공업체를 순차적으로 호출하는 페일오버 체인(failover chain)을 구축하여 이력서 기반 AI 채팅 서비스를 구현한 사례를 소개합니다.
핵심 포인트
- Groq, OpenRouter 등 무료 티어 API를 순차적으로 시도하는 페일오버 로직 구현
- OpenAI 호환 API를 활용하여 제공업체 교체를 용이하게 설계
- 빈 환경 변수가 API 호출 실패를 유발하지 않도록 처리하는 주의사항
- 모델별 문장 부호 특성을 극복하기 위한 프록시 단계의 스트림 재작성 기법
제 이력서는 PDF이며, PDF는 질문에 답할 수 없습니다. 그래서 저는 ask.hiten.dev를 구축했습니다. 이는 제 실제 경력 이력에 근거한 스트리밍 채팅(streaming chat)으로, 채용 담당자가 "다른 시니어 프론트엔드 엔지니어 대신 왜 당신을 채용해야 합니까?"라고 물으면 실제 답변을 얻을 수 있는 서비스입니다.
이 프로젝트를 흥미롭게 만든 제약 조건은 총 추론(inference) 예산이 0이라는 점입니다. OpenAI 청구서도 없고, 호스팅된 벡터 DB(vector DB)도 없으며, 아무것도 없습니다. 이를 위해 실제로 무엇이 필요했는지 소개합니다.
4개의 무료 제공업체와 페일오버 체인 (failover chain)
단일 무료 티어(free tier)는 낯선 사람들에게 제공할 만큼 충분히 신뢰할 수 없습니다. Groq의 무료 티어는 하루 10만 토큰(tokens)으로 제한되어 있으며, 저는 첫날에 그 제한에 도달했습니다. OpenRouter의 무료 모델들은 생겼다 사라지기를 반복합니다. Cerebras는 사용자가 몰리는 시간에 가끔 대기열(queue)에 넣기도 합니다.
해결책은 지루하지만 효과적입니다. 바로 모든 것이 OpenAI 호환(OpenAI-compatible)되는 순서가 정해진 제공업체 체인을 만들고, 응답이 올 때까지 요청마다 순차적으로 탐색하는 것입니다.
Groq (llama-3.3-70b) -> OpenRouter (gpt-oss-120b:free) -> NVIDIA (llama-3.3-70b) -> Cerebras (gpt-oss-120b)
각 제공업체는 단순히 기본 URL(base URL), 키(key), 그리고 모델 이름(model name)만 있으면 됩니다. API 라우트는 순서대로 각 업체를 시도하며, 본문(body)이 포함된 첫 번째 2xx 응답이 승리하고 응답은 그대로 스트리밍됩니다. 클라이언트는 X-Provider 헤더를 받게 되므로, 저는 로그를 통해 누가 무엇을 제공했는지 확인할 수 있습니다.
중요했던 두 가지 세부 사항은 다음과 같습니다:
-
빈 환경 변수(env vars)는 해제(unset)된 것이 아닙니다. Docker Compose의
${VAR:-}는 빈 문자열을 생성하며, 이는 Node의??기본값 설정을 무력화합니다. 모든 키는""를undefined로 강제 변환하는 헬퍼(helper)를 거칩니다. 그렇지 않으면 키가 없는 제공업체가 "존재"하는 것으로 간주되어 모든 요청을 실패하게 만듭니다. -
일일 토큰 제한(token-per-day cap)을 저렴하게 조사할 수는 없습니다. 저의 헬스 체크(health check)는 각 제공업체의
GET /models를 호출합니다 (인증 확인, 60초 캐시). 이는 "키가 작동하며 서비스가 가동 중임"을 알려줄 뿐, "토큰이 남아 있음"을 알려주지는 않습니다. 페일오버 체인이 이 간극을 메워줍니다. 일일 토큰 제한(TPD)에 걸린 제공업체는 빠르게 실패하고 다음 업체가 업무를 이어받습니다.
모든 제공업체가 다운된 경우, 페이지 자체에 그 사실을 표시합니다. 상태 확인(Health check)은 렌더링 시점에 서버 측에서 실행되며, 망가진 채팅창 대신 짧은 점검 안내 메시지가 나타납니다. 사용자가 입력을 마친 후에 실패할 수 있는 채팅 UI를 배포하지 마세요.
오픈 웨이트 모델(Open-weight models)은 서식 명령을 따르지 않습니다
제 사이트의 어조는 모든 곳에서 엠 대시(em dash)와 둥근 따옴표(curly quotes)를 피합니다. 시스템 프롬프트는 점점 더 절박한 방식으로 "plain ASCII punctuation only(오직 일반 ASCII 문장 부호만 사용)"라고 명시합니다. Llama 3.3은 대부분 이를 준수합니다. gpt-oss-120b는 절대 따르지 않습니다.
모델과 싸우는 대신, 프록시(proxy)에서 스트림을 다시 작성합니다. 각 SSE data: 청크(chunk)를 파싱하여 델타 콘텐츠(delta content)를 정규화(둥근 따옴표를 곧은 따옴표로 변경, 모든 대시 변형 처리, NBSP, 말줄임표, 화살표, 불렛 처리)한 뒤 다시 방출합니다. 클라이언트는 모델의 타이포그래피적 취향을 결코 보지 못합니다.
이와 동일한 아이디어를 더 광범위하게 적용할 수 있습니다. 모델 출력에 대해 반드시 보장해야 하는 것이 있다면, 프롬프트가 아니라 모델 이후의 코드에서 강제하세요.
벡터 DB 없는 그라운딩(Grounding)
여기에는 RAG(검색 증강 생성)가 없습니다. 그라운딩 문서(grounding document)는 시스템 프롬프트로 주입된, 직접 작성한 약 3.5k 토큰 분량의 제 이력서 버전입니다. 이 정도 규모에서는 벡터 스토어(vector store)를 사용하는 것이 과잉 엔지니어링(overengineering)입니다. 전체 코퍼스(corpus)가 컨텍스트(context) 안에 여유 있게 들어갑니다.
어려운 부분은 환각(hallucination)된 구체적인 정보를 막는 것이었습니다. 초기 버전에서는 제 프로젝트의 사용자 수를 자신 있게 지어내곤 했습니다. 해결책은 화이트리스트(whitelist)였습니다. 프롬프트에 모델이 제 경력에 대해 언급할 수 있는 유일한 숫자들을 나열하고, 정직한 공백(모르는 부분)에 대해서는 한 줄로 답변한 뒤 실제 강점으로 유도하도록 지시했습니다. 15개의 테스트로 구성된 Playwright 스위트(suite)를 통해 금지된 문자가 나타나지 않는지, 의심스러운 숫자가 나타나지 않는지, 그리고 프레이밍(framing)이 올바르게 유지되는지를 검증합니다.
일반적인 경로를 무료로 만들기
대부분의 방문자는 시작 칩(starter chips, "왜 당신을 채용해야 하나요?", "핵심 스택", "지금 바로 투입 가능한가요?") 중 하나를 클릭합니다. 이러한 프롬프트는 고정된 문자열이므로, 응답은 한 시간 동안 인메모리(in-memory)에 캐싱됩니다. 첫 번째 방문자가 토큰 비용을 지불하며, 나머지 모든 사람은 X-Provider: cache와 함께 0ms 만에 답변을 받습니다. 후속 제안들도 동일한 칩을 재사용하므로 이 역시 캐시를 활용하게 됩니다.
상위로 전송되는 히스토리(History)는 메시지 개수가 아닌 문자 예산(약 3k 토큰)에 따라 트리밍(trimming)되며, 이는 대화가 길어질 때 일일 제한(daily caps)을 보호합니다.
나머지 스택
- Astro 5 SSR (node 어댑터): Oracle ARM 프리 티어(free-tier) VM 상의 Docker에서 실행
- Vanilla JS 클라이언트: SSE 파싱, 소규모의 안전한 마크다운 렌더러(markdown renderer), sessionStorage 지속성(persistence), 중단(abort) 버튼. 프레임워크 없음; 클라이언트 전체가 파일 하나로 구성됨
- Playwright: E2E 테스트용, Forgejo (자체 호스팅): CI용
- 호스팅 비용: VM은 프리 티어(free tier). 추론(Inference): 0원
여러분이 복제(copy)했으면 하는 것들
- 무료 제공자(free providers)를 체이닝(Chain)하세요; 절대 하나에만 의존하지 마세요
- 프롬프트(prompt)가 아닌 코드에서 출력 보장(output guarantees)을 강제하세요
- 서버 측 상태 확인(health check)을 통해 UI 접근을 제어하세요
- 모델이 중요한 사항에 대해 언급할 수 있는 사실들을 화이트리스트(Whitelist)로 관리하세요
- 고정된 프롬프트를 캐싱(Cache)하세요; 대부분의 트래픽은 동일한 다섯 가지 질문입니다
직접 시도해 보세요: ask.hiten.dev. 그리고 만약 여러분이 채용 중(정규직 또는 계약직)이라면, 이 채팅이 왜 그것이 좋은 생각인지 기꺼이 설명해 줄 것입니다. 저의 나머지 작업물은 hiten.dev에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기