GPT-4로 하루 10,000개의 채용 공고를 스코어링하며 겪은 문제점들
요약
GPT-4를 활용해 매일 1만 개의 채용 공고를 스코어링하는 프로덕션 RAG 파이프라인 구축 경험을 공유합니다. 비용, 속도 제한, 청킹 전략 및 임베딩 모델 선택 등 실제 운영 단계에서 마주하는 기술적 도전과 해결책을 다룹니다.
핵심 포인트
- 복잡한 스키마 추출 시 2단계 청킹 전략을 통해 오류율을 12%에서 2% 미만으로 개선
- 대규모 데이터 처리 시 API 속도 제한(Rate Limits) 및 재시도 로직 관리의 중요성
- 임베딩 모델 선택 시 재현율과 비용 간의 트레이드오프 고려 (small vs large)
- 메타데이터 통합 및 비용 절감을 위한 벡터 스토어(pgvector) 선택 사례
제 스코어링 파이프라인 (pipeline)이 하루치 배치 (batch) 데이터를 대상으로 처음 실행되었을 때, 47분이 소요되었고 86달러의 비용이 발생했습니다. 두 번째 실행에서는 요청의 절반이 속도 제한 (rate limits)에 걸리고 재시도 로직 (retry logic)이 너무 공격적이었기 때문에 3시간이 걸렸습니다. 세 번째 날에는 처리되지 않은 공고 대기열 (queue)이 시스템이 스코어링할 수 있는 속도보다 더 빠르게 늘어났습니다.
그때 저는 RAG 데모 (RAG demo)와 프로덕션 RAG 파이프라인 (production RAG pipeline)의 차이를 배웠습니다.
제가 구축한 시스템은 매일 10,000개 이상의 채용 공고를 처리하고, GPT-4 함수 호출 (function calling)을 통해 구조화된 필드 (structured fields)를 추출하며, 각 공고를 후보자 프로필과 비교하여 점수를 매기고, REST API를 통해 결과를 제공합니다. 이 시스템은 몇 달 동안 프로덕션 환경에서 실행되고 있습니다. 실제 아키텍처 (architecture)가 어떻게 구성되어 있는지, 그리고 대부분의 튜토리얼이 어떤 어려운 부분들을 생략하는지 알려드리겠습니다.
청킹 전략 (Chunking Strategy): 문서 크기가 생각보다 중요한 이유
가장 단순한 접근 방식은 전체 채용 공고 내용을 하나의 청크 (chunk)에 쏟아붓고 끝내는 것입니다. 하지만 요구 사항, 책임 범위, 그리고 상투적인 법률 문구가 뒤섞인 2,000단어 분량의 게시물에서 18개의 구조화된 필드를 추출해야 한다면 이 방식은 통하지 않습니다.
저는 2단계 전략 (two-pass strategy)을 채택했습니다. 첫 번째 단계: 문장 경계 탐지 (sentence boundary detection)와 주제 전환 (topic shifts)을 사용하여 원문 설명을 의미론적 섹션 (semantic sections)으로 나눕니다. 두 번째 단계: 각 섹션을 집중된 추출 스키마 (extraction schema)와 함께 GPT-4에 전달합니다.
interface ExtractionSchema {
role_title: string;
required_skills: string[];
...
핵심 통찰: 복잡한 스키마를 가진 하나의 큰 청크보다, 명시적인 스키마를 가진 더 작은 청크들이 더 신뢰할 수 있는 구조화된 출력 (structured output)을 생성합니다. 단일 청크 추출에서 멀티 청크 (multi-chunk) 추출로 전환했을 때 오류율이 12%에서 2% 미만으로 떨어졌습니다. 트레이드오프 (tradeoff)는 공고당 API 호출 횟수가 늘어난다는 점이며, 이는 비용 문제로 이어집니다.
임베딩 모델 (Embedding Model) 선택과 벡터 스토어 (Vector Store)의 현실
저는 세 가지 임베딩 설정(embedding setups)을 평가했습니다: OpenAI의 text-embedding-3-small, text-embedding-3-large, 그리고 Ollama를 통한 오픈 소스 대안입니다. 채용 공고의 경우, 재현율(recall) 지표에서 small 모델과 large 모델 사이의 차이는 미미했습니다. small 모델은 23배 더 저렴합니다.
벡터 스토어(vector store)의 경우, 50,000개의 공고 데이터셋을 사용하여 Pinecone과 pgvector를 테스트했습니다. Pinecone은 설정이 더 쉬웠습니다. pgvector는 더 저렴했고, 제 PostgreSQL 인스턴스에 이미 메타데이터가 저장되어 있었기 때문에 네트워크 홉(network hop)을 제거할 수 있었습니다. 저는 pgvector를 선택했습니다.
진정한 비용은 벡터 스토어 자체가 아닙니다. 대규모로 이루어지는 임베딩 생성(embedding generation) 비용입니다. 하루 10,000개의 공고를 처리할 때, small 모델의 임베딩 비용은 약 0.40달러입니다. large 모델은 5.20달러가 들 것입니다. 한 달 동안 이 차이는 임베딩 비용만으로 144달러 대 1,872달러에 달합니다.
비용 최적화: Batch API가 모든 것을 바꾸었습니다
OpenAI의 Batch API는 제가 발견한 가장 큰 비용 조절 레버(cost lever)입니다. 이는 처리를 지연시키는 대신 표준 API 가격에서 50% 할인을 제공합니다. 실시간 스코어링(scoring)이 필요하지 않은 채용 공고의 경우, 이러한 트레이드오프(tradeoff)는 공짜나 다름없습니다.
const batchRequest = {
custom_id: `listing-${listingId}`,
method: 'POST',
...
저는 2시간마다 제출물을 배치(batch)로 처리합니다. 결과는 30분에서 4시간 이내에 돌아옵니다. 비용은 하루 전체 실행 시 86달러에서 동일한 볼륨 기준 32달러로 떨어졌습니다. 이미 몇 시간 전의 데이터에 대해 몇 시간의 지연 시간(latency)을 허용함으로써 63%의 비용 절감을 달성한 것입니다.
어디에서나 추천되는 스트리밍(streaming) 방식은 여기서 실수였을 것입니다. 스트리밍은 지연 시간이 중요한 사용자 대면 채팅(user-facing chat)에는 도움이 됩니다. 하지만 배치 스코어링의 경우, 이점 없이 복잡성과 비용만 증가시킵니다.
에러 핸들링 (Error Handling): 아무도 쓰지 않는 부분
첫 주에 속도 제한(rate limits) 때문에 파이프라인이 세 번이나 중단되었습니다. 제가 배운 점은 다음과 같습니다.
OpenAI의 속도 제한은 모델별 및 조직(organization)별로 적용됩니다. 만약 동일한 키에서 GPT-4와 GPT-4o-mini를 호출하고 있다면, 이들은 풀(pool)을 공유합니다. GPT-4 호출이 급증하면 제한을 모두 소진하여 저렴한 GPT-4o-mini 요청까지 차단할 수 있습니다.
저의 해결책은 모델 티어(tier)별로 별도의 큐(queue)를 가진 토큰 버킷 (Token Bucket)을 사용하는 것이었습니다.
class TokenBucket {
private tokens: number;
private lastRefill: number;
...
여기서 중요한 부분은 100ms의 버퍼 (buffer)입니다. 속도 제한 (Rate limit) 적용은 즉각적이지 않습니다. 버킷이 다시 채워지는 바로 그 순간에 재시도(retry)를 하면 여전히 429 오류를 받게 됩니다. 작은 버퍼 하나가 안정적인 파이프라인과, 유휴 상태와 속도 제한 사이를 오가는 불안정한 파이프라인 사이의 차이를 만듭니다.
스코어링 정확도를 위한 평가 (Evals)
저는 500개의 레이블링된 테스트 세트를 대상으로 매주 평가 (evaluation)를 수행합니다. 이 평가는 세 가지를 확인합니다: 필드 추출 정확도 (field extraction accuracy), 스코어링 일관성 (동일한 공고를 두 번 스코어링했을 때 유사한 결과가 나와야 함), 그리고 환각률 (hallucination rate, 소스 텍스트에 없는 데이터로 필드가 채워지는 현상)입니다.
환각 체크는 가장 교묘한 버그들을 잡아냅니다. 초기에는 모델이 보상(compensation)에 대한 언급이 없는 공고에 대해 급여 범위를 지어내기 시작했습니다. 스키마 (schema)에는 salary_range가 선택 사항 (optional)으로 되어 있었지만, 모델은 그럴듯해 보이는 숫자로 이를 채우고 있었습니다. 해결책은 함수 호출 스키마 (function call schema)에 존재 여부 가드 (presence guard)를 도입하여, 모델이 선택적 필드를 채우기 전에 명시적인 불리언 (boolean) 플래그를 요구하도록 하는 것이었습니다.
const schema = {
has_salary_info: { type: 'boolean' },
salary_range: {
...
이 패턴은 LLM이 선택적 필드를 추출하는 모든 곳에서 채택할 가치가 있습니다. 이 장치가 없다면, 모델은 정확해 보이지만 실제로는 틀린 데이터를 자신 있게 지어낼 것입니다.
여전히 고민 중인 트레이드오프 (Tradeoff)
가장 해결되지 않은 큰 갈등 요소는 AI 설명 재작성 (description rewrite) 파이프라인입니다. 제가 직접 구축했고 잘 작동했지만, 클라이언트는 100만 개 이상의 공고를 처리할 때 GPT-4급 모델을 사용하는 비용이 너무 높다는 이유로 이를 중단시켰습니다. 저는 경제성을 맞출 수 있는 대안으로, 약 23배 더 저렴한 DeepSeek V4 Flash를 검토하고 있습니다.
만약 귀하의 팀이 프로덕션 RAG 파이프라인을 구축 중이며, 규모 확장 시의 비용 통제나 신뢰성 문제로 고민하고 있다면, 그것이 바로 제가 도움을 드릴 수 있는 부분입니다. 무엇이 효과적이었고 무엇이 그렇지 않았는지에 대해 기꺼이 의견을 나누고 싶습니다.
작성자: Abdul Rehman, 프로덕션 SaaS, MVP 및 AI 자동화를 구축하는 풀스택 AI 엔지니어. 더 많은 정보는 PrimeStrides에서 확인하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기