1. 项目概述:为什么我们需要一个统一的LLM调用层?

如果你最近在折腾大语言模型(LLM)的应用开发,无论是想做个智能客服、内容生成工具,还是企业内部的知识问答系统,大概率会遇到一个头疼的问题:API调用太“碎”了。OpenAI的GPT-4、Anthropic的Claude、Google的Gemini,还有国内的一众模型,每家都有自己的SDK、认证方式、参数命名和返回格式。今天想试试Claude的上下文更长,明天发现某个开源模型性价比更高,后天客户要求必须部署在私有环境……每切换一次,代码就得跟着大改一通,调试、适配、异常处理,全是重复劳动。

这就是 BerriAI/litellm 这个项目要解决的核心痛点。它不是一个新模型,而是一个极其聪明的“翻译官”和“调度中心”。简单说, litellm 为开发者提供了一个统一的接口,让你可以用一套完全相同的代码,去调用市面上几乎所有主流的闭源和开源大语言模型。你不再需要为每个模型单独写适配层,只需要关心“我想让模型做什么”,而把“怎么调用模型”的脏活累活交给 litellm

我第一次接触它是在为一个客户做模型选型评估时,当时需要同时对比GPT-4、Claude-2和本地部署的Llama 2在相同任务上的表现。如果没有 litellm ,我可能得写三个不同的脚本,处理三种不同的响应解析逻辑,光是整理对比结果就够喝一壶了。用了 litellm 之后,我只需要定义好提示词和需要测试的模型列表,一个循环就搞定了所有调用和结果收集,效率提升了不止一个量级。它真正把开发者从繁琐的API差异中解放出来,让我们能更专注于提示工程、业务逻辑和应用本身。

2. 核心设计思路:抽象、统一与扩展

litellm 的设计哲学非常清晰,可以用三个词概括: 抽象、统一、扩展 。理解这个设计思路,能帮你更好地驾驭它,而不是仅仅把它当作一个“调包”工具。

2.1 抽象:定义标准的LLM交互范式

litellm 做的第一件事,也是最重要的一件事,就是抽象。它观察了所有主流LLM提供商(OpenAI, Anthropic, Cohere, Hugging Face等)的接口,提炼出了一个最小、最通用的交互范式。这个范式主要包含几个核心元素:

  1. 消息(Messages) : 一个包含 role (如 user , assistant , system )和 content 的列表。这直接采用了OpenAI ChatCompletion的格式,因为它已成为事实上的行业标准。
  2. 模型(Model) : 一个字符串标识符,如 gpt-4 claude-2 huggingface/meta-llama/Llama-2-7b-chat-hf litellm 内部维护了一个庞大的模型名称映射表。
  3. 参数(Parameters) : 如 temperature (创造性)、 max_tokens (最大输出长度)、 top_p (核采样)等。 litellm 会将通用参数名“翻译”成各平台API实际接受的参数名。

通过这种抽象,无论底层是哪个模型,上层的调用代码看起来几乎一模一样。这种一致性极大地降低了认知负担和代码复杂度。

2.2 统一:提供一致性的调用接口

基于上述抽象, litellm 对外暴露了几个简洁一致的函数接口,最核心的就是 completion acompletion (异步版本)。

import litellm
# 调用OpenAI
response = litellm.completion(
    model="gpt-4",
    messages=[{"role": "user", "content": "你好,请介绍一下你自己。"}]
)
# 调用Anthropic Claude
response = litellm.completion(
    model="claude-2",
    messages=[{"role": "user", "content": "你好,请介绍一下你自己。"}]
)
# 调用通过Ollama本地运行的模型
response = litellm.completion(
    model="ollama/llama2",
    messages=[{"role": "user", "content": "你好,请介绍一下你自己。"}]
)

看到没?除了 model 参数变了,其他代码纹丝不动。返回的 response 对象结构也是统一的,你可以用 response.choices[0].message.content 来获取回复内容,无需关心底层API返回的是JSON的哪个字段。

2.3 扩展:拥抱整个LLM生态

“统一”并不意味着封闭。 litellm 的强大之处在于其惊人的扩展性。它通过“模型别名”系统和“自定义模型”功能,几乎可以接入任何LLM服务。

  • 内置支持 : 开箱即用支持超过100种模型,包括所有OpenAI格式兼容的API、Anthropic、Cohere、Replicate、Hugging Face Inference Endpoints、Azure OpenAI等。
  • 开源模型集成 : 通过 ollama vllm sglang 等本地推理服务器,可以轻松调用Llama、Mistral、Qwen等开源模型。
  • 自定义模型 : 如果你公司内部有一个私有模型API,只要它接受类似OpenAI的请求格式(或稍作调整),你可以通过几行配置就将其接入 litellm 的管理体系。

这种扩展性使得 litellm 不仅能用于今天的模型选型,更能成为未来技术栈演进的稳定基础。当有更好的新模型出现时,你可以无缝切换,而应用层代码无需改动。

3. 核心功能深度解析与实操要点

掌握了设计思路,我们来深入看看 litellm 提供的几个杀手级功能。这些功能让它从一个简单的“API包装器”变成了一个强大的“LLM应用开发框架”。

3.1 模型路由与负载均衡

在生产环境中,我们通常不会把鸡蛋放在一个篮子里。可能同时使用多个API密钥,或者需要根据成本、延迟、地域等因素动态选择模型。 litellm router 功能就是为此而生。

你可以创建一个路由,里面包含多个“模型部署”( model_list ),每个部署有自己的模型名称、API密钥、基础URL等。 router 还支持复杂的路由策略:

  • 最少请求(Least Busy) : 将请求发给当前正在处理的请求数最少的模型。
  • 简单轮询(Simple Round Robin) : 依次使用列表中的模型。
  • 基于延迟的路由(Latency-based) : 自动将请求路由到延迟最低的端点。
  • 自定义函数 : 你可以写自己的逻辑来决定使用哪个模型。
from litellm import Router
model_list = [
    {
        "model_name": "gpt-4", # 路由逻辑使用的模型名
        "litellm_params": { # 实际调用参数
            "model": "azure/gpt-4",
            "api_key": "your-azure-key",
            "api_base": "https://your-endpoint.openai.azure.com/"
        }
    },
    {
        "model_name": "gpt-4",
        "litellm_params": {
            "model": "gpt-4",
            "api_key": "your-openai-key",
        }
    },
]
router = Router(model_list=model_list, routing_strategy="least-busy")
# 现在,所有对“gpt-4”的请求都会被router智能分配
response = await router.acompletion(
    model="gpt-4",
    messages=[{"role": "user", "content": "Hello"}]
)

实操心得 : 在设置 router 时,我强烈建议给每个 model_list 中的条目加上一个 “model_name” ,这个名称是你自己定义的逻辑名称(如 “fast-gpt-4” “cheap-gpt-35” ),而 litellm_params 里的 “model” 才是真实的提供商模型标识符。这样解耦后,你在业务代码中引用的是稳定的逻辑名,后端具体是哪个供应商的哪个模型,可以随时在路由配置中更改,实现无感切换。

3.2 智能故障转移与重试

网络抖动、API限流、服务暂时不可用……在生产中太常见了。 litellm 内置了强大的故障转移(Fallback)和重试(Retry)机制。

  • 模型级故障转移 : 在 router 中,你可以为每个模型指定一个或多个备用模型( fallbacks )。当主模型调用失败时,会自动按顺序尝试备用模型。
model_list = [
    {
        "model_name": "gpt-4",
        "litellm_params": {
            "model": "gpt-4",
            "api_key": "sk-...",
        },
        "model_info": {"fallbacks": ["claude-2", "gemini-pro"]} # 故障转移链
    }
]
  • 请求级重试 litellm.completion() 函数本身支持 num_retries 参数。当遇到可重试的错误(如429请求过多)时,它会自动等待并重试。
  • 上下文窗口溢出处理 : 这是一个非常贴心的功能。如果你请求的上下文长度超过了模型的最大限制, litellm 可以自动尝试将消息列表压缩(通过总结之前的对话)或者直接切换到支持更长上下文的备用模型(如从 gpt-3.5-turbo 切换到 claude-2-100k )。

注意事项 : 故障转移虽好,但需谨慎使用,尤其是涉及不同模型时。因为不同模型的行为和输出风格可能有差异,可能会影响用户体验的一致性。建议将故障转移用于非关键路径,或者确保备用模型在任务上的表现与主模型大致相当。对于关键任务,更推荐使用具有相同模型的多个API端点配合负载均衡和重试。

3.3 成本计算与使用量分析

LLM API调用是按Token计费的,成本控制是每个项目都必须面对的。 litellm 在每次调用后,都会在返回对象中附带详细的成本和使用量信息。

response = litellm.completion(model="gpt-4", messages=messages)
print(f"本次调用消耗: {response._hidden_params['usage']}")
print(f"预估成本: ${response._hidden_params['cost']}")

更重要的是, litellm 可以与 Langfuse OpenTelemetry 等可观测性平台集成,或者将其自带的 Callback 系统接入你自己的监控和日志系统。你可以实时跟踪每个项目、每个用户、每个模型的Token消耗和成本,设置预算告警,这对于SaaS产品或内部服务的管理至关重要。

一个简单的成本监控回调示例:

from litellm import completion
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def my_custom_logger(
    kwargs,                 # 请求参数
    completion_response,    # 响应对象
    start_time,
    end_time
):
    model = kwargs.get("model", "")
    messages = kwargs.get("messages", [])
    cost = completion_response._hidden_params.get("cost", 0)
    usage = completion_response._hidden_params.get("usage", {})
    logger.info(f"Model: {model}, Cost: ${cost:.6f}, Usage: {usage}")

# 设置全局回调
litellm.success_callback = [my_custom_logger]
# 此后所有调用都会触发日志记录

3.4 流式输出与异步支持

对于需要长时间生成文本的应用(如聊天、长文写作),流式输出(Streaming)能极大提升用户体验,让用户感觉响应更快。 litellm 原生支持流式响应。

response_stream = litellm.completion(
    model="gpt-4",
    messages=messages,
    stream=True
)
for chunk in response_stream:
    delta = chunk.choices[0].delta.content
    if delta is not None:
        print(delta, end="", flush=True) # 逐块打印输出

同时,所有主要函数都有异步版本(如 acompletion ),方便在 asyncio 框架(如FastAPI)中使用,避免阻塞事件循环,提高并发处理能力。

4. 实战部署:从本地开发到生产环境

了解了核心功能,我们来看看如何在实际项目中部署和使用 litellm 。我将以一个简单的“智能问答服务”为例,展示从本地调试到生产部署的完整流程。

4.1 环境准备与基础配置

首先,安装 litellm 。建议使用虚拟环境。

pip install litellm

最基本的配置是设置环境变量来存储你的API密钥。 litellm 会按照 <提供商大写>_API_KEY 的格式自动读取。

# 在你的 .env 文件或 shell 配置中
export OPENAI_API_KEY='sk-...'
export ANTHROPIC_API_KEY='sk-ant-...'
export COHERE_API_KEY='...'

如果你想管理多个同类型密钥(比如多个OpenAI组织的密钥),或者想将配置写在代码里,也可以在初始化时传入:

import litellm
litellm.openai_key = "sk-..."
litellm.anthropic_key = "sk-ant-..."
# 或者通过 completion 参数传入
response = litellm.completion(
    model="claude-2",
    messages=messages,
    api_key="your-claude-key"
)

4.2 构建一个健壮的模型路由服务

在生产环境中,我们通常会部署一个独立的 litellm 代理服务,而不是在每一个应用实例里直接调用API。这样做的好处是集中管理、监控、缓存和降级。

步骤一:编写路由配置文件 ( config.yaml )

model_list:
  - model_name: gpt-4-turbo-primary
    litellm_params:
      model: gpt-4-turbo
      api_key: ${OPENAI_API_KEY_1}
      rpm: 10000 # 每分钟请求数限制(客户端限流)
  - model_name: gpt-4-turbo-backup
    litellm_params:
      model: gpt-4-turbo
      api_key: ${OPENAI_API_KEY_2}
      rpm: 10000
  - model_name: claude-3-sonnet
    litellm_params:
      model: claude-3-sonnet-20240229
      api_key: ${ANTHROPIC_API_KEY}
  - model_name: ollama-llama3
    litellm_params:
      model: ollama/llama3
      api_base: "http://localhost:11434" # 本地Ollama服务

litellm_settings:
  drop_params: true # 忽略不支持的参数,提高兼容性
  set_verbose: true # 生产环境建议设为false

router_settings:
  routing_strategy: “least-busy”
  num_retries: 3
  retry_after: 1 # 重试等待秒数
  timeout: 30 # 全局超时

步骤二:启动代理服务器

litellm 提供了命令行工具,一键启动一个兼容OpenAI API格式的代理服务器。

# 使用配置文件启动
litellm --config ./config.yaml
# 或者直接指定模型列表
litellm --model gpt-4 claude-3-sonnet --num_retries 3 --retry_after 1

服务器启动后,默认在 http://localhost:4000 提供服务。它完全兼容OpenAI的API接口。这意味着,你之前所有使用 openai 库的代码,只需要把 base_url api_key 改一下,就能无缝切换到你的 litellm 代理,享受路由、故障转移等所有高级功能。

# 以前直接调用OpenAI
from openai import OpenAI
client = OpenAI(api_key="sk-...")
response = client.chat.completions.create(model="gpt-4", messages=messages)

# 现在通过litellm代理调用
from openai import OpenAI
client = OpenAI(api_key="dummy-key", # 代理服务器可能配置了自己的认证,或无需认证
                base_url="http://localhost:4000")
response = client.chat.completions.create(model="gpt-4-turbo-primary", messages=messages)
# 实际上,请求会被路由到config.yaml中配置的“gpt-4-turbo-primary”端点

4.3 集成到Web应用框架(以FastAPI为例)

更常见的场景是,我们将 litellm 作为自己后端服务的一部分。下面是一个集成到FastAPI的示例,包含简单的认证和日志。

from fastapi import FastAPI, HTTPException, Depends, Header
from pydantic import BaseModel
import litellm
from litellm import Router
import logging
import yaml

# 加载配置
with open('config.yaml', 'r') as f:
    config = yaml.safe_load(f)

# 初始化路由
router = Router(model_list=config['model_list'], **config.get('router_settings', {}))

app = FastAPI(title="LLM统一网关")

# 简单的API密钥认证(生产环境应用更安全的方案)
async def verify_api_key(x_api_key: str = Header(None)):
    valid_keys = ["my-secret-key-1", "my-secret-key-2"]
    if x_api_key not in valid_keys:
        raise HTTPException(status_code=401, detail="Invalid API Key")
    return x_api_key

class CompletionRequest(BaseModel):
    model: str
    messages: list
    stream: bool = False
    temperature: float = 0.7

@app.post("/v1/chat/completions")
async def create_chat_completion(
    request: CompletionRequest,
    api_key: str = Depends(verify_api_key)
):
    try:
        # 将请求转发给litellm路由
        response = await router.acompletion(
            model=request.model,
            messages=request.messages,
            stream=request.stream,
            temperature=request.temperature
        )
        if request.stream:
            # 处理流式响应,需要返回一个生成器
            async def stream_generator():
                async for chunk in response:
                    yield f"data: {chunk.model_dump_json()}\n\n"
                yield "data: [DONE]\n\n"
            return StreamingResponse(stream_generator(), media_type="text/event-stream")
        else:
            return response
    except Exception as e:
        logging.error(f"Completion error: {e}", exc_info=True)
        # litellm会将底层API错误转换为统一的异常类型
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health_check():
    return {"status": "healthy", "model_count": len(router.model_list)}

这个简单的服务就具备了模型路由、密钥管理、统一错误处理等生产级功能。你可以在此基础上添加更复杂的特性,如请求限流、缓存、更细致的监控等。

5. 高级特性与性能调优

当你的服务量上来后,一些高级特性和性能调优技巧就变得非常重要。

5.1 请求缓存

对于内容生成类应用,相同的提示词往往会产生相同的输出。为这些请求设置缓存可以显著降低成本、提高响应速度。 litellm 支持内存缓存和Redis缓存。

from litellm.caching import Cache
# 使用内存缓存(适合单进程)
litellm.cache = Cache()
# 使用Redis缓存(适合多进程/分布式部署)
import redis
redis_client = redis.Redis(host='localhost', port=6379)
litellm.cache = Cache(type="redis", redis_client=redis_client, ttl=3600) # TTL 1小时

# 启用缓存的调用
response1 = litellm.completion(model="gpt-4", messages=messages, caching=True)
# 相同的请求会立即从缓存返回
response2 = litellm.completion(model="gpt-4", messages=messages, caching=True)
assert response1.choices[0].message.content == response2.choices[0].message.content

注意事项 : 缓存虽好,但要慎用。对于 temperature 大于0的请求,由于输出具有随机性,缓存可能会导致返回不符合预期的“旧”结果。通常建议只对 temperature=0 的确定性请求开启缓存。另外,涉及用户会话历史的请求,其缓存键(由 model messages 等参数生成)可能非常长且唯一,导致缓存命中率极低,白白浪费内存/Redis资源。

5.2 请求限流与预算控制

为了防止某个用户或某个功能消耗过多资源,需要在网关层面实施限流。 litellm router 支持基于RPM(每分钟请求数)和TPM(每分钟Token数)的限流。

# 在config.yaml的litellm_params中为每个模型端点设置
model_list:
  - model_name: gpt-4
    litellm_params:
      model: gpt-4
      api_key: sk-...
      rpm: 60  # 该端点每分钟最多60个请求
      tpm: 60000 # 该端点每分钟最多消耗60000个Token

此外,你可以结合像 SlowAPI FastAPI-Limiter 这样的中间件,在API网关层面实现基于IP或API密钥的全局限流。

对于预算控制,除了前面提到的成本回调,你还可以实现一个中间件,在每次调用前检查该用户或项目的累计成本是否已超预算,如果超出则直接拒绝请求或降级到更便宜的模型。

5.3 上下文管理与Token节省

LLM的上下文窗口是宝贵的资源,也是成本的主要构成部分(特别是长上下文模型)。 litellm 提供了一些辅助功能来管理上下文。

  • 自动Truncation : 如果消息总长度超过模型限制,可以设置 litellm.drop_params=True ,并配合 max_tokens 参数, litellm 会尝试自动截断过长的消息。但更推荐的做法是自己在业务逻辑里处理。
  • 总结式上下文窗口(Experimental) : 这是一个实验性功能,当对话历史太长时, litellm 可以调用另一个LLM来总结之前的对话,从而压缩上下文。这需要谨慎使用,因为总结可能丢失细节。

更实用的做法是在应用层设计好上下文管理策略

  1. 只保留最近N轮对话 : 这是最简单有效的方法。
  2. 向量检索(RAG) : 将历史对话或知识库文档存入向量数据库,每次只检索最相关的片段作为上下文,这是目前处理长上下文的主流方案。 litellm 本身不提供RAG,但它与 LangChain LlamaIndex 等框架可以完美配合。
  3. 分层总结 : 在对话过程中,定期(例如每10轮)用模型对之前的对话做一个简短总结,然后用这个总结代替原始的长篇历史,开始新的对话段。

6. 常见问题排查与实战避坑指南

即使有了 litellm 这样的利器,在实际开发和运维中还是会踩到一些坑。下面是我和团队在实践中总结的一些典型问题及解决方案。

6.1 调用失败与错误处理

问题: 调用 litellm.completion() 时抛出异常,如何快速定位?

排查步骤:

  1. 开启详细日志 : 首先,设置 litellm.set_verbose=True 。这会让 litellm 打印出详细的请求和响应日志,包括最终发往真实API的URL、请求头和请求体。这是最直接的调试手段。
  2. 检查模型别名 : 确认你传入的 model 参数字符串是 litellm 支持的。你可以通过 litellm.model_list 查看所有支持的模型。常见的错误是混淆了提供商前缀,比如 claude-2 是对的,而 anthropic/claude-2 可能不对(取决于版本)。
  3. 检查认证信息 : 确保对应的环境变量(如 OPENAI_API_KEY )已正确设置,且密钥有效、有余额、未过期。对于Azure OpenAI,还需要检查 api_base api_version
  4. 检查网络与代理 : 如果公司网络有代理,需要为 litellm 配置。可以通过环境变量 http_proxy / https_proxy ,或者在代码中为 httpx litellm 底层使用的HTTP客户端)配置代理。
  5. 理解错误类型 litellm 会捕获底层API错误并转换为统一的异常。常见的如:
    • BadRequestError : 通常是请求参数有问题,比如消息格式错误、温度值超出范围。
    • AuthenticationError : API密钥错误。
    • RateLimitError : 触发了速率限制。 litellm 会自动重试,但如果持续触发,需要检查你的调用频率或升级配额。
    • ServiceUnavailableError : 上游服务暂时不可用。 litellm 的故障转移机制应该能处理。

6.2 流式响应中断或不完整

问题: 在使用 stream=True 时,响应有时会中途断开,或者前端接收到的数据不完整。

解决方案:

  1. 检查超时设置 : 流式响应是长时间连接,网络或服务器端的超时设置可能导致连接被过早关闭。确保你的HTTP客户端(如 requests , httpx )和服务端(如你的FastAPI应用)的超时时间设置得足够长。在 litellm 侧,可以通过 timeout 参数设置。
  2. 正确处理服务器发送事件(SSE) : 前端在接收SSE流时,需要正确解析 data: 前缀。确保你的后端在发送每个chunk时,格式是 f”data: {chunk_json}\\n\\n” ,最后发送 ”data: [DONE]\\n\\n”
  3. 网络稳定性 : 不稳定的网络连接是流式中断的常见原因。考虑在客户端增加重连逻辑。
  4. 使用异步客户端 : 在Python异步环境中(如FastAPI),使用 litellm.acompletion(stream=True) 并配合 async for 循环来消费流,性能更佳,不易阻塞。

6.3 性能瓶颈分析与优化

问题: 服务响应慢,吞吐量上不去。

排查与优化方向:

  1. 定位延迟来源 : 使用 litellm.set_verbose=True 查看每次调用的详细耗时。延迟可能来自:
    • 网络延迟 : 特别是调用海外API时。考虑使用地理位置上更近的API端点(如Azure的区域端点)。
    • 模型本身推理速度 : 更大的模型通常更慢。对于实时性要求高的场景,可以考虑使用更快的模型(如 gpt-3.5-turbo vs gpt-4 ),或者使用 litellm router 将请求路由到延迟最低的端点。
    • Token生成速度 : 流式响应可以改善感知延迟,但总生成时间取决于模型和输出长度。
  2. 实施并发与批处理 : 对于大量独立的请求,使用异步( asyncio )并发发送可以极大提升总体吞吐量。 litellm acompletion 对此有良好支持。对于多个相似的请求,如果上游API支持(如OpenAI的Batch API),可以考虑批处理以减少网络开销。
  3. 启用缓存 : 如前所述,对重复的确定性请求启用缓存,是提升性能和降低成本最有效的方式之一。
  4. 监控与扩容 : 监控你的 litellm 代理服务器的CPU、内存和网络I/O。如果成为瓶颈,可以考虑水平扩展,部署多个代理实例,并用负载均衡器(如Nginx)分发请求。

6.4 与现有架构的集成问题

问题: 我们已经有一套基于 openai 库的旧代码,如何平滑迁移到 litellm

迁移策略:

  1. 最小侵入方案(代理模式) : 如前所述,部署一个 litellm 代理服务器(兼容OpenAI API)。然后,只需将现有代码中 OpenAI 客户端的 base_url 指向这个代理, api_key 可替换为代理所需的认证(或留空)。这是迁移成本最低、最安全的方式,可以逐步进行。
  2. 替换SDK方案 : 如果希望更紧密地集成 litellm 的高级功能(如路由、故障转移),可以直接将 openai.ChatCompletion.create 的调用替换为 litellm.completion 。由于接口高度相似,通常只需修改导入和函数名,参数基本一致。注意处理一些细微差别,比如 litellm model 参数包含了提供商信息。
  3. 并行运行与验证 : 在迁移过程中,可以在一段时间内同时运行新旧两套逻辑,对比输出结果和性能,确保迁移无误。

litellm 的价值在于它提供了一种“未来证明”的LLM调用方式。它抽象了底层模型的差异,让你今天的代码在明天的新模型面前依然有效。它更像是一个强大的中间件,而非一个临时工具。投入时间学习和部署它,对于任何严肃的LLM应用项目来说,都是一笔非常划算的投资。

Logo

免费领 100 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐