본문으로 건너뛰기
Community실험 리포트

Fable 콘솔 종료 전에 워크플로를 API로 옮긴 순서와 함정

박지훈

@mlops_park

7월 7일부터 Fable 콘솔·웹 UI 접근이 막히고 API 전용으로 바뀐다고 해서, 콘솔에서 손으로 돌리던 워크플로를 마감 전에 코드로 옮겼다. 그 기록이다.

마감 전에 한 순서

공지를 보고 제일 먼저 한 건 코드가 아니라 목록화였다. 콘솔·웹 UI로 손수 돌리던 워크플로가 6개였는데, 매일 자동으로 돌아야 하는 건 2개뿐이고 나머지 4개는 사람이 가끔 여는 거라 급하지 않았다. 순서는 이렇게 잡았다.

  1. API 키 발급 + 시크릿 분리 (콘솔 닫히기 전에 키부터 확보)
  2. 자동 2개를 SDK 호출로 이관
  3. 콘솔에 저장된 시스템 프롬프트·설정을 코드로 복사
  4. 비용 로깅
  5. 나머지 4개는 마감 후

키를 먼저 뽑은 이유는 리스크 대비 비용이 제일 쌌기 때문이다. 콘솔이 닫히면 발급 화면이 어찌 바뀔지 몰랐고, 키를 시크릿 매니저에 넣는 건 5분이면 끝났다.

콘솔 설정을 코드로 옮기다 400을 맞다

첫 번째 함정은 시스템 프롬프트였다. 콘솔에선 시스템 프롬프트가 별도 칸이라 그대로 messages 배열에 {"role":"system"}으로 넣었다. OpenAI 습관이었다. 바로 400. Anthropic Messages API는 role이 user/assistant만 허용이고, 시스템 프롬프트는 최상위 system 파라미터로 따로 넣어야 한다.

둘째는 max_tokens였다. 콘솔에선 안 건드리던 값인데 API에선 필수다. 1024로 박아뒀다가 긴 출력이 조용히 잘렸다. stop_reason이 "end_turn"이 아니라 "max_tokens"인 걸 로그에서 보고 알았다. 이후 모든 응답에 stop_reason을 찍게 했다.

스트리밍·재시도가 출력을 두 번 뱉다

자동 워크플로 하나가 결과를 DB에 바로 append 했는데, 스트리밍 중 연결이 끊겨 재시도가 돌면 앞부분이 두 번 저장됐다. 토큰을 받는 족족 커밋한 게 원인이었다. 스트림이 message_stop까지 완주했을 때만 커밋하도록 버퍼링으로 바꿔 잡았다.

재시도는 지수 백오프에 지터를 꼭 섞었다. 429가 여러 워커에서 동시에 터지면 백오프가 동기화돼 같은 순간 재시도가 몰린다. 429의 retry-after 헤더가 있으면 그 값을 우선했다. 서버가 알려주는 대기시간이 무작정 2·4·8초 배수보다 정확했다.

반대로, 사람이 여는 4개엔 스트리밍을 안 붙였다. 실시간으로 볼 필요가 없고 논스트리밍이 코드도 에러 처리도 짧다. 스트리밍은 출력이 길어 타임아웃이 걱정되거나 UI에 토큰이 흘러야 할 때만 값어치를 한다.

비용 로깅과 데이터 보존

콘솔은 사용량을 대시보드에 알아서 찍어줬지만 API는 안 남기면 사라진다. 그래서 호출마다 input/output 토큰 수·모델·stop_reason·지연시간을 한 줄 JSON으로 처음부터 남겼다. 응답 usage 필드의 토큰 수를 그대로 적재했다. 평균이 입력 약 2.1k, 출력 약 0.8k 토큰이었고, 이 분포를 알고 나서야 어디를 캐싱할지 판단이 섰다.

트레이드오프가 있다. 반복되는 시스템 프롬프트·긴 지시문은 프롬프트 캐싱이 이득이지만, 캐시는 쓰기 비용과 짧은 TTL이 있어 저빈도 호출엔 오히려 손해다. 그래서 캐싱은 매일 도는 2개에만 걸었다.

데이터 보존 정책도 이번에 같이 바뀌었다. 고객 데이터를 30일 이상 저장·재사용하는 데 제한이 생겨, 우리 로그에 프롬프트 원문을 통째로 남기던 걸 토큰 수·해시만 남기게 정리했다. 비용 로깅엔 원문이 필요 없었다.

이 글에 대한 Q&A

이 글에 질문하기

이 글에서 궁금한 점이 있다면 먼저 질문을 남겨보세요.

댓글

첫 댓글을 남겨보세요.