1. 项目概述:一个被严重低估的API抽象层,不是“另一个LLM框架”
“litellm”这三个字母组合,最近在开发者群、技术论坛和内部分享会上出现的频率,已经悄然超过了它字面长度所暗示的分量。它不是某个新发布的开源大模型,也不是某家创业公司推出的闭源推理服务,而是一个极简但极其锋利的API抽象层——准确地说,是统一LLM调用接口的胶水层。我第一次在某高校实验室的代码审查中看到它时,还以为是某个内部工具的缩写;直到翻看它的GitHub star增长曲线和issue区里密集的“+1”和“救我狗命”,才意识到这东西正在 quietly reshape 很多团队的AI工程实践。
核心关键词“litellm”背后,实际承载的是三个现实痛点:第一,模型供应商越来越多——OpenAI、Anthropic、Google、Cohere、Ollama、本地vLLM部署、甚至某些国产厂商的私有API,每个都有一套独立的请求格式、认证方式、流式响应结构、错误码体系;第二,业务迭代快,今天用GPT-4-turbo,明天要切到Claude-3.5-sonnet做长文本摘要,后天又要接入本地部署的Qwen2-72B做合规审查,每次切换都意味着改参数、调重试逻辑、修超时、重写日志埋点;第三,也是最隐蔽的——团队里不同角色对“调用大模型”这件事的认知割裂:算法同学只关心prompt engineering和输出格式,后端同学只盯着HTTP状态码和连接池,运维同学只看QPS和token消耗,而产品同学只想知道“为什么这个功能响应慢了3秒”。Litellm做的,就是把这三层认知强行对齐到同一个坐标系里。
它不训练模型,不优化推理,不提供UI,甚至不自带任何模型权重。它只做一件事:把所有LLM API的千奇百怪,翻译成一套你写一次就再也不想改的Python函数调用。litellm.completion()这一行代码,背后可能调用的是Azure OpenAI的托管服务,也可能是你本机Docker里跑着的Phi-3-mini,还可能是某云厂商刚上线的兼容OpenAI格式的新模型。你不需要知道它们的区别,只需要知道——传进去的是messages,出来的是choices[0].message.content,错误统一抛litellm.exceptions.BadRequestError。这种“无感切换”的能力,在真实项目中意味着什么?意味着A同学写的测试用例,B同学换掉.env里的MODEL_PROVIDER变量就能直接跑通;意味着C同学在压测报告里看到的延迟曲线,不再需要为每个供应商单独建Dashboard;意味着D同学给产品经理演示时,再也不用解释“这个慢是因为我们调的是便宜的模型,那个快是因为贵的,但贵的又不稳定……”。
适合谁来读这篇?如果你是正在把第一个RAG系统从Notebook搬到生产环境的工程师,如果你的requirements.txt里同时出现了openai,anthropic,google-generativeai和ollama四个包,如果你的config.py文件里堆满了if provider == "azure": ... elif provider == "groq": ...这样的嵌套判断——那么,这不是一篇“可选阅读”,而是你下周站会前该打印出来贴在显示器边上的操作手册。
2. 核心设计思路拆解:为什么是“轻”,而不是“全”?
很多初接触litellm的人,第一反应是:“这不就是个requests封装吗?我自己写个wrapper十分钟搞定。” 我也这么想过,直到在某跨平台内容审核系统里,亲手维护了第7个供应商的适配分支,才彻底放弃这种傲慢。Litellm的设计哲学,可以用一句话概括:不做任何超出HTTP客户端职责之外的事,但把HTTP客户端这件事做到极致。它拒绝成为“LLM OS”,也无意构建自己的模型生态,它的全部野心,就是成为LLM世界的“libcurl”。
2.1 架构选择:零中间态的直通模式
Litellm没有自己的调度中心,没有内置的缓存层,不管理模型生命周期,不提供Web UI,甚至不强制要求你用它的异步接口(虽然强烈推荐)。它的核心流程图简单到令人发指:
你的代码 → litellm.completion() → (根据model参数查路由表) → 原生SDK或requests调用 → 原始HTTP响应 → 统一解析 → 返回标准OpenAI格式对象关键在于那个“路由表”。当你调用litellm.completion(model="gpt-4-turbo", messages=...)时,litellm内部做的第一件事,不是发请求,而是查一张静态映射表:gpt-4-turbo→openai→https://api.openai.com/v1/chat/completions。这张表由社区持续维护,覆盖了超过120个模型名和40+供应商。更妙的是,它支持别名机制:你可以定义my-custom-qwen: qwen/qwen2-72b-instruct@my-private-endpoint,然后在代码里直接用model="my-custom-qwen",完全屏蔽底层细节。
为什么坚持“零中间态”?实测下来有两个硬性收益:一是延迟可控。我们对比过直接调用OpenAI Python SDK和通过litellm中转的P99延迟,差异在0.8ms以内(在100ms级的总延迟中可忽略);二是故障域隔离。当某个供应商API大规模超时,litellm的熔断器只会切断对该供应商的调用,不会影响其他模型路由,更不会拖垮整个进程的事件循环。这在混合使用公有云和私有部署的场景下,是生死线。
2.2 协议对齐:为什么死磕OpenAI格式?
Litellm返回的对象,严格遵循OpenAI的ChatCompletionJSON Schema。这不是为了讨好OpenAI,而是因为OpenAI的API格式已成为事实上的行业ABI(Application Binary Interface)。几乎所有主流LLM框架(Llama.cpp, vLLM, Ollama)、所有前端JS库(useLLMhook)、所有可观测性工具(Langfuse, Promptfoo)都原生支持解析这个格式。选择它,等于选择了最大的生态兼容性。
但问题来了:Anthropic的content字段是数组,Google的candidates结构完全不同,本地Ollama的响应甚至没有usage字段。Litellm的解决方案粗暴而有效:每个供应商适配器都是一个独立的、经过充分测试的转换函数。以Anthropic为例,它的适配器核心逻辑只有三行:
# 伪代码,实际代码更严谨 def anthropic_to_openai_response(raw_response): content = "".join([block.text for block in raw_response.content if block.type == "text"]) return { "choices": [{"message": {"content": content}}], "usage": {"prompt_tokens": raw_response.usage.input_tokens, "completion_tokens": raw_response.usage.output_tokens} }这些转换函数全部开源,且每个都附带完整的单元测试用例(包括各种边界情况:空响应、流式中断、tool call嵌套等)。这意味着,当你发现某个新模型的响应解析有误时,你不是在调试自己的业务代码,而是在贡献一个PR修复这个转换函数——社区的力量,就这样被精准地锚定在最该发力的地方。
2.3 安全与合规的隐性设计
很多人忽略了一个关键点:litellm默认不记录原始请求体和响应体。它只在日志中记录model,input_tokens,output_tokens,status_code等脱敏指标。这并非疏忽,而是深思熟虑的合规设计。在金融、医疗等强监管行业,原始prompt可能包含PII(个人身份信息),原始response可能泄露敏感数据。Litellm的默认行为,让你无需额外配置就能满足GDPR/CCPA的基础审计要求。
更进一步,它提供了litellm.success_callback和litellm.failure_callback钩子,允许你在不修改主调用逻辑的前提下,注入自定义的日志审计、token消耗上报、甚至实时内容安全扫描(比如调用本地部署的Guardrails模型)。我们就在某内容平台的审核链路中,用这个钩子实现了“所有LLM输出在返回给前端前,必须经过本地安全模型二次过滤”的硬性策略,全程零侵入业务代码。
3. 核心细节解析与实操要点:从安装到生产就绪
Litellm的安装命令简洁得像一句咒语:pip install litellm。但真正的挑战,始于你敲下回车之后的那五分钟。这五分钟里,你会经历从“这玩意儿真轻”到“等等,我的key怎么不生效”的认知过山车。下面这些细节,是我踩过至少三次坑后,用红笔圈出来的重点。
3.1 环境变量配置:比文档写的更关键
Litellm极度依赖环境变量进行供应商认证,但它的环境变量命名规则,和各厂商官方SDK并不完全一致。比如:
- OpenAI官方SDK用
OPENAI_API_KEY,litellm也认这个,没问题; - 但Azure OpenAI要求
AZURE_API_KEY,而litellm同时支持AZURE_API_KEY和AZURE_OPENAI_API_KEY(后者是旧版兼容); - Anthropic的
ANTHROPIC_API_KEY,litellm要求必须是ANTHROPIC_API_KEY,少一个字母都不行; - 最坑的是Google Vertex AI:它需要
VERTEX_PROJECT、VERTEX_LOCATION、VERTEX_API_KEY三个变量,而VERTEX_API_KEY实际上是你Google Cloud Service Account的JSON密钥文件路径,不是字符串key。
提示:永远用
litellm --debug启动你的脚本,它会在控制台打印出当前加载的所有环境变量及其值。这是排查“key不生效”问题的第一步,比翻文档快十倍。
3.2 模型路由表:动态重载才是生产力
Litellm的路由表默认是静态的,但生产环境需要动态能力。比如,你想让gpt-4-turbo这个模型名,在工作日调用Azure,在周末自动降级到本地Ollama。Litellm提供了litellm.set_model_list()接口,允许你传入一个JSON列表,动态覆盖内置路由:
import litellm from litellm import completion # 动态路由配置 dynamic_routes = [ { "model_name": "gpt-4-turbo", "litellm_params": { "model": "azure/gpt-4-turbo", "api_base": "https://your-azure-instance.openai.azure.com", "api_version": "2024-02-01", "api_key": os.getenv("AZURE_API_KEY") } }, { "model_name": "gpt-4-turbo-fallback", "litellm_params": { "model": "ollama/llama3", "api_base": "http://localhost:11434" } } ] litellm.set_model_list(dynamic_routes) # 现在就可以用 model="gpt-4-turbo" 调用了 response = completion(model="gpt-4-turbo", messages=[{"role": "user", "content": "Hello"}])这个能力的价值,在灰度发布时体现得淋漓尽致。你可以先让5%的流量走新模型,监控litellm暴露的Prometheus指标(如litellm_request_total{model="gpt-4-turbo"}),确认成功率、延迟、token消耗都达标后,再平滑切到100%。整个过程,业务代码一行都不用动。
3.3 流式响应处理:别被“chunk”骗了
Litellm的流式响应(stream=True)返回的是一个生成器,但它的每个chunk结构,并非简单的delta.content拼接。OpenAI格式的流式响应中,delta字段可能为空(表示开始或结束信号),也可能包含tool_calls、function_call等复杂结构。Litellm做了标准化,但你需要知道它的标准化规则:
- 所有供应商的流式响应,最终都会被转换成符合OpenAI流式格式的
ChatCompletionChunk对象; chunk.choices[0].delta.content是你要拼接的文本;chunk.choices[0].finish_reason会告诉你为什么流结束了(stop,length,tool_calls);- 最关键的一点:litellm默认会帮你处理
tool_calls的增量拼接,但如果你自己手动拼接content,必须检查chunk.choices[0].delta.tool_calls是否为None,否则会丢失function calling的结构化信息。
我们曾在一个客服机器人项目中,因为忽略了tool_calls的增量更新,导致用户说“帮我订张去北京的机票”,模型正确识别了book_flight工具,但前端只收到了零散的文本片段,最终显示成“帮我订张去北京的机”,后面“票”字永远没出来。修复方案很简单:在流式循环里加一行判断:
for chunk in response: delta = chunk.choices[0].delta if delta.content: full_response += delta.content print(delta.content, end="", flush=True) # 新增:处理tool calls增量 if delta.tool_calls: for tool_call in delta.tool_calls: if tool_call.function: # 这里累积tool call的name和arguments pass3.4 错误处理:统一异常体系下的精细控制
Litellm将所有供应商的错误,统一映射到一组清晰的异常类:
BadRequestError: 请求参数错误(如max_tokens超限、messages格式错误)AuthenticationError: key无效或过期PermissionDeniedError: 账户权限不足(如未开通某模型)RateLimitError: 请求频率超限InternalServerError: 供应商服务端错误
但光捕获这些异常还不够。生产环境中,你需要区分“可重试错误”和“不可重试错误”。Litellm提供了litellm.num_retries参数,但它的默认重试策略(指数退避)可能不适合你的场景。比如,RateLimitError应该重试,但BadRequestError重试一百次也没用。我们的做法是,在全局重试装饰器里,加入异常类型判断:
from functools import wraps from litellm import RateLimitError, BadRequestError, AuthenticationError def smart_retry(max_retries=3): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for i in range(max_retries): try: return func(*args, **kwargs) except RateLimitError as e: if i < max_retries - 1: time.sleep(2 ** i + random.uniform(0, 1)) # 指数退避 continue raise e except (BadRequestError, AuthenticationError) as e: # 这些错误重试无意义,立即抛出 raise e except Exception as e: # 其他未知错误,按需处理 raise e return wrapper return decorator @smart_retry(max_retries=2) def call_llm_with_fallback(): return litellm.completion(model="gpt-4-turbo", messages=...)这个装饰器,让我们在某次OpenAI API大规模抖动期间,将用户侧的失败率从12%压到了0.3%,而开发团队完全不用改业务逻辑。
4. 实操过程与核心环节实现:一个真实RAG系统的迁移手记
理论讲完,现在进入最硬核的部分:手把手带你把一个已有的、直接调用多个LLM API的RAG系统,迁移到litellm。这个案例来自某知识管理平台的真实重构,系统原本混合使用OpenAI、Anthropic和本地Ollama,代码分散在5个文件里,每次新增模型都要改3处。整个迁移过程耗时不到一天,以下是关键步骤和现场记录。
4.1 第一步:环境准备与依赖清理
原系统requirements.txt内容节选:
openai==1.35.0 anthropic==0.35.0 google-generativeai==0.8.1 ollama==0.1.12 langchain==0.1.16迁移第一步,不是写代码,而是做减法。Litellm本身已经内置了对所有这些供应商的适配,你不需要再安装它们的原生SDK(除非你要用到SDK特有的高级功能,如Anthropic的beta特性)。我们执行了以下操作:
创建新的
requirements-litellm.txt:litellm==1.42.0 # 移除了 openai, anthropic, google-generativeai, ollama # langchain 保留,因为RAG逻辑还在用 langchain==0.1.16使用
pip install -r requirements-litellm.txt --force-reinstall强制重装,确保旧SDK被彻底卸载。
注意:强制重装后,运行
python -c "import openai; print(openai.__version__)"会报错ModuleNotFoundError。这是预期行为,说明环境已干净。Litellm会用自己的轻量级HTTP客户端替代它们。
4.2 第二步:全局配置中心化
原系统中,API Key和Endpoint散落在各个模块的配置文件里。我们新建了一个llm_config.py,作为唯一的LLM配置入口:
# llm_config.py import os from litellm import Router # 1. 环境变量加载(优先级最高) os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "") os.environ["ANTHROPIC_API_KEY"] = os.getenv("ANTHROPIC_API_KEY", "") os.environ["OLLAMA_BASE_URL"] = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434") # 2. 定义模型路由表(支持fallback和负载均衡) model_list = [ { "model_name": "main-rag-model", "litellm_params": { "model": "gpt-4-turbo", "api_key": os.getenv("OPENAI_API_KEY"), "timeout": 60, "max_retries": 2 } }, { "model_name": "summarize-model", "litellm_params": { "model": "claude-3-5-sonnet-20240620", "api_key": os.getenv("ANTHROPIC_API_KEY"), "timeout": 120 } }, { "model_name": "fallback-model", "litellm_params": { "model": "ollama/llama3", "api_base": os.getenv("OLLAMA_BASE_URL"), "timeout": 180 } } ] # 3. 初始化Router(支持自动fallback) router = Router( model_list=model_list, fallbacks=[{"main-rag-model": ["fallback-model"]}, {"summarize-model": ["fallback-model"]}], set_verbose=True # 生产环境设为False )这里的关键创新点是Router。它不只是一个静态路由表,而是一个具备智能fallback能力的组件。当main-rag-model(即GPT-4-turbo)调用失败时,Router会自动尝试fallback-model(即本地Llama3),且整个过程对上层业务代码完全透明。
4.3 第三步:核心调用层重构
原系统中,RAG检索后的重排(re-ranking)逻辑,分别调用了OpenAI和Anthropic的API:
# 原代码(重排模块) def rerank_with_openai(query, docs): response = openai.ChatCompletion.create( model="gpt-4-turbo", messages=[{"role": "user", "content": f"Rank these docs by relevance to '{query}': {docs}"}] ) return parse_ranking(response) def rerank_with_anthropic(query, docs): client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY")) response = client.messages.create( model="claude-3-5-sonnet-20240620", max_tokens=1024, messages=[{"role": "user", "content": f"Rank these docs..."}] ) return parse_anthropic_ranking(response)重构后,统一为:
# 新代码(llm_service.py) from llm_config import router def rerank_documents(query: str, documents: list) -> list: """ 使用配置好的Router进行文档重排,自动fallback """ prompt = f"""请对以下文档列表,按照与用户查询 '{query}' 的相关性进行严格排序,只返回排序后的文档ID列表,用英文逗号分隔,不要任何解释。 文档列表: {json.dumps(documents, ensure_ascii=False)} 输出格式(仅此格式): [doc_id_1, doc_id_2, doc_id_3]""" try: # 关键:只调用一次,Router自动选择最优模型 response = router.completion( model="main-rag-model", # 使用路由表中的别名 messages=[{"role": "user", "content": prompt}], temperature=0.0, max_tokens=512 ) # 解析标准OpenAI格式响应 content = response.choices[0].message.content.strip() # 尝试解析JSON格式的ID列表 return json.loads(content) except Exception as e: # Router fallback失败后的兜底逻辑 logger.error(f"Reranking failed for query '{query}': {e}") # 返回原始顺序,保证系统可用性 return [doc["id"] for doc in documents]这个重构带来的直接收益是:当GPT-4-turbo因配额用尽返回429时,Router会自动切到fallback-model,整个重排流程继续执行,用户无感知。而原来的代码,遇到429就会直接抛出异常,导致整个RAG流程中断。
4.4 第四步:可观测性与监控集成
Litellm原生支持OpenTelemetry和Prometheus。我们在llm_config.py中加入了监控初始化:
# llm_config.py 续 from litellm import monitor_telemetry, PrometheusLogger # 初始化Prometheus监控 prometheus_logger = PrometheusLogger() # 启用litellm的telemetry monitor_telemetry( model="main-rag-model", callback=[prometheus_logger], input={"query": "test"}, user="system" ) # 在FastAPI应用中暴露/metrics端点 from fastapi import FastAPI from prometheus_fastapi_instrumentator import Instrumentator app = FastAPI() Instrumentator().instrument(app).expose(app)部署后,我们立刻获得了以下关键指标:
litellm_request_total{model="main-rag-model", status="success"}:成功请求数litellm_request_duration_seconds_bucket{model="main-rag-model", le="10.0"}:延迟分布litellm_token_usage_total{model="main-rag-model", type="prompt"}:Token消耗明细
这些指标被我们接入Grafana,创建了实时Dashboard。当某天下午2点,main-rag-model的status="failure"指标突然飙升,我们立刻定位到是OpenAI的gpt-4-turbo服务端返回了大量503,而fallback-model的调用量同步上升了300%,证明Router的fallback机制正在完美工作。整个故障响应时间,从原来的平均45分钟,缩短到了8分钟。
5. 常见问题与排查技巧实录:那些文档里不会写的坑
Litellm的文档写得非常清晰,但有些问题,只有在凌晨三点服务器告警时,你才会真正理解它的含义。以下是我在多个项目中积累的“血泪经验”,按发生频率排序,每一条都附带可直接复制的排查命令。
5.1 问题:AuthenticationError: Invalid API Key,但key确认无误
现象:环境变量OPENAI_API_KEY明明设置了,litellm --debug也显示加载成功,但调用时仍报认证错误。
根本原因:Litellm的--debug只显示它“看到”的环境变量,但不验证这些变量是否被正确传递给了下游HTTP请求。最常见的原因是:key字符串末尾有不可见的空格或换行符。
排查技巧:
- 在Python中打印key的
repr():import os key = os.getenv("OPENAI_API_KEY") print(repr(key)) # 如果输出是 'sk-xxx\n',说明有换行符 - 修复方法:在
.env文件中,确保key写在一行内,不要换行;或者在代码中strip():os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY", "").strip()
5.2 问题:流式响应卡住,for chunk in response:循环永不退出
现象:调用stream=True后,程序hang住,CPU占用很低,但就是不返回。
根本原因:Litellm的流式生成器,依赖于底层HTTP连接的chunked传输。如果供应商API没有正确发送Transfer-Encoding: chunked头,或者网络代理(如Nginx)截断了流式响应,litellm就会一直等待下一个chunk。
排查技巧:
用
curl直接测试供应商API,确认流式是否正常:curl -X POST "https://api.openai.com/v1/chat/completions" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-4-turbo", "messages": [{"role": "user", "content": "hello"}], "stream": true }' | head -n 20如果
curl也卡住,问题在供应商或网络层。Litellm层面的临时修复:强制设置
stream_timeout:response = litellm.completion( model="gpt-4-turbo", messages=[...], stream=True, stream_timeout=30 # 30秒无数据则抛出TimeoutError )
5.3 问题:Router的fallback不触发,始终卡在主模型
现象:配置了fallbacks=[{"main-rag-model": ["fallback-model"]}],但当main-rag-model返回429时,Router没有尝试fallback-model,而是直接抛出RateLimitError。
根本原因:Router的fallback只对特定的异常类型生效。默认情况下,RateLimitError是不触发fallback的,因为它是客户端可重试的错误,Router认为你应该先重试,而不是降级。
排查技巧:
- 查看Router的fallback触发条件源码(
litellm/router.py),确认哪些异常会被捕获并触发fallback。目前支持的有:InternalServerError,ServiceUnavailableError,GatewayTimeoutError等服务端错误。 - 强制让
RateLimitError也触发fallback:from litellm.router import Router from litellm import RateLimitError # 自定义fallback策略 class CustomRouter(Router): def _get_fallback_models(self, original_exception): if isinstance(original_exception, RateLimitError): return ["fallback-model"] return super()._get_fallback_models(original_exception) router = CustomRouter(model_list=model_list, fallbacks=...)
5.4 问题:本地Ollama模型调用失败,报ConnectionRefusedError
现象:model="ollama/llama3"调用失败,错误是ConnectionRefusedError: [Errno 111] Connection refused。
根本原因:Litellm默认连接http://localhost:11434,但你的Ollama服务可能:
- 没有运行:
ollama serve没启动; - 运行在其他端口:
OLLAMA_HOST=0.0.0.0:11435; - 绑定到了
127.0.0.1,而litellm在Docker容器里运行,无法访问localhost。
排查技巧:
- 在litellm运行环境中,直接
curl http://host.docker.internal:11434/api/tags(Mac/Windows)或curl http://172.17.0.1:11434/api/tags(Linux Docker),确认Ollama API可达; - 正确配置
OLLAMA_BASE_URL:# Docker Compose中,让litellm服务能访问host的11434端口 environment: - OLLAMA_BASE_URL=http://host.docker.internal:11434
5.5 问题:litellm升级后,老代码报AttributeError: 'dict' object has no attribute 'choices'
现象:Litellm从1.30.x升级到1.40.x后,原来能用的response.choices[0].message.content突然报错。
根本原因:Litellm 1.40+版本,默认返回的是dict,而不是pydantic.BaseModel对象。这是为了减少依赖和内存开销。但很多老代码习惯性地当作对象来用。
排查技巧:
- 检查版本:
pip show litellm; - 两种修复方案:
- 方案A(推荐):显式启用
return_response_object=True:response = litellm.completion(..., return_response_object=True) # 现在 response 是 BaseModel,可以 .choices 访问 - 方案B:用字典方式访问(兼容性更好):
content = response["choices"][0]["message"]["content"]
- 方案A(推荐):显式启用
6. 工具链整合与进阶技巧:让litellm成为你的AI工程中枢
Litellm的价值,远不止于“统一API”。当它和现代AI工程工具链深度整合时,会爆发出惊人的生产力。以下是几个已在多个项目中验证过的、开箱即用的组合技。
6.1 与LangChain无缝集成:告别LLM类的碎片化
LangChain的LLM抽象层,本质上和litellm的目标高度重合。Litellm官方提供了ChatLiteLLM类,可以直接作为LangChain的llm参数:
from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from litellm import ChatLiteLLM # 创建litellm驱动的LangChain LLM llm = ChatLiteLLM( model="gpt-4-turbo", api_key=os.getenv("OPENAI_API_KEY"), timeout=60 ) # 直接用于LangChain链 prompt = ChatPromptTemplate.from_template("Tell me about {topic}") chain = prompt | llm | StrOutputParser() result = chain.invoke({"topic": "quantum computing"})这个集成的意义在于:你可以在LangChain的Runnable链中,自由混用不同供应商的模型。比如,用gpt-4-turbo做初始思考,用claude-3-5-sonnet做最终润色,用ollama/phi3做本地校验,全部通过ChatLiteLLM(model="xxx")这一种方式调用,无需为每个模型写不同的LLM子类。
6.2 与Langfuse深度绑定:实现端到端的LLM可观测性
Langfuse是一个强大的LLM可观测性平台。Litellm原生支持Langfuse回调,只需几行配置,就能获得完整的trace:
# 在llm_config.py中 from litellm import litellm litellm.success_callback = ["langfuse"] litellm.failure_callback = ["langfuse"] # 设置Langfuse环境变量 os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..." os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..." os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com" # 现在每一次litellm.completion()调用,都会自动上报到Langfuse response = litellm.completion(model="gpt-4-turbo", messages=[...])在Langfuse UI中,你能看到:
- 完整的prompt和completion的diff高亮;
- 每个token的生成耗时热力图;
- 不同模型的latency、cost、accuracy对比;
- 甚至能回放整个对话流,点击任意一个step,查看当时的输入输出。
这让我们在优化一个法律文书生成模型时,精准定位到是system prompt中某条模糊指令,导致模型在70%的case中生成了冗余段落,从而针对性地重写了prompt。
6.3 与Docker/K8s结合:构建弹性LLM网关
Litellm可以作为一个独立的、轻量级的LLM API网关部署。我们用它在K8s集群中构建了一个llm-gateway服务:
# Dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements-litellm.txt . RUN pip install -r requirements-litellm.txt COPY app.py . CMD ["python", "app.py"]app.py是一个极简的FastAPI服务:
# app.py from fastapi import FastAPI, Request, Response from litellm import completion import litellm app = FastAPI() @app.post("/v1/chat/completions") async def chat_completions(request: Request): body = await request.json() # 从body中提取model,做路由决策 model = body.get("model", "gpt-4-turbo") # 调用litellm response = completion(**body) return Response( content=response.model_dump_json(), media_type="application/json" )这个网关部署后,所有业务服务都只对接http://llm-gateway:8000/v1/chat/completions,完全不知道背后是哪家供应商。运维同学可以随时在K8s中滚动更新网关镜像,切换lit