Skip to content

Split View: LiteLLM 실전 사용법 — 100개 이상 LLM을 하나의 인터페이스로

|

LiteLLM 실전 사용법 — 100개 이상 LLM을 하나의 인터페이스로

개요

LLM 애플리케이션을 만들다 보면 곧 같은 문제에 부딪힌다. 공급자마다 SDK가 다르고, 요청 파라미터가 다르고, 응답 형태가 다르다. OpenAI 코드로 시작했다가 Anthropic으로 바꾸려면 클라이언트 초기화부터 메시지 파싱까지 전부 손봐야 한다. LiteLLM은 바로 이 지점을 없앤다.

LiteLLM(BerriAI 제작)은 두 가지로 구성된다. 하나는 Python SDK이고, 다른 하나는 Proxy Server(AI Gateway)다. 둘 다 공통 목표가 있다. 100개 이상의 LLM API를 OpenAI 요청/응답 포맷으로 통일해서 호출한다는 것이다. 여기에 비용 추적, 재시도, 로드 밸런싱, 폴백, 가드레일, 로깅 같은 운영 기능이 얹힌다.

이 글은 빠르게 생산성을 얻기 위한 실전 퀵스타트다. 설치하고 첫 호출을 날리고, 여러 공급자를 바꿔 끼우고, 스트리밍과 Router, Proxy까지 최소 경로로 훑는다. LiteLLM의 내부 구조와 심화 운영 패턴을 깊게 다룬 LiteLLM 완전 가이드가 이미 이 블로그에 있으니, 원리와 프로덕션 세부 설정이 필요하면 그쪽을 함께 보면 된다. 이 글은 의도적으로 가볍게 유지한다.

핵심 관점 하나를 먼저 잡고 가자. LiteLLM은 "또 하나의 공급자"가 아니라, 여러 공급자 위에 놓는 단일 통합 계층이다. 그리고 그 아래에는 OpenRouter처럼 다시 수백 개 모델을 묶어주는 라우터도 넣을 수 있다. 즉 LiteLLM 하나로 직접 연결한 공급자와 OpenRouter를 통한 공급자를 모두 같은 코드로 부를 수 있다.

1. 설치와 첫 호출

설치는 한 줄이다. pip을 쓰든 uv를 쓰든 차이가 없다.

pip install litellm
# uv 사용 시
uv add litellm

가장 작은 호출은 다음과 같다. completion 함수 하나에 modelmessages만 넘기면 된다. 응답은 OpenAI 형태 그대로 온다.

from litellm import completion
import os

os.environ["OPENAI_API_KEY"] = "sk-..."

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

여기서 눈여겨볼 점은 응답 접근 방식이다. resp.choices[0].message.content는 OpenAI SDK를 써봤다면 익숙한 그 구조다. 어떤 공급자를 부르든 이 형태가 유지되기 때문에, 응답을 다루는 코드는 한 번만 짜면 된다.

SDK 단계에서도 간단한 안정성 옵션을 바로 줄 수 있다. num_retries로 재시도 횟수를, timeout으로 응답 대기 시간을 지정하면 일시적 오류에 조금 더 강해진다. 이런 옵션은 Router 없이 단일 호출에서도 동작한다.

from litellm import completion

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    num_retries=2,
    timeout=30,
)
print(resp.choices[0].message.content)

2. 여러 공급자를 같은 코드로 호출

공급자를 바꾸는 방법은 두 단계뿐이다. model 문자열의 접두사(prefix) 를 바꾸고, 그 공급자의 환경 변수를 설정한다. 그게 전부다. 메시지 구조도, 응답 파싱도 그대로다.

공급자model 접두사 예시환경 변수
OpenAIopenai/gpt-4oOPENAI_API_KEY
Anthropicanthropic/claude-3-5-sonnet-20241022ANTHROPIC_API_KEY
Gemini (AI Studio)gemini/gemini-1.5-proGEMINI_API_KEY
Vertex AIvertex_ai/gemini-1.5-proGCP 자격 증명
AWS Bedrockbedrock/...AWS 자격 증명
Azure OpenAIazure/...Azure 설정
Ollama (로컬)ollama/llama3없음 (로컬 실행)

예를 들어 Anthropic으로 바꾸면 이렇게 된다.

from litellm import completion
import os

os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..."

resp = completion(
    model="anthropic/claude-3-5-sonnet-20241022",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

로컬 모델로 실험하고 싶다면 Ollama를 띄운 뒤 접두사만 ollama/로 바꾸면 된다. API 키도 필요 없다.

from litellm import completion

resp = completion(
    model="ollama/llama3",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

이 구조 덕분에 "개발은 로컬 Ollama, 스테이징은 Gemini, 프로덕션은 Bedrock" 같은 전환을 코드 변경 없이 환경 변수와 모델 문자열만으로 처리할 수 있다.

3. LiteLLM으로 OpenRouter 쓰기

여기가 이 글에서 특히 강조하고 싶은 조합이다. OpenRouter는 그 자체로 300개 이상의 모델을 하나의 API로 묶어주는 서비스다. LiteLLM을 그 위에 얹으면, OpenRouter의 넓은 모델 커버리지에 LiteLLM의 균일한 인터페이스와 운영 기능(재시도, 폴백, 비용 추적)을 함께 얻는다.

방법은 앞과 동일한 패턴이다. OPENROUTER_API_KEY를 설정하고, 모델 문자열을 openrouter/로 시작한 뒤 그 뒤에 OpenRouter의 모델 id를 붙인다.

from litellm import completion
import os

os.environ["OPENROUTER_API_KEY"] = "sk-or-..."

resp = completion(
    model="openrouter/openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

접두사 구조를 뜯어보면 이해가 쉽다. 앞의 openrouter/는 LiteLLM에게 "OpenRouter를 통해 보내라"고 지시하고, 뒤의 openai/gpt-4o는 OpenRouter가 인식하는 모델 id다. 즉 openrouter/ 뒤에는 OpenRouter가 지원하는 어떤 모델 id든 그대로 올 수 있다.

이 조합의 실용적 이점은 분명하다. 직접 계약한 몇몇 공급자는 LiteLLM으로 직결하고, 그 외 실험적이거나 다양한 모델은 OpenRouter를 통해 붙이면서, 애플리케이션 코드는 전부 동일한 completion 호출로 유지할 수 있다. 모델을 늘리는 데 드는 통합 비용이 사실상 0에 수렴한다.

4. 스트리밍과 비동기

토큰을 실시간으로 흘려보내려면 stream=True만 추가하고 반환값을 순회하면 된다. 델타(delta)에서 조각을 꺼내 이어 붙이는 형태다.

from litellm import completion

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Write a haiku about the sea"}],
    stream=True,
)

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

delta.contentNone으로 오는 청크도 있으므로 or ""로 방어하는 것이 관례다. 이렇게 하면 빈 조각에서 예외가 나지 않는다.

비동기 코드에서는 acompletion을 쓴다. 시그니처는 completion과 같고, await만 붙이면 된다.

import asyncio
from litellm import acompletion

async def main():
    resp = await acompletion(
        model="anthropic/claude-3-5-sonnet-20241022",
        messages=[{"role": "user", "content": "Hello"}],
    )
    print(resp.choices[0].message.content)

asyncio.run(main())

스트리밍과 비동기는 조합해서 쓸 수 있다. FastAPI 같은 async 웹 프레임워크에서 토큰을 서버 전송 이벤트로 흘려보낼 때 이 두 가지를 함께 쓰게 된다.

5. Router — 로드 밸런싱과 폴백

호출량이 늘고 안정성이 중요해지면 SDK의 Router 클래스가 등장한다. Router는 여러 배포(deployment)를 하나의 별칭(alias)으로 묶어, 요청을 분산하고 실패를 자동으로 흡수한다.

먼저 model_list를 정의한다. 각 항목은 겉으로 보이는 이름 model_name과 실제 호출 파라미터 litellm_params를 가진다. 같은 model_name을 여러 배포에 붙이면, Router가 그 별칭으로 온 요청을 여러 배포에 나눠 보낸다.

from litellm import Router

model_list = [
    {
        "model_name": "my-gpt",
        "litellm_params": {
            "model": "openai/gpt-4o",
            "api_key": "sk-...",
        },
    },
    {
        "model_name": "my-gpt",
        "litellm_params": {
            "model": "azure/gpt-4o-deployment",
            "api_key": "...",
            "api_base": "https://your-resource.openai.azure.com",
        },
    },
]

router = Router(model_list=model_list)

resp = router.completion(
    model="my-gpt",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

Router의 진짜 가치는 폴백(fallback)에 있다. 실패 종류에 따라 세 가지 폴백을 구분해서 설정할 수 있다.

폴백 종류트리거비고
fallbacks429/500 계열 오류다른 모델로 넘김
context_window_fallbacks컨텍스트 초과enable_pre_call_checks=True 필요
content_policy_fallbacks콘텐츠 정책 거부다른 모델로 우회

세 가지를 함께 건 예시는 다음과 같다. 컨텍스트 폴백을 쓰려면 사전 체크를 켜야 한다는 점만 주의하면 된다.

from litellm import Router

router = Router(
    model_list=model_list,
    fallbacks=[{"my-gpt": ["claude"]}],
    context_window_fallbacks=[{"my-gpt": ["claude-long"]}],
    content_policy_fallbacks=[{"my-gpt": ["safe-model"]}],
    enable_pre_call_checks=True,
)

이 외에도 Router는 재시도(retries), 쿨다운(cooldowns), 타임아웃(timeouts)을 함께 관리한다. 한 배포가 계속 실패하면 잠시 쿨다운 목록에 넣어 트래픽을 보내지 않고, 일정 시간 뒤 복귀시키는 식이다. 애플리케이션 코드는 여전히 router.completion(model="my-gpt", ...) 한 줄이면 된다.

6. Proxy Server (AI Gateway)

여러 팀과 앱이 하나의 게이트웨이를 공유해야 한다면 Proxy Server가 답이다. Proxy는 별도 프로세스로 떠서, 어떤 OpenAI 호환 클라이언트든 받아들이는 게이트웨이 역할을 한다.

먼저 config.yamlmodel_list를 쓴다. API 키는 os.environ/로 환경 변수를 참조하게 하는 것이 안전하다.

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY
  - model_name: claude
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022
      api_key: os.environ/ANTHROPIC_API_KEY

그다음 설정 파일을 지정해 프록시를 띄운다. 기본적으로 http://0.0.0.0:4000에서 대기한다.

litellm --config config.yaml

이제 아무 OpenAI 호환 클라이언트든 base URL을 프록시로 향하게 하고, 더미/베이스 키를 넣으면 그대로 동작한다. 실제 공급자 키는 프록시가 config에서 들고 있으므로 클라이언트에는 노출하지 않는다.

from openai import OpenAI

client = OpenAI(
    base_url="http://0.0.0.0:4000",
    api_key="anything",  # 프록시가 관리하는 가상 키/더미 키
)

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

Proxy 계층이 얹어주는 것은 단순 프록싱을 넘어선다. 팀별 가상 키(virtual keys) 발급, 예산(budgets) 한도, 비용 추적 대시보드, 요청 로깅이 모두 게이트웨이에서 중앙 관리된다. 애플리케이션은 진짜 공급자 키를 몰라도 되고, 운영팀은 누가 얼마나 썼는지 한곳에서 본다.

7. SDK vs Proxy, 그리고 실전 팁

둘 중 무엇을 쓸지는 규모로 갈린다.

  • SDK를 쓸 때: 단일 앱이나 스크립트. 라이브러리를 import해서 코드 안에서 직접 호출하면 되고, 별도 인프라가 필요 없다. 개인 프로젝트, 배치 작업, 단일 서비스에 적합하다.
  • Proxy를 쓸 때: 여러 팀 또는 여러 앱이 하나의 게이트웨이를 공유해야 할 때. 중앙 집중식 키 관리, 예산, 비용 대시보드가 필요하면 프록시가 정답이다. 조직 단위 LLM 플랫폼을 만들 때 자연스럽게 이 방향으로 간다.

실전에서 유용한 몇 가지를 정리한다.

  1. 응답 코드는 한 번만: 어떤 공급자를 부르든 resp.choices[0].message.content 형태가 유지되므로, 응답 파싱 로직은 공급자마다 다시 짤 필요가 없다.
  2. 키는 환경 변수로: SDK든 프록시 config든 키를 코드에 박지 말고 환경 변수로 주입한다. 프록시에서는 os.environ/ 참조를 쓴다.
  3. 모델 문자열이 곧 스위치: 공급자 전환은 접두사 교체와 환경 변수 설정, 이 두 가지가 전부다. 배포 환경별로 모델 문자열만 바꾸는 전략이 잘 통한다.
  4. OpenRouter를 옆에 둔다: 자주 쓰는 공급자는 직결하고, 실험적이거나 다양한 모델은 openrouter/ 접두사로 붙이면 통합 비용 없이 선택지를 넓힐 수 있다.
  5. 작게 시작해서 올린다: 처음엔 SDK로 시작하고, 팀이 늘고 중앙 관리가 필요해지는 시점에 같은 model_list 개념을 그대로 Proxy config로 옮기면 된다.

정리하면, LiteLLM은 공급자 파편화를 코드 밖으로 밀어낸다. 첫 호출은 몇 줄이면 되고, 필요해지는 순간 Router로 안정성을, Proxy로 조직 차원의 통제를 더할 수 있다. 더 깊은 내부 동작과 프로덕션 세부 설정은 LiteLLM 완전 가이드에서 이어서 보면 된다.

참고 자료

LiteLLM in Practice — 100+ LLMs Behind One Interface

Overview

Build an LLM app for long enough and you hit the same wall: every provider ships a different SDK, different request parameters, and a different response shape. Start with OpenAI code, then try to move to Anthropic, and you end up rewriting everything from client setup to message parsing. LiteLLM removes exactly that friction.

LiteLLM (by BerriAI) comes in two pieces. One is a Python SDK; the other is a Proxy Server (an AI Gateway). Both share a single goal: call 100+ LLM APIs in the OpenAI request/response format. On top of that uniform surface, LiteLLM layers operational features — cost tracking, retries, load balancing, fallbacks, guardrails, and logging.

This post is a practical quickstart meant to get you productive fast. Install it, make your first call, swap providers, then move through streaming, the Router, and the Proxy along the shortest path. A deeper LiteLLM complete guide already lives on this blog and covers internals and production configuration in depth, so reach for that when you need the mechanics. This post stays deliberately light.

Fix one framing first. LiteLLM is not "yet another provider." It is a single unified layer you put over many providers. And underneath it you can even place a router like OpenRouter that itself bundles hundreds of models. That means one LiteLLM setup can call both your directly-connected providers and providers reached through OpenRouter using the same code.

1. Install and First Call

Installing is a one-liner. It makes no difference whether you use pip or uv.

pip install litellm
# with uv
uv add litellm

The smallest possible call looks like this. You hand the single completion function a model and messages, and the response comes back in OpenAI shape.

from litellm import completion
import os

os.environ["OPENAI_API_KEY"] = "sk-..."

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

Note the access pattern. resp.choices[0].message.content is the exact structure you already know from the OpenAI SDK. Because that shape holds no matter which provider you call, you only write your response-handling code once.

You can hand simple reliability options straight to the SDK too. Set num_retries for the retry count and timeout for how long to wait, and a single call becomes a bit more resilient to transient errors. These options work on a plain call without any Router.

from litellm import completion

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    num_retries=2,
    timeout=30,
)
print(resp.choices[0].message.content)

2. Calling Many Providers with the Same Code

Switching providers takes just two steps. Change the prefix of the model string, and set that provider's environment variable. That is the whole change. The message structure and the response parsing stay identical.

ProviderExample model prefixEnvironment variable
OpenAIopenai/gpt-4oOPENAI_API_KEY
Anthropicanthropic/claude-3-5-sonnet-20241022ANTHROPIC_API_KEY
Gemini (AI Studio)gemini/gemini-1.5-proGEMINI_API_KEY
Vertex AIvertex_ai/gemini-1.5-proGCP credentials
AWS Bedrockbedrock/...AWS credentials
Azure OpenAIazure/...Azure config
Ollama (local)ollama/llama3none (runs locally)

Moving to Anthropic, for example, looks like this.

from litellm import completion
import os

os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..."

resp = completion(
    model="anthropic/claude-3-5-sonnet-20241022",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

To experiment with a local model, start Ollama and change the prefix to ollama/. No API key required.

from litellm import completion

resp = completion(
    model="ollama/llama3",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

Thanks to this structure, transitions like "local Ollama in development, Gemini in staging, Bedrock in production" become a matter of environment variables and model strings — no code changes at all.

3. Using OpenRouter through LiteLLM

This is the combination this post most wants to highlight. OpenRouter is, on its own, a service that bundles 300+ models behind a single API. Put LiteLLM on top of it and you get OpenRouter's broad model coverage together with LiteLLM's uniform interface and operational features (retries, fallbacks, cost tracking).

The method is the same pattern as before. Set OPENROUTER_API_KEY, then start the model string with openrouter/ followed by the OpenRouter model id.

from litellm import completion
import os

os.environ["OPENROUTER_API_KEY"] = "sk-or-..."

resp = completion(
    model="openrouter/openai/gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

Break the prefix apart and it makes sense. The leading openrouter/ tells LiteLLM "route this through OpenRouter," and the trailing openai/gpt-4o is the model id that OpenRouter recognizes. In other words, after openrouter/ you can put any model id OpenRouter supports.

The practical payoff is clear. Wire your few contracted providers directly into LiteLLM, attach the more experimental or varied models through OpenRouter, and keep all your application code on the same completion call. The integration cost of adding another model effectively drops to zero.

4. Streaming and Async

To stream tokens in real time, add stream=True and iterate over the return value. You pull each fragment from the delta and concatenate.

from litellm import completion

resp = completion(
    model="openai/gpt-4o",
    messages=[{"role": "user", "content": "Write a haiku about the sea"}],
    stream=True,
)

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

Some chunks arrive with delta.content set to None, so guarding with or "" is the convention. That keeps an empty fragment from raising an exception.

In async code, use acompletion. The signature matches completion; you just add await.

import asyncio
from litellm import acompletion

async def main():
    resp = await acompletion(
        model="anthropic/claude-3-5-sonnet-20241022",
        messages=[{"role": "user", "content": "Hello"}],
    )
    print(resp.choices[0].message.content)

asyncio.run(main())

Streaming and async compose. When you stream tokens as server-sent events from an async web framework like FastAPI, you end up using both together.

5. Router — Load Balancing and Fallbacks

Once call volume grows and reliability matters, the SDK's Router class enters. The Router groups several deployments under one alias, spreads requests across them, and absorbs failures automatically.

First define a model_list. Each entry has a public-facing model_name and the real call parameters in litellm_params. Attach the same model_name to multiple deployments and the Router will distribute requests sent to that alias across those deployments.

from litellm import Router

model_list = [
    {
        "model_name": "my-gpt",
        "litellm_params": {
            "model": "openai/gpt-4o",
            "api_key": "sk-...",
        },
    },
    {
        "model_name": "my-gpt",
        "litellm_params": {
            "model": "azure/gpt-4o-deployment",
            "api_key": "...",
            "api_base": "https://your-resource.openai.azure.com",
        },
    },
]

router = Router(model_list=model_list)

resp = router.completion(
    model="my-gpt",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

The Router's real value is in fallbacks. You can configure three kinds, distinguished by the type of failure.

Fallback kindTriggerNote
fallbacks429/500-class errorsroutes to another model
context_window_fallbackscontext exceededneeds enable_pre_call_checks=True
content_policy_fallbackscontent-policy refusalreroutes to another model

Here is an example wiring all three. The only thing to watch is that context-window fallbacks require the pre-call check to be on.

from litellm import Router

router = Router(
    model_list=model_list,
    fallbacks=[{"my-gpt": ["claude"]}],
    context_window_fallbacks=[{"my-gpt": ["claude-long"]}],
    content_policy_fallbacks=[{"my-gpt": ["safe-model"]}],
    enable_pre_call_checks=True,
)

Beyond that, the Router also manages retries, cooldowns, and timeouts. If one deployment keeps failing, it goes onto a cooldown list so no traffic is sent to it for a while, then returns after a set interval. Your application code is still a single line: router.completion(model="my-gpt", ...).

6. Proxy Server (AI Gateway)

When multiple teams and apps need to share one gateway, the Proxy Server is the answer. The Proxy runs as its own process and acts as a gateway that accepts any OpenAI-compatible client.

First write a model_list into config.yaml. It is safest to have API keys reference environment variables via os.environ/.

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY
  - model_name: claude
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022
      api_key: os.environ/ANTHROPIC_API_KEY

Then point at the config file to start the proxy. By default it listens on http://0.0.0.0:4000.

litellm --config config.yaml

Now point any OpenAI-compatible client's base URL at the proxy and pass a dummy/base key, and it just works. The real provider keys are held by the proxy from its config, so they are never exposed to the client.

from openai import OpenAI

client = OpenAI(
    base_url="http://0.0.0.0:4000",
    api_key="anything",  # virtual/dummy key managed by the proxy
)

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
)
print(resp.choices[0].message.content)

What the Proxy layer adds goes beyond plain proxying. Per-team virtual keys, budget limits, a cost-tracking dashboard, and request logging are all managed centrally at the gateway. Applications never need the real provider keys, and your operations team sees who spent what in one place.

7. SDK vs Proxy, and Practical Tips

Which one you use comes down to scale.

  • Use the SDK when: it is a single app or script. Import the library and call it directly inside your code — no separate infrastructure needed. Good for personal projects, batch jobs, and single services.
  • Use the Proxy when: multiple teams or apps must share one gateway. If you need centralized key management, budgets, and cost dashboards, the Proxy is the right call. Building an org-wide LLM platform naturally trends this direction.

A few things that pay off in practice.

  1. Write response code once: no matter which provider you call, the resp.choices[0].message.content shape holds, so you never rewrite response parsing per provider.
  2. Keys via environment variables: whether SDK or proxy config, inject keys through environment variables instead of hardcoding them. In the proxy, use the os.environ/ reference.
  3. The model string is the switch: switching providers is just prefix replacement plus setting an env var. The strategy of changing only the model string per deployment environment works well.
  4. Keep OpenRouter alongside: wire your frequent providers directly and attach experimental or varied models with the openrouter/ prefix, widening your options at no integration cost.
  5. Start small, then grow: begin with the SDK, and when the team grows and central management is needed, carry the same model_list concept straight over into a Proxy config.

To sum up, LiteLLM pushes provider fragmentation out of your code. The first call takes a few lines, and the moment you need it you can add reliability with the Router and org-level control with the Proxy. For deeper internals and production configuration, continue with the LiteLLM complete guide.

References