Azure 환경을 운영하다 보면 LLM 공급자를 하나만 쓰는 팀을 만나기가 점점 어려워진다. 공급자가 늘어날 때마다 키를 어디에 두고 호출 코드를 어떻게 맞출지 고민하게 된다. 그 질문이 나올 때마다 후보로 거론되는 도구가 LiteLLM이라 이번에 한 번 정리해 둔다.
LiteLLM(BerriAI/litellm)은 여러 LLM 공급자를 OpenAI 호환 형식 하나로 호출하게 해 주는 오픈소스다. Python 코드 안에서 쓰는 SDK와, 팀이 함께 쓰는 게이트웨이 서버로 띄우는 Proxy 두 가지 방식이 있다. 앱 하나에서 공급자를 바꾸거나 폴백을 거는 정도라면 SDK로 충분하다. 여러 팀의 키, 예산, 사용량을 한곳에서 관리해야 하면 Proxy를 쓴다.
1. 개요
먼저 용어부터 짚고 가자. LLM 게이트웨이(AI Gateway)는 앱과 LLM 공급자 사이에서 요청을 대신 전달하는 중간 계층이다. 공급자별 API 차이를 숨기고, 키와 사용량을 한곳에서 다루려고 둔다. LiteLLM은 이 역할을 라이브러리(SDK)와 서버(Proxy) 두 형태로 제공한다.
게이트웨이가 필요한 이유는 LLM API의 요청 형식이 공급자마다 조금씩 다르기 때문이다.
- OpenAI:
messages배열을 쓴다. - Anthropic Messages API: system 프롬프트를 별도 필드로 받는다.
- AWS Bedrock: AWS 서명 인증을 쓴다.
- Azure OpenAI: 모델 이름 대신 배포(deployment) 이름과
api-version을 요구한다.
예를 들어 개발팀은 Anthropic 모델을, 데이터팀은 Bedrock을 쓰고, 테스트는 로컬 Ollama로 돌린다고 하자. 같은 "요약해 줘" 기능을 OpenAI에서 Anthropic으로 옮기려면 SDK 교체, 응답 파싱 수정, 에러 처리 수정이 함께 따라온다. 공급자가 셋이면 이 분기 코드도 세 벌이 된다.
지금 구조가 이 상태인지는 코드베이스에서 공급자별 SDK import와 키 환경 변수가 몇 벌인지 세어 보면 드러난다. 두 벌 이상이고 앞으로 더 늘어날 예정이라면 중간 계층을 검토할 시점이다.
LiteLLM은 이 차이를 중간에서 흡수한다. 호출하는 쪽은 OpenAI Chat Completions 형식으로 요청을 보내고, LiteLLM이 공급자 형식으로 바꿔 보낸다. 응답도 OpenAI 형식으로 돌려주므로 모델을 바꿀 때 model 문자열만 바꾸면 된다.
- 저장소:
github.com/BerriAI/litellm(오픈소스, 엔터프라이즈 기능은 별도 라이선스) - 지원 공급자: 공식 문서 기준 100개 이상(OpenAI, Anthropic, AWS Bedrock, Azure OpenAI, Google Vertex AI, Ollama 등). 목록은 계속 바뀌므로 공식 Providers 문서에서 확인한다.
- 모델 지정 방식:
공급자/모델접두어. 예:openai/gpt-4o,anthropic/claude-sonnet-4-20250514,azure/<배포이름>
선택지는 LiteLLM SDK, LiteLLM Proxy, 그리고 클라우드 관리형 게이트웨이 세 가지로 좁혀진다. 아래에서는 SDK와 Proxy의 차이, 설치와 최소 설정, 어떤 팀에 맞는지, 운영 시 주의점을 차례로 알아보자.
2. SDK와 Proxy의 차이
같은 프로젝트지만 쓰는 위치가 다르다. 항목별로 나누면 다음과 같다.
- 형태: SDK는 Python 라이브러리이고, Proxy는 독립 HTTP 서버(AI Gateway)다.
- 호출하는 쪽: SDK는 Python 앱에서만 쓴다. Proxy는 OpenAI 호환 엔드포인트라서 언어와 상관없이 OpenAI 클라이언트나 curl로 호출한다.
- 라우팅: 둘 다 로드밸런싱, 폴백, 재시도를 지원한다. SDK는
Router클래스로, Proxy는config.yaml로 설정한다. - 로깅: 둘 다 콜백으로 Langfuse 같은 관측 도구에 보낼 수 있다.
- 공급자 키 위치: SDK는 앱마다 환경 변수에 둔다. Proxy는 서버에만 두고, 사용자에게는 가상 키(virtual key)를 나눠 준다.
- 중앙 관리: Proxy에만 있다. 가상 키 발급, 키별 예산과 rate limit, 팀 단위 사용량 추적, 관리자 UI를 서버 하나에서 여러 팀이 공유한다.
- 운영 부담: SDK는 따로 없다. Proxy는 서버 프로세스와 마스터 키를 운영해야 하고, 가상 키나 사용량 추적을 쓰려면 PostgreSQL도 함께 운영한다.
둘을 가르는 기준은 라우팅 기능이 아니라 중앙 관리다. 폴백과 로드밸런싱만 필요하면 SDK의 Router로도 해결된다. 키와 예산, 사용량을 여러 팀에 걸쳐 한곳에서 통제해야 할 때 Proxy가 의미를 가진다.
가상 키는 Proxy가 발급하는 자체 API 키다. 실제 OpenAI나 Azure 키는 Proxy 안에 두고, 팀이나 사용자별로 가상 키를 나눠 준 다음 키마다 쓸 수 있는 모델과 예산을 건다. 실제 키가 개발자 노트북이나 CI 변수로 퍼지지 않는다는 점이 Proxy를 쓰는 가장 큰 이유다.
3. SDK 설치와 호출
3.1 설치
PyPI 기준 최신 릴리스는 Python 3.10 이상을 요구한다. RHEL 8 기본 Python 3.6이나 3.9에서 그대로 pip install하면 설치가 안 되거나 아주 오래된 버전이 깔린다.
처음 해보면 여기서 자주 막힌다. 그래서 저는 Python 버전부터 확인하고 venv를 따로 만든다.
$ python3 --version Python 3.12.3 $ python3 -m venv ~/venv/litellm $ source ~/venv/litellm/bin/activate (litellm) $ pip install 'litellm==1.106.1' ... Successfully installed ... litellm-1.106.1 ... (litellm) $ pip show litellm | head -2 Name: litellm Version: 1.106.1
버전 번호는 시점에 따라 다를 수 있다. 공식 README는 uv add litellm을 안내하는데, pip로 설치해도 같은 PyPI 패키지다. 버전을 ==로 고정하는 이유는 7절에서 설명한다.
3.2 completion 호출
공급자 키를 환경 변수로 넣고 model 문자열만 바꿔 호출한다.
$ export OPENAI_API_KEY="sk-..."
$ export ANTHROPIC_API_KEY="sk-ant-..."
$ cat hello.py
from litellm import completion
messages = [{"role": "user", "content": "LiteLLM을 한 문장으로 설명해 줘"}]
for model in ["openai/gpt-4o", "anthropic/claude-sonnet-4-20250514"]:
resp = completion(model=model, messages=messages)
print(model, "=>", resp.choices[0].message.content)
$ python hello.py
두 응답 모두 resp.choices[0].message.content로 읽는다. 응답 내용은 모델마다 다르니 출력은 생략한다.
이 부분이 LiteLLM의 핵심이다. Anthropic SDK의 응답 구조를 따로 알 필요가 없다.
3.3 Azure OpenAI와 Ollama
Azure OpenAI는 모델 이름 자리에 배포 이름을 넣는다. 처음엔 azure/gpt-4o처럼 모델명을 넣기 쉬운데, 배포 이름이 모델명과 다르면 DeploymentNotFound 계열 에러가 날 수 있다. 포털에서 만든 배포 이름을 그대로 쓴다.
$ export AZURE_API_KEY="..." $ export AZURE_API_BASE="https://<리소스이름>.openai.azure.com/" $ export AZURE_API_VERSION="<사용할 api-version>" # 코드에서는 resp = completion(model="azure/<배포이름>", messages=messages)
로컬 Ollama는 키가 필요 없고 api_base만 넘긴다.
resp = completion(
model="ollama/llama3",
messages=messages,
api_base="http://localhost:11434",
)
3.4 Router로 로드밸런싱과 폴백
Router는 SDK 안에서 여러 배포를 별칭 하나로 묶어 주는 클래스다. 같은 model_name을 가진 항목끼리 부하를 나누고, 실패하면 fallbacks에 지정한 별칭으로 넘긴다.
from litellm import Router
router = Router(
model_list=[
{"model_name": "chat-default", "litellm_params": {"model": "azure/<배포이름>"}},
{"model_name": "chat-default", "litellm_params": {"model": "openai/gpt-4o"}},
{"model_name": "claude", "litellm_params": {"model": "anthropic/claude-sonnet-4-20250514"}},
],
fallbacks=[{"chat-default": ["claude"]}],
)
resp = router.completion(model="chat-default", messages=messages)
키는 3.2, 3.3에서 export한 환경 변수에서 읽는다. 로그를 관측 도구로 보내려면 litellm.success_callback = ["langfuse"]처럼 콜백을 등록한다. 이 model_list 구조는 4절의 Proxy config.yaml과 같아서 나중에 Proxy로 옮길 때 그대로 가져갈 수 있다.
4. Proxy 설치와 최소 설정
4.1 설치
Proxy는 proxy extra를 붙여 설치한다. 서버 실행에 필요한 패키지가 함께 들어온다.
(litellm) $ pip install 'litellm[proxy]==1.106.1'
4.2 config.yaml 작성
model_list가 핵심이다. model_name은 클라이언트가 요청에 넣는 별칭이고, litellm_params는 실제로 호출할 공급자 모델과 접속 정보다.
키는 os.environ/변수명 형식으로 환경 변수에서 읽는다. 파일에 키를 직접 적지 않기 위해서다.
$ cat config.yaml
model_list:
- model_name: chat-default
litellm_params:
model: azure/<배포이름>
api_base: os.environ/AZURE_API_BASE
api_key: os.environ/AZURE_API_KEY
api_version: os.environ/AZURE_API_VERSION
- model_name: chat-default
litellm_params:
model: openai/gpt-4o
api_key: os.environ/OPENAI_API_KEY
- model_name: claude
litellm_params:
model: anthropic/claude-sonnet-4-20250514
api_key: os.environ/ANTHROPIC_API_KEY
litellm_settings:
drop_params: True
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
각 설정의 의미는 다음과 같다.
model_name이 같은 항목이 둘이면(chat-default) Proxy가 두 배포 사이에서 로드밸런싱한다. Azure와 OpenAI를 같은 별칭으로 묶어 두는 구성을 현장에서 자주 쓴다.drop_params: True는 공급자가 지원하지 않는 파라미터를 에러 대신 버리고 보내게 한다.master_key는 Proxy 관리자 키다. 공식 문서 기준sk-로 시작해야 한다. 설정하면 모든 요청에Authorization: Bearer헤더가 필요하다.
4.3 Proxy 기동
$ export LITELLM_MASTER_KEY="sk-$(openssl rand -hex 24)" $ litellm --config config.yaml ... INFO: Uvicorn running on http://0.0.0.0:4000 (Press CTRL+C to quit)
공식 문서 예시는 4000번 포트로 뜬다. 설정이 의도대로 읽혔는지 확인하려면 --detailed_debug를 붙여 다시 띄운다.
서버는 정상으로 떴는데 요청 시점에 인증 에러가 난다면, 확인할 원인 중 하나는 공급자 키 환경 변수가 Proxy 프로세스에 전달되지 않은 경우다. export하지 않은 셸에서 실행했거나, systemd로 돌리면서 EnvironmentFile에 변수를 빠뜨렸을 수 있다.
4.4 curl 호출
요청의 model에는 공급자 모델명이 아니라 config의 model_name 별칭을 넣는다.
$ curl -s http://localhost:4000/chat/completions \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-d '{
"model": "chat-default",
"messages": [{"role": "user", "content": "ping"}]
}'
{"id":"chatcmpl-...","object":"chat.completion","model":"...",
"choices":[{"index":0,"message":{"role":"assistant","content":"..."},"finish_reason":"stop"}],
"usage":{"prompt_tokens":...,"completion_tokens":...,"total_tokens":...}}
응답이 OpenAI 형식이라 기존 OpenAI SDK 코드는 base_url만 바꾸면 그대로 붙는다.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:4000", api_key="sk-가상키")
resp = client.chat.completions.create(
model="claude",
messages=[{"role": "user", "content": "ping"}],
)
Anthropic 모델을 OpenAI 클라이언트로 부르는 구성이다. 응답 형식을 JSON으로 강제하는 방법은 예전에 LLM 응답 JSON 강제 글에 따로 정리해 두었다.
5. 가상 키 발급
여기서부터는 선택 사항이다. 모델 호출만 한다면 DB 없이 마스터 키 하나로 운영할 수 있다. 가상 키 발급, 키별 예산, 사용량 추적을 쓰려면 PostgreSQL이 필요하고, DATABASE_URL 환경 변수로 연결 문자열을 넘긴다.
$ export DATABASE_URL="postgresql://litellm:<비밀번호>@127.0.0.1:5432/litellm" $ export LITELLM_SALT_KEY="$(openssl rand -hex 32)" $ litellm --config config.yaml
LITELLM_SALT_KEY는 DB에 저장하는 공급자 키 같은 값을 암호화하는 키다. 모델을 추가한 뒤에 바꾸면 저장된 자격 증명을 복호화하지 못한다. 처음 띄울 때 정하고 시크릿 저장소에 보관한다.
키 발급은 /key/generate에 마스터 키로 요청한다. 키마다 쓸 수 있는 모델을 제한할 수 있다.
$ curl -s http://localhost:4000/key/generate \
-H "Authorization: Bearer $LITELLM_MASTER_KEY" \
-H 'Content-Type: application/json' \
-d '{"models": ["chat-default"], "metadata": {"team": "backend"}}'
{"key":"sk-...", ...}
예산(max_budget), 만료(duration), rate limit 같은 옵션은 Virtual Keys 문서에 정리되어 있다.
키별 사용량과 비용을 트레이스 단위로 더 자세히 보려면 로깅 콜백으로 Langfuse에 보내는 구성을 많이 쓴다. 자체 호스팅 방법은 Langfuse 자체 호스팅으로 LLM 앱 관측하기에 정리했다.
6. 어떤 팀에 맞는가
SDK가 맞는 경우
- Python 앱 하나에서 공급자를 바꿔 가며 테스트하거나 Router로 폴백, 로드밸런싱을 걸고 싶은 경우
- 키 관리를 앱 배포 파이프라인에서 이미 하고 있는 경우
Proxy가 맞는 경우
- 여러 팀과 서비스가 LLM을 쓰고, 실제 공급자 키를 한곳에만 두고 싶은 경우
- 팀별 예산과 사용량을 나눠 봐야 하는 경우
- Python 외 언어(Java, Node.js, Go)에서도 같은 엔드포인트로 호출해야 하는 경우
- Azure OpenAI 리전 할당량이 부족해서 여러 배포로 부하를 나누거나 OpenAI로 폴백하는 설정을 여러 서비스가 공유해야 하는 경우
맞지 않는 경우
- 공급자 하나만 쓰고 바꿀 계획이 없는 경우. 공식 SDK를 바로 쓰는 쪽이 의존성이 하나 적다.
- 공급자 고유 기능(최신 베타 API, 특정 도구 형식)을 출시 직후부터 써야 하는 경우. 변환 계층이 따라오기 전까지 기다려야 할 수 있다.
- Proxy를 운영할 인력이 없는 경우. Azure API Management의 AI 게이트웨이 기능처럼 클라우드 관리형 게이트웨이를 먼저 검토한다.
제 판단으로는 공급자가 둘 이상이고 LLM을 쓰는 팀이 둘 이상이면 Proxy를 띄울 가치가 있다. 그보다 작은 규모라면 SDK로 시작하고, 중앙 관리가 필요해지면 Proxy로 옮기는 순서를 권한다. 앱 코드가 OpenAI 형식이라 나중에 옮겨도 base_url 정도만 바뀐다.
7. 주의사항
7.1 마스터 키와 DB 보호
Proxy는 모든 공급자 키를 모아 두는 지점이다. 뚫리면 피해 범위가 키 하나가 아니라 전체로 번진다.
- 마스터 키는 관리자 API와 관리자 UI 로그인에 쓰인다. 시크릿 저장소(Azure Key Vault, AWS Secrets Manager 등)에 두고 주기적으로 교체한다.
- DB를 붙였다면
LITELLM_SALT_KEY와DATABASE_URL도 같은 수준으로 보호한다. DB 덤프와 salt 키가 함께 새면 저장된 공급자 키를 복원할 수 있다. - 4000번 포트를 인터넷에 직접 열지 않는다. 내부망이나 TLS를 붙인 리버스 프록시 뒤에 두고, 관리자 UI 접근은 따로 제한한다.
- 운영에서는
litellm_settings의set_verbose를 꺼 둔다. 디버그 로그에 요청 내용이 남을 수 있다.
7.2 버전 고정과 공급망 주의
2026년 3월 PyPI에 악성 코드가 들어간 litellm 1.82.7, 1.82.8이 올라온 사고가 있었다(GitHub Security Advisory GHSA-5mg7-485q-xm76, Critical). Advisory는 해당 버전을 설치한 적이 있다면 그 환경의 자격 증명을 침해된 것으로 보고 폐기, 교체(revoke, rotate)하라고 안내한다.
LLM 게이트웨이는 여러 공급자 키를 들고 있는 패키지라 이런 사고의 피해가 크다. 그래서 아래 항목은 꼭 지킨다.
pip install litellm처럼 버전 없이 설치하지 않는다.==로 고정하고, 가능하면 해시까지 고정한다(pip install --require-hashes -r requirements.txt).- 업그레이드는 릴리스 노트와 Security Advisory를 확인한 뒤 스테이징에서 먼저 한다.
- Docker 이미지는
:latest를 쓰지 않는다. 공식 README는 cosign 서명 검증과-stable태그 사용을 안내한다.
# 지금 설치된 버전 점검 $ pip show litellm | grep Version Version: 1.106.1 # 1.82.7 또는 1.82.8을 설치한 적이 있는 환경이면 # 그 환경에서 접근 가능했던 자격 증명을 모두 폐기하고 새로 발급한다
8. 정리
- LiteLLM은 여러 LLM 공급자를 OpenAI 호환 형식 하나로 호출하게 해 주는 오픈소스다.
- SDK도 Router로 로드밸런싱과 폴백, 콜백으로 로깅을 지원한다. 가상 키, 예산, 팀 단위 사용량처럼 중앙 관리가 필요해지면 Proxy를 쓴다.
- Proxy 최소 구성은
config.yaml의model_list와master_key다. 가상 키와 사용량 추적을 쓸 때만 PostgreSQL과LITELLM_SALT_KEY를 더한다. - 운영에서는 마스터 키, salt 키, DB를 시크릿으로 다루고 패키지 버전을 고정한다.
참고 문서: https://docs.litellm.ai/docs/, https://docs.litellm.ai/docs/proxy/configs, https://docs.litellm.ai/docs/proxy/prod