혼자서 AI 채용 검색 에이전트를 만들었습니다 — 전체 스택 공개
요약
1인 개발자가 구축한 AI 채용 검색 에이전트 'Reclaim'의 전체 기술 스택과 개발 과정을 소개합니다. Next.js, FastAPI, Supabase, Gemini 등을 활용하여 이력서 파싱부터 직무 매칭까지 수행하는 에이전트 설계 방식을 다룹니다.
핵심 포인트
- Next.js와 FastAPI를 분리하여 무거운 Python 작업의 효율성 확보
- Gemini 모델을 활용해 이력서의 실질적 역량 평가 및 직무 매칭 수행
- 추론 수준에 따라 Gemini 모델 티어를 다르게 라우팅하여 비용 최적화
- Clerk, Stripe, Supabase 등 완성도 높은 외부 서비스 조합 활용
지난 몇 달 동안 밤과 주말을 반납하며 엔지니어를 위한 AI 채용 검색 에이전트인 Reclaim을 혼자서 만들었습니다. 이 에이전트는 사용자의 이력서(résumé)를 읽고, 솔직하게 점수를 매기며, 실제 채용 중인 직무와 매칭해 줍니다. 현재 reclaim.careers에서 서비스 중입니다 (무료 스캔, 가입 불필요).
이 글은 출시 소식을 알리는 글이 아닙니다. 스택에 대한 분석이며, 더 유용하게는 무엇이 고장 났었는지에 대한 기록입니다. 어떻게 구축되었는지 소개하겠습니다.
스택 (The stack)
프론트엔드 (Frontend) — Vercel 기반의 Next.js. App Router를 사용했으며, 적절한 곳에 서버 컴포넌트 (server components)를 적용했습니다. Vercel을 호스팅으로 선택한 이유는 push 시 배포되는 루프가 마찰 없이 매끄러웠고, 인프라 제어보다는 1인 개발 속도를 최적화하는 데 집중했기 때문입니다.
백엔드 (Backend) — Render 기반의 FastAPI. 모든 것을 Next API 라우트에서 처리하는 대신 Python 백엔드를 분리했습니다. 그 이유는 무거운 작업들(이력서 파싱 (résumé parsing), 매칭 파이프라인 (matching pipeline), 스크래핑 (scraping))이 Python 네이티브이며, 프론트엔드의 요청 라이프사이클 (request lifecycle)로부터 이를 격리하고 싶었기 때문입니다.
데이터베이스 (Database) — Supabase + Prisma. 내부적으로는 Postgres를 사용합니다. 스키마 (schema)와 타입 안정성이 보장된 쿼리 (type-safe queries)를 위해 Prisma를 사용하고, 관리형 Postgres 및 인증 관련 데이터를 위해 Supabase를 사용합니다.
인증 (Auth) — Clerk. 회원가입, 세션, 전체 ID 계층 (identity layer)을 처리합니다. Clerk에 대해서는 아래에서 더 자세히 다루겠습니다. 제가 가장 많은 시간을 허비한 부분이 바로 이곳이기 때문입니다.
결제 (Payments) — Stripe. 라이브 모드이며, 체험 기간이 포함된 구독 티어 (subscription tiers)를 제공합니다. 웹훅 (webhook)에서 티어 결정 시 룩업 키 (lookup keys)를 사용하여 가격 변경 시 코드 수정이 필요 없도록 했습니다.
실제 "AI" — Gemini. 이 부분이 흥미로운 지점이므로 별도의 섹션으로 다룹니다.
Gemini가 실제 작업을 수행합니다
제가 가장 중요하게 생각하는 점은, AI가 단순히 옆에 붙어 있는 챗봇 (chatbot)이 아니라는 것입니다. Gemini가 실제 제품의 의사결정을 내립니다.
이력서 읽기 (Résumé reading): PDF를 파싱 (parse)하고, 실제 구조를 추출하며, 점수를 매깁니다. 이때 키워드 밀도 (keyword density)가 아니라, 주장하는 내용이 실질적으로 뒷받침되는지를 기준으로 평가합니다. 전체적인 전제는 정직함입니다. 대부분의 AI 이력서 도구들은 채용 관리 시스템 (ATS)을 통과하기 위해 키워드를 무분별하게 채워 넣지만(keyword-stuffing), 이는 면접 단계에서 본인의 이력서를 제대로 증명하지 못하게 되어 역효과를 낳습니다. Reclaim은 그 반대로 동작합니다. 사용자가 진정으로 강점을 가진 부분과 무리하게 확장한 부분을 표시해 줍니다.
매칭 (Matching): 3,000개 이상의 실제 채용 공고 코퍼스 (corpus) (Greenhouse, Lever, Ashby, Workday 등 수백 개의 기업 게시판에서 스크래핑한 데이터)를 기준으로 이력서 점수를 매기며, 실제 기술이 일치하는 부분과 과하게 의욕을 부리는 부분을 드러냅니다.
한 가지 실제 엔지니어링 결정 사항: 사용자당 비용을 합리적으로 유지하기 위해, 각 작업에 필요한 추론 (reasoning) 수준에 따라 서로 다른 Gemini 모델 티어 (tier)로 작업을 라우팅 (route)합니다. 무료 스캔은 더 저렴하고 빠른 모델에서 실행되며, 더 무거운 파싱/매칭 작업은 더 강력한 모델을 사용합니다. 전체 온보딩 (onboarding)당 비용은 약 5센트 정도이며, 이는 비용이 제약 사항이 아니었음을 의미합니다. 배포 (distribution)와 전환 (conversion)이 제약 사항이었습니다 (이것은 별도의 포스팅을 할 가치가 있는 교훈입니다).
문제가 발생했던 부분 (유용한 부분)
Clerk 웹훅 (webhook)의 트레일링 뉴라인 (trailing-newline) 버그. 프로덕션 (production) 전환 시, 웹훅의 서명 검증 (signature verification)이 조용히 실패했습니다. 원인은 서명 비밀키 (signing secret)가 환경 변수로 들어갈 때 끝에 줄바꿈 문자가 포함되었기 때문이었습니다. 바이트 단위로 확인했을 때 비밀키는 올바르게 보였지만, 실제로는 그렇지 않았습니다. 보이지 않는 문제로 인해 몇 시간을 허비했습니다. 교훈: 서명 검증이 실패하고 비밀키가 "올바르게 보일" 때는 공백 (whitespace)을 가장 먼저 확인하십시오.
웹훅 경로에서의 P2002 고유 제약 조건 (unique-constraint) 충돌. 웹훅 이벤트들이 쌓이면서 동일한 사용자 레코드를 생성하려고 경쟁(race)했고, 이로 인해 Prisma의 고유 제약 조건이 작동했습니다. 핸들러 (handler)를 멱등적 (idempotent)으로 만들어야 했습니다. 즉, 단순한 생성 (create) 대신 업서트 (upsert) 의미론을 사용해야 했습니다.
조용한 자금 누수였습니다. 스코어링 경로(scoring path)가 권한이 없는 사용자들을 위해 Gemini를 호출하고 있었고, 결제할 가능성이 없는 사람들에게 API 비용을 낭비하고 있었습니다. 시스템은 "작동"하고 있었기에(에러 없음, 올바른 출력), 오히려 그것이 위험했습니다. 로그와 청구서를 읽는 것 외에는 비용 버그를 발견할 방법이 없었기 때문입니다. 권한 확인(entitlement check) 단계 뒤로 이를 제한했습니다. 교훈: 작동은 하지만 실행되어서는 안 되는 기능은, 에러를 발생시키지 않으면서 돈을 낭비하는 버그입니다.
핵심 요약 (Takeaways)
- Python을 분리하세요. 만약 당신의 AI 작업이 Python 기반(Python-native)이라면, 이를 JS 프레임워크의 요청 사이클(request cycle)에 억지로 끼워 넣지 마세요. 별도의 FastAPI 서비스를 구축한 것은 추가적인 배포 대상(deploy target)을 만드는 가치가 있었습니다.
- 조회 키(Lookup keys) > 하드코딩된 가격 ID(hardcoded price IDs). Stripe의 가격 변경이 재배포를 요구해서는 안 됩니다.
- 무서운 버그는 에러를 던지지 않는 버그입니다. 끝에 붙는 줄바꿈(trailing newlines), 비용 누수(cost leaks), 레이스 컨디션(race conditions) 등은 모두 "작동"합니다. 로그와 청구서를 읽는 것이 에러 핸들러(error handler)가 잡아내지 못하는 것을 잡아냅니다.
- 비용이 어려운 부분은 아니었습니다. 온보딩당 약 $0.05 수준이었기에, 진짜 문제는 AI 비용이 아니라 적절한 사용자를 사이트로 유입시키는 것이었습니다. 구축하는 것은 쉬운 20%에 불과합니다.
직접 확인하고 싶다면 reclaim.careers에서 확인하실 수 있습니다 — 이력서 스캔은 가입 없이 무료입니다. 스택(stack), Gemini 라우팅(routing), 또는 스크래핑(scraping)에 대해 궁금한 점이 있다면 댓글로 무엇이든 기쁘게 답변해 드리겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기