litellm实战:统一大模型API接入,解决多模型切换与网关部署
我一直觉得做AI应用开发最磨人的不是算法本身而是“接模型”这件事。今天接OpenAI明天换Claude后天又加一个国产模型每个服务的接口风格不一样鉴权方式不一样返回字段也不一样SDK版本一升级还得跟着改代码。后来我在项目里把litellm引入进来用了一段时间确实省掉了很多重复劳动今天写一篇实战向的总结聊聊这个工具怎么用、适合解决什么问题以及我踩过的一些坑。litellm做的事情其实很直接它把市面上主流的大模型API全部转成OpenAI风格的调用格式对外提供一个统一的接口。你只需要写一份代码通过模型名称的不同来区分配置不管是OpenAI、Anthropic、Google Gemini还是本地的Ollama、vLLM都能用同一套代码去调。这个思路听起来简单但真用起来就会发现它能帮你省掉的适配工作比想象中多得多。1. 先弄明白litellm到底解决了什么问题1.1 多模型API调用的格式分裂过去几年我在好几个项目里同时接入多家模型服务每次接到一个新模型供应商时都要经历同样的流程读它的API文档、安装专属SDK、处理好鉴权头、把请求参数改成它要求的字段名、再解析返回结构。如果只是做一个Demo这倒无所谓但真到了项目需要支持多个模型备选或者做模型切换时这套重复工作就会成倍膨胀。举个很常见的例子OpenAI的chat/completions接口请求体里用messages数组传对话而某国产模型服务可能要求包一层额外的extra_body字段还可能要求用不同的鉴权方式Anthropic的API又要求你额外传system参数并指定模型版本。如果你在代码里手写针对每家供应商的适配层后续任何一家的接口稍有变化你的代码就要跟着改。而且不同供应商的错误码、重试策略也完全不一样处理起来非常麻烦。1.2 litellm的核心定位一个统一的协议转换层litellm的本质是“协议转换层”你可以把它想象成一个万能转接头——一头插进各家大模型API另一头统一输出成OpenAI风格的接口。项目内部通过维护模型名称与供应商的映射关系自动识别你要调用哪家模型然后帮你完成请求格式的转换、鉴权信息的注入、以及返回结果的标准化。这个设计带来的价值是多方面的。一方面是代码层面你只依赖一套OpenAI风格的接口另一方面是运维层面litellm还支持以代理服务的方式部署把模型入口收敛到一个统一代理服务上团队里其他人不需要关心底层接的是哪家模型只要拿到代理服务地址和key就能调用。平时我们项目经理催着要进度这种收敛真的能显著降低协作成本。更重要的是litellm并不改变模型本身的能力。它只是做格式转换和路由转发底层还是调用原厂API所以模型的输出质量不会因为中间多了这一层而下降。从我的经验看这个转换层的性能开销也很小基本可以忽略。2. 快速部署与基础配置2.1 安装litellm两种身份使用方式litellm的安装非常简单一个pip命令就能完成pip install litellm装完之后你主要有两种使用方式。第一种是把它当作Python SDK直接嵌入到自己的代码里适合在脚本或者服务内部做模型调用第二种是把它以代理服务的方式启动对外暴露一个OpenAI兼容的HTTP接口适合给团队或者多服务共享使用。我在本地调试时一般先用SDK模式等到需要给多个模块共用模型入口时再切换成代理模式。这两种模式不是互斥的一套代码里可以同时用只是场景不同。2.2 通过环境变量配置模型接入在SDK模式下litellm主要通过环境变量来识别API密钥。你可以把密钥写在环境变量里也可以在调用时显式传入api_key参数。我个人建议用环境变量因为这样代码里不会明文出现密钥方便后续迁移。export OPENAI_API_KEYsk-xxxx export ANTHROPIC_API_KEYsk-ant-xxxx export GEMINI_API_KEYAIzaxxxx export OPENROUTER_API_KEYsk-or-xxxx如果你用的是某个兼容OpenAI格式的自建服务比如本地部署的vLLM或Ollama则可以通过自定义api_base来指定服务地址from litellm import completion response completion( modelopenai/local-model, messages[{role: user, content: 你好}], api_basehttp://localhost:8000/v1, api_keysk-local, )这里的模型名格式是供应商前缀/模型名比如openai/gpt-4o、anthropic/claude-3-5-sonnet-20241022、ollama/llama3.1。litellm会根据前缀自动选择合适的供应商SDK完成调用。2.3 第一次调用最简单的OpenAI兼容请求配置好环境变量后第一次调用比想象中还简单import litellm response litellm.completion( modelgpt-4o, messages[ {role: system, content: 你是一个乐于助人的助手。}, {role: user, content: 请用一句话介绍你自己}, ], ) print(response.choices[0].message.content)如果你已经熟悉OpenAI的SDK那么完全可以把litellm.completion看成openai.ChatCompletion.create的替代品只是model参数变得更灵活了。官方还明确过litellm的接口和参数兼容OpenAI的chat.completions.create所以现有项目迁移成本很低。我遇到过一个需求——项目里原来的代码基于OpenAI SDK要临时切换到一个国产模型做对比测试。当初要是硬改代码就要动很多调用位置但换成litellm后只需要把model参数换成供应商前缀和模型名的组合逻辑零改动爽快得很。3. Proxy网关模式进阶功能3.1 用配置文件声明模型路由与参数当你要把litellm部署成团队共享的代理服务时推荐用配置文件的方式管理模型列表。先写一个config.yamlmodel_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: local-llama litellm_params: model: ollama/llama3.1 api_base: http://localhost:11434 litellm_settings: drop_params: true set_verbose: true然后启动代理服务litellm --config config.yaml --port 4000这样你的团队就拥有了一个OpenAI兼容的本地网关调用方只需要把base_url改成http://localhost:4000然后用你定义的模型名比如gpt-4o、claude-sonnet、local-llama就能统一调用底层不同的模型。这里有个很重要的设计model_name只是给外部调用用的别名真实的litellm_params.model才是底层供应商的模型标识。这样即使某一天你从GPT-4o切到别家模型只要保持别名不变调用方根本感知不到变化。3.2 负载均衡与自动容灾代理模式下最实用的功能之一是负载均衡和自动容灾。你可以在config.yaml中为一个逻辑模型配置多个实际端点litellm会在这些端点之间自动分发请求。model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY model_info: priority: 1 - model_name: gpt-4o litellm_params: model: azure/gpt-4o api_key: os.environ/AZURE_API_KEY api_base: https://your-resource.openai.azure.com model_info: priority: 2 router_settings: routing_strategy: simple-shuffle fallbacks: [{gpt-4o: [claude-sonnet]}] num_retries: 3 timeout: 30这段配置表达了两个关键的事情第一同一个gpt-4o别名下同时挂了OpenAI官方和Azure两个端点请求会按策略分发当某个端点不可用时自动切换第二如果gpt-4o整体不可用则降级到claude-sonnet模型。fallbacks配置在真实生产环境里非常有用它保证你的服务不会因为单一模型供应商出问题而彻底不可用。关于路由策略litellm支持多种选择比如simple-shuffle是随机分发least-busy是根据负载分发usage-based-routing则会结合历史调用成本与延迟来做决策。我一般在内部测试环境用simple-shuffle在生产环境用least-busy因为后者能更均衡地利用多个配额的资源。3.3 模型映射、重命名与统一参数模型映射是litellm另一个让我觉得贴心的功能。某次我们把一个用GPT-4o的应用迁到自建模型上想让调用方暂时不感知变化就在配置里做了这样的映射model_list: - model_name: gpt-4o litellm_params: model: openai/qwen-plus api_base: https://your-gateway/v1 api_key: os.environ/QWEN_API_KEY配置好后外部请求仍用gpt-4o这个名字发起但实际由qwen-plus模型响应。这种映射还有个好处你可以借机把供应商的版本信息隐藏起来内部想换哪个版本就换哪个对上层完全透明。除此之外litellm还支持统一注入一些参数前缀比如max_tokens、temperature等。比如某个模型不支持logprobs字段你可以设置drop_params: true让litellm自动丢弃不支持的参数避免请求失败。我第一次用这个功能时少写了很多if-else判断体验确实不错。4. 生产环境多用户、成本与安全4.1 虚拟密钥与用户维度控制如果代理服务只是自己用直接配一个静态key就够了。但团队场景下你大概率需要给不同成员或不同业务线分配独立的虚拟密钥方便管控配额和追溯调用来源。litellm的/key/generate接口就是干这个的curl -X POST http://localhost:4000/key/generate \ -H Authorization: Bearer sk-admin-key \ -H Content-Type: application/json \ -d { models: [gpt-4o, claude-sonnet], max_budget: 100, budget_duration: 1mo }生成的虚拟key只能调用指定模型列表里的模型并且有月度预算上限。这个机制非常适合给多个内部项目分配模型成本每个项目一把key各自设置预算月底通过litellm的报表接口一拉能直接看出谁用了多少token、花了多少钱。我还习惯在虚拟key上附加元数据比如user_id或者team_id这样在日志里排查问题时就非常方便能看到一次报错到底是哪个业务线触发的。团队成员离职或者项目下线时直接吊销对应key其他key不受影响。4.2 缓存与缓存控制策略litellm支持缓存功能既可以用内置的内存缓存也可以接入Redis。开启缓存后如果遇到完全相同的请求代理服务可以直接返回缓存的响应不用每次都打到模型供应商那里。配置方式如下litellm_settings: cache: true cache_params: type: redis host: localhost port: 6379 ttl: 3600对于聊天场景这种精确匹配的缓存命中率并不高因为用户问题很少一字不差。但在一些固定模板类的调用上——比如生成同样的日报摘要、相同的系统提示加上固定输入——缓存能显著降低成本和延迟。Redis缓存也支持语义缓存但我实际用下来语义缓存需要调节相似度阈值调试成本不低如果业务没有很强的重复性建议先用精确匹配的缓存。注意启用prompt caching后如果业务对数据时效性很敏感一定要设置合理的TTL或者通过请求参数强制绕过缓存。我遇到过因为缓存导致返回旧数据的问题后来在关键请求上加了no-cache参数才解决。4.3 可观测性日志、报表与使用分析生产环境里模型调用不能只看功能是否正常还要关注成本走势和延迟分布。litellm提供了多种观测手段最基础的是控制台日志设置set_verbose: true后会在终端输出每次请求的详细信息和消耗的token数量进阶一点可以接入Prometheus监控指标或者把回调数据发到日志分析平台。我在一个多租户项目里同时跑了两个模型供应商月底结算成本时如果靠手工统计会很痛苦。后来我调了litellm自带的/spend/logs接口按虚拟key拉取了所有调用的token消耗明细几分钟就算清楚了每个业务线的成本分摊省了不少对账时间。就算没有专门的报表平台litellm自带的这些接口也足够撑起中小规模团队的需求。5. 常见问题与排查技巧5.1 典型报错与解决方式速查下面的表格整理了我实际运行中碰到过的高频问题以及对应的排查思路。报错现象可能原因解决方式AuthenticationError: Invalid API key环境变量未正确配置或供应商侧密钥失效检查os.environ里的key是否与供应商控制台一致新生成的key需要重新加载进程NotFoundError: Model not found模型名拼写错误或该模型名未在config中声明用litellm.model_list打印可用模型列表确认model_name别名与实际供应商模型名一致TimeoutError: Request timed out上游模型响应过慢或者代理层超时设置过短调大router_settings.timeout对长文本生成场景单独设置更长的超时时间RateLimitError: 429供应商侧限流或代理层配置了速率限制启用litellm的重试机制按供应商配额分散请求开启多个端点负载均衡返回内容被截断max_tokens设置过低或模型本身输出长度上限影响调大max_tokens查看供应商模型的上下文窗口与max output长度请求参数某个字段不被支持不同供应商的API参数不兼容开启drop_params: true让litellm自动丢弃不受支持的字段5.2 几个容易踩坑的细节第一个坑是API版本兼容性。有些模型供应商会要求请求体中带上api-version参数比如Azure OpenAI。如果你用Azure端点别忘了在配置里显式写明api_version否则请求可能直接报错。litellm虽然有默认值但不同版本间行为不完全一致还是自己指定更稳妥。第二个坑是不同模型的上下文窗口差异。同一个应用在GPT-4o上跑得好好的切到一个上下文窗口较小的模型之后长对话直接报错或者效果明显下降。这不是litellm的问题但litellm的model_info里可以配置max_input_tokens和max_output_tokens建议配置好让路由层在请求前就能判断上下文是否超限避免白白浪费一次请求。第三个坑是自定义供应商时的请求格式。虽然litellm主打OpenAI兼容但有些自建网关或者企业内部模型不完全兼容。这种情况下你可能需要在litellm_params里补充自定义参数甚至写自定义回调来做请求预处理。刚开始用的时候不要假设所有模型都是100%兼容先打印实际请求日志确认一下格式再批量接入能省掉很多排错时间。第四个坑是日志级别。set_verbose: true在调试时很有用但在生产环境会输出大量日志影响性能不说还可能把敏感的请求内容打出来。我一般只在调试环境开生产环境保持默认关闭需要排查问题时临时开一小段时间定位完问题立刻关掉。5.3 性能调优与成本控制建议谈到性能流式输出是我强烈建议优先开启的功能。模型接口的首次响应时间和完整响应时间差距很大如果应用场景是聊天或者生成式任务开启流式输出后用户体验会好很多。litellm的completion调用支持streamTrue代理模式下的/chat/completions也天然兼容流式返回迁移成本很低。成本控制方面除了前面提到的虚拟key预算还有一个思路是利用不同模型的价差做混用。比如需要快且便宜的简单任务走轻量模型复杂的推理任务才用旗舰模型。以前手写代码时要自己做这套路由现在在litellm里通过模型别名和路由策略的组合就能实现。我在某个信息抽取场景里把大约七成简单请求分发到便宜模型上剩下的复杂请求走贵模型整体账单比原来下降了一截效果也没有明显波动。另外务必养成定期拉取调用日志的习惯。litellm会把每次调用的prompt_tokens、completion_tokens和延迟都记录下来这些数据不仅能帮你优化路由策略还能在模型供应商发起账单时作为对账依据。按照我目前的用法litellm已经在好几个项目里充当模型接入的统一入口了。从最初的SDK模式到后来的Proxy代理模式它解决的问题也从“少写适配代码”延伸到了“统一管理多模型接入、成本和权限”。如果你正在被各种模型的API差异折磨或者团队里不同项目都在各自接模型花半天时间把litellm搭起来大概率能省下后面几周的维护时间。一个小建议先在测试环境接两三个模型跑通流程观察几天日志确认路由和fallback符合预期后再上生产不要一上来就铺开这样风险可控得多。

相关新闻

el-tag组件实战:从基础用法到高阶玩法,避开后台开发那些坑

el-tag组件实战:从基础用法到高阶玩法,避开后台开发那些坑

用了这么多年前端框架&#xff0c; <el-tag> 大概是我在管理后台项目里见到频率最高的组件之一。小到一条订单的状态标记&#xff0c;大到一套完整的标签管理系统&#xff0c;它都能参与其中。但多数人只把它当成一个带颜色的圆角块&#xff0c;真正遇到动态增删、样式…

2026/10/13 17:11:12 阅读更多 →
el-tag标签组件实践:状态映射、动态编辑与样式定制全攻略

el-tag标签组件实践:状态映射、动态编辑与样式定制全攻略

做过不少后台管理界面之后&#xff0c;你会发现很多所谓“小功能”反而最考验细节。比如一个简简单单的el-tag标签&#xff0c;看起来就是几个彩色圆角块&#xff0c;但用好了能让状态展示、筛选分类、动态编辑都变得顺手不少&#xff0c;用不好就会出现样式错乱、关闭后无法恢…

2026/10/13 17:11:12 阅读更多 →
金字塔原理:职场沟通结构化表达的四个原则与实战技巧

金字塔原理:职场沟通结构化表达的四个原则与实战技巧

废话不多说&#xff0c;这可能是你在职场沟通中最值得补的一课。我见过太多人&#xff0c;明明业务能力很强&#xff0c;可一开口就败下阵来&#xff1a;汇报讲了十分钟&#xff0c;领导眉头越皱越紧&#xff1b;邮件写了几百字&#xff0c;对方回复“所以你想表达什么”&#…

2026/10/13 17:10:09 阅读更多 →

最新新闻

PS中英文对照表制作指南:从菜单导出到PDF生成与版本维护

PS中英文对照表制作指南:从菜单导出到PDF生成与版本维护

简介&#xff1a;这份PS中英文对照表PDF面向Photoshop初学者及需要查阅英文界面术语的设计从业者&#xff0c;帮助解决中文版与英文版菜单名称对应困难、切换语言环境后找不到功能入口的问题。资源包共1个PDF文件&#xff0c;大小约18KB&#xff0c;轻量易存&#xff0c;适合打…

2026/10/13 17:57:33 阅读更多 →
23个AI角色协同开发:gstack如何重构软件研发流程

23个AI角色协同开发:gstack如何重构软件研发流程

第一次跑通 gstack 的完整流程时&#xff0c;我在终端里看到的不是一行行的错误日志&#xff0c;而是一群 AI 在互相评审、打回、修改、验证&#xff0c;最后交给我一份可以直接启动的前后端工程。那感觉很像你刚入职一家新公司&#xff0c;站会上产品、前端、后端、测试坐在一…

2026/10/13 17:57:33 阅读更多 →
PDMS出图全流程实战:从ADP自动出图到DRAFT手动标注与打印

PDMS出图全流程实战:从ADP自动出图到DRAFT手动标注与打印

简介&#xff1a;这份PDF面向工厂三维布置设计领域的技术人员与PDMS初学者&#xff0c;系统讲解Plant Design Management System中的出图功能&#xff0c;帮助读者掌握从模型到施工图纸的完整流程。内容围绕自动出图&#xff08;ADP&#xff09;与手动出图&#xff08;DRAFT&am…

2026/10/13 17:57:33 阅读更多 →
Java核心知识点实战梳理:集合、并发、JVM与Java 8新特性

Java核心知识点实战梳理:集合、并发、JVM与Java 8新特性

很多人问我&#xff0c;Java知识点那么多&#xff0c;到底该怎么梳理&#xff1f;网上的资料五花八门&#xff0c;有的讲原理讲到源码深处&#xff0c;有的又浅得跟没讲一样。我自己从写业务代码到啃源码&#xff0c;再到现在带团队做技术评审&#xff0c;踩过的坑不少&#xf…

2026/10/13 17:57:33 阅读更多 →
Python第二次作业深度解析:数据流、面向对象与异常处理实战

Python第二次作业深度解析:数据流、面向对象与异常处理实战

1. 作业概览&#xff1a;这绝不是一次简单的“课后练习”如果你只把这份“Python第二次作业”当成一次常规的语法巩固训练&#xff0c;那可能真的低估它了。我在实际批改和复现这类作业时发现&#xff0c;很多同学的卡点并不在“会不会写代码”&#xff0c;而在“能不能把学过的…

2026/10/13 17:57:33 阅读更多 →
弗洛伊德解梦:噩梦为何也是潜意识的愿望满足?

弗洛伊德解梦:噩梦为何也是潜意识的愿望满足?

你有没有做过这种梦&#xff1a;明天就要交一份至关重要的方案&#xff0c;梦里你却发现自己连文件都找不到&#xff0c;或者更离谱&#xff0c;直接站在空无一人的考场里&#xff0c;试卷上的字全在飘。惊醒之后心跳半天才缓过来&#xff0c;第一反应往往是“这梦太丧了”。但…

2026/10/13 17:56:30 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目&#xff0c;我最大的感受是&#xff0c;大部分坑不是出在技术前沿&#xff0c;而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节&#xff0c;最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了&#xff0c;很难不碰到舵机控制这道坎。做智能小车要转方向&#xff0c;做机械臂要抬关节&#xff0c;做云台要偏航俯仰&#xff0c;背后都是同一个东西在干活&#xff1a;舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”&#xff1f;——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台&#xff1f;或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜&#xff1f;又或者&#xff0c;在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →