Felo API PlatformFelo API Platform

비용

사용량이 늘어도 모델 비용을 예측 가능하게 유지하기

모든 작업을 같은 OpenAI-compatible endpoint로 보내고 작업을 수행할 수 있는 가장 저렴한 모델로 라우팅하며 각 응답이 반환하는 토큰 사용량을 기록합니다.

소요 시간20분난이도중급업데이트2026-10-29
대상플랫폼 팀API 제품 책임자엔지니어링 리드

프롬프트 복사

대괄호 항목을 바꾼 뒤 Claude Code, Codex 또는 터미널에 접근할 수 있는 AI에 붙여넣으세요. Felo 스킬을 설치하고 크레딧을 쓰기 전에 확인한 뒤 워크플로를 실행합니다.

우리 모델 지출을 검토하고 이 작업들을 위한 라우팅 계획을 제안해 주세요: [작업 또는 기능 목록].

제약: 월 예산 [금액], 지연 목표 [목표], 품질 기준 [기준].
무엇이든 추천하기 전에 라이브 모델 카탈로그를 읽으세요.

다음을 작성해 주세요.
1. 작업 -> 모델 추천 표.
2. 한 단계 내릴 수 있는 작업과 품질 위험.
3. 작업과 기능별 사용 로그 계획.
4. 프로덕션을 바꾸기 전에 측정할 것.

규칙:
- 현재 모델 ID와 가격을 라이브 카탈로그에서 읽으세요. 기억에서 요금을 인용하지 마세요.
- 프로덕션 설정을 바꾸거나 실제 트래픽을 보내지 마세요.
- 테스트 호출에 크레딧을 쓰기 전에 먼저 물어보세요.
- 롤백 계획과 기능별 지출 한도를 유지하세요.
- 계획을 ./routing-plan.md에 쓰고 채팅에서 요약해 주세요.

복사 내용에는 설치 단계와 안전 규칙도 포함됩니다.

완성되는 것

실행 시 예제 출력입니다. 실시간 생성이 아닙니다.

라우팅 계획 예시(발췌)
작업현재제안위험
티켓 분류강한 모델빠른 모델낮음
답변 초안강한 모델강한 모델없음
체인지로그 요약강한 모델빠른 모델중간

바꾸기 전에: 일주일 동안 작업별 사용량을 기록하고 무작위 50개 출력에서 품질을 비교한다.

실제 호출

개발자용: 같은 워크플로를 실행 순서대로 담은 복사 가능한 코드.

실제 API 호출 보기
  1. 모든 작업을 같은 endpoint로 보내기

    OpenAI-compatible endpoint와 키를 통일해 공급자마다 SDK를 나누지 않습니다.

    routing.ts
    const BASE_URL = "https://openapi.felo.ai/api/v1";
    const headers = {
      Authorization: `Bearer ${process.env.FELO_API_KEY}`,
      "Content-Type": "application/json",
    };
    
    type Completion = {
      choices: { message: { content: string } }[];
      usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number };
    };
    
    async function chat(model: string, prompt: string): Promise<Completion> {
      const res = await fetch(`${BASE_URL}/chat/completions`, {
        method: "POST",
        headers,
        body: JSON.stringify({ model, messages: [{ role: "user", content: prompt }] }),
      });
      if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
      return res.json() as Promise<Completion>;
    }
    
    const summary = await chat(
      "claude-haiku-5-5",
      "Summarize this changelog in three bullets: …",
    );
    console.log(summary.choices[0].message.content);

    예제 출력

    {
      "choices": [{ "message": { "content": "• …" } }],
      "usage": { "prompt_tokens": 812, "completion_tokens": 96, "total_tokens": 908 }
    }
  2. 작업별로 모델 라우팅하기

    작업 복잡도로 빠른 모델이나 강한 모델을 고르고 먼저 현재 기준을 기록합니다.

    routing.ts
    type TaskKind = "classify" | "extract" | "summarize" | "draft" | "review";
    
    const MODEL_ROUTES: Record<TaskKind, string> = {
      classify: "claude-haiku-5-5",
      extract: "claude-haiku-5-5",
      summarize: "claude-haiku-5-5",
      draft: "claude-sonnet-5-5",
      review: "claude-sonnet-5-5",
    };
    
    export async function route(kind: TaskKind, prompt: string) {
      const model = MODEL_ROUTES[kind];
      const result = await chat(model, prompt);
      return {
        model,
        text: result.choices[0].message.content,
        usage: result.usage,
      };
    }

    예제 출력

    {
      "model": "claude-haiku-5-5",
      "text": "• …",
      "usage": { "prompt_tokens": 812, "completion_tokens": 96, "total_tokens": 908 }
    }
  3. 사용량을 읽고 기록하기

    각 응답의 usage를 읽어 기능별로 token과 지연을 기록합니다.

    routing.ts
    type UsageRecord = {
      model: string;
      kind: TaskKind;
      promptTokens: number;
      completionTokens: number;
      totalTokens: number;
    };
    
    const usageLog: UsageRecord[] = [];
    
    function logUsage(kind: TaskKind, model: string, usage: Completion["usage"]) {
      const entry: UsageRecord = {
        model,
        kind,
        promptTokens: usage.prompt_tokens,
        completionTokens: usage.completion_tokens,
        totalTokens: usage.total_tokens,
      };
      usageLog.push(entry);
      console.log(JSON.stringify(entry)); // ship this line to your metrics store
      return entry;
    }
    
    export function tokensByModel() {
      return usageLog.reduce<Record<string, number>>((totals, entry) => {
        totals[entry.model] = (totals[entry.model] ?? 0) + entry.totalTokens;
        return totals;
      }, {});
    }
    
    const result = await route("summarize", "Summarize this changelog in three bullets: …");
    logUsage("summarize", result.model, result.usage);
    console.log(tokensByModel());

    예제 출력

    {"model":"claude-haiku-5-5","kind":"summarize","promptTokens":812,"completionTokens":96,"totalTokens":908}
    { "claude-haiku-5-5": 18234, "claude-sonnet-5-5": 4201 }
  4. 하나의 키로 모든 공급자 다루기

    같은 Felo 키로 카탈로그의 모델을 호출해 공급자 관리를 줄입니다.

    routing.ts
    async function chatOnChannel(model: string, prompt: string, channel?: string) {
      const res = await fetch(`${BASE_URL}/chat/completions`, {
        method: "POST",
        headers: {
          ...headers,
          ...(channel ? { "X-Felo-Channel": channel } : {}),
        },
        body: JSON.stringify({ model, messages: [{ role: "user", content: prompt }] }),
      });
      if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
      return res.json() as Promise<Completion>;
    }
    
    // Leave `channel` undefined for the default supplier.
    export function routePinned(kind: TaskKind, prompt: string, channel?: string) {
      return chatOnChannel(MODEL_ROUTES[kind], prompt, channel);
    }
  5. 가격은 라이브 카탈로그에서 읽기

    요금을 고정하지 않고 모델 카탈로그와 가격 페이지에서 현재 가격을 읽습니다.

    routing.ts
    type Catalog = {
      data: { id: string; owned_by: string; model_kind: string }[];
    };
    
    export async function chatModelIds() {
      const res = await fetch(
        `${BASE_URL}/models?exclude_video_and_decisions=true`,
        { headers },
      );
      if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
      const catalog = (await res.json()) as Catalog;
      return catalog.data
        .filter((model) => model.model_kind === "chat")
        .map((model) => model.id);
    }
    
    // Price `tokensByModel()` against https://openapi.felo.ai/pricing and
    // https://openapi.felo.ai/models. Keep the links, not the rates, in your code.

    예제 출력

    ["claude-haiku-5-5", "claude-sonnet-5-5"]

포함되는 것

  • 통일된 endpoint와 키
  • 작업별 빠른/강한 모델 선택
  • 각 응답의 토큰 사용량 로그
  • 코드에 고정하지 않고 라이브 카탈로그에서 읽는 가격

하지 않는 것

  • 청구서 절감을 보장하지 않음
  • 프로덕션 트래픽을 자동 전환하지 않음
  • 모델 id와 가격은 변하므로 항상 라이브 카탈로그를 읽음
  • 품질 평가를 대신하지 않음. 자체 샘플 필요
  • 청구나 관측 시스템을 대체하지 않음

자주 묻는 질문

청구서가 줄어드나요?

어떤 작업을 더 저렴한 계층으로 내릴 수 있는지 제안하지만 샘플 출력을 비교하고 사용량을 기록해야 절감이 현실이 됩니다.

자동으로 모델을 호출하나요?

테스트 호출을 승인할 때만 합니다. 기본은 카탈로그를 읽고 제안을 쓰며 프로덕션 트래픽은 건드리지 않습니다.

어떤 모델이 더 저렴한지 어떻게 알나요?

라이브 모델 카탈로그와 가격 페이지를 읽습니다. 가격이 변하므로 워크플로는 요금을 고정하지 않습니다.

공급자를 고정할 수 있나요?

네, 지연이 가격보다 중요할 때 가능합니다. 고정은 선택이지 재시도 전략이 아니며 fallback은 직접 추가합니다.

무엇을 기록해야 하나요?

호출마다 작업, 기능, 모델, prompt token, completion token, 총 token, 지연을 기록합니다. 기능별로 집계한 뒤 예산을 정합니다.

리뷰 한 번 비용은?

카탈로그 읽기는 모델 호출을 만들지 않습니다. 테스트 호출은 사용량 기반이며 프롬프트는 호출 전에 승인을 요청합니다.

관련 워크플로

내 방식으로 바꾸기

같은 레시피를 실제 배포 방식에 맞게 조정합니다.

먼저 기록만 하기

프로덕션 전에 일주일 사용량을 기록한 뒤 다운그레이드를 판단합니다.

기능별 예산 정하기

각 기능에 월 상한을 두고 초과 시 경고하거나 축소합니다.

지연 우선으로 공급자 고정

지연이 가격보다 중요하면 공급자를 고정하고 자체 fallback을 더합니다.

이 레시피의 배경이 되는 참고 페이지와 다음에 볼 곳.