무료 AI API 하나만 물려서 쓰면 반드시 하루 어딘가에서 한도에 걸린다. 이걸 해결하려고 만든 게 3단 폴백 구조의 작은 FastAPI 서버였다.

순서는 Cloudflare Workers AI → Groq → Gemini다. 처음엔 Gemini를 1순위로 뒀는데, 자동응답 기능이 Gemini의 하루 500회 한도를 낮 시간에 다 써버리면 정작 사람이 직접 명령어를 칠 때 쓸 한도가 안 남는 문제가 있었다. 그래서 “사실상 무제한"이라고 생각했던 Cloudflare Workers AI를 앞으로 보내고, 응답 형식이 엄격하게 필요한 자동응답 호출만 별도로 Groq부터 시작하도록 순서를 분기했다.

여기서 만만치 않았던 건 “얼마나 남았는지"를 정확히 아는 일이었다. Groq는 응답 헤더에 x-ratelimit-remaining-tokens를 주긴 하는데, 공식 문서를 다시 읽어보니 이게 분당(TPM) 수치였다 — 하루 예산(TPD)이 아니었다. 몇 초 만에 다시 차는 숫자를 하루 한도처럼 보여주고 있었던 셈이라, 실제로 하루 예산이 거의 바닥났는데도 대시보드는 멀쩡해 보이는 착시가 있었다. 고친 방법은 매 호출마다 실제로 쓴 토큰(usage.total_tokens)을 직접 파일에 누적 기록하고, UTC 자정 기준으로 리셋하는 자체 카운터를 만드는 것이었다. 429 에러 메시지 안에 실제 하루 사용량(“Used 198960” 같은 문자열)이 실려 오는 걸 발견하고는, 이 값으로 누적치를 주기적으로 보정하는 로직도 추가했다 — 어떤 이유로든 우리 쪽 카운트가 실제와 어긋났을 때를 대비한 것이다.

Cloudflare/Gemini는 아예 잔여치를 알려주는 헤더가 없어서 다른 접근을 썼다. 모든 AI 요청을 공급자·이벤트(성공/한도소진)·토큰수와 함께 로그로 남기고, “리셋 이후 다음 소진까지"를 한 구간으로 묶어 집계했다. 처음엔 달력 날짜(UTC 자정)로 묶었는데, 실측해보니 각 공급사의 실제 리셋 시각이 자정과 정확히 안 맞았다. 그래서 429 응답을 구간의 끝으로, 그 뒤 첫 “충분히 큰” 성공 요청을 새 구간의 시작으로 재정의했다. “충분히 큰"이라는 조건이 필요했던 이유도 실측 중에 나왔다 — 완전히 소진된 상태에서도 아주 작은 요청 하나는 가끔 통과하는 경우가 있었는데(롤링 윈도우의 좁은 틈), 이걸 리셋으로 잘못 인식하면 실제로는 한 구간인 걸 여러 개로 쪼개버리는 문제가 생겼다.

작은 사이드 프로젝트처럼 보이지만, 결국 “무료 티어 사용 한도를 정확히 재는 일"이 “AI를 죽지 않게 계속 쓰는 일"보다 더 까다로운 문제였다.