LiteLLM 统一多模型 API 接入:网关化改造实践指南
手头同时维护好几家大模型API接入的时候最让人崩溃的不是哪个模型更聪明而是接口格式从来都不统一。有的厂商要求请求体里写max_tokens另一家偏要写max_output_tokens有的返回里直接把答案放在choices[0].message.content另一家塞在content[0].text里最要命的是鉴权方式还不一样。项目里的封装代码越堆越多但每换一个模型就要改一版请求逻辑谁都不敢轻易动那团“祖传代码”。后来我在一个新项目里把 litellm 接了进去用同一套代码同时调用不同厂商的模型密钥统一管理成本也能按项目拆分团队里再接新模型只需要在配置里加一行。litellm 本质上是一个大模型网关它把各家模型服务的接口翻译成统一的格式你只需要学会一种调用方式就能操控后面一串模型。它既能当 Python SDK 在代码里用也能起一个独立的代理服务让整个团队共享一套 API 入口。如果你是个人开发者、前后端团队或者正在做一个需要对接多家模型平台的系统这篇文章值得看完。1. 多模型接入的真实痛点为什么我最终选择加一个抽象层1.1 接口格式千奇百怪统一调用成了奢望我不止一次见过这样的代码项目里有一套call_gpt()函数然后复制粘贴出一套call_claude()再改一版call_gemini()三个函数内部逻辑高度相似却又各自维护各自的请求头、超时时间、错误码解析。这事在只有一两个模型时还能勉强撑着一旦模型数量超过三个你就要维护 N 套请求代码、N 套异常处理、N 种流式解析逻辑。更麻烦的是同一家厂商还经常升级模型参数旧的max_tokens被废弃、换成max_completion_tokens你压根不知道底层哪一次改动会让接口崩掉。而 litellm 做的事情非常“笨”但有效把所有厂商的请求统一转写成其原生格式再把他们返回的内容统一包装成你熟悉的结构。这意味着你的业务代码只需要关心response.choices[0].message.content不用关心后面到底是什么模型返回的。1.2 密钥和权限管理失控才是最可怕的隐患我见过某个团队把所有模型密钥直接灌进前端环境变量理由是“后端没时间封装”。也见过有人把三四个密钥写死在代码仓库里离职之后密钥跟着流出去直到某天账单暴涨才反应过来。这些场景的共同点是密钥的散落、不可轮换、没有审计。litellm 的做法很有意思它允许你定义“虚拟密钥”。你可以给每个项目、每个成员单独生成一个 key并且这个 key 只允许访问某几个模型、每个月只允许消耗一定额度。外部成员拿到的永远是这个虚拟 key即使泄露你也可以单独吊销而不影响其他密钥。这样一来密钥管理从“一锅乱炖”变成了“按人按项目隔离”。1.3 模型切换和容灾基本靠手写而且写得一塌糊涂很多团队嘴上说着“多模型容灾”实际上就是 catch 异常之后换下一个模型重试。但重试的逻辑远没有想象中简单什么时候算超时哪些错误需要换模型哪些错误换了也白换限流了是要等还是跳这些问题没有统一答案的时候每个业务方都会自己写一套最后行为各不相同。litellm 在 SDK 层面直接支持了 fallback 机制也就是一个模型请求失败后自动尝试下一个模型并且可以自定义触发 fallback 的条件。对于大部分场景这已经帮你把容灾这事从“业务代码”里抽离出来了。1.4 账单只能看总览没办法按项目拆分多模型时代的隐藏痛点还有成本归因。不同模型单价天差地别如果所有请求都走同一个 key月底账单上只有一条总费用你根本分不清是哪个业务线烧的钱。这个需求听起来不性感但实际做项目时非常折磨人。litellm 的代理模式自带用量统计和费用归因每一次请求都会记录模型、调用方 key、输入输出 token 数和估算费用你可以按 key、按模型、按时间维度去查。这笔账算清楚了任何业务方想接入新模型时都能当场评估成本而不是等月底被账单吓一跳。2. LiteLLM 的四层设计统一格式、转发代理、密钥池、用量日志2.1 统一格式层所有模型都长一个样litellm 把不同模型的 API 格式统一成 OpenAI 兼容风格。这不是说它强制要求你自建一套新协议而是让熟悉 OpenAI 接口的开发者几乎零成本上手。比如你想同时调用不同家的大模型代码可以写成下面这样from litellm import completion response completion( modelgpt-4o, messages[{role: user, content: 你好}], ) print(response.choices[0].message.content)如果换一个模型你只需要把model参数换成对应的模型名。litellm 内部通过模型名称前缀自动识别该走哪套协议比如带anthropic/前缀走 Anthropic 风格带gemini/前缀走 Gemini 风格。你还以指定每个模型具体的接入方式response completion( modelanthropic/claude-3-5-sonnet-20241022, messages[{role: user, content: 你好}], )这种设计最舒服的地方在于业务侧能看到的世界是收敛的。模型厂商改了内部参数由 litellm 去适配业务代码不用跟着改。2.2 转发代理层让团队只面对一个 API 地址SDK 模式适合单机脚本、后端微服务这种场景。但如果是多人协作的团队我更推荐部署 litellm 的代理服务器模式。它本质上是一个跑在服务器上的轻量服务监听一个端口对外暴露 OpenAI 兼容的/v1/chat/completions接口。这样做的效果是你团队里的所有后端服务只需要把 base_url 指向这台代理服务器把 api_key 设置为服务器上生成的虚拟密钥就能像调用 OpenAI 官方接口一样使用任意已接入模型。代理层在中间负责模型路由、负载均衡、密钥鉴权和日志记录。这种架构对前端和小团队特别友好因为他们根本不需要知道背后接入了哪些模型也不需要处理各家复杂的环境变量。2.3 密钥池层上游密钥与虚拟密钥隔离密钥池机制是 litellm 代理从“能用”到“好用”的关键分水岭。我可以在代理配置里注册多个真实的上游密钥比如各家模型平台的 key这些真实 key 对外不可见。然后我用管理接口创建一批虚拟 key发给团队成员或各个服务使用。虚拟 key 能绑定具体模型名单、设置额度上限、支持失效时间。假设某个合作方的 key 泄露了我直接在管理端吊销那一把虚拟 key真实 key 和其他人的 key 完全不受影响。这个机制比让每个人都拿着真实 key 到处跑安全得多。2.4 用量日志层每一分钱都花得明明白白日志层记录的核心字段包括请求时间、模型名、虚拟 key 标识、输入 token 数、输出 token 数、估算费用、响应延迟。litellm 会把不同厂商返回里的 token 使用量字段统一解析并落库然后按照模型单价计算出花销。实际项目中我会定期按虚拟 key 拉一次费用报表看看哪条业务线的 token 开销明显异常及时调整模型策略。如果没有这层数据多模型混用一段时间后成本基本就是一笔糊涂账。3. 从零接入实战Python SDK 把三个模型串在同一段代码里3.1 安装与基础配置安装非常简单直接用 pippip install litellm然后设置环境变量。不同厂商的密钥各用各的环境变量名称export OPENAI_API_KEYsk-... export ANTHROPIC_API_KEYsk-ant-... export GEMINI_API_KEYyour-gemini-key如果你用的是代理模式环境变量也可以不写在代码里而是统一写在服务器的配置文件中。SDK 模式则更灵活支持运行时直接传api_key参数。3.2 一个请求自动路由到多个模型最基础的多模型调用就是这样from litellm import completion models [ gpt-4o, anthropic/claude-3-5-sonnet-20241022, gemini/gemini-1.5-pro, ] for model in models: try: response completion( modelmodel, messages[{role: user, content: 用一句话解释什么是依赖注入}], ) print(f{model}: {response.choices[0].message.content}) except Exception as e: print(f{model} 调用失败: {e})这段代码的价值在于你不需要为每个厂商分别写请求函数也不需要关心各家消息格式的差异。litellm 会帮你把messages转译成目标模型需要的结构。3.3 给请求加上 fallback一个挂了自动切换容灾不是等到模型挂了才去改代码而是在调用前就声明好备胎列表from litellm import completion response completion( modelgpt-4o, messages[{role: user, content: 介绍一下云原生的核心思想}], fallbacks[ {model: anthropic/claude-3-5-sonnet-20241022}, {model: gemini/gemini-1.5-pro}, ], )当主模型遇到超时、服务不可用、鉴权失败等错误时litellm 会自动按顺序尝试 fallback 列表里的模型。你还可以通过参数控制重试次数和超时时间from litellm import completion response completion( modelgpt-4o, messages[{role: user, content: 你好}], num_retries2, request_timeout30, )这里的num_retries是指在单个模型上失败后的重试次数request_timeout是单次请求的秒数超时。这两个参数看起来简单但大多数手写容灾代码恰恰忽略了超时控制导致某个模型卡住时整个接口也跟着卡死。3.4 异步批量调用处理并发需求如果是服务端场景同步调用会阻塞线程性能堪忧。litellm 也提供了异步接口import asyncio from litellm import acompletion async def run(): response await acompletion( modelgpt-4o, messages[{role: user, content: 写一段异步处理消息的 Python 代码}], ) return response.choices[0].message.content async def main(): results await asyncio.gather(*[run() for _ in range(5)]) print(results) asyncio.run(main())配合信号量做并发限制会更稳妥避免一下子把上游 API 的配额打爆sem asyncio.Semaphore(10) async def limited_run(): async with sem: return await run()这段代码解决的实际问题是当你同时给多个模型发请求做对比评测时异步并发能节省大量时间。4. 把 LiteLLM 部署成团队级 AI 网关一个 config 文件管住所有模型4.1 配置文件里定义模型列表代理模式是我目前最推荐团队使用的形态。第一步写一个config.yaml把你的模型资源都登记进去model_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: gemini-pro litellm_params: model: gemini/gemini-1.5-pro api_key: os.environ/GEMINI_API_KEYmodel_name是暴露给使用方的“逻辑模型名”也就是客户端那边填写的名字。litellm_params.model是 litellm 真正要调度的目标模型。这样设计的好处是底层模型随便换只要不换逻辑名调用方代码一行都不用改。同时可以加一些全局策略litellm_settings: drop_params: true num_retries: 2 request_timeout: 60drop_params: true的意思是当某个上游模型不支持某些参数时自动丢弃而不是报错。这个开关非常实用因为不同厂商对参数的支持范围差异很大。4.2 启动代理验证接口启动命令litellm --config config.yaml --port 8000启动后在另一个终端里用 curl 验证curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-1234 \ -d { model: gpt-4o, messages: [{role: user, content: 你好}] }如果返回的结构和 OpenAI 原生一致说明网关已经跑通了。这里的sk-1234是代理默认的主密钥生产环境务必替换并通过管理接口创建虚拟密钥。4.3 模型别名与多模型负载均衡除了简单映射你还可以把多个上游模型配置成同一个“逻辑模型名”实现按权重轮询model_list: - model_name: my-assistant litellm_params: model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY - model_name: my-assistant litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY这样客户端请求my-assistant时网关会在两个模型之间做轮询分发。这个功能很适合做 A/B 对比测试或者单纯想把流量分摊到不同模型上降低成本。如果你想控制流量比例还可以在 litellm_settings 里配置模型权重。实际团队里我更常用的是把“便宜且快”的模型和“贵但聪明”的模型配成一个逻辑名让网关自动分流业务侧无感知。4.4 创建虚拟密钥给成员或服务单独授权代理启动后可以用管理接口创建虚拟密钥curl -X POST http://localhost:8000/key/generate \ -H Authorization: Bearer sk-1234 \ -H Content-Type: application/json \ -d { models: [gpt-4o, claude-sonnet], max_budget: 20.0, budget_duration: 30d }返回结果里会有一个新的sk-开头字符串这个就是虚拟 key。你把这个 key 下发给对应的业务方它只能调用models里指定的模型并且 30 天内最多消费 20 美元。预算用完后这个 key 会自动失效不会影响其他 key。4.5 看日志和花费代理服务提供了一组管理端点比如/spend/logs可以按 key、按模型查询请求记录和费用。你会看到类似这样的表格虚拟 key模型输入 token输出 token估算费用请求时间sk-abc...gpt-4o15234210.021 USD2025-01-10 10:15:03sk-def...claude-sonnet8021650.012 USD2025-01-10 10:16:44这个表格我每周拉一次基本就能掌握各业务线的大模型成本走势。比起月底对着平台账单猜这个粒度要清楚得多。5. 实测两个月之后的踩坑清单与优化习惯5.1 模型名写错导致的神秘 404代理模式部署完之后最常遇到的报错不是配置错误而是“404 model not found”。排查下来绝大多数是因为客户端请求里的model名字没有对应 config 里的model_name。这里有个认知误区很多同事以为填的是上游模型名比如gpt-4o直接填进去但代理配置里定义的是别名gpt-4o看起来一样实际上订单的是逻辑名。如果我在 config 里把逻辑名改成my-gpt客户端就得填my-gpt而不用管上游真名叫什么。建议在 config 开头注释里写清楚每个逻辑名对应哪个上游模型省得后面维护的人靠猜。5.2 流式返回格式的差异litellm 的兼容性做得不错但流式模式stream的体验有些细微差别。不同的上游模型在流式返回时增量内容的字段不完全一致litellm 会尽量统一但我在使用某些小众模型时偶尔会遇到第一个 chunk 里content为null的情况。处理方式很简单客户端解析流式返回时遇到content为null的 chunk 直接跳过不要抛错。这个看似低级的问题实际开发时很容易被忽略导致线上偶发报错。5.3 超时参数的默认值坑litellm 的默认request_timeout可能比你预期的更短。某些长输出任务比如生成一篇长文模型思考时间加上生成时间很容易超过默认超时然后报超时错误。这时候你加大模型本身的能力并没有用得在配置里显式调大超时时间litellm_settings: request_timeout: 120另外重试次数不要盲目调大。对于限流429错误重试是有用的但对于上下文超长、参数非法这类 4xx 错误重试只会白白浪费请求。litellm 支持按错误类型做重试策略生产环境建议只对 429、5xx 这类瞬时错误重试。5.4 成本估算与实际账单存在偏差litellm 的费用估算基于模型单价和 token 数这个数字比较接近真实账单但不是绝对精确。一方面某些模型有缓存折扣价另一方面不同计费粒度会有差异。我的习惯是用 litellm 的报表做趋势分析和横向对比最终以各家平台账单为准。如果发现某个 key 的花费异常先用 litellm 的日志定位再去找平台账单核对。5.5 接入新模型前先做小流量验证这是我最想强调的优化习惯。任何新模型接入 litellm 之后先别急着全量切流量。我通常会在 config 里加一个新逻辑名先手动调用几次验证请求参数是否合适、返回内容是否完整解析、价格是否符合预期。确认没问题后再把流量切到新模型上。因为 litellm 的转发能力很强切换模型的成本变得很低所以反而容易让人草率上线。但从稳定性角度讲模型表现的差异只有真实验证过才敢放心。5.6 日常运维的几个小习惯最后分享几个我实践下来很有用的习惯。定期清理虚拟 key长时间不用的 key 及时吊销防止成为安全隐患。监控错误日志litellm 的日志里有各模型的错误码分布经常能看到某个模型频繁 5xx这时就该考虑把它移出主链路。模型路由配合缓存如果业务中有大量重复问题可以在 litellm 前面加一层响应缓存把相同请求直接命中缓存返回节省成本和延迟。不要把真实密钥直接写在 config 里尽量使用os.environ占位符由服务器环境变量注入避免配置文件泄露。我自己的感受是litellm 把“接多家模型”这件脏活累活从业务代码里剥离了出去。它不会让某个模型变聪明但能让你的系统更从容地面对模型更新、厂商故障和多模型切换。对于一个长期迭代的项目来说这层抽象省下的维护成本远比最开始配置它的那点时间多得多。

相关新闻

macOS升级Ruby全攻略:Homebrew与rbenv双路线详解

macOS升级Ruby全攻略:Homebrew与rbenv双路线详解

1. 升级前先定位:你的macOS里Ruby到底从哪来先说个我自己的真实经历。前阵子有个项目一直报You have already activated ...,查了大半天,最后发现根源就是macOS自带的Ruby版本太老,而项目依赖的新版gem不认它。如果你也在Mac上写脚…

2026/10/13 4:46:13 阅读更多 →
AI评测高分可信吗?拆解数据污染与奖励黑客下的防作弊评测体系

AI评测高分可信吗?拆解数据污染与奖励黑客下的防作弊评测体系

最近我在验证一个新模型的实际效果,跑了一套网上流传很广的公开评测题,得分高得吓人,几乎每个维度都是"顶尖水平"。可等我把它放到自己的真实业务场景里,用同样的学科方向、只是换了一批数字和背景数据,表现…

2026/10/13 4:46:13 阅读更多 →
把500个JavaScript效果源代码变成自己的可检索素材库

把500个JavaScript效果源代码变成自己的可检索素材库

简介:一份汇集500个JavaScript前端特效源码的压缩包(RAR格式),面向Web开发初学者与进阶者,用于快速获取交互功能灵感与实现方法。资源共516个文件,其中502个htm演示页面可直接在浏览器打开,另有…

2026/10/13 4:45:13 阅读更多 →

最新新闻

Spring Boot 3.x API密钥轮换机制:从状态机设计到平滑过渡

Spring Boot 3.x API密钥轮换机制:从状态机设计到平滑过渡

做后端开发这几年,只要接口走的是 API 密钥认证,密钥轮换就是一个绕不开的话题。Spring Boot 3.x 项目里,密钥轮换的坑尤其隐蔽:表面上看,更新一下数据库里的 key,客户端换一下配置就行,但实际一…

2026/10/13 7:38:09 阅读更多 →
深度学习优化算法与参数初始化:从梯度下降到动量实战

深度学习优化算法与参数初始化:从梯度下降到动量实战

2020年2月16日,我的深度学习笔记写到第八篇。上一篇从经验风险最小化聊到泛化误差,基本把“优化目标”讲清楚了;但目标归目标,真正让模型训练起来的是优化算法本身。这篇笔记把镜头拉到更新公式这一层,重点拆两块&…

2026/10/13 7:38:09 阅读更多 →
真三MOD专用蓝宝石渲染管线2.2b优化指南

真三MOD专用蓝宝石渲染管线2.2b优化指南

简介:本资源是魔兽争霸III玩家专用的自定义地图优化版本,面向熟悉真三国无双类RPG地图的中高级玩家,解决原版平衡性不足、技能冗余或运行卡顿等问题。压缩包为RAR格式,共2个文件:核心地图文件BT蓝宝石专署2.2b.w3x&…

2026/10/13 7:38:09 阅读更多 →
SpringBoot毕业生就业去向登记管理系统:毕设源码与二次开发指南

SpringBoot毕业生就业去向登记管理系统:毕设源码与二次开发指南

简介:这份资源是面向计算机专业毕业生与Java初学者的一套完整就业去向登记管理系统源码,可直接作为毕业设计或课程设计参考。系统基于SpringBoot框架开发,覆盖需求分析、总体设计、详细设计与功能测试全流程,包含功能结构、数据结…

2026/10/13 7:38:09 阅读更多 →
Spring Boot 3.x API密钥认证与密钥轮换实战指南

Spring Boot 3.x API密钥认证与密钥轮换实战指南

“你的服务对接了十几个下游调用方,大家约定好每次请求头里带一个 API Key,服务端校验通过才放行。密钥用了一年,安全团队突然要求立即轮换,你改配置、重启、发通知,结果重启期间服务不可用,调用方新旧密钥…

2026/10/13 7:38:09 阅读更多 →
链表分解:从内存布局到指针偏移的底层实践

链表分解:从内存布局到指针偏移的底层实践

1. 项目概述:这不是“拆链子”,而是把链表“掰开揉碎”讲清楚 “链表的分解”这五个字,乍看像一道算法题的标题,但实际在一线教学、代码评审和系统调试中,它代表一种极其关键的底层思维训练——不是单纯地写个反转或删…

2026/10/13 7:37:09 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

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

周新闻

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

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

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

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

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

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

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

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

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →