AWS에서 서버리스 CFP 에이전트를 구축한 방법
요약
AWS의 서버리스 서비스를 활용하여 컨퍼런스 발표 기회(CFP)를 자동으로 수집, 필터링 및 순위를 매기는 에이전트 구축 사례를 소개합니다. Amazon Bedrock과 Lambda를 사용하여 비용 효율적이고 관리 부담이 없는 자동화 파이프라인을 구현했습니다.
핵심 포인트
- AWS Lambda를 엔진으로 사용하여 서버 관리 없이 실행되는 서버리스 아키텍처 구현
- Amazon Bedrock을 활용해 발표자 프로필과 CFP 간의 적합성 자동 비교 및 순위 산출
- EventBridge Scheduler를 통한 정기적인 작업 실행 및 Slack 알림 자동화
- 개인 사용자를 위한 저비용 고효율의 에이전트 구축 방법론 제시
컨퍼런스에서 발표를 하신다면, Call for Papers (CFP)을 찾는 과정이 정말 고된 일이라는 것을 아실 겁니다. 마감일은 수십 개의 사이트에 흩어져 있고, 절반은 이미 마감되었으며, 어떤 CFP를 봤는지 또는 지원했는지 기억하기란 불가능합니다. 저는 매주 이걸 손으로 처리하고 있었는데도 좋은 기회를 놓치고 있었습니다.
그래서 저만의 에이전트를 만들었습니다. 이 에이전트는 AWS에서 완전히 서버리스(serverless)로 작동하며, 스케줄에 맞춰 깨어나 여러 출처에서 CFP를 가져오고, Amazon Bedrock의 모델을 사용하여 제 발표자 프로필과 비교하여 순위를 매긴 다음, 좋은 기회들을 Slack으로 전송합니다. 이 게시물에서는 이것이 어떻게 구성되었는지, 그리고 더 중요하게는 제가 왜 특정 AWS 구성 요소를 선택했는지 설명합니다.
무엇을 만들었나
전체 시스템은 매일 아침 스스로 실행되는 파이프라인입니다:
- 몇 개의 컨퍼런스 애그리게이터(aggregator)에서 열린 CFP를 가져옵니다.
- 이를 하나의 일관된 형태로 정규화합니다.
- 제가 정의한 규칙(주제, 국가, 발표 유형, 마감일 등)에 따라 필터링합니다.
- Bedrock의 모델을 사용하여 생존한 후보들을 제 발표자 프로필과 비교하여 순위를 매깁니다.
- 결과를 JSON으로 저장하고 Slack으로 알림을 보냅니다.
지켜봐야 할 서버가 없습니다. 실행되지 않을 때는 저에게 거의 비용이 들지 않습니다. 제가 집중하고 싶은 부분이 바로 이 부분입니다... 왜냐하면 AWS 선택 사항들이 '매일 실행되는 에이전트'를 개인 예산으로 구축할 수 있게 만들기 때문입니다.
문제를 가까이서 살펴보기
수동 버전은 이런 모습이었습니다. 다섯 개에서 여섯 개의 탭을 열고, 컨퍼런스 목록을 스크롤하며, 제가 이미 지원한 내용이 적힌 메모와 교차 참조하고, 마감일이 지났는지 계산하고, 그리고 새로운 것이 끊임없이 나타나기 때문에 다음 주에 이 모든 과정을 반복해야 했습니다.
가장 나빴던 부분은 기억력 문제였습니다. 지난 화요일에 봤던 CFP가 다음 월요일에는 다시 새것처럼 보였습니다. 저는 '지난번 확인한 이후로 이게 새로운 것인지' 또는 '이미 거절했다고 말했는지'에 대한 지속적인 감각이 없었습니다. 이것이야말로 AWS의 약간의 접착제(glue)가 완벽하게 처리할 수 있는, 상태를 유지하며 반복적으로 스케줄에 따라 실행되는 잡무입니다.
AWS 아키텍처
그 구조는 다음과 같습니다. 모든 박스는 서버리스 (Serverless) 또는 관리형 (Managed) 서비스이므로, 작업이 실행되지 않을 때는 아무것도 실행되지 않습니다.
EventBridge Scheduler (cron)
│
▼
...
중요한 구성 요소들을 살펴보겠습니다.
Lambda는 전체 엔진입니다
전체 파이프라인은 하나의 Lambda 함수로 구성됩니다. 가져오기 (Fetch), 정규화 (Normalize), 필터링 (Filter), 순위 매기기 (Rank), 쓰기 (Write)... 이 모든 과정이 단일 핸들러 (Handler) 내부에서 일어납니다. 항상 켜져 있는 박스도 없고, 밤새 유휴 상태로 놓여 있는 컨테이너 (Container)도 없습니다. 함수가 실행되어 1~2분 내에 작업을 수행한 뒤 다시 잠듭니다.
핸들러는 사실상 단계들을 오케스트레이션 (Orchestration) 하는 것에 불과합니다:
def lambda_handler(event, context):
cfps = fetch_all_sources() # 피드 가져오기
cfps = normalize(cfps) # 하나의 일관된 스키마 (Schema)로 정규화
...
이것이 개인용 에이전트를 위해 Lambda를 사용하는 이유입니다. 배포 단위가 바로 함수 그 자체라는 점이죠. 저는 가동 시간 (Uptime)에 대해 비용을 지불하는 것이 아니라, 실제로 실행되는 하루 90초에 대해서만 비용을 지불합니다.
DynamoDB는 에이전트의 메모리입니다
이것은 저의 가장 큰 고충을 해결해 준 방법이었습니다. 모델은 실행 간의 상태를 기억하지 못하므로, DynamoDB를 통해 메모리를 부여합니다. 하나의 작은 테이블에 세 가지를 저장합니다: 저의 필터 규칙 (Filter rules), 스피커 프로필 (Speaker profile), 그리고 이미 노출된 모든 CFP ID의 "확인됨 (Seen)" 로그입니다.
import boto3
table = boto3.resource("dynamodb").Table("cfp-agent")
...
"확인됨" 로그 덕분에 에이전트는 똑같은 목록을 계속 보여주는 대신 "어제 이후로 새로 올라온 것들입니다"라고 말할 수 있습니다. 요청당 요금제 (Pay-per-request pricing)를 사용하는 DynamoDB는 여기에 매우 적합합니다. 하루에 발생하는 소수의 아주 작은 읽기 및 쓰기 작업은 비용이 거의 0에 수렴하며, 용량 (Capacity)에 대해 고민할 필요도 없습니다.
Bedrock이 순위를 매깁니다
결정론적 규칙 (Deterministic rules)이 저렴한 비용으로 1차 필터링을 수행합니다. 마감되었거나, 국가가 틀렸거나, 주제가 맞지 않는 것들을 걸러냅니다. 하지만 "이 컨퍼런스가 실제로 나에게 적합한가?"라는 질문은 키워드 매칭이 아닌 판단의 영역입니다. 바로 이 지점에서 Amazon Bedrock 기반의 모델이 등장합니다.
나는 나의 프로필(약력, 전문 지식, 목표, 과거 강연 내용)과 살아남은 각 CFP(Call for Papers)를 모델에 입력하며, 모델은 관련성 점수(relevance score), 지원/고려/건너뜀(apply / consider / skip)에 대한 추천, 그리고 짧은 이유를 반환합니다. 모델은 상태를 유지하지 않는(stateless) 특성을 가지기 때문에, DynamoDB에서 가져온 프로필이 추천 결과가 실제로 '나'를 기반으로 하게 만드는 핵심 요소가 됩니다.
prompt = f"""당신은 컨퍼런스 CFP를 연사에게 매칭합니다.
연사 프로필 (SPEAKER PROFILE):
...
내가 초기에 결정한 한 가지는 '조용한 폴백(silent fallback)'을 하지 않는 것이었습니다. 만약 Bedrock에 접속할 수 없다면, 실행은 명확하게 실패(fail loudly)해야 하며 대시보드에 배너 형태로 에러를 기록해야 합니다. 겉보기에는 괜찮아 보이지만 실제로는 키워드 매칭에 기반한 저하된 품질의 추측 목록을 조용히 받는 것보다, 랭킹 시스템이 고장 났다는 사실을 아는 편이 훨씬 낫기 때문입니다.
EventBridge Scheduler가 이를 스크립트가 아닌 에이전트로 만듭니다
이것을 "생각날 때 실행하는 스크립트"에서 "알아서 처리하는 에이전트"로 탈바꿈시키는 요소는 바로 EventBridge Scheduler입니다. 크론 표현식(cron expression)이 매일 아침 Lambda를 실행합니다. 이는 시간대(timezone)를 인식하므로, 내가 일광 절약 시간(daylight-saving) 계산을 할 필요 없이 내 시간 기준으로 오전 6시에 실행됩니다.
cron(0 6 * * ? *) # 매일 오전 6:00
그게 전부입니다. 이 한 줄이 내가 직접 사용해야 하는 도구와, 내가 깨어나기 전에 조용히 제 할 일을 마치는 도구 사이의 차이를 만듭니다.
대시보드를 위한 S3 + CloudFront
Lambda는 결과를 JSON 파일로 S3에 기록하고, 정적 HTML 대시보드는 CloudFront를 통해 이를 읽어옵니다. API도 없고, 브라우저에서의 데이터베이스 쿼리도 없습니다... 그저 CDN에 있는 JSON 파일 하나뿐입니다. 대시보드는 내가 실제로 항목을 둘러보고, 검색하고, 관심 없는 항목을 숨기고, 지원하고 싶은 항목을 별표(star) 표시하는 공간입니다. 저렴하고, 빠르며, 실행 중인 서버도 없습니다.
제대로 작동하나요?
네. 매주 사이트들을 수동으로 뒤지던 방식에서, 좋은 항목들은 이미 순위가 매겨져 있고 새로운 항목들은 표시되어 있는 하나의 대시보드를 여는 방식으로 바뀌었습니다. 최근 실행 결과, 여러 소스에서 1,000개가 넘는 CFP를 가져왔고, 내 규칙을 통과한 수백 개로 압축했으며, Bedrock은 실제로 지원할 가치가 있는 수십 개를 골라냈습니다. 이것이 바로 핵심입니다... 나는 천 개가 아니라, 그 수십 개만 확인하면 됩니다.
그리고 에이전트가 저에게 보여준 것을 기억하기 때문에, 마침내 제가 놓치고 있었던 "지난번 이후로 새로 올라온" 신호를 얻을 수 있게 되었습니다. 이제 더 이상 동일한 목록을 다시 읽으며 이미 봤던 것인지 고민할 필요가 없습니다.
고려해야 할 사항 (THINGS TO CONSIDER)
피드 데이터(Feed data)는 틀릴 수 있습니다. 커뮤니티에서 관리하는 피드는 때때로 오래되었거나 완전히 잘못된 마감일을 포함하기도 합니다. 저는 날짜를 재확인하기 위해 실제로 CFP 페이지를 렌더링하는 별도의 검증 단계를 추가했지만, 이는 여러분의 에이전트가 입력값만큼만 성능을 발휘한다는 점을 상기시켜 줍니다... 입력값을 수정할 수 있는 방식으로 구축하세요.
Bedrock 비용은 볼륨에 따라 확장됩니다. 하루에 수백 개의 CFP를 순위 매기는 것은 저렴하지만, 무료는 아닙니다. 호출을 배치(Batching) 처리하고 저렴한 규칙 기반 필터링(rule-based filtering)을 먼저 수행하면 모델 비용을 낮게 유지할 수 있습니다. 모델에 모든 것을 보내지 마세요... 무료 검사를 통과한 것들만 보내세요.
서버리스 상태(Serverless state)는 저장 공간이 필요합니다. 에이전트가 실행 사이에 무언가를 기억해야 하는 순간, 이를 저장할 내구성이 있는 장소가 필요합니다. 저에게는 DynamoDB가 마찰이 적은 정답이었지만, 교훈은 나중에 덧붙이는 것이 아니라 초기에 상태(state)를 계획해야 한다는 것입니다.
시작하기 (GET STARTED)
만약 약간의 판단력이 필요하면서도 반복적으로, 정해진 일정에 따라 실행해야 하는 작업이 있다면, 이 구조를 가져다 쓸 가치가 있습니다. 작업을 위한 Lambda, 기억을 위한 DynamoDB, 판단을 위한 Bedrock, 스스로 실행하기 위한 EventBridge, 그리고 출력을 위한 S3를 사용하는 방식입니다. 모든 구성 요소는 유휴 상태일 때 0으로 확장(scales to zero)되므로, 매일 실행되는 에이전트라도 비용이 거의 들지 않습니다.
저는 CFP를 놓치는 것에 지쳐서 이것을 만들었습니다... 이제 저는 그저 대시보드를 확인하기만 하면 됩니다. 만약 여러분이 컨퍼런스에서 발표를 하거나 하고 싶다면, 그것만으로도 이 시스템을 설정하는 데 드는 몇 시간의 가치는 충분합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기