Skip to content

필사 모드: OpenRouter 사용법 — 하나의 API로 300개 이상 LLM 쓰기

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

개요

여러 LLM을 다루기 시작하면 금세 같은 문제에 부딪힌다. OpenAI, Anthropic, Google, Meta의 모델을 섞어 쓰고 싶은데 각 공급자마다 별도의 SDK, 별도의 인증, 별도의 결제, 별도의 요청 스키마가 있다. 모델을 하나 바꾸려면 클라이언트 코드를 다시 짜야 하고, 공급자 하나가 다운되면 서비스가 멈춘다.

OpenRouter는 이 지점을 정리한다. 하나의 엔드포인트와 하나의 API 키로 60개 이상 공급자의 300개 이상(최대 약 500개) 모델에 접근할 수 있고, 인터페이스는 OpenAI API와 완전히 호환된다. 즉 이미 OpenAI SDK를 쓰고 있다면 base_urlapi_key만 바꿔서 그대로 Claude, Gemini, Llama를 호출할 수 있다.

이 글은 OpenRouter를 왜 게이트웨이로 쓰는지, 핵심 개념은 무엇인지, 그리고 키 발급부터 라우팅/폴백/스트리밍까지 실제로 어떻게 붙이는지를 단계별로 다룬다.

1. 왜 게이트웨이 계층이 필요한가

LLM 애플리케이션은 처음에는 단일 모델로 충분해 보인다. 하지만 운영에 들어가면 공통 요구가 생긴다.

  • 작업마다 최적 모델이 다르다. 요약은 저렴한 모델로, 복잡한 추론은 프런티어 모델로 나누고 싶다.
  • 한 공급자에 종속되고 싶지 않다. 가격이 오르거나 장애가 나면 다른 모델로 옮겨야 한다.
  • 결제와 사용량을 한곳에서 보고 싶다. 공급자마다 대시보드를 따로 여는 것은 비효율적이다.

게이트웨이는 이 문제를 애플리케이션 코드 밖으로 끌어낸다. 클라이언트는 항상 같은 엔드포인트에 요청하고, 모델을 바꾸는 것은 문자열 하나(model 필드)를 교체하는 일이 된다. 공급자 라우팅, 폴백, 과금 집계는 게이트웨이가 처리한다.

직접 호출 방식OpenRouter 게이트웨이
공급자마다 SDK/인증/스키마가 다름하나의 엔드포인트, 하나의 키, OpenAI 호환 스키마
모델 교체 시 코드 변경model 문자열만 교체
공급자별 결제 계정크레딧 하나로 통합 과금
장애 시 직접 폴백 구현models 배열/provider 라우팅으로 자동 폴백

2. 핵심 개념

하나의 엔드포인트

모든 호출은 하나의 베이스 URL로 간다.

https://openrouter.ai/api/v1

채팅 완성(chat completions)은 https://openrouter.ai/api/v1/chat/completions로 요청한다. 인증은 표준 Bearer 토큰 헤더를 쓴다.

Authorization: Bearer YOUR_OPENROUTER_API_KEY

모델 ID

모델은 provider/model 형식의 문자열로 지정한다. 대표적인 예는 다음과 같다.

모델 ID설명
openai/gpt-4oOpenAI GPT-4o
anthropic/claude-3.5-sonnetAnthropic Claude 3.5 Sonnet
google/gemini-2.0-flash-expGoogle Gemini 2.0 Flash (실험)
meta-llama/llama-3.3-70b-instructMeta Llama 3.3 70B Instruct
openrouter/auto자동 라우터 — OpenRouter가 요청에 맞는 모델을 선택

무료 변형은 ID 끝에 :free가 붙는다. 전체 목록과 각 모델의 가격/컨텍스트 길이는 모델 페이지에서 확인할 수 있고, 프로그램적으로는 /api/v1/models 엔드포인트로 조회한다.

크레딧과 무료 티어

무료 티어는 하루 약 50건의 무료 요청과 25개 이상의 무료 모델(ID가 :free로 끝나는)을 제공한다. 유료 사용은 크레딧 기반의 종량제이며, 실제로 실행된 모델의 토큰 단위로 과금된다. 크레딧을 충전할 때 약 5.5%의 소액 수수료가 붙지만, 호출 자체의 비용은 그 요청을 실제로 처리한 모델의 단가를 따른다.

3. 키 발급과 첫 curl 요청

먼저 openrouter.ai/keys에서 API 키를 발급한다. 키를 환경 변수에 넣어 두면 편하다.

export OPENROUTER_API_KEY="sk-or-여기에-발급받은-키"

이제 가장 단순한 요청을 curl로 보내 본다.

curl https://openrouter.ai/api/v1/chat/completions \
  -H "Authorization: Bearer $OPENROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "openai/gpt-4o", "messages": [{"role":"user","content":"Hello"}] }'

응답은 OpenAI의 chat completions와 동일한 구조로 돌아온다. 모델을 바꾸고 싶으면 model 값만 anthropic/claude-3.5-sonnet이나 google/gemini-2.0-flash-exp로 교체하면 된다. 나머지 요청 형태는 그대로다.

4. OpenAI SDK 드롭인

OpenRouter가 OpenAI 호환이라는 말의 실질적 의미는, 공식 OpenAI SDK를 그대로 쓰되 접속 지점만 OpenRouter로 돌린다는 것이다.

Python

from openai import OpenAI

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key="YOUR_OPENROUTER_API_KEY",
)

completion = client.chat.completions.create(
    model="anthropic/claude-3.5-sonnet",
    messages=[
        {"role": "user", "content": "게이트웨이가 왜 유용한지 한 문장으로 설명해줘"},
    ],
)

print(completion.choices[0].message.content)

OpenAI(...) 생성자에서 base_urlapi_key만 OpenRouter 값으로 바꾸면, 이후 client.chat.completions.create(...) 호출 방식은 평소와 완전히 같다. 응답 본문은 completion.choices[0].message.content로 읽는다.

TypeScript

TypeScript도 같은 아이디어다. openai npm 클라이언트에 baseURLapiKey만 지정한다.

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://openrouter.ai/api/v1',
  apiKey: process.env.OPENROUTER_API_KEY,
});

const completion = await client.chat.completions.create({
  model: 'anthropic/claude-3.5-sonnet',
  messages: [{ role: 'user', content: 'Hello' }],
});

console.log(completion.choices[0].message.content);

SDK를 쓰지 않고 순수 fetch로 POST 해도 된다. 엔드포인트와 헤더만 맞추면 동일하게 동작한다.

const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'openai/gpt-4o',
    messages: [{ role: 'user', content: 'Hello' }],
  }),
});

const data = await res.json();
console.log(data.choices[0].message.content);

5. 모델 선택과 라우팅

공급자 라우팅 오버라이드

같은 모델을 여러 공급자가 제공하는 경우가 있다. 이때 OpenRouter는 기본적으로 가격, 지연시간, 가용성을 기준으로 공급자를 자동 선택한다. 특정 정책을 강제하고 싶으면 요청에 provider 필드를 넣어 오버라이드한다. 예를 들어 공급자 우선순위(order), 폴백 허용 여부(allow_fallbacks), 정렬 기준(sort) 등을 지정할 수 있다.

{
  "model": "meta-llama/llama-3.3-70b-instruct",
  "messages": [{ "role": "user", "content": "Hello" }],
  "provider": {
    "sort": "throughput",
    "allow_fallbacks": true
  }
}

모델 레벨 폴백 — models 배열

공급자 라우팅과 별개로, 여러 모델을 순서대로 시도하는 폴백도 지정할 수 있다. models 배열을 넘기면 앞에서부터 차례로 시도한다.

{
  "models": [
    "openai/gpt-4o",
    "anthropic/claude-3.5-sonnet",
    "meta-llama/llama-3.3-70b-instruct"
  ],
  "messages": [{ "role": "user", "content": "Hello" }]
}

폴백은 레이트 리밋, 다운타임, 컨텍스트 길이 초과, 모더레이션 오류 같은 상황에서 트리거된다. 중요한 점은 과금 방식이다. 여러 모델을 나열해도 실제로 실행되어 응답을 만든 모델의 토큰에 대해서만 과금된다. 시도했다가 실패한 모델에는 비용이 붙지 않는다.

자동 라우터 — openrouter/auto

모델을 직접 고르기 애매할 때는 openrouter/automodel 값으로 지정하면 된다. OpenRouter가 요청 내용을 보고 적절한 모델로 라우팅한다. 초기 프로토타이핑이나 "일단 잘 되는 모델"이 필요한 단계에서 유용하다.

6. 스트리밍 / 툴 호출 / 구조화 출력

이 세 가지 기능은 모두 OpenAI API와 같은 방식으로 동작한다.

스트리밍

stream=True(TypeScript는 stream: true)를 넘기면 응답을 청크 단위로 받는다.

stream = client.chat.completions.create(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "짧은 시 하나 써줘"}],
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="")

툴/함수 호출

툴(함수) 호출도 OpenAI 스키마 그대로다. tools 배열에 함수 정의를 넣고, 모델이 반환한 tool_calls를 처리한 뒤 결과를 다시 메시지로 넣어 준다.

{
  "model": "openai/gpt-4o",
  "messages": [{ "role": "user", "content": "서울 날씨 알려줘" }],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "도시의 현재 날씨를 반환한다",
        "parameters": {
          "type": "object",
          "properties": { "city": { "type": "string" } },
          "required": ["city"]
        }
      }
    }
  ]
}

구조화 출력

JSON 스키마 기반 구조화 출력도 지원한다. response_formatjson_schema를 지정하면 응답이 해당 스키마를 따르도록 강제할 수 있다.

{
  "model": "openai/gpt-4o",
  "messages": [{ "role": "user", "content": "샘플 사용자 하나 만들어줘" }],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "user",
      "schema": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "age": { "type": "integer" }
        },
        "required": ["name", "age"]
      }
    }
  }
}

7. 어트리뷰션 헤더와 사용량 확인

OpenRouter는 openrouter.ai의 리더보드(어떤 앱이 어떤 모델을 많이 쓰는지)를 위한 선택적 어트리뷰션 헤더를 지원한다. 필수는 아니며, 넣으면 자기 앱을 리더보드에 노출할 수 있다.

  • HTTP-Referer — 자기 사이트 URL
  • X-Title — 자기 앱 이름

OpenAI SDK에서는 이 헤더를 기본 헤더로 넘긴다. Python은 default_headers, JavaScript는 defaultHeaders를 쓴다.

client = OpenAI(
    base_url="https://openrouter.ai/api/v1",
    api_key="YOUR_OPENROUTER_API_KEY",
    default_headers={
        "HTTP-Referer": "https://your-site.example",
        "X-Title": "My App",
    },
)
const client = new OpenAI({
  baseURL: 'https://openrouter.ai/api/v1',
  apiKey: process.env.OPENROUTER_API_KEY,
  defaultHeaders: {
    'HTTP-Referer': 'https://your-site.example',
    'X-Title': 'My App',
  },
});

사용 가능한 모델 목록과 메타데이터는 /api/v1/models 엔드포인트로 조회한다.

curl https://openrouter.ai/api/v1/models \
  -H "Authorization: Bearer $OPENROUTER_API_KEY"

8. 실무 팁과 주의점

  • 비용은 항상 실제로 실행된 하위 모델의 단가를 따른다. openrouter/automodels 폴백을 쓸 때는 어떤 모델이 실행될지에 따라 단가가 달라진다는 점을 염두에 둔다.
  • :free 모델은 무료지만 레이트 리밋이 낮다. 프로덕션 트래픽을 무료 모델에만 의존하면 금방 한도에 걸린다. 프로토타이핑이나 저부하 용도로 쓰는 것이 안전하다.
  • API 키는 반드시 서버 사이드에 둔다. 브라우저 번들이나 클라이언트 코드에 키를 노출하면 그대로 도난당한다. 프런트엔드에서 호출해야 한다면 자체 백엔드를 프록시로 두고 그 뒤에서 OpenRouter를 호출한다.
  • 모델을 바꿔도 응답 스키마가 OpenAI 호환으로 유지되므로, 파싱 코드를 모델마다 다시 짤 필요는 없다. 다만 모델별로 툴 호출이나 구조화 출력 지원 수준이 다를 수 있으니, 새 모델로 바꿀 때는 해당 기능을 실제로 확인한다.
  • 폴백 배열은 안정성을 높이지만, 뒤쪽에 비싼 모델을 두면 장애 시 비용이 튈 수 있다. 폴백 순서는 가용성뿐 아니라 단가도 고려해서 배치한다.

참고 자료

현재 단락 (1/175)

여러 LLM을 다루기 시작하면 금세 같은 문제에 부딪힌다. OpenAI, Anthropic, Google, Meta의 모델을 섞어 쓰고 싶은데 각 공급자마다 별도의 SDK, 별도의...

작성 글자: 0원문 글자: 6,724작성 단락: 0/175