마루옵스서버 · 네트워크 · AI 기술 노트

소개문의

[Local LLM 구축하기 24] OpenRouter API와 요금·전송 경로 확인

CERN 데이터센터 건물

참고 사진 · CERN 데이터센터 건물 · 사진: Unnerving duck / 원본 / CC BY-SA 4.0 · 목록용 크기 조정

Local LLM 구축하기 · 24/30 · 에이전트와 연결

전체 연재와 학습 순서 · 실습 코드 ZIP

OpenRouter를 사용하면 여러 외부 모델 제공자에 공통 API로 요청할 수 있습니다. 모델은 외부에서 실행되므로 자료가 어디로 가는지와 요금을 함께 확인해야 해요. 가상의 질문으로 호출 과정을 살펴보겠습니다.

Python과 별도의 OpenRouter 계정·API 키가 필요합니다. 비용과 제공자 조건을 확인한 뒤 실습용 키를 준비합니다.

요청 경로와 가격 단위

요청은 내 프로그램에서 OpenRouter를 거쳐 선택된 모델 제공자로 이동합니다. 모델 ID와 입력·출력 단가, 라우팅, 데이터 처리 조건을 확인합니다. 무료 표시가 있더라도 가용성과 호출 제한은 따로 확인해야 합니다.

특정 요금을 이 글의 고정 기준으로 사용하지 않습니다. 카탈로그 API와 서비스 화면은 가격 단위가 다를 수 있어요. 토큰당 단가를 100만 토큰당 가격으로 환산할 때는 100만을 곱해 비교합니다.

실습용 키와 한도

공식 사이트에서 계정과 필요한 크레딧을 준비합니다. 실습용 API 키를 발급하고 가능한 지출 제한을 설정합니다. 키는 본문이나 스크린샷, 클라이언트 JavaScript에 넣지 않습니다. 요청에는 가상의 질문만 사용합니다.

예제는 getpass로 키 입력을 화면에서 가립니다. 환경변수를 쓰는 경우에도 공유 셸의 기록이나 진단 출력에 노출되지 않게 관리합니다. 이미 노출된 키는 파일에서 지우는 데 그치지 않고 폐기한 뒤 다시 발급합니다.

현재 모델 ID로 API 호출

import getpass
import json
import os
import urllib.request

ROOT = "https://openrouter.ai/api/v1"

def catalog():
    with urllib.request.urlopen(ROOT + "/models", timeout=30) as response:
        return json.load(response)["data"]

def cloud_chat(question, model):
    models = {m["id"]: m for m in catalog()}
    if model not in models:
        raise ValueError("Model ID is not in the current catalog")
    key = os.environ.get("OPENROUTER_API_KEY") or getpass.getpass("OpenRouter API key: ")
    if not key:
        raise ValueError("API key is required")
    body = {"model": model, "messages": [{"role": "user", "content": question}],
            "max_tokens": 256, "stream": False}
    req = urllib.request.Request(ROOT + "/chat/completions",
        data=json.dumps(body, ensure_ascii=False).encode("utf-8"),
        headers={"Authorization": "Bearer " + key,
                 "Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=120) as response:
        result = json.load(response)
    return result["choices"][0]["message"]["content"], result.get("usage", {})

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--list", action="store_true")
    args = parser.parse_args()
    if args.list:
        rows = [m for m in catalog() if "qwen" in m["id"].lower()]
        for m in rows[:12]:
            print(m["id"], m.get("context_length"), m.get("pricing"))
    else:
        model = input("현재 카탈로그의 모델 ID: ").strip()
        question = input("외부 전송 가능한 가상 질문: ").strip()
        if not 1 <= len(question) <= 1000:
            raise SystemExit("Use a question of 1 to 1000 characters")
        answer, usage = cloud_chat(question, model)
        print(answer)
        print("usage:", usage)
.\.venv\Scripts\python.exe .\openrouter_demo.py --list
.\.venv\Scripts\python.exe .\openrouter_demo.py

목록에서 확인한 정확한 모델 ID를 넣습니다. max_tokens=256은 한 요청의 출력 제한이며 월 비용의 상한은 아닙니다. 입력량과 반복 호출, 제공자 요금, 계정 한도를 함께 관리합니다. 실습 API 호출에도 비용이 발생할 수 있습니다.

제공자 정책과 오류 처리

OpenRouter의 개인정보·라우팅 설정과 실제 제공자의 정책을 함께 읽습니다. 같은 모델 이름으로도 제공자가 달라질 수 있어요. 업무 자료를 보낼 때는 허용된 제공자와 보관 조건, fallback으로 바뀔 수 있는 경로를 확인합니다.

401이면 인증, 402면 잔액·결제 조건, 429면 속도 제한을 우선 살펴봅니다. 실패한 요청을 무한히 반복하지 않습니다. 로컬 모델과 비교할 때는 질문과 출력 한도를 맞추고, 네트워크 지연과 외부 전송 여부도 기록합니다.

참고 자료

문서·모델 태그 확인 기준: 2026년 10월 18일. 기본 실습 환경: Windows 11 x64.


댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

닉네임·이메일·댓글 내용·IP 주소·브라우저 정보는 댓글 게시 및 관리에 사용됩니다. 이메일과 접속 정보는 공개 댓글에 표시하지 않습니다. 개인정보 요청은 운영자 이메일로 보내 주세요. 개인정보처리방침