FastAPI와 AlloyDB를 사용하여 구직 활동을 자동화하기 위해 6개의 Gemini 에이전트를 오케스트레이션한 방법
요약
FastAPI와 AlloyDB를 활용하여 6개의 Gemini 에이전트를 오케스트레이션하는 구직 자동화 시스템 구축 사례를 소개합니다. 단일 프롬프트 방식의 한계를 극복하기 위해 상태 관리, 지속성, 동시성을 보장하는 비동기 파이프라인 아키텍처를 제안합니다.
핵심 포인트
- 단일 프롬프트 에이전트의 한계를 극복하기 위한 오케스트레이션 계층의 중요성
- AlloyDB를 활용한 에이전트 상태(State) 관리 및 데이터 지속성 확보
- FastAPI와 asyncio를 이용한 비동기 에이전트 파이프라인 구현
- 스크레이퍼부터 검증 에이전트까지 역할이 분리된 6단계 에이전트 구조
온라인에서 보는 대부분의 "AI 에이전트 (AI agent)" 튜토리얼은 미화된 단일 프롬프트 래퍼 (single-prompt wrappers)에 불과합니다. 데모용으로는 작동할지 모르지만, 상태 (state), 메모리 (memory), 또는 병렬 실행 (concurrent execution)을 도입하는 순간 무너져 버립니다.
지난주, 핵심 인프라에 30개의 커밋을 밀어 넣던 중, 강력한 구직 엔진을 구축하는 것은 LLM의 문제가 아니라 오케스트레이션 계층 (orchestration layer)의 문제라는 것을 깨달았습니다. 저는 6개의 에이전트 Gemini 아키텍처를 사용하여 JobHunt.ai를 구축했으며, 상태를 관리하기 위한 AlloyDB와 같은 견고한 데이터베이스 계층이 없다면 여러분의 에이전트는 그저 값비싼 난수 생성기에 불과하다는 것을 배웠습니다.
문제점: 왜 단일 프롬프트 에이전트가 실패하는가
구직 지원을 자동화하는 에이전트를 구축하고 있다면, 단순히 API에 문자열을 보내는 것이 아닙니다. 여러분은 다음을 관리해야 합니다:
- 컨텍스트 (Context): 이전 에이전트가 무엇을 결정했는가?
- 지속성 (Persistence): 프로세스가 충돌한다면 (반드시 충돌하게 되어 있습니다), 다시 재개할 수 있는가?
- 동시성 (Concurrency): 이벤트 루프 (event loop)를 차단하지 않고 이력서를 파싱하고, 직무 기술서 (JD)를 스크래핑하며, 자기소개서를 동시에 작성할 수 있는가?
저의 아키텍처에서는 단순한 스크립트 기반 실행에서 벗어나 상태 유지형 비동기 파이프라인 (stateful, asynchronous pipeline)으로 전환했습니다.
아키텍처: 6개의 에이전트, 1개의 단일 진실 공급원 (Source of Truth)
저는 FastAPI 엔드포인트에 의해 트리거되고 AlloyDB를 통해 조정되는 6개의 특화된 에이전트를 중심으로 JobHunt.ai를 설계했습니다:
- 스크레이퍼 (The Scraper): JD 데이터를 가져옵니다.
- 이력서 파서 (The Resume Parser): PaddleOCR/Gemini를 통해 엔티티 (entities)를 추출합니다.
- 매칭 에이전트 (The Matching Agent): 적합도 (이력서 vs JD)를 점수화합니다.
- 맞춤화 에이전트 (The Tailoring Agent): 불렛 포인트 (bullet points)를 다시 작성합니다.
- 자기소개서 에이전트 (The Cover Letter Agent): 피치 (pitch)를 생성합니다.
- 검증 에이전트 (The Validation Agent): JD 요구 사항에 대해 최종 "정상성 검사 (sanity check)"를 수행합니다.
기술 스택
- 엔진 (Engine): FastAPI (비동기 실행은 타협할 수 없는 요소입니다).
- 두뇌 (Brain): Google Gemini (Google ADK를 통해 사용).
- 데이터베이스 (Database): AlloyDB (PostgreSQL 호환, 에이전트 상태를 위한 JSONB의 엔터프라이즈급 처리).
- 오케스트레이션 (Orchestration): Python
asyncio+ 워크플로우 트리거를 위한 n8n.
구현: AlloyDB에서 상태 관리하기
핵심 비결은 에이전트 핸드오프(handoffs)를 처리하는 방식에 있습니다. 저는 메모리 상에서 거대한 객체를 전달하지 않습니다. 대신 "에이전트 상태 (Agent State)"를 AlloyDB에 기록하고, 다음 에이전트에게 transaction_id를 전달합니다.
FastAPI 서비스에서 상태 전이(state transition)를 구성하는 방식은 다음과 같습니다:
from fastapi import FastAPI, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession
from models import AgentState
...
AlloyDB를 사용함으로써 PostgreSQL의 성능을 누리면서도, 복잡한 에이전트 로그를 JSONB 형식으로 저장할 수 있는 능력을 얻었습니다. 이를 통해 에이전트가 정확히 어느 지점에서 실패했는지 쿼리할 수 있으며, 이는 6개의 에이전트가 동시에 실행되는 환경에서 디버깅할 때 매우 중요합니다.
주의할 점 (The Gotchas)
- "환각 버퍼 (Hallucination Buffer)": Gemini 1.5를 사용하더라도 에이전트들은 창의력을 발휘하곤 합니다. 저는 Pydantic 모델을 사용하여 엄격한 스키마 검증(schema validation) 단계를 구현했습니다. 만약 에이전트가 스키마와 일치하지 않는 JSON을 반환하면, 파이프라인은 즉시 중단됩니다.
- 동시성 제한 (Concurrency Limits): 6개의 에이전트를 동시에 실행하면 생각보다 훨씬 빠르게 Google Cloud의 속도 제한(rate limits)에 도달하게 됩니다. 저는 요청을 대기열에 넣기 위해 FastAPI에 간단한 세마포어(semaphore) 시스템을 구현했습니다.
- Webpack/프론트엔드 문제: 이번 주에
switchwithai-frontend를 빌드하면서 거대한js-yamlwebpack 에러를 겪었습니다. 해결 방법은 버퍼 폴리필(buffer polyfill)을 추가하는 것이었습니다. AI 도구를 만들고 있다면, 프론트엔드가 Python 백엔드와 통신하도록 만드는 데 필요한 "글루 코드 (glue code)"의 중요성을 절대 과소평가하지 마세요.
왜 표준 Postgres 대신 AlloyDB인가?
제가 AlloyDB를 선택한 이유는 Google 생태계와의 통합 때문입니다. 이미 Google ADK와 Gemini를 사용하고 있기 때문에, 동일한 VPC 내에서 데이터베이스를 관리하면 FastAPI 인스턴스와 데이터 저장소 간의 지연 시간(latency)을 줄일 수 있습니다. 대량의 구직 데이터를 처리할 때는, 20ms~50ms의 지연 시간 절감이 수백 번의 API 호출을 거치며 큰 차이를 만들어냅니다.
향후 계획
현재 저의 초점은 루프를 완성하는 것입니다. 구직 엔진을 위한 문서 스캔을 처리할 수 있도록 VIN OCR Pipeline (PaddleOCR v5 사용)을 개선하고 있으며, 이를 통해 이력서 인입(ingestion) 과정이 생성(generation) 과정만큼 정확하도록 보장할 계획입니다.
이번 주에는 제 rishh-website 리포지토리(repo)에도 업데이트를 반영했습니다. 구체적으로는 서비스 계정(service account) 디버깅 과정을 정리했는데, 깔끔한 워크플로우(workflow)가 없다면 6개의 에이전트(agent) 전반에 걸쳐 자격 증명(credentials)을 관리하는 것은 악몽과 같기 때문입니다.
핵심 요점: 단순히 에이전트를 구축하는 데 그치지 마세요. 에이전트가 무엇을 했는지 추적하는 시스템을 구축하세요. 만약 "뇌의 기억" 역할을 하는 데이터베이스 계층(database layer)이 없다면, 당신은 AI 애플리케이션을 만드는 것이 아니라 프로토타입(prototype)을 만들고 있는 것입니다.
여러분은 프로덕션(production) 환경에서 에이전트 상태(agent state)를 관리하기 위해 어떤 방식을 사용하시나요? 전용 상태 머신(state machine)을 사용하시나요, 아니면 단순히 데이터베이스 행(database rows)에 의존하시나요? 댓글로 알려주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기