Cloudflare AI Gateway로 AI 요청 캐싱하고 모니터링하기

Cloudflare AI Gateway로 AI 요청 캐싱하고 모니터링하기

앱에 LLM 호출 한 줄을 붙이는 건 쉽습니다. 그런데 그게 프로덕션에 올라가는 순간 이야기가 달라지죠. 같은 질문이 하루에도 수백 번 들어오는데 매번 돈을 내고 모델을 부르고 있진 않은지, OpenAI가 잠깐 흔들릴 때 서비스가 같이 멈추진 않을지, 이번 달 토큰 비용이 얼마나 나왔는지, 누가 우리 키로 폭주하고 있진 않은지… 정작 “프롬프트 보내고 답 받기”는 한 줄인데 그 주변을 지키는 일이 훨씬 많습니다.

게다가 Cloudflare Workers AI의 자체 모델로 시작했다가도, 복잡한 추론은 GPT나 Claude로 넘기고 싶어지면 제공업체가 둘, 셋으로 늘어납니다. 그때마다 SDK를 따로 깔고, 키를 따로 관리하고, 비용도 대시보드 세 군데를 들여다봐야 하면 금방 피곤해지고요.

Cloudflare AI Gateway는 이 모든 AI 요청 앞에 두는 프록시 한 겹입니다. 우리 코드와 제공업체 사이에 끼어서 캐싱, 폴백, 속도 제한, 로그, 비용 추적을 대신 처리해줘요. 이번 글에서는 AI Gateway가 정확히 무엇인지부터 게이트웨이에 연결하는 세 가지 방법과 게이트웨이 인증, 그리고 캐싱, 폴백, 동적 라우팅, 모니터링까지 차근차근 살펴보겠습니다.

AI Gateway란? 프록시지 리셀러가 아닙니다

가장 먼저 짚고 넘어가야 할 게 AI Gateway의 정체입니다. 비슷해 보이는 세 가지를 헷갈리기 쉽거든요.

우선 Workers AI는 Cloudflare가 자기 GPU에서 자기 모델(@cf/로 시작하는 라마, 미스트랄 등)을 직접 돌려주는 서비스입니다. 우리가 키를 들고 다닐 필요가 없고, 비용은 Cloudflare에 냅니다. 말하자면 모델을 직접 파는 가게예요.

반대로 OpenRouterVoid AI 같은 서비스는 리셀러(reseller) 입니다. 자기들이 OpenAI나 Google과의 관계를 대신 떠안고, 우리는 그들의 키 하나로 여러 모델을 부른 뒤 그들의 단위(크레딧, 뉴런 등)로 정산받죠. 제공업체 키를 우리가 들고 있지 않다는 게 핵심입니다.

AI Gateway는 둘 중 어느 쪽도 아닙니다. AI Gateway는 프록시예요. 모델을 팔지도 않고, 제공업체와의 관계를 대신 떠안지도 않습니다. 그저 우리 요청이 제공업체로 나가는 길목에 서서, 지나가는 트래픽을 캐싱하고 기록하고 통제할 뿐이에요. 그래서 키는 여전히 우리가 들고 있어야 하고, 토큰 비용도 OpenAI나 Google에 직접 냅니다. 이걸 BYOK(Bring Your Own Keys), 즉 “네 키는 네가 가져와라” 방식이라고 부릅니다.

이 구분이 왜 중요할까요? 며칠 전 Void AI 글에서 openai/gpt-4.1-mini를 키 없이 부르는 게 어떻게 가능한지 따져봤는데, 그게 가능했던 건 Void가 리셀러라 자기 자격증명으로 대신 호출해줬기 때문입니다. 반면 우리가 AI Gateway를 직접 쓸 때는 그런 마법이 없습니다. 게이트웨이는 길을 깔아줄 뿐, 통행료(토큰 비용)는 우리가 우리 키로 냅니다. “같은 openai/gpt-4o인데 왜 한쪽은 키가 필요하고 한쪽은 아니냐”의 답이 바로 여기, 프록시냐 리셀러냐에 있어요.

미리 보는 지도: 키를 얼마나 내려놓을 것인가

본격적으로 들어가기 전에 큰 그림부터 그려두겠습니다. AI Gateway에 연결하는 길은 세 갈래인데, 처음 문서를 읽으면 엔드포인트와 키와 결제가 뒤섞여 헷갈리기 쉬워요. 하지만 갈림길의 기준은 사실 하나입니다. 방금 말한 “우리가 드는 키”를 얼마나 내려놓을 것인가예요.

방식키 주인키 위치결제엔드포인트
BYOK (No Store Key)매 요청 헤더내가 제공업체에gateway.ai.cloudflare.com/.../{provider}
BYOK (Store Key)Cloudflare에 저장내가 제공업체에gateway.ai.cloudflare.com/.../{provider}
Unified BillingCloudflare없음Cloudflare 크레딧api.cloudflare.com/.../ai

언제 뭘 고르면 될까요?

  • 제공업체가 하나고 일단 빨리 붙여보고 싶다 → BYOK (No Store Key). 기존 코드에서 baseURL 한 줄만 바꾸면 됩니다.
  • 키를 코드와 환경변수에서 치우고 싶다 → BYOK (Store Key). 키를 Cloudflare에 저장해두고 요청에서는 참조만 해요.
  • 여러 제공업체를 오가며 쓰고, 키 관리 자체를 없애고 싶다 → Unified Billing. 내 키 대신 Cloudflare 크레딧으로 결제합니다.

아래로 갈수록 관리할 비밀이 줄어드는 대신, 결제 관계가 제공업체에서 Cloudflare로 넘어갑니다. 이 글도 위에서 아래로, 키를 하나씩 내려놓는 순서로 진행할게요. 중간에 게이트웨이 자체를 잠그는 인증이 한 번 끼어드는데, 두 번째 방법부터는 그 인증이 전제 조건이기 때문입니다.

게이트웨이 만들고 첫 요청 보내기

AI Gateway는 Cloudflare 대시보드에서 게이트웨이를 하나 만드는 것으로 시작합니다. 이름만 정하면 끝이라 1분도 안 걸려요. 만들고 나면 두 가지 식별자가 생깁니다. 계정 ID(account_id)와 방금 만든 게이트웨이 ID(gateway_id)인데, 앞으로 모든 요청 URL이 이 둘로 조립됩니다.

https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/{provider}

맨 뒤의 {provider} 자리에 openai, anthropic, google-ai-studio 같은 제공업체 이름을 넣으면 됩니다. 그럼 어떻게 우리 코드를 이 주소로 보낼까요? 놀랍게도 기존 SDK 코드에서 baseURL 한 줄만 바꾸면 됩니다.

먼저 OpenAI SDK를 설치하고요.

$ bun add openai
$ npm install openai

평소 OpenAI를 부르던 코드에서 baseURL만 게이트웨이 주소로 갈아끼웁니다.

src/index.ts
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: env.OPENAI_API_KEY, // 키는 여전히 우리가 들고 있습니다 (BYOK)
  baseURL: `https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.GATEWAY_ID}/openai`,
});

const completion = await client.chat.completions.create({
  model: "gpt-4o",
  messages: [{ role: "user", content: "안녕하세요!" }],
});

apiKey에 우리 OpenAI 키가 그대로 들어간다는 점을 눈여겨보세요. 지도의 첫 번째 방법인 BYOK (No Store Key), Cloudflare 문서 표현으로는 ‘Request Headers’ 방식이 바로 이 모습입니다. 요청은 게이트웨이를 거쳐 OpenAI로 가고, 응답도 같은 길로 돌아오지만, 그 사이에 캐싱, 로깅, 속도 제한이 자동으로 얹힙니다. 우리 코드 입장에서 달라진 건 주소 한 줄뿐이고요.

키는 코드에 직접 적으면 안 되겠죠. Workers 환경이라면 Wrangler 시크릿으로 등록해 env로 꺼내 쓰는 게 정석입니다.

참고로 여기서는 외부 제공업체인 OpenAI를 예로 들었지만, Cloudflare 자체 추론 서비스인 Workers AI의 @cf/* 모델도 같은 게이트웨이로 태울 수 있어요. 그 방법은 글 뒤에서 따로 다룹니다.

게이트웨이 자체를 인증으로 잠그기

여기서 한 가지 빈틈이 보입니다. 게이트웨이 URL만 알면 누구나 우리 게이트웨이로 요청을 흘려보낼 수 있다면 곤란하겠죠. 그래서 AI Gateway는 게이트웨이 자체에 대한 인증을 따로 제공합니다. 인증을 켜면 게이트웨이 전용 토큰을 cf-aig-authorization 헤더에 실어야만 요청이 통과해요.

게이트웨이 인증 토큰 추가
import OpenAI from "openai";

const openai = new OpenAI({
  apiKey: env.OPENAI_API_KEY, // 제공업체 키 (OpenAI로 전달됨)
  baseURL: `https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.GATEWAY_ID}/openai`,
  defaultHeaders: {
    "cf-aig-authorization": `Bearer ${env.CF_AIG_TOKEN}`, // 게이트웨이 키 (Cloudflare가 검사)
  },
});

헤더가 두 개로 늘어난 게 핵심입니다. Authorization(또는 apiKey)은 게이트웨이를 통과해 제공업체로 전달되는 키이고, cf-aig-authorization은 게이트웨이 입구에서 Cloudflare가 검사하는 키예요. 둘의 역할이 다르니 별도로 관리해야 합니다. 이 토큰까지 갖춰야 비로소 “아무나 못 들어오는” 게이트웨이가 됩니다.

한 가지 더 짚어둘 게 있어요. 여기서 cf-aig-authorization에 싣는 “게이트웨이 토큰”은 사실 AI Gateway 권한을 가진 Cloudflare API 토큰입니다. 그래서 gateway.ai.cloudflare.com 쪽 엔드포인트에서는 cf-aig-authorization으로 싣지만, 글 뒤에서 볼 REST API(api.cloudflare.com)에서는 같은 토큰이 표준 Authorization 헤더로 들어가요. 같은 토큰을 헤더 이름만 바꿔 싣는 셈이죠.

다만 이 토큰은 계정 스코프라 특정 게이트웨이 하나로 권한을 좁힐 수 없다는 점은 알아둬야 해요. AI Gateway Run 권한이 있는 토큰이면 계정 안의 모든 게이트웨이로 요청을 보낼 수 있고, 저장해둔 BYOK 키까지 그대로 소비할 수 있습니다. 그러니 게이트웨이나 테넌트 사이를 진짜로 격리하려면 토큰 권한에 기대지 말고, 계정을 나누거나 Worker의 AI Gateway 바인딩을 쓰는 편이 안전합니다.

키를 게이트웨이에 맡기기

인증까지 챙겼으니 이제 키를 한 단계 내려놓을 차례입니다. 지도의 두 번째 방법, BYOK (Store Key)인데요. 대시보드에서 게이트웨이의 Provider Keys에 OpenAI나 Anthropic 키를 추가하면 Cloudflare Secrets Store에 암호화되어 보관됩니다. 그러면 프로젝트마다 키를 환경변수로 심을 필요가 사라지고, 키가 한곳에 모이니 회전(rotation)도 한 번에 끝나며, 평문 키가 여기저기 흩어질 일도 없어요. 저장한 키별로 예산이나 속도 제한 같은 정책을 거는 것도 가능하고요.

저장한 키는 게이트웨이 엔드포인트로 요청할 때 참조합니다. 요청에서 provider 키 헤더는 빼고, 대신 앞 절에서 만든 게이트웨이 인증 토큰(cf-aig-authorization)과 어느 저장 키를 쓸지 고르는 cf-aig-byok-alias만 실으면 진짜 키는 게이트웨이가 알아서 끼워 넣어요. 인증을 먼저 다룬 이유가 여기 있습니다. 저장 키는 인증을 켠 게이트웨이에서만 쓸 수 있거든요.

curl https://gateway.ai.cloudflare.com/v1/${CF_ACCOUNT_ID}/${GATEWAY_ID}/openai/chat/completions \
  -H "cf-aig-authorization: Bearer ${CF_AIG_TOKEN}" \
  -H "cf-aig-byok-alias: default" \
  -H "Content-Type: application/json" \
  -d '{ "model": "gpt-4o", "messages": [{ "role": "user", "content": "안녕하세요!" }] }'

위 예시의 cf-aig-byok-alias: default에서 default는 그냥 예시 이름이 아니라 특별한 이름입니다. 이 헤더를 아예 빼면 게이트웨이는 이름이 정확히 default인 저장 키를 자동으로 골라 써요. 그래서 헤더 없이 쓰고 싶다면 키를 default alias로 저장해두면 되고, 여러 키를 상황별로 바꿔 쓰려면 alias를 명시하면 됩니다. 여기엔 함정이 하나 있는데, 저장한 키가 하나뿐일 때는 존재하지도 않는 alias를 실어 보내도 그 단일 키로 조용히 처리되어 응답이 돌아오는 경우가 있어요. “요청이 돌아온다”가 “alias가 맞게 설정됐다”를 뜻하진 않으니, 잘못된 alias가 섞여도 한동안 모를 수 있습니다. 로그에서 실제로 어떤 키가 쓰였는지 한 번 확인해두는 게 안전해요.

한 가지 더, alias의 진짜 가치는 뒤에 있는 키가 서로 다를 때 나옵니다. 같은 키에 alias만 여러 개 달아두면 로그상 사용량 구분과 라우팅 라벨만 얻을 뿐이에요. 독립적인 폐기, 키별 한도, 유출 시 격리 같은 실질적 이점은 alias마다 다른 키를 넣어야 비로소 생깁니다. 프로젝트나 환경을 진짜로 분리하고 싶다면 라벨만 나눌 게 아니라 키 자체를 나눠야 하는 거죠.

키를 아예 내려놓기: REST API와 Unified Billing

마지막은 키를 아예 들지 않는 방법입니다. 들어가기 전에 지금까지 방식의 불편을 하나 짚을게요. 앞의 두 방법은 URL에 제공업체가 박혀 있어서(.../openai), OpenAI와 Anthropic, Google을 섞어 쓰기 시작하면 제공업체마다 baseURL을 바꿔가며 불러야 합니다. 이럴 때는 엔드포인트 하나에 model 값을 {provider}/{model} 형식으로 넣어 게이트웨이가 알아서 라우팅하게 하면 돼요.

예전에는 이걸 게이트웨이의 compat 엔드포인트(.../compat)로 했는데, 이 방식은 이제 deprecated됐습니다. Cloudflare는 같은 OpenAI 호환 기능을 REST API(api.cloudflare.com/client/v4/accounts/{account_id}/ai/v1/chat/completions)로 제공해요. 기존 compat 통합은 계속 동작하지만, 새로 만든다면 REST API를 쓰는 게 맞습니다.

src/index.ts
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: env.CLOUDFLARE_API_TOKEN, // OpenAI 키가 아니라 Cloudflare API 토큰
  baseURL: `https://api.cloudflare.com/client/v4/accounts/${env.CF_ACCOUNT_ID}/ai/v1`,
  defaultHeaders: { "cf-aig-gateway-id": env.GATEWAY_ID },
});

// OpenAI 모델
const a = await client.chat.completions.create({
  model: "openai/gpt-4o",
  messages: [{ role: "user", content: "TypeScript의 장점은?" }],
});

// Anthropic 모델 — 같은 코드, model만 변경
const b = await client.chat.completions.create({
  model: "anthropic/claude-sonnet-4-20250514",
  messages: [{ role: "user", content: "TypeScript의 장점은?" }],
});

응답 형식이 OpenAI 호환으로 통일되니, 제공업체별로 SDK를 따로 깔거나 파싱을 분기할 필요가 없어요. “가벼운 모델로 만들어보고 나중에 더 똑똑한 모델로 갈아끼우는” 실험이 model 문자열 하나 바꾸는 일이 됩니다.

코드의 cf-aig-gateway-id 헤더도 눈여겨보세요. REST API의 URL에는 게이트웨이 이름이 들어갈 자리가 없어서, 어느 게이트웨이를 거칠지는 이 헤더가 정합니다. 빼먹으면 요청이 계정의 default 게이트웨이로 조용히 흘러가, 캐싱, 로그, 정책이 내가 의도한 게 아닌 엉뚱한 게이트웨이에 붙어버려요. 특정 게이트웨이를 쓰고 있다면 이 헤더를 꼭 실어야 합니다.

그리고 결정적인 변화가 하나 있어요. apiKey에 들어가는 게 내 OpenAI 키가 아니라 Cloudflare API 토큰입니다. 앞서 게이트웨이 인증에서 본 그 계정 토큰과 같은 종류죠. 예전엔 내 provider 키가 요청에 실려 제공업체까지 갔는데, REST API에서는 Cloudflare가 인증과 결제를 대신 맡아요. 외부 모델 비용이 제공업체가 아니라 Cloudflare 크레딧에서 빠지는 거죠. 이게 지도의 세 번째 방법, Unified Billing입니다. Cloudflare가 제공업체 자격증명과 과금까지 맡으니, 우리는 크레딧만 충전해두면 돼요.

Workers 안이라면 REST API 대신 바인딩으로 부를 수도 있습니다.

src/index.ts
// Unified Billing: provider 키 없이, Cloudflare가 과금까지 처리
const resp = await env.AI.run(
  "openai/gpt-4.1-mini",
  { messages: [{ role: "user", content: "안녕하세요!" }] },
  { gateway: { id: "{gateway_id}" } },
);

그럼 게이트웨이는 BYOK와 Unified Billing을 어떻게 가를까요? 기준은 단순합니다. 요청에 쓸 provider 키가 있느냐예요. 헤더에 실었든 저장해뒀든 내 키가 마련돼 있으면 그 키로 호출하고 비용은 내가 제공업체에 냅니다. 아무 키도 없이 Cloudflare 토큰으로만 부르면 Unified Billing으로 떨어져 Cloudflare 크레딧에서 빠지고요. 지도의 표에서 엔드포인트가 갈렸던 것도 같은 이유예요. 내 키를 쓰면 gateway.ai.cloudflare.com, Cloudflare가 결제하면 api.cloudflare.com/.../ai로 가지만, 둘 다 같은 게이트웨이를 거쳐 캐싱과 로그는 똑같이 붙습니다.

한 가지만 짚어두면, provider 키가 코드에서 사라져도 게이트웨이에 접근하는 인증 자체는 남습니다. 인증을 켠 게이트웨이라면 그 토큰이, Unified Billing이라면 Cloudflare API 토큰이 필요하죠. 그러니 “비밀이 0이 된다”기보다 흩어져 있던 제공업체 키가 게이트웨이 한곳으로 모인다고 보는 게 정확해요. 참고로 Unified Billing은 외부 제공업체에만 적용되고, Workers AI @cf/* 모델은 그대로 Workers AI 요금으로 청구됩니다.

캐싱과 속도 제한으로 비용 다스리기

이제부터가 게이트웨이를 쓰는 진짜 이유입니다. 첫 번째는 캐싱이에요. 똑같은 프롬프트가 반복해서 들어오면, 모델을 다시 부르지 않고 저장해둔 응답을 바로 돌려줍니다. 그만큼 토큰 비용이 빠지고 응답도 즉시 나가죠. 자주 묻는 질문이나 고정된 분류 작업처럼 입력이 겹치는 경우에 효과가 큽니다.

캐싱은 대시보드에서 게이트웨이 단위로 켤 수도 있고, 요청마다 세밀하게 제어할 수도 있습니다. 예를 들어 특정 응답은 1시간만 캐싱하고 싶다면 캐시 수명(TTL)을 지정하면 돼요.

게이트웨이를 거치면 속도 제한도 공짜로 따라옵니다. “10분에 100건”처럼 한도를 걸어두면, 누군가 우리 키로 폭주하거나 버그로 요청이 무한히 나가는 사고를 게이트웨이 단에서 막아줍니다. 키가 새어 나가도 피해 규모를 게이트웨이가 1차로 잘라주는 셈이라, BYOK 방식에서 특히 든든한 안전장치예요.

속도 제한이 “요청 건수”를 막는 거라면, 요청 수가 아니라 “돈”을 기준으로 막고 싶을 때는 지출 한도(Spend Limits)가 있습니다. 토큰 사용량과 모델 단가로 실제 누적 비용을 계산해 예산을 넘기면 429로 요청을 끊어주는데, 모델이나 제공업체별로도, 사용자나 팀 같은 커스텀 메타데이터별로도 한도를 걸 수 있어요. Unified Billing이든 BYOK든 단가를 아는 모델이면 똑같이 적용됩니다. “이 사용자는 하루 $200까지”, “이 모델은 하루 $50까지” 같은 통제를 게이트웨이 단에서 거는 셈이라, 속도 제한과 짝을 이루는 비용 안전장치예요.

폴백과 재시도로 끊기지 않게 만들기

프로덕션에서 가장 무서운 건 제공업체가 잠깐 흔들릴 때입니다. OpenAI가 5xx를 뱉기 시작하면 우리 서비스도 같이 멈추니까요. AI Gateway의 유니버설 엔드포인트(Universal Endpoint)는 여기에 답을 줍니다. 제공업체를 순서대로 나열해두면, 앞 모델이 실패할 때 자동으로 다음 모델로 넘어가요.

이때는 OpenAI SDK 대신 게이트웨이 루트 주소로 직접 요청을 보냅니다. 본문에 제공업체 설정을 배열로 넣는다는 점만 다릅니다.

폴백과 재시도 설정
curl 'https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}' \
  --header 'Content-Type: application/json' \
  --data '[
    {
      "provider": "openai",
      "endpoint": "chat/completions",
      "headers": { "Authorization": "Bearer {OPENAI_KEY}", "Content-Type": "application/json" },
      "config": { "maxAttempts": 2, "retryDelay": 1000, "backoff": "exponential" },
      "query": { "model": "gpt-4o", "messages": [{ "role": "user", "content": "안녕!" }] }
    },
    {
      "provider": "workers-ai",
      "endpoint": "@cf/meta/llama-3.1-8b-instruct",
      "headers": { "Authorization": "Bearer {CF_TOKEN}", "Content-Type": "application/json" },
      "query": { "messages": [{ "role": "user", "content": "안녕!" }] }
    }
  ]'

위 예시는 먼저 OpenAI gpt-4o를 시도하고, 실패하면 Workers AI의 라마로 자동 전환합니다. 비싼 상용 모델을 1순위로 두되, 그게 죽으면 저렴한 엣지 모델로라도 서비스를 이어가는 전략이죠.

각 제공업체의 config에는 재시도 정책도 붙일 수 있습니다. maxAttempts는 최대 시도 횟수, retryDelay는 재시도 간격(밀리초), backoff는 간격을 늘려가는 방식입니다. constant는 일정 간격으로, exponential은 점점 간격을 벌리며 재시도해요. 일시적인 네트워크 오류나 순간적인 혼잡은 대부분 재시도로 넘어가니, 폴백까지 가기 전에 한 번 더 기회를 주는 셈입니다.

Dynamic Routing으로 라우팅을 대시보드에서 관리하기

방금 본 폴백 설정은 강력하지만 한 가지 한계가 있어요. 라우팅 규칙이 매 요청 본문에 박혀 있다는 점입니다. 제공업체 순서나 조건을 바꾸려면 코드를 고치고 다시 배포해야 하죠. Dynamic Routing은 이 라우팅 로직을 코드 밖, 대시보드로 빼냅니다. 시각적 빌더로 흐름(route)을 한 번 정의해두면 앱은 그 route 이름만 부르면 돼요.

route는 노드를 연결해 만드는 이름 붙은, 버전 관리되는 흐름입니다. 시작 노드에서 출발해 여러 노드를 거쳐 가는데, 대표적인 노드는 이렇습니다.

  • 조건(Conditional): 요청에 실어 보낸 메타데이터로 분기합니다. 예를 들어 유료 사용자는 gpt-4o로, 무료 사용자는 저렴한 모델로 보내는 식이에요.
  • 퍼센트(Percentage): 트래픽을 비율로 쪼갭니다. “10%만 새 모델로” 같은 A/B 테스트나 점진적 롤아웃에 쓰고요.
  • 속도/예산 한도(Rate/Budget Limit): 한도를 넘으면 자동으로 폴백 노드로 넘깁니다. 특정 모델에 월 예산을 걸어두는 게 가능해져요.
  • 모델(Model): 실제 제공업체와 모델을 고릅니다. 방금 본 폴백 체인도 여기서 모델 노드를 이어 만들 수 있습니다.

각 수정은 draft 버전으로 쌓이고, 배포하면 즉시 적용되며 문제가 생기면 곧바로 이전 버전으로 롤백할 수 있어요. 라우팅을 바꾸는 데 코드 배포가 필요 없어진 겁니다.

만든 route는 model 값에 dynamic/<route-이름>을 넣어 부릅니다. 앱 코드 입장에선 모델 이름 하나가 route 전체를 가리키는 셈이에요.

curl https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/compat/chat/completions \
  -H "cf-aig-authorization: Bearer {CF_AIG_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{ "model": "dynamic/my-route", "messages": [{ "role": "user", "content": "안녕하세요!" }] }'

여기서 Dynamic Routing은 앞서 깔아둔 두 가지를 전제로 합니다. route의 모델 노드가 호출에 쓸 키가 필요하니 저장형 키(BYOK)를 미리 등록해두어야 하고, 게이트웨이 인증도 켜져 있어야 해요. 그래서 cf-aig-authorization 토큰으로 게이트웨이 엔드포인트에 부릅니다.

한 가지 솔직히 짚어둘 게 있어요. 위 호출 경로가 바로 앞서 REST API 절에서 deprecated라고 한 그 compat입니다. 그런데 Dynamic Routing은 현재 이 경로로만 호출돼요. 공식 예제가 curl, SDK, 바인딩 모두 compat을 쓰고, REST API(api.cloudflare.com/.../ai)로 부르는 길은 문서에 아직 없습니다. route가 저장형 키와 인증을 전제로 해서 Unified Billing용 REST API와는 결이 다른 탓인데, 어쨌든 “일반 호출은 REST API로 옮겨가라면서 Dynamic Routing은 deprecated된 compat을 쓰게 하는” 어정쩡함이 남아 있습니다. 이 부분은 Cloudflare 쪽 정리를 좀 더 기다려봐야 할 회색지대예요.

응답이 실제로 어디로 갔는지는 응답 헤더의 cf-aig-modelcf-aig-provider로 확인할 수 있습니다. 조건 분기나 퍼센트 분할이 의도대로 도는지 점검할 때 유용해요.

정리하면, 폴백 한 줄을 넘어 “유료 사용자만 고급 모델”, “트래픽 5%로 신모델 실험”, “이 모델은 월 예산까지만” 같은 운영 정책을 코드는 그대로 둔 채 대시보드에서 거는 것, 그게 Dynamic Routing입니다.

로그와 분석으로 들여다보기

게이트웨이를 거치는 모든 요청은 자동으로 기록됩니다. 대시보드에서 요청 수, 토큰 사용량, 비용, 응답 지연 시간, 성공률, 캐시 적중률을 한눈에 볼 수 있어요. 제공업체가 여럿이어도 한 화면에서 비교할 수 있다는 게 큰 장점입니다. 어떤 모델이 느린지, 어디서 비용이 새는지, 캐싱이 실제로 먹히는지를 따로 코드를 짜지 않고도 확인할 수 있거든요.

비용 추적을 더 정확히 하고 싶다면, 요청별로 단가를 직접 지정할 수도 있습니다. 게이트웨이가 모르는 모델이나 우리만의 특별 요율을 쓸 때 유용해요. cf-aig-custom-cost 헤더에 입력과 출력 토큰당 단가를 넣으면 그 값으로 비용이 집계됩니다.

요청별 커스텀 단가 지정
curl https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/openai/chat/completions \
  --header "Authorization: Bearer $OPENAI_KEY" \
  --header 'Content-Type: application/json' \
  --header 'cf-aig-custom-cost: {"per_token_in":0.000001,"per_token_out":0.000002}' \
  --data '{ "model": "gpt-4o-mini", "messages": [{ "role": "user", "content": "안녕!" }] }'

여기에 더해 유해 콘텐츠나 민감 정보를 걸러내는 가드레일 같은 통제 기능도 게이트웨이 단에서 적용할 수 있습니다. 모델 호출 코드를 건드리지 않고 정책을 한곳에서 거는 것, 그게 프록시 레이어의 매력이에요.

Workers AI 모델도 게이트웨이로

지금까지는 OpenAI나 Anthropic 같은 외부 제공업체를 게이트웨이에 태웠는데요. Cloudflare 자체 추론 서비스인 Workers AI@cf/* 모델도 똑같이 게이트웨이를 거칠 수 있습니다. 방법은 두 가지예요.

Cloudflare Workers 위에서 작업 중이라면 바인딩이 가장 간단합니다. URL을 조립할 것도 없이 env.AI.run()의 세 번째 인자로 gateway 설정만 넘기면, 같은 추론 호출이 자동으로 게이트웨이를 거쳐요.

src/index.ts
export default {
  async fetch(request, env): Promise<Response> {
    const response = await env.AI.run(
      "@cf/meta/llama-3.1-8b-instruct",
      { prompt: "Cloudflare로 AI 추론을 하면 좋은 점은?" },
      {
        gateway: {
          id: "{gateway_id}",
          skipCache: false, // 캐싱 사용
          cacheTtl: 3360, // 캐시 수명(초)
        },
      },
    );
    return Response.json(response);
  },
} satisfies ExportedHandler<Env>;

추론 코드는 그대로 두고 gateway 옵션만 더했을 뿐인데, 이제 이 호출에도 캐싱, 로깅, 분석이 전부 따라붙습니다. 참고로 이 바인딩은 @cf/*뿐 아니라 openai/gpt-4o 같은 외부 모델도 받기 때문에, Workers 안에서는 이 한 줄로 모든 제공업체를 다룰 수 있어요.

Workers가 아닌 환경이라면 HTTP로도 됩니다. 앞서 REST API 절에서 쓴 Cloudflare 통합 AI API(api.cloudflare.com/.../ai)에 model@cf/meta/llama-3.1-8b-instruct처럼 @cf/ 모델로 바꾸면 돼요. 인증(Cloudflare API 토큰)과 cf-aig-gateway-id 헤더는 똑같습니다.

어느 쪽이든 Workers AI 모델도 외부 제공업체와 똑같이 캐싱, 폴백, 로그의 혜택을 그대로 받습니다. Workers AI로 시작한 프로젝트가 프로덕션으로 넘어갈 때 가장 손이 덜 가는 경로예요.

요금은 어떻게 되나요

가장 반가운 소식은 AI Gateway의 핵심 기능이 무료라는 점입니다. 대시보드 분석, 캐싱, 속도 제한은 추가 비용 없이 쓸 수 있어요. 다시 강조하지만 게이트웨이는 모델을 팔지 않으므로, 토큰 비용은 어차피 제공업체에 직접 내는 것이고 게이트웨이가 그 위에 마진을 얹지 않습니다.

비용이 생기는 지점은 로그를 많이, 오래 보관할 때입니다. 로그는 Workers 무료 플랜에서 전체 게이트웨이를 합쳐 10만 건, 유료 플랜에서는 게이트웨이당 1,000만 건까지 저장됩니다. 웬만한 사용량은 이 한도 안에 들어오고요. 로그를 외부로 실시간 전송하는 Logpush는 유료 플랜에서만 제공됩니다.

정리하면, 일상적인 캐싱과 모니터링, 폴백은 사실상 공짜로 누리고, 비용은 로그를 얼마나 길게 쌓아두느냐 정도에서만 신경 쓰면 됩니다.

마치며

지금까지 Cloudflare AI Gateway를 캐싱과 폴백, 동적 라우팅, 로그 분석, 비용 추적, 인증까지 살펴봤습니다. 결국 AI Gateway는 모델을 파는 가게(Workers AI)도, 모델을 대신 사다 주는 리셀러(Void)도 아닌 프록시라는 점이 가장 중요해요. 우리 키는 우리가 들고(BYOK), 게이트웨이는 그 길목에서 캐싱과 폴백, 로그를 책임질 뿐입니다. 그래서 기존 코드에서 baseURL 한 줄만 바꾸면 곧바로 프로덕션급 제어판을 얻는 거죠.

여기서 한 걸음 더 나아가고 싶다면 두 방향이 있습니다. Cloudflare 자체 모델로 비용을 더 아끼고 싶다면 Workers AI를, 키 관리와 사용량 계량까지 통째로 위임하고 싶다면 그 위에 리셀러 레이어를 얹은 Void AI를 살펴보세요. AWS 환경이라면 비슷한 역할을 하는 AWS Bedrock도 비교해볼 만하고요.

더 자세한 내용은 AI Gateway 공식 문서를 참고하세요.

This work is licensed under CC BY 4.0CCBY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord