news 2026/9/3 14:05:25

模型调用成本失控?用Gate模式治理重复与昂贵请求

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
模型调用成本失控?用Gate模式治理重复与昂贵请求

你很难在第一天就发现 LLM 调用的重复问题。上线一个 AI 翻译功能时,请求量小,单次返回几百毫秒,费用也不起眼。直到某个星期账单突然翻倍,你翻日志才发现,同一篇 9000 字的合同摘要,被同一个下游任务调了 11 次。真正贵的地方不是模型不会回答,而是你的后端在反复问同一个问题。

ModelGate 这个名字给我的第一感觉是:它不是要挡住模型,而是要建立一个“门禁”,让每次调用都留下记录。它要解决的不是生成质量,而是后端系统的调用治理问题——哪些 LLM 调用是重复的、哪些调用是昂贵的,以及为什么它们会一次次发生。

用一句话概括我的判断:ModelGate 这类工具的价值不在“更快”,而在让模型调用成本从不可见变成可见,再从可见变成可收敛。文章后面所有内容,都围绕这个判断展开。

1. 真正要治理的不是模型能力,而是不可见的重复和成本

很多团队做 LLM 功能时,第一版都是“把接口调通”。这当然没有错,因为功能没跑通之前,谈成本没有意义。问题是,跑通之后很多人就没有再往前走一步,没有去问:同一个请求是否被多次发送?同一个结果是否被反复计算?

1.1 单次调用不贵,但重复调用是线性放大的

一次 LLM 调用的费用由输入 token、输出 token 和模型单价决定。单个请求可能只要几分钱,看起来好像无所谓。但如果这个请求被重复执行 100 次,成本就是单次的 100 倍。

更隐蔽的是,重复调用往往不会在日志里显示为“报错”。它看起来像正常请求,返回也正常,只是同样的输入被反复送进模型。账单上涨不会触发接口报错,所以很多团队直到看云账单时才意识到出了问题。

这里有一个不算复杂的成本模型:

总成本 = 单次调用成本 × 调用次数

单次调用成本可以通过模型单价和 token 数算出来。真正难以控制的是“调用次数”背后的业务逻辑。

1.2 重复调用通常不是恶意攻击,而是业务逻辑没有收敛

从工程经验看,后端里的重复 LLM 调用通常来自下面几类情况。

第一,同一个业务事件被多个流程分别处理。比如用户提交一条消息后,系统要判断意图、生成摘要、再去做检索。不同模块各自调用一次模型,输入里有一段很长且重复的公共历史对话。单独看每个模块都有必要,合起来看就是浪费。

第二,异步任务没有幂等。队列消费失败后重新投递,定时任务没有清理上一次的执行状态,消息重复消费,同一个记录被两个 worker 同时处理。于是同一份文本被反复送去生成摘要。

第三,前端行为引发重复请求。用户连续点击“生成”,或者提交按钮没有置灰,同样的 prompt 被发送到后端。后端如果没有做幂等控制,不会知道这是同一个请求。

第四,prompt 模板里塞进了大量动态内容。比如把整个知识库片段、整封邮件、整份合同都放进上下文,每次调用都一样。这种情况下,调用本身可能没有错,但上下文里绝大多数 token 是冗余的。

这些场景都不是模型能力问题,而是系统设计问题。ModelGate 这类工具,本质上是在帮你把系统问题暴露出来。

1.3 昂贵调用通常来自上下文失控,而不是模型单价高

单次调用昂贵,不一定是因为选了大模型。很多时候是因为上下文太长、输出配置太浪费,或者重试机制把一次请求变成了多次计费。

举个例子。你让模型总结一份文档,直接把 5 万字全文塞进 prompt。模型确实有能力理解,但真正有价值的输入可能只需要前 5000 字,再加上文档标题和关键章节。多出的 4.5 万字,每次调用都在付费。

再比如,模型返回给用户只需要 200 字,但你的 max_tokens 设置成了 4096。这不会直接导致多扣钱,因为最终按实际 token 计费;可如果模型在生成长文本时跑偏,生成了大量无用内容,输出成本就会明显上升。

还有一种典型情况是超时重试。客户端请求模型时超时,于是重新发一次请求。但服务端可能已经处理完第一次请求,只是响应没有及时返回。结果就是一次用户请求背后产生了两次甚至更多次计费调用。

这些成本很难通过“模型选择”解决,必须通过调用链路的记录和观测来解决。

2. ModelGate 的位置:放在业务代码和大模型之间

ModelGate 想做的工作,翻译成工程语言是:在所有 LLM 调用前面加一层统一门面,让请求先经过它,再到达模型供应商。这一层可以记录、识别、统计,甚至拦截一些明显不合理的调用。

2.1 Gate 的两种姿态:先观察,再拦截

一个常见的误区是,团队刚接入这类 gate 就想让它帮你拦截重复请求。我先劝你一句:不要急。

ModelGate 如果只有观察模式,它不会影响业务。请求照常发出,只是把调用信息记录下来。你先知道现状,再决定要不要管。

如果直接开启拦截模式,风险很高。判断“重复”这件事很容易误伤,尤其是在语义重复但需要不同结果的场景里。用户问“今天天气怎么样”,这个问题昨天也有人问过,但今天和昨天的天气完全不同,命中缓存返回旧结果就是灾难。

所以更稳妥的接入顺序是:

  1. 先开启观察模式,记录所有调用。
  2. 跑几天数据,找出重复和昂贵请求的真实规模。
  3. 针对具体场景设计拦截规则。
  4. 小流量验证规则,再逐步放开。

这个顺序也适用于你自己设计同类工具。

2.2 统一出口是这一切的前提

无论 ModelGate 是独立服务,还是集成在代码里的 SDK,它都需要所有 LLM 请求从一个统一出口经过。如果你的代码直接散落着多个 provider SDK 调用,gate 根本无能为力。

这里需要做一次技术债清理,把底层 HTTP 调用统一到一个 client 函数中。例如后端是 Python,可以先把调用收口为chat_completion这样的统一函数,后续再接入网关。

# llm_gate.py def chat_completion(model, messages, **kwargs): started = time.time() # 这里是统一的 provider 调用入口 response = call_model_provider(model=model, messages=messages, **kwargs) log_llm_call( model=model, messages=messages, latency_ms=(time.time() - started) * 1000, input_tokens=response.usage.prompt_tokens, output_tokens=response.usage.completion_tokens, ) return response

这个统一入口不需要一开始做得很复杂,能记录关键字段就够了。关键是所有调用都必须经过它。

2.3 在中间层能做的不只是记录

当请求统一经过 gate 层后,你可以做三类事情。

第一类是计量。计算每次调用的 token、延迟、成本和调用来源。

第二类是识别。通过请求内容 hash、用户 ID、会话 ID、模型 ID 组合出重复特征,在日志里标记重复请求。

第三类是策略执行。如果某类请求已经被验证适合缓存,可以在 gate 层直接返回缓存结果;如果某段时间预算已经超了,可以对非核心请求做降级。

大部分团队一开始只需要第一类和第二类。策略执行要等到有足够数据后再说,否则容易做出错误判断。

3. 先跑通最小闭环:从一次调用的日志开始

如果 ModelGate 在你后端里还没有落地,我并不建议你第一天就搭一套完整的可视化报表。更实际的做法是,先设计一条可以被分析的调用日志,把它接入统一出口,跑一天数据,再用脚本分析。

3.1 记录一条可分析的调用日志

日志至少要包含这些字段,我用一张表来表示:

字段含义为什么需要
timestamp调用发生时间用于时间窗口聚合
trace_id一次用户请求的追踪 ID串联同一个上游请求里的多次调用
session_id会话 ID分析同一会话内的重复行为
user_id用户或租户 ID成本分摊和个性化判断
model使用的模型名称计算价格和判断模型路由是否合理
prompt_hash规范化后的消息摘要第一层精确重复检测
input_tokens输入 token 数成本估算和上下文长度判断
output_tokens输出 token 数成本估算和输出浪费判断
latency_ms调用耗时发现超时和性能瓶颈
cache_hit是否命中缓存判断缓存策略是否有效
retry_count重试次数发现重试导致的多倍计费
source调用来源模块定位哪个业务方产生最多浪费

3.2 给 prompt 做一个稳定 hash

要判断重复,最常见的手段是给 prompt 计算一个稳定的 hash。注意,不能直接对原始 messages 做 hash,因为里面可能有动态时间、随机数、无意义的 trace 信息。

一个简单做法是:先对消息做一次归一化,去掉会导致误判的字段,再计算 hash。

import hashlib import json def stable_hash(messages, ignore_keys=("now", "timestamp", "request_id")): normalized = [] for item in messages: if isinstance(item, dict): item = {k: v for k, v in item.items() if k not in ignore_keys} normalized.append(item) payload = json.dumps(normalized, sort_keys=True, ensure_ascii=False) return hashlib.sha256(payload.encode("utf-8")).hexdigest()

这个函数帮助找到“完全相同的请求内容”。它会漏掉语义相似但字面上不完全相同的请求,这没关系,先解决最确定的问题。

3.3 先做每天一次的离线分析

如果你还没有可视化的 dashboard,可以每天凌晨跑一个脚本,统计昨天的调用日志。

重点看三个指标:

  1. prompt_hash聚合,调用次数最多的 Top 请求。
  2. modelinput_tokens + output_tokens估算成本,累计成本最高的 Top 请求。
  3. source聚合,看哪个模块产生了最多调用与成本。

这类分析不需要很精准,能看出 top 异常就足够。真正复杂的情况,等到你已经处理完最明显的问题后再处理。

3.4 日志本身不能影响主流程

给 LLM 调用加日志,听起来简单,落地时有一点必须注意:日志逻辑不能阻断或拖慢主流程。

记录日志时如果发生异常,不能抛出业务异常,否则模型功能会直接失败。常见做法是用异步写入、本地队列或直接让log_llm_call吞掉异常。

另一个风险是隐私。不要把所有原始 prompt 和 response 完整写入生产日志,尤其当系统面向 C 端用户时,消息里可能有敏感信息。更稳妥的做法是记录 hash、截断后的摘要、token 数和必要统计字段。原始内容只在需要排查的最小范围内保留,并做好权限控制。

4. 把重复找出来:先精确,后语义

重复检测是 ModelGate 这类工具的核心能力。许多团队的误区是想一步到位做“语义重复识别”,结果模型成本又增加一层,而且效果还不稳定。正确的路径应该是分层检测。

4.1 第一层:基于规范化 Key 的精确去重

先把这些组合作为精确重复的判断条件:

  • prompt_hash
  • model
  • temperature
  • max_tokens
  • 时间窗口
  • 用户或会话范围

例如,同一个用户在同一小时内提交了 5 次完全相同的总结请求,且模型参数一致,就可以判定为重复。即便不能直接拦截,也应该在报表中标记出来。

在接入 ModelGate 时,我一般建议先跑一次精确去重的离线分析。这部分数据准确率高、解释成本低,最适合给团队做第一轮治理。

4.2 第二层:针对模板化问题的语义去重

当精确 hash 无法发现问题,但你已经从报表里看到一些文本高度相似、成本又很高的调用,这时才引入语义层面的近似检测。

思路是:将提示词或消息中的关键文本做 embedding,然后计算向量相似度。相似度高于某个阈值时,视为同一类请求。

但这里有两个前置问题。

一是 embedding 调用本身也有成本。如果每天有几十万次 LLM 调用,你不可能全部做 embedding 再聚类。通常只对离线抽样或高成本请求做语义分析。

二是阈值没有固定值。不同业务下,0.92 和 0.98 的意义完全不同。你需要用小样本人工看一批结果,确认阈值不会把“两个需要不同回答的问题”合并在一起。

4.3 边界:哪些“看起来重复”不能直接拦截

不是所有相同问题都适合返回同一个答案。下面这些场景,即使请求高度相似,也不应该直接合并。

实时性要求高的场景。查天气、查股价、查快递轨迹,结果随时在变,命中旧缓存会直接造成错误。

个性化场景。同一个模板生成的“推荐方案”,用户 A 和用户 B 的背景不同,重复请求看起来相似,但模型需要结合每次的用户画像重新推理。

随机性要求高的场景。如果用户刻意希望每次生成不同内容,比如文案创意,直接拦截会破坏产品体验。

不同业务上下文。两个不同的业务模块可能发送相同的文本,但它们背后的指令和约束不同,结果也就不同。

所以,ModelGate 的“重复检测”只应该定位成发现工具,而不是自动执行工具。是否处理、怎么处理,需要业务方案共同确定。

5. 把昂贵找出来:成本是一套估算,不是账单

模型供应商的账单通常滞后且聚合,你很难从中看到某一次业务调用的成本。ModelGate 需要在请求发生时估算成本,并把成本打回到业务维度上去。

5.1 成本估算至少需要模型单价和 token 数

估算核心公式很简单:

成本 = (输入 token 数 / 1,000,000) × 输入单价 + (输出 token 数 / 1,000,000) × 输出单价

实际落地时要注意:不同模型版本、不同上下文缓存模式、不同批处理接口,单价都可能不同。价格表需要独立配置,不能写死在业务代码里。

PRICE_CONFIG = { "model-a": {"input_price_per_million": 5.0, "output_price_per_million": 15.0}, "model-b": {"input_price_per_million": 0.5, "output_price_per_million": 1.5}, } def estimate_cost(model, input_tokens, output_tokens): price = PRICE_CONFIG.get(model) if not price: return None input_cost = input_tokens / 1_000_000 * price["input_price_per_million"] output_cost = output_tokens / 1_000_000 * price["output_price_per_million"] return input_cost + output_cost

注意,价格会变化,配置要留出入口随时更新。估算值用于横向比较,帮助定位异常,它不等同于最终账单。

5.2 昂贵并不只看单次调用,还要看累计成本

单次最贵的调用当然值得关注,但它不一定是账单的大头。真正危险的是单次看起来不贵、调用次数却巨大的请求。

比如单次成本只有 0.01 美元,看起来完全不用在意。但如果一天被调用 10 万次,成本就是 1000 美元。这种请求往往不是偶然,而是某个循环、定时任务或高频接口造成的。

所以在分析中,至少看两个排序维度:

  • 按“单次调用成本”降序,找极端异常。
  • 按“聚合后总成本”降序,找大盘贡献者。

ModelGate 的报表如果只展示“单次最贵”,会漏掉另一半真相。

5.3 延迟、失败与重试也要一起看

成本还有一个不怎么被计入但影响巨大的维度:重试。一次调用如果因为网络超时被客户端重试 3 次,最终只有 1 次成功,但实际计费可能是 2 到 4 次。

协议层有时无法从客户端精确判断服务端是否处理了请求。所以日志里一定要记录retry_count和每次重试的开始时间。如果发现大量请求的retry_count很高,要先排查网络超时配置、连接池和上游限流,而不是急着优化 prompt。

另外,延迟也是一个信号。模型延迟高时,用户等不到结果就会刷新或再次点击,这又会产生新的调用。所以一个“慢接口”不只会让用户体验变差,还会从行为层面制造更多重复成本。

6. 发现之后怎么办:收敛重复与预算的实践路径

工具把问题找出来,只是第一步。真正有长期价值的,是你基于这些报告把系统改到“不需要重复调用”的程度。

6.1 从 Top 重复和 Top 成本开始治理

拿到 ModelGate 第一份分析报告时,不要试图一次性解决所有问题。我建议先挑出两个 Top 列表里的前三项,逐个分析成因。

处理时走一遍这个判断流程:

  1. 这个调用是用户明确触发的,还是内部流程自动触发的?
  2. 它的输出结果会随时间变化吗?
  3. 如果结果可复用,上游业务能接受多旧的数据?
  4. 如果不可复用,是因为 prompt 里有动态内容,还是因为模型本身有随机性?
  5. 如果用小模型或精简 prompt 能达到效果,能不能切换?

这个流程可以帮助你将问题归入缓存、幂等、裁剪、路由或模型降级这几类方案。

6.2 加缓存不是无脑缓存

降低重复调用最直接的手段是结果缓存。但它有前提:你清楚地定义了缓存 key、TTL 和生效范围。

一个相对安全的缓存 key 结构是:

业务域:用户维度:模型:规范化请求 hash

TTL 需要按业务容忍度设置。对于固定知识型问题,可以是几小时甚至一天;对于需要较新鲜结果的场景,可能只适合几十秒。

缓存命中时不代表一定正确。如果用户明确要求“不要用旧结论”,你必须跳过缓存直接调用模型。

6.3 从流量治理升级到预算治理

当重复和昂贵调用已经被压缩一轮后,ModelGate 的另一个价值才会体现:它可以变成一个预算治理闸门。

你可以设置日预算或小时预算。当成本超过阈值,系统自动对低优先级调用做降级或告警。比如非核心推荐场景可以返回规则兜底,而不是继续请求模型。

这类能力需要模型调用方配合,给每次调用标注优先级。否则 gate 层不知道哪些请求可以先被丢弃。

从实践来看,“设置预算上限并让系统自动收敛”是 LLM 后端长期运行的必要条件。模型调用不像传统 API,它是没有固定账单预期的,只有加一层预算控制,才敢把功能放到生产环境。

7. 如果接入后仍然觉得混乱:按这张链路排查

ModelGate 本身是一个观测和治理工具,但它也会引入新问题。实际接完这类网关后,团队经常遇到“为什么报表数据不对”“为什么有些重复没识别出来”的情况。这时不要瞎调,按下面的链路一层层查。

7.1 现象层:先看异常的具体表现

常见的现象有:

  • 没有任何日志或指标。
  • 部分请求没有被统计到。
  • 报表中成本偏高或偏低。
  • 开启拦截后业务效果异常。
  • 网关本身导致接口延迟上升。

先确认现象,再决定下一步。不要直接怀疑模型供应商或业务代码。

7.2 输入层:检查消息、hash 和字段

没有统计到部分请求,优先检查这些请求是否真的经过了统一出口。如果业务代码里有人绕过了统一 client,直接调用了 provider SDK,gate 根本看不到。

prompt_hash 没有统计出重复,则要检查 messages 里是否包含动态字段。时间戳、随机数、trace_id 这些字段会让相同语义的请求产生不同 hash,需要做归一化。

成本字段缺失,常见原因是 provider 返回的 usage 结构不一致,或者模型版本不同导致字段签名不同。

7.3 环境与架构层:检查任务调度和调用路径

如果重复调用来自异步任务,不要只盯着 LLM 配置,还要看任务调度。队列里同一个 job 是否被多个 worker 抢到?定时任务是否因为上一次执行超时,又启动了下一个实例?

多副本部署时,内存级缓存天然无法共享。如果团队是用本地缓存给 LLM 结果做复用,在多个副本下命中率会很低。要检查缓存是否应该放到 Redis 这类共享存储中。

7.4 参数与业务需求层:检查模型配置

如果某个请求被判定为重复,但你不确定是否可以缓存,要把 temperature、max_tokens、system prompt 也放进判断维度。

例如,相同问题在 temperature=0.2 和 temperature=1.0 下产生的结果可能差异很大,gate 默认会认为它们是两类请求。这是保守设计,不是 bug。

还要确认成本估算是否和实际计费口径一致。有些供应商对缓存输入 token 会打折,如果你的价格表没有区分,估算就可能偏高。

7.5 工具边界层:区分“工具 bug”和“使用方式问题”

ModelGate 能发现重复,不代表它能理解业务背后的潜台词。它无法知道“用户刚刚修改了资料,不想再获得旧答案”这件事。

所以当报表出现误判时,不要急着说工具不行。先问:是不是配置的忽略字段太多?是不是缓存的 TTL 太长?是不是把个性化场景也纳入了默认缓存?

把工具当成一个显眼但可配置的观测层,而不是一个会替你思考的策略层,使用体验会顺很多。

8. 什么阶段适合 ModelGate,什么阶段先不要上

最后聊一聊边界。这类工具确实有用,但不是所有项目都需要立刻引入复杂能力。

8.1 适合什么团队

如果你的后端已经满足下面几个条件,接入 ModelGate 或参考它做一套内部方案会比较合适:

  • 已经有稳定的 LLM 业务功能,每天调用量上千次或更多。
  • 代码里已经有一个统一调用入口,或者你愿意花钱花时间做统一改造。
  • 团队对成本敏感,模型账单已经不能靠“拍脑袋”判断。
  • 有日志系统或可以快速接入日志系统。
  • 业务中有较多固定 prompt、定时任务、批量任务或重复性摘要场景。

这种团队接入后通常能很快看到效果,因为过往浪费已经积累在系统里,只差一个“照妖镜”。

8.2 不适合什么场景

如果你的项目还在 demo 阶段,每天只调几十次模型,或者需求经常变化,先不要投入做复杂的 go 网关、缓存和预算系统。

更推荐的做法是:手写一个简单的日志函数,或者直接用原有链路追踪工具,记录模型调用的 token 和成本。当调用量增长到让你开始焦虑时,再上正式工具。

另外,如果你的业务逻辑高度个性化,每次调用都必须依赖实时状态,那么重复检测的价值会大打折扣。你仍然需要成本观测,但“缓存去重”这层要谨慎。

8.3 从内部脚本到工程化,差的不是功能,而是稳定流程

自己写一个 LLM 调用分析脚本很简单,难的是让它每天稳定跑、准确识别、能让团队达成共识,并且不因为误报被丢弃。

一个可用的内部方案至少要包含:稳定的日志采集、按业务维度的聚合、价格配置、异常告警,以及每周 review 的节奏。

ModelGate 这类工具真正的长期价值,不只是某一个重复拦截功能,而是让团队养成一种习惯:每一次模型调用都应被记录,每一笔成本都应有归因,每一个重复都应经过业务确认后处理。

当你把模型调用看作一种需要治理的资源,而不是一种无限供给的能力时,LLM 应用才真正进入生产阶段。

所以我的建议也很简单:先别急着做复杂系统。从今天开始,统一你后端的 LLM 调用入口,记录每一次请求的关键字段,把重复和昂贵的东西列出来。你会很快发现,ModelGate 这个名字里最重要的不是 Model,而是 Gate——你需要在每一笔模型费用流出去之前,先给自己建一道看得见的闸门。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 14:05:20

从生成到执行:Robocity与2026年具身智能机器人技术栈解析

进入2026年,AI领域的竞争逻辑正在发生一个微妙但关键的变化:人们不再满足于让模型“说出正确答案”,而是开始要求它“做成一件实事”。聊天、写作、生成代码只是预热,真正的战场正在转向物理世界和复杂任务系统。如果把2025年看作…

作者头像 李华
网站建设 2026/9/3 14:04:35

一人一车318高原段:用Python分析GPX轨迹与日照金山预测

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

作者头像 李华
网站建设 2026/9/3 14:04:24

涡旋电磁波雷达MATLAB仿真:从原理到成像的全链路实现

简介:本资源是一套面向高校本科生毕业设计与专业课程实践的MATLAB涡旋电磁波雷达成像仿真系统,聚焦轨道角动量(OAM)电磁波在雷达目标成像中的建模、信号处理与图像重建全流程。资源包含58个文件,以46个核心MATLAB函数&…

作者头像 李华
网站建设 2026/9/3 14:03:47

Awesome Privacy 语音加密书籍:通话安全的技术指南

Awesome Privacy 语音加密书籍:通话安全的技术指南 在数字化时代,语音通话作为日常沟通的重要方式,其安全性日益受到关注。网络监听、数据泄露等威胁使得普通通话面临极大风险。本文将从技术角度出发,详细介绍语音加密的原理、实…

作者头像 李华
网站建设 2026/9/3 13:57:45

苹果CMS影视模板开发实战:从觅知V2.0看二次开发与生态集成

简介:这是一套专为苹果CMS系统设计的高清影视类响应式前端模板——觅知V2.0爱电影MizhiADY模板源码,面向影视站搭建者、站长及PHPMySQL轻量级建站学习者,解决苹果CMS站点缺乏现代UI、幻灯片配置复杂、主题后台缺失等常见痛点。压缩包共210个文…

作者头像 李华
网站建设 2026/9/3 13:56:50

set关键字全场景解析:从SQL、环境变量到C++与深度学习

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

作者头像 李华