본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 28. 01:05

자율적으로 일자리를 찾아다니는 AI 에이전트를 구축했습니다 — 실제로 효과가 있었던 것들

요약

구직 활동을 자동화하는 AI 에이전트 구축 과정과 시행착오를 다룹니다. LLM의 낙관적 편향을 해결하기 위한 구조화된 점수 산정 방식과 Playwright를 활용한 정교한 데이터 스크래핑 전략을 소개합니다.

핵심 포인트

  • LLM의 낙관적 편향을 막기 위해 다차원 가중치 기반의 구조화된 점수 산정 도입
  • Playwright를 활용해 인간과 유사한 마우스 움직임 및 지연 시간을 구현하여 차단 방지
  • BullMQ를 이용해 스크래핑 작업과 메인 앱을 분리하는 디커플링 아키텍처 구축
  • 동적 렌더링 및 봇 탐지를 우회하기 위한 정교한 브라우저 자동화 기술 적용

6개월 전, 저는 구직 활동을 자동화하기 위한 AI 에이전트(AI agent)를 만들기 시작했습니다. 단순한 이력서(CV) 최적화 도구나 채용 공고 애그리게이터(aggregator)가 아닙니다. 브라우저를 열고, 여러 채용 게시판을 검색하며, 각 공고를 읽고, 지원자의 이력서와 비교하여 점수를 매긴 뒤, 인간의 개입 없이 전체 파이프라인을 관리하는 에이전트입니다. 이것은 무엇이 고장 났는지, 무엇이 저를 놀라게 했는지, 그리고 현재의 아키텍처(architecture)가 어떤 모습인지에 대한 이야기입니다.

만약 여러분이 AI 구직 자동화나 일반적인 에이전트 시스템(agentic systems)을 연구하고 있다면, 이 내용 중 일부는 익숙할 것입니다. 또한 일부는 여러분의 시간을 일주일 정도 아껴줄 것입니다.

첫 번째 버전이 쓸모없었던 이유

초기 프로토타입(prototype)은 창피할 정도로 순진했습니다. 저는 직무 기술서(job description)와 이력서(CV)를 프롬프트(prompt)에 넣고 GPT에게 매칭 점수를 달라고 요청했습니다. 지원자가 2년의 경력만 있음에도 불구하고, GPT는 시니어 스태프 엔지니어(senior staff engineer) 역할에 대해 87%라는 점수를 자신 있게 반환했습니다. 돌이켜보면 문제는 명확했습니다. LLM(Large Language Models)은 낙관적이라는 점입니다. 엄격한 제약 조건(hard constraints)이 없으면, LLM은 거절할 이유보다는 매칭할 이유를 찾아냅니다.

두 번째 버전에서는 구조화된 점수 산정(structured scoring) 방식을 도입했습니다. 저는 평가를 기술 중첩(skills overlap), 연차 적합성(seniority fit), 위치(location), 급여 범위(salary band), 역할 유형(role type)의 다섯 가지 가중치 차원으로 나누었습니다. 각 차원은 합산 점수가 계산되기 전에 독립적으로 점수가 매겨졌습니다. 이것만으로도 위양성(false positives)을 약 60% 줄일 수 있었습니다.

하지만 더 큰 문제는 점수 산정이 아니었습니다. 바로 데이터 품질이었습니다.

진짜 병목 구간: 대규모의 깨끗한 채용 데이터를 확보하는 것

채용 게시판들은 스크래핑(scraping)되는 것을 원하지 않습니다. 그들은 동적 렌더링(dynamic rendering), 허니팟 필드(honeypot fields), 로그인 벽(login walls), 그리고 CAPTCHA를 사용합니다. 단순한 fetch 호출은 뼈대만 있는 HTML이나 봇 탐지 페이지를 반환할 뿐입니다.

실제로 효과가 있었던 접근 방식: 인간과 유사한 상호작용 패턴을 사용하는 Playwright입니다. 베지에 곡선 (Bezier curve) 마우스 움직임, 키 입력 사이의 무작위 지연 (randomised delays), 현실적인 뷰포트 크기 (viewport sizes), 그리고 요청 간에 유지되는 세션 쿠키 (session cookies)를 활용합니다. 먼저 채용 공고를 스캔하여 (URL과 메타데이터를 확보), 각 URL에 대해 상세 정보 스크래핑 (detail scrape)을 실행합니다. 이러한 요청들을 분산시키는 것이 중요합니다. 10초 안에 50개의 채용 페이지를 호출하면 차단당할 것입니다. 하지만 동일한 50개의 요청을 지연 시간에 변동을 주며 8분에 걸쳐 분산시키면 차단되지 않습니다.

브라우저 자동화 (browser automation)는 :3001/:3002 포트의 별도 서버 프로세스에서 실행됩니다. :3000 포트의 메인 Next.js 앱은 BullMQ를 통해 스크래핑 작업 (scrape jobs)을 큐에 쌓습니다. 이러한 디커플링 (decoupling)은 필수적이었습니다. 스크래핑은 느리고 상태를 유지해야 하는 (stateful) 작업이므로, API 레이어를 차단하게 해서는 안 되기 때문입니다. 단 하나의 Playwright 인스턴스가 실패한다고 해서 채팅 인터페이스 전체가 다운되어서는 안 됩니다.

저는 세 개의 프로세스를 동시에 실행합니다:

npm run dev           # Next.js :3000
npm run browser-server  # 브라우저 자동화 (Browser automation) :3001/:3002
npm run workers       # BullMQ 작업/gmail 큐 (job/gmail queues)

이들을 분리해 두었기에, 다른 부분에 지장을 주지 않고 세션 중간에 브라우저 서버를 재시작할 수 있습니다.

점수 산정 아키텍처 (Scoring Architecture)의 실제 모습

각 채용 공고는 사용자가 확인하기 전에 파이프라인 (pipeline)을 거칩니다. 단순화된 흐름은 다음과 같습니다:

  1. 텍스트 추출 (Text extraction) — HTML 제거, 공백 정규화, 급여 범위 추출 (있는 경우)
  2. 트리거 분류 (Trigger classification) — 이것이 job_search, cv_review, 또는 일반적인 대화(general turn)인지 결정합니다. 이는 어떤 도구와 컨텍스트 레이어 (context layers)를 로드할지 결정하는 관문 역할을 합니다.
  3. CV 임베딩 매칭 (CV embedding match) — 후보자의 CV 벡터에 대한 코사인 유사도 (cosine similarity)를 통해 빠르고 근사적인 점수를 산출합니다.
  4. LLM 심층 점수 산정 (LLM deep score) — Gemini (Vertex AI를 통해)가 5가지 차원을 점수화하고 구조화된 JSON을 반환합니다.
  5. 중복 체크 (Dedup check) — 동일한 채용 공고를 두 번 점수 산정하는 것을 방지하기 위해 (company + title + location)의 해시값에 대해 Redis 조회를 수행합니다.
  6. 파이프라인 쓰기 (Pipeline write) — 점수가 매겨진 채용 공고가 모든 메타데이터와 함께 Postgres에 저장됩니다.

점수 산정 프롬프트(scoring prompt)는 가장 많은 반복 작업(iteration)이 이루어진 곳입니다. 초기 버전은 지침(instructions), 이력서(CV) 텍스트, 채용 공고(job description)를 모두 하나로 연결한 단일 긴 프롬프트를 사용했습니다. 이로 인해 컨텍스트(context)가 엉망이 되었습니다. 해결책은 이력서 텍스트를 6,000자로 제한하는 것이었습니다(대부분의 이력서는 3~5KB이며, 처음에 설정했던 12,000자 제한은 턴당 약 1,500개의 토큰을 낭비하고 있었습니다). 또한, 모든 턴마다 이력서를 불러오는 대신 트리거가 cv_review일 때만 이력서 컨텍스트를 로드하도록 수정했습니다.

하루에 30턴을 수행하는 1,000명의 동시 사용자가 있을 때, 프롬프트에서 줄인 1킬로바이트(KB)는 매일 수백만 개의 토큰을 절약합니다. 규모가 커질 때 프롬프트 효율성(prompt efficiency)은 있으면 좋은 기능이 아니라, 비용 모델 그 자체입니다.

기록할 가치가 있는 실패 사례들

실패 1: 환각 파이프라인 (The hallucination pipeline). get_pipeline 도구의 초기 버전은 실제 데이터베이스 쿼리 결과가 아닌 LLM의 텍스트 출력 결과값을 반환했습니다. 사용자는 존재하지 않는 채용 공고를 보게 되었습니다. 해결책은 간단하지만 당혹스러웠습니다. 항상 실제 도구 결과(tool result)를 렌더링하고, LLM이 구조화된 데이터(structured data)를 스스로 설명(narrate)하게 두지 않는 것이었습니다.

실패 2: 제한 없는 메모리 파일 (Uncapped memory files). 에이전트는 사용자별 메모리 파일(컨텍스트 노트, 장기 기억, 증거 로그)에 내용을 기록합니다. 제한(cap)이 없으면 이 파일들은 무한정 커집니다. 3주간의 테스트 후, 일부 파일은 수천 줄에 달했습니다. 저는 슬라이딩 윈도우(sliding window) 방식을 사용하여 이를 50~100개의 항목으로 제한했습니다. 파일은 agents/atlas/users/{userId}/ 아래에 네임스페이스(namespaced)되어 사용자별로 관리되므로 사용자 간 데이터 유출은 없습니다. 하지만 제한 없는 성장은 조용히 성능을 저하시킵니다.

실패 3: 요청 핸들러 내의 동기식 Vertex AI 호출 (Synchronous Vertex AI calls in request handlers). 초기 API 라우트(routes)는 Vertex AI를 직접 호출하여 응답을 차단(block)했습니다. 느린 모델 응답(때로는 8~12초)이 핸들러를 잠가버렸습니다. 이제 비용이 많이 드는 모든 작업은 BullMQ를 통해 처리됩니다. API가 작업을 큐에 넣으면(enqueue), 워커(worker)가 이를 처리하고, 결과가 DB에 저장되면 클라이언트가 폴링(poll)하는 방식입니다. 지루하지만 올바른 방법입니다.

에이전트가 이러한 예외 상황(edge cases)을 어떻게 처리하는지에 대해 더 자세히 알고 싶다면 Atlas Job OS 가이드: AI 채용 지원 자동화를 읽어보세요. 그곳에서 봇 탐지(bot detection) 측면에 대해 더 심도 있게 다루었습니다.

실질적인 교훈 (Practical Takeaways)

유사한 것을 구축하고 있다면, 처음부터 다르게 해야 할 점들을 다음과 같이 말씀드리고 싶습니다.

브라우저 자동화(browser automation)를 API 레이어와 즉시 분리하세요. Next.js API 라우트 내부에서 Playwright를 실행하지 마세요. 고생하게 될 것입니다.

낙관적 점수 산정(optimistic scoring)보다 비관적 점수 산정(pessimistic scoring)이 더 낫습니다. 거절할 이유를 먼저 구축하세요. 높은 오탐률(false-positive rate)은 보수적인 매칭률보다 사용자 신뢰를 더 빠르게 무너뜨립니다.

모든 LLM 컨텍스트(context)를 공격적으로 제한하세요. 프롬프트의 모든 토큰은 사용자 및 턴(turn)이 늘어남에 따라 복리로 증가하는 비용입니다. 느낌(vibes)이 아니라 실제 수치로 프롬프트를 감사(audit)하세요.

속도 제한(rate limiting) 및 사용자별 상태 관리를 위해 Redis를 사용하세요. 무언가를 변경하는 모든 사용자 작업은 속도 제한기(rate limiter)를 거쳐야 합니다. 모든 Redis 키는 TTL과 함께 {scope}:{userId}:{key} 형식의 네임스페이스(namespace)를 가져야 합니다. 예외는 없습니다.

인간과 유사한 브라우저 동작은 선택 사항이 아닙니다. 대규모로 채용 게시판을 스크래핑(scraping)하고 있다면, 당신은 탐지 시스템과의 군비 경쟁 속에 있는 것입니다. 베지에 마우스 곡선(Bezier mouse curves)과 무작위 지연(randomised delays)은 최소한의 실행 가능한 접근 방식(minimum viable approach)입니다.

이를 위해 제가 만든 도구인 Atlas Job OS는 현재 실제 운영 중인 SaaS 제품입니다. 아직 완성된 것은 아닙니다. 점수 산정 방식은 여전히 제가 원하는 수준에 도달하지 못했고, 아웃리치(outreach) 자동화는 진행 중이며, CV(이력서) 생성 기능은 거칠게 구현되어 있습니다. 하지만 핵심 루프(core loop)는 작동합니다: 검색, 점수 산정, 파이프라인, 아웃리치. 이를 구축하면서 제가 읽었던 그 어떤 튜토리얼보다 에이전트 시스템 설계(agentic system design)에 대해 더 많은 것을 배웠습니다.

가장 어려웠던 부분은 AI가 아니었습니다. 그것은 바로 배관 작업(plumbing)이었습니다.

저자 소개

Tushar는 자율적인 구직, CV 점수 산정(CV scoring) 및 지원 관리(application management)를 위한 AI 에이전트 플랫폼인 Atlas Job OS의 설립자입니다. 그는 빌드 인 퍼블릭(build in public) 방식으로 작업하며, 에이전트 시스템(agentic systems), LLM 아키텍처(LLM architecture), 그리고 AI 제품 스케일링(scaling AI products)의 현실에 대해 글을 씁니다. 문의는 team@atlasjob.tech로 가능합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0