安装与第一次调用
LiteLLM 是纯 Python 包,依赖很轻。安装:
pip install litellm
# 或者如果你用 uv (推荐)
uv add litellm
目前主流版本在 1.50+ 以上,API 比较稳定。
准备一把真实的 OpenAI key,直接运行:
import os from litellm import completion os.environ["OPENAI_API_KEY"] = "sk-..." resp = completion( model="gpt-4o-mini", messages=[{"role": "user", "content": "Hello, who are you?"}], ) print(resp.choices[0].message.content)
如果你用过 OpenAI SDK,这段代码几乎一模一样——这正是 LiteLLM 的用意:它把调用和返回都伪装成 OpenAI 的样子,你原来怎么写还怎么写。
client = OpenAI() 这种"客户端对象"。它是函数式的:from litellm import completion,然后直接调。所有 key 通过环境变量或函数参数传入。这是它和原生 SDK 最明显的风格差异。
model 字符串:provider 前缀的魔法
LiteLLM 能知道"去调哪一家",全靠 model 字符串里的前缀。它有两种命名约定:
约定一:OpenAI 家的裸模型名(无前缀)
OpenAI 是 LiteLLM 的默认 provider,所以 OpenAI 的模型名不带前缀:
completion(model="gpt-4o", ...) completion(model="gpt-4o-mini", ...) completion(model="o1-preview", ...) completion(model="o3-mini", ...)
约定二:其他厂商 provider/model-id
非 OpenAI 一律加前缀,中间一个斜杠:
| Provider | 前缀 | 示例 model 字符串 |
|---|---|---|
| Anthropic Claude | anthropic/ | anthropic/claude-sonnet-4-5 |
| Google Gemini | gemini/ | gemini/gemini-2.0-flash |
| Azure OpenAI | azure/ | azure/my-gpt4-deployment |
| AWS Bedrock | bedrock/ | bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0 |
| Vertex AI | vertex_ai/ | vertex_ai/gemini-2.0-flash |
| Ollama 本地 | ollama/ | ollama/llama3.2 |
| Together AI | together_ai/ | together_ai/meta-llama/Llama-3-70b |
| Groq | groq/ | groq/llama-3.3-70b-versatile |
| DeepSeek | deepseek/ | deepseek/deepseek-chat |
| Moonshot | moonshot/ | moonshot/moonshot-v1-8k |
| 智谱 | zhipu/ | zhipu/glm-4 |
| 任意 OpenAI 兼容 | openai/ + base_url | openai/qwen-max |
注意两个坑:
- claude 有短别名:直接写
claude-sonnet-4-5(不加anthropic/)也能工作,LiteLLM 会识别 "claude-*" 并路由到 Anthropic。但写全前缀更明确。 - Azure 的 model 是 deployment 名,不是 OpenAI 的模型名。你在 Azure Portal 创建 deployment 时取的名字,才是这里要填的值。
model="anthropic-claude-sonnet-4-5" 或 model="claude/claude-sonnet-4-5",都会报 BadRequestError: LLM Provider NOT provided。正确的是 斜杠分隔:anthropic/claude-sonnet-4-5。
API Key 的三种传入方式
LiteLLM 找 key 的顺序(从高到低优先级):
- 函数参数
api_key="..."显式传入(优先级最高) - 特殊环境变量,比如
OPENAI_API_KEY、ANTHROPIC_API_KEY、GEMINI_API_KEY - 调用
litellm.api_key = "..."全局设置(不推荐,线程不安全)
生产环境几乎都用第 2 种——环境变量。但有时你需要在同一进程里调多个账号(比如一个客户的 OpenAI key + 另一个客户的 key),这时就用第 1 种:
resp = completion( model="gpt-4o-mini", messages=[...], api_key="sk-tenant-A-key", # 这次用这把 key api_base="https://api.openai.com/v1", # 可选: 自定义 base )
每家 provider 对应的环境变量名在文档里列得很清楚,日常高频的几个:
VERTEXAI_PROJECT + ADC 凭证。{PROVIDER}_API_KEY。messages:对话的 "universal container"
LiteLLM 的 messages 永远是 OpenAI 格式的列表:
messages = [
{"role": "system", "content": "你是一个严谨的数学家。"},
{"role": "user", "content": "1+1 等于几?"},
{"role": "assistant", "content": "等于 2。"},
{"role": "user", "content": "为什么?"},
]
四种 role 全家桶:
[{type:"text"...}, {type:"image_url"...}])。LiteLLM 在底下做的三件事
你看到的是统一的 OpenAI 格式,但 LiteLLM 在发请求前会根据 provider 做翻译。以 system prompt 为例:
| Provider | 原生 system 写法 | LiteLLM 怎么翻译 |
|---|---|---|
| OpenAI | 放在 messages[0],role 为 system | 原样发送 |
| Anthropic | 独立字段 system: "...",不能在 messages 里 | 把 role=system 的那条抽出来,填进请求体 system 字段 |
| Gemini | 独立字段 system_instruction | 同样抽出来填进 system_instruction |
| Bedrock Converse | 独立字段 system: [{text: "..."}] | 抽出来包装成 list 格式 |
正是因为有这层翻译,你才能写一次 system 到处跑。但这种翻译也有代价:多 system 不保证都被保留。如果你的 messages 里有两条 role=system(比如为了实验做 prompt stacking),有些 provider 只会接受第一条。跨 provider 代码建议只放一条 system。
多模态:图片与音频
OpenAI 格式里,多模态的 content 是一个数组,每个元素一个 part:
messages = [{
"role": "user",
"content": [
{"type": "text", "text": "这张图里有什么?"},
{"type": "image_url",
"image_url": {"url": "https://example.com/cat.jpg"}},
],
}]
resp = completion(model="gpt-4o", messages=messages)
print(resp.choices[0].message.content)
同一份 messages,换成 model="anthropic/claude-sonnet-4-5",LiteLLM 会自动把 URL 下载并转成 base64 塞进 Claude 格式(Claude 原生不支持 image URL,只吃 base64 或它家的 files API)。这是第 6 章的重头戏。
核心参数全景
除了 model 和 messages,还有一堆常用参数。LiteLLM 把它们按"OpenAI 名字"对齐,不管底层 provider 叫什么:
| OpenAI 名字 | 作用 | 底层各家叫什么 |
|---|---|---|
temperature | 随机性 0~2 | 各家都叫 temperature,但范围可能是 0~1 |
max_tokens | 最大输出 token | Anthropic 必填;Gemini 叫 maxOutputTokens;LiteLLM 统一成 max_tokens |
top_p | 核采样 | 大部分通用 |
stream | 是否流式 | 各家都有,协议格式不同 |
stop | 停止词,可传字符串或列表 | Anthropic 叫 stop_sequences |
n | 返回几个 candidate | 很多厂商不支持,传了会忽略 |
response_format | 强制 JSON | 第 6 章详讲 |
tools | 函数调用定义 | 第 5 章详讲 |
seed | 可复现采样 | 部分厂商支持 |
user | 终端用户 ID(审计用) | OpenAI/Bedrock 支持 |
timeout | 请求超时(秒) | LiteLLM 自己实现 |
厂商专属参数怎么传
有时你必须用某家的独有参数,比如 Gemini 的 topK、Anthropic 的 top_k。LiteLLM 提供 extra_body 直通:
resp = completion( model="gemini/gemini-2.0-flash", messages=messages, temperature=0.3, extra_body={"topK": 40}, # 原样塞进请求体 )
这是"抽象层的后门"——当你确实需要厂商特性,又不想绕开 LiteLLM 时的逃生出口。副作用是用了 extra_body 的代码就不再是 provider-agnostic 了,换家会 silently 失效。
drop_params:参数兼容的遮羞布
你可能遇到这种场景:你给 Anthropic 传了 frequency_penalty=0.5,Anthropic 根本不支持这个参数——直接报错。LiteLLM 默认会把不支持的参数丢给 provider,让底层报错;但如果你打开 drop_params,它会静默丢弃不支持的参数。
import litellm litellm.drop_params = True # 全局打开 # 或者按调用打开 resp = completion( model="anthropic/claude-sonnet-4-5", messages=messages, frequency_penalty=0.5, # Anthropic 不支持 presence_penalty=0.5, # Anthropic 不支持 drop_params=True, # 会被悄悄丢掉, 不报错 )
适合开:你写的是跨 provider 的通用代码,传的参数是"锦上添花",丢了也没事(如 presence_penalty 这种调优参数)。
不适合开:你传的是语义关键参数(如
tools、response_format),被丢了业务就崩了——你应该让它报错,而不是拿着个废返回继续跑。
返回对象:ModelResponse 大拆解
不管底层是谁,completion() 返回的都是一个 ModelResponse 对象。它 高度兼容 OpenAI SDK 的返回结构,你可以把它当成 OpenAI 的 ChatCompletion 来用:
resp = completion(model="gpt-4o-mini", messages=messages) print(type(resp)) # <class 'litellm.types.utils.ModelResponse'> print(resp) # 类似: # ModelResponse( # id='chatcmpl-AbC123', # choices=[Choices( # finish_reason='stop', # index=0, # message=Message(content='...', role='assistant', tool_calls=None) # )], # created=1730000000, # model='gpt-4o-mini-2024-07-18', # object='chat.completion', # system_fingerprint='fp_xxx', # usage=Usage(completion_tokens=23, prompt_tokens=45, total_tokens=68) # )
最常用的五个路径
choices[0]——即使只有一个 candidate 也要用下标。stop(自然结束)/ length(达到 max_tokens)/ tool_calls(要调工具)/ content_filter(被审核拦)。ModelResponse 支持像 dict 一样用
有些老代码风格喜欢下标访问,LiteLLM 的 ModelResponse 同时支持:
# 对象访问 print(resp.choices[0].message.content) # 字典访问 (兼容 OpenAI v0 风格代码) print(resp["choices"][0]["message"]["content"]) # 转成 dict data = resp.model_dump() # Pydantic 风格 # 或者 resp.json() 拿 JSON 字符串
这就是为什么你用 OpenAI SDK 的旧 Agent 框架换到 LiteLLM 底层,几乎不用改代码——它主动伪装得非常彻底。
换模型的正确姿势
回到第 1 章最后那个 "哇!" 时刻,我们把它改写得更工业一点。看一段"先用 GPT,内部 A/B 看看 Claude 和 Gemini 的表现"的代码:
from litellm import completion import os, time # 环境变量通常放在 .env 里, 通过 python-dotenv 加载 os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY") os.environ["ANTHROPIC_API_KEY"] = os.getenv("ANTHROPIC_API_KEY") os.environ["GEMINI_API_KEY"] = os.getenv("GEMINI_API_KEY") def classify(text: str, model: str) -> dict: """把一段客服工单分到 [bug, feature, billing, other] 四类""" resp = completion( model=model, messages=[ {"role": "system", "content": "只输出四个类别之一: bug/feature/billing/other"}, {"role": "user", "content": text}, ], temperature=0, max_tokens=10, timeout=10, ) return { "label": resp.choices[0].message.content.strip(), "in_tokens": resp.usage.prompt_tokens, "out_tokens": resp.usage.completion_tokens, "cost_usd": resp._hidden_params.get("response_cost", 0), } ticket = "Your app crashed on Android 14 when I opened my profile page" for m in ["gpt-4o-mini", "anthropic/claude-haiku-4-5", "gemini/gemini-2.0-flash"]: t = time.time() r = classify(ticket, m) r["latency_s"] = round(time.time() - t, 2) print(m, r)
输出大致是(数字每次会变):
gpt-4o-mini {'label': 'bug', 'in_tokens': 38, 'out_tokens': 1, 'cost_usd': 0.0000059, 'latency_s': 0.53}
anthropic/claude-haiku-4-5 {'label': 'bug', 'in_tokens': 41, 'out_tokens': 1, 'cost_usd': 0.0000105, 'latency_s': 0.71}
gemini/gemini-2.0-flash {'label': 'bug', 'in_tokens': 40, 'out_tokens': 1, 'cost_usd': 0.0000043, 'latency_s': 0.48}
三家模型、一套业务逻辑,价格-延迟-准确率一眼就能对比。这套对比能力本来要花一周写的胶水代码,现在只要一个循环——这就是 provider 抽象层的直接收益。
同步 vs 异步:acompletion
到这里都是同步代码。如果你在 FastAPI / aiohttp / Celery worker 里,应该用异步版本 acompletion:
import asyncio from litellm import acompletion async def main(): resp = await acompletion( model="gpt-4o-mini", messages=[{"role": "user", "content": "Hi"}], ) print(resp.choices[0].message.content) asyncio.run(main())
签名几乎完全一样,只是前面加了个 await。底层是 httpx.AsyncClient 和 provider 自己的 SDK 异步方法。并发时性能差异很大——第 4 章会做详细 benchmark。
本地 mock:不联网测代码
写代码阶段,你不想每 debug 一次就烧几毛钱(或者根本没网)。LiteLLM 有个 mock_response 参数,返回写死的字符串:
resp = completion( model="gpt-4o-mini", messages=[{"role": "user", "content": "任何东西"}], mock_response="这是假返回, 用来测 pipeline 逻辑", ) print(resp.choices[0].message.content) # 这是假返回, 用来测 pipeline 逻辑
带 mock 的响应同样有 usage、_hidden_params 这些字段(数字是估算的),所以下游处理链不会崩。写单元测试、在 CI 里跑都很爽——见第 11 章的测试章节。
最容易踩的六个坑
- model 字符串拼错:
claude/claude-sonnet-4-5(错,应该anthropic/)。错误信息LLM Provider NOT provided就是它。 - 忘了 max_tokens:Anthropic 的
max_tokens是必填的,不传会直接报BadRequestError。跨 provider 代码建议永远显式传一个值(比如 1024)。 - temperature 范围不同:OpenAI 是 0~2,Anthropic 是 0~1。你传 1.5 给 Claude 会被拒。安全值是 0~1。
- n > 1 默认不支持:很多厂商不支持多 candidate,要么用
num_retries多次调用,要么看 provider 文档。 - 多 system message:有些 provider 会把多个 system 合并,有些只取第一条。最安全是只放一条。
- 使用 SDK 的
client.chat.completions风格:LiteLLM 是函数式的completion(...),不是对象方法。如果你非要 OpenAI 对象风格,用下一节讲的"Drop-in replacement"。
Drop-in replacement:你不想改代码怎么办
假设你已经有一份几千行的 OpenAI SDK 代码,全都是 client.chat.completions.create(...) 这种写法,你不想改。LiteLLM 提供了一个"兼容层":
from litellm import OpenAI # 注意是从 litellm 导入, 不是 openai client = OpenAI() resp = client.chat.completions.create( model="anthropic/claude-sonnet-4-5", # 换成 Claude, 只改这一行 messages=[{"role": "user", "content": "hi"}], max_tokens=256, ) print(resp.choices[0].message.content)
LiteLLM 的 OpenAI 类提供了 OpenAI SDK 的完整方法签名,但底层走 completion()。最少代码改动迁移时非常实用——但要注意这不是 litellm 官方强力推荐的主力写法,它的维护优先级低于 completion()。
另一条更彻底的路是用第 10 章要讲的 Proxy 模式:你的代码继续用官方 openai SDK,只是把 base_url 指向你部署的 LiteLLM Proxy。那时就连 import 都不用改。
本章小结
- LiteLLM 的主力 API 就一个函数:
completion(model, messages, **kwargs) - model 字符串的
provider/model-id前缀是路由的关键 - messages 永远是 OpenAI 格式,LiteLLM 在底下做 system / 多模态 / 参数的翻译
- 参数命名都对齐 OpenAI,
drop_params可静默丢掉不支持的参数 - 返回
ModelResponse兼容 OpenAI 的 ChatCompletion 结构,_hidden_params["response_cost"]帮你算好钱 - 异步用
acompletion,测试用mock_response,迁移用from litellm import OpenAI