news 2026/9/4 5:50:20

Vercel AI SDK 与 AI Gateway 实践:从流式对话到网关缓存与回退

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vercel AI SDK 与 AI Gateway 实践:从流式对话到网关缓存与回退

Vercel AI SDK 和 AI Gateway 是最近很多人问的一对工具组合。它们解决的问题一句话可以讲明白:AI SDK 负责让聊天式、流式输出的 AI 功能快速接到你的前端应用里,AI Gateway 负责让请求在多个模型服务和多种策略之间走得更稳、更省、更可控。如果你正在用 Next.js、React 这类生态做应用,又不想被某个模型厂商的 API 细节绑死,这篇文章会比较对胃口。我会从环境准备、最小 Demo、网关接入一直讲到报错排查,尽量按实际落地顺序拆开写。

整个实践过程中我自己的最大感受是:这两个东西单个看都不复杂,但很多人把它们理解成同一个东西,结果在排查问题时分不清是页面端、服务端还是网关层出了问题。下面先解决这个认知问题,再进入实操。

1. 先弄清:AI SDK 和 AI Gateway 是两层东西,不是一个概念

1.1 AI SDK 解决的是应用侧“怎么把 AI 能力接进来”

Vercel AI SDK 是一套面向 JavaScript / TypeScript 应用的 AI 开发工具。它最常见的价值,是把“前端页面里的输入框、消息列表、流式文本渲染”和“后端调用模型接口的逻辑”封装成一套统一写法。

举个例子:如果没有 SDK,你要自己处理用户输入、组装 messages 数组、调用模型接口、解析流式响应、从返回的 chunk 里拼文本、还要处理中断和错误。这个链路第一次写可能觉得不难,但一旦要支持流式输出、多轮对话、停止生成、重新生成,代码复杂度会立刻上来。AI SDK 里的 useChat、streamText 这类 API,就是把这几层重复工作收拢,让你把注意力留在业务上。

我建议先把它理解成:SDK 是站在应用开发者这边的工具库,它解决的是“我如何在最快时间内写好一个带 AI 对话功能的应用”。

1.2 AI Gateway 解决的是请求该怎么走后端链路

AI Gateway 和 AI SDK 不在一个层级。它更靠近模型服务提供方,是一个代理层。

实际开发中你会遇到这些场景:

  • 同一个应用要根据业务切换不同模型,甚至在同一套代码里做 A/B 对比。
  • 多条业务线都调用模型接口,需要统一看到谁在调用、消耗了多少 token、响应延迟是多少。
  • 某个模型服务出问题或限流时,希望请求自动落到备用模型上,而不是用户直接看到报错。
  • 同一个请求重复发生,模型接口返回内容完全一样,但每次都要付费、都要等网络时间。

AI Gateway 这类代理层,就是在模型 API 前面再加一层,把统一入口、缓存、模型回退、日志监控、简单的限流这一类通用能力下沉到网关。应用代码不再直接面对某个模型厂商的 baseURL 和 key,而是面对你自己的网关地址和网关 key。

这时候你会发现 SDK 和 Gateway 的边界其实很清楚:SDK 负责你的应用怎么调用,Gateway 负责你的调用到了网关之后怎么被转发、缓存、观察和容错。

1.3 为什么两件事要先分开理解

很多教程会直接给一个“AI SDK 接入 AI Gateway”的示例代码,但如果你没分清层,遇到问题时会很痛苦。

举几个我实际见过的现象:

  • 前端按钮一直转圈,你以为是 useChat 用错了,实际是服务端 Route Handler 还没被正确调用。
  • 服务端已经收到请求,但模型返回 401,你以为是 SDK 问题,实际是调用的网关地址或者密钥填错了。
  • 同一个问题反复问,结果每次都一样,你以为是模型“记住了历史”,实际是网关缓存命中了。
  • 换了模型效果变化不大,你以为是 prompt 没写好,实际是网关缓存一直返回旧模型的结果。

这些问题如果一开始就把 SDK 和 Gateway 的职责分开,排查路径会短很多。后面每个大问题我会再结合具体表现展开。

2. 动手前先准备环境:账号、项目、密钥三件套

2.1 注册一个 Vercel 免费账号,创建测试项目

如果你还没有 Vercel 账号,可以先去官网注册一个免费账号。注意点有两个:一是注册流程本身并不复杂,GitHub 账号可以直接授权登录;二是免费账号适合做学习测试,但在正式批量业务之前,最好再去确认一下当前套餐的资源限制和计费规则,尤其是想重度使用网关缓存、日志和模型调用时,免费档并不等于无限档。

创建项目时,最简单的路径不是从零初始化一个复杂应用,而是先用 Vercel 提供的模板创建,或者在本地先建一个 Next.js 项目再通过 Git 仓库导入。我自己的习惯是:第一轮测试尽量用最小项目,不要一开始就引入 UI 组件库、状态管理、数据库这种额外依赖。AI SDK 相关的排错,前置依赖越少越好判断。

项目建好后,你会得到类似xxx.vercel.app的默认访问域名。这个域名用来验证“部署本身是否成功”是足够的。但要注意,默认域名在不同网络环境下的访问速度可能差别很大,这个问题通常不是代码问题,而是网络链路问题。如果是为了正式业务,记得提前规划自定义域名和生产环境部署位置,不要等演示时才意识到访问不通。

2.2 环境变量:别把模型密钥硬编码进页面

这是新手最容易踩的坑。

调用模型服务和 AI Gateway 都需要密钥。密钥应该放在环境变量里,而不是直接写在代码中。在 Next.js 项目里,通常需要在项目根目录创建.env.local文件,写入类似下面的内容:

# 这里的内容属于示例占位,实际值需要从你的模型服务商或网关控制台获取 AI_GATEWAY_BASE_URL="你的网关地址" AI_GATEWAY_API_KEY="你的网关密钥"

字符串别加引号?这里写的是示意,具体看你在哪个环境里写。.env.local默认不应该提交到 Git 仓库。

有一点要特别提醒:Next.js 里环境变量分客户端和服务端。凡是要在浏览器端读取的变量,必须以NEXT_PUBLIC_开头;而模型密钥、网关密钥这类变量,只允许在服务端代码中使用。如果你把密钥写进客户端组件,等于把密钥直接暴露给所有访问页面的用户。等上了生产环境再发现,可能已经产生不必要的费用甚至安全问题。

AI Gateway 本身的价值之一就是帮你少暴露真实模型 key。你的应用只需要保存一个“网关侧分配的 key”,真实模型服务商的 key 可以留在网关配置里。这样即使前端应用出问题,真实模型 key 也不会直接流出去。

2.3 本地运行前先把版本依赖看一遍

跑示例项目前,我建议先确认几项环境信息:

  • Node.js 版本是否与企业项目要求匹配,常见问题是某台机器 Node 版本太老,装新依赖时报 engine 不匹配。
  • Next.js 版本。AI SDK 在不同时期对 Next.js App Router 的支持程度有差异,如果你的项目是旧的 Pages Router 结构,代码写法要做对应调整。
  • 包管理器是否统一。团队里不要一个用 npm、一个用 pnpm、一个用 yarn,否则锁文件反复冲突,浪费不少时间。

这里给的不是精确版本号,因为这些依赖更新频率很高。落地时最好的做法是打开官方文档的 Quick Start,按照它当前推荐的版本组合安装。不要拿几个月前的博客代码直接跑,很多时候报错不是 API 变了,而是你用的 SDK 版本和教程里的不一样。

安装依赖的基础命令通常是:

npm install ai @ai-sdk/react

如果你计划在服务端调用模型,可能还需要装对应模型服务商的适配包。实际要装哪些包,以你使用的 SDK 文档和模型适配方式为准。

3. 用 AI SDK 跑通一个最小流式对话

3.1 页面端 useChat 接入

先别贪多,目标就一个:页面上有一个输入框,用户输入后能收到一段流式文本回复。

在 Next.js App Router 项目里,最简单的页面结构可以是这样。这里以客户端组件为例,因为 useChat 这个 API 负责管理前端聊天状态:

"use client"; import { useChat } from "@ai-sdk/react"; export default function ChatPage() { const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat(); return ( <div style={{ maxWidth: 600, margin: "40px auto", padding: "0 16px" }}> <div style={{ minHeight: 300, border: "1px solid #ddd", padding: 12 }}> {messages.map((m) => ( <div key={m.id} style={{ marginBottom: 8 }}> <strong>{m.role === "user" ? "你" : "助手"}:</strong> <span>{m.content}</span> </div> ))} </div> <form onSubmit={handleSubmit} style={{ display: "flex", gap: 8, marginTop: 16 }}> <input value={input} onChange={handleInputChange} placeholder="输入消息" style={{ flex: 1, padding: 8 }} /> <button type="submit" disabled={isLoading}> {isLoading ? "生成中" : "发送"} </button> </form> </div> ); }

代码里没有写任何模型 API 细节。useChat 默认会向当前站点的/api/chat发送 POST 请求,并把多轮消息数组传给服务端。这是 AI SDK 给前端带来的直接好处:状态管理、消息追加、流式文本接收都已经被封装过一轮。

新手最容易在这里犯一个认知错误,以为 useChat 真的能“自己调用模型”。实际上它只是帮你组织请求和渲染流式文本,真正调用模型的位置在服务端接口里。缺少服务端接口时,按钮点击后大概率是报 404 或者一直 loading。

3.2 服务端 Route Handler 流转发

接下来需要新建一个接口,接收前端传过来的 messages,然后调用模型服务。

在 App Router 中,对应目录是app/api/chat/route.ts。一个最小实现可以长这样:

import { streamText } from "ai"; // 这里的 createOpenAI 只是示例。 // 不同模型服务商的接入方式以 SDK 当前文档为准。 import { createOpenAI } from "@ai-sdk/openai"; const provider = createOpenAI({ baseURL: process.env.AI_GATEWAY_BASE_URL, apiKey: process.env.AI_GATEWAY_API_KEY, }); export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: provider("你使用的模型标识"), messages, }); return result.toDataStreamResponse(); }

这段代码的逻辑是:服务端收到前端消息后,把它转发给一个“看起来像 OpenAI 服务”的地址。这里我把 baseURL 指向了网关地址,模型 key 也使用网关 key。如果一切配置正确,模型生成的文本会以流式方式返回给页面。

streamText 返回的对象不是普通 JSON,而是一个可流式输出的响应体。useChat 能自动识别并解析这种数据流,所以页面端无需手动处理 getReader 或 read() 这类底层逻辑。

从我个人测试经验看,这段代码能不能一次跑通,通常取决于三个外部条件:

  • 网关地址是不是真的可访问。
  • 网关 key 是否具有对应模型调用权限。
  • 你填写的模型标识符是否在网关配置里存在。

如果页面一直不输出,先不要怀疑 useChat,先 curl 一下服务端接口或看服务端日志,确认请求是否真的发出去了。

3.3 判断成功和失败:别只看“有没有字”

最小 Demo 跑通后,不要只看“页面能出字”就认为一切正常,还要观察几个维度。

第一,是否是流式输出。如果页面等待很久后一次性出现整段回复,说明流式链路可能没生效,或者中间走了一层非流式代理。流式输出会让首字出现得更早,体验更接近真人打字。

第二,是否支持多轮。连续发两条消息,第二条消息是否把第一条内容作为上下文带给模型。这取决于前端 useChat 维护的消息结构和服务端 messages 透传是否完整。AI SDK 默认会把历史消息一起发送,你自己写服务端转发时一定要保持 messages 原样透传,不要只取最后一条。

第三,浏览器网络面板有没有把/api/chat的响应流完整铺开。你可以打开控制台,看响应类型是不是text/event-stream或者类似流式类型。如果不是,大概率是 SDK 方法或模型服务不支持流式返回。

第四,加载状态是否正常结束。如果按钮一直处于“生成中”,但网络面板已经拿到完整响应,那问题多半在前端事件处理或者响应解析上。

注意:第一次跑 Demo 时不要同时开模型回退、缓存、多路输出这些功能。先把最基础的一条链路跑通,再逐步加层。

4. 把 AI Gateway 插到中间,观察它的生产作用

4.1 Gateway 统一入口与 SDK 远程模型配置

真正把 AI Gateway 接进来后,你的应用代码结构变化通常不大,主要变的是模型调用地址和密钥。

以前你的服务端代码可能直接连某个模型厂商的接口,现在改成连接你的网关地址;以前应用里可能保存多个厂商的 key,现在只保存一个网关 key。模型的真实服务能力,在网关控制台里进行配置。

这种结构调整最直接的好处是:模型厂商切换不再需要改应用代码。

比如今天你的业务用模型 A,明天想把默认模型切成模型 B。如果没有网关,你需要改服务端代码、重发版本、重新配置密钥。如果有网关,你可以在控制台把模型 A 的流量部分或全部指向模型 B,甚至做灰度比例。应用代码里填写的始终是网关地址和网关 key。

AI SDK 在这里扮演的角色是“统一调用语法”。它允许你通过 provider 适配器来调用不同模型服务,而 SDK 层的流式处理、错误处理、消息输出方式可以保持稳定。所以一个比较舒适的架构是:

  • 页面层:使用 AI SDK 的 useChat,不关心后端用的什么模型。
  • 服务层:使用 AI SDK 的 streamText 等 API,把模型请求转发给网关。
  • 网关层:配置真实模型、缓存策略、回退模型、日志采集。

4.2 缓存和自动回退是最容易感知的两个收益

很多人第一次接触 AI Gateway,会觉得它只是一个“换了个 baseURL 的代理”,没什么特别。直到业务里出现重复请求或者上游不稳定,网关的价值才明显。

先看缓存。某些业务场景里,用户会频繁触发相同或相似的提问,例如在线文档里的“帮我重写这段文案”,同一段文案在一天内可能被几十个人改写。没有缓存时,每次请求都会消耗一次模型服务额度,并等待完整的网络延迟。网关开启缓存后,如果请求参数和命中策略匹配,可以直接把之前保存的响应返回给应用。

缓存带来的收益不是“模型变聪明了”,而是“相同问题不必重复付费”。对学习项目来说,它让你做接口测试时更从容;对生产项目来说,它可以明显降低重复调用的成本和延迟。

再看自动回退。你的依赖链是:应用 → 网关 → 主模型。如果主模型服务出现限流、故障或者特定格式参数不支持,网关可以按预设顺序自动切换到备用模型。用户可能只会觉得响应稍慢了一点点,或者根本没有感知。

需要注意边界:回退不是把不同模型的输出强行保证一致。不同模型的输出风格、响应格式、上下文能力都有差别。回退只解决“可用性”问题,不解决“完全一致”问题。如果你的业务对输出格式要求极其严格,回退后也要通过校验层把关。

4.3 日志、token 和成本不能靠猜

网关的另一个价值是观测。

直接调用模型服务时,你要查看一次请求用了多少 token、延迟多高、成本和错误码,通常需要登录模型服务商后台。如果应用只调一个模型还好,一旦涉及多个模型、多个业务线,后台切换成本很高。通过网关后,一次请求从进入到转发到返回,整条链路的日志可以统一沉淀。

实际中我比较关注几个指标:

  • 请求总数、成功数、失败数。
  • 平均首字延迟和完整响应耗时。
  • token 消耗量,尤其是缓存命中前后 token 的明显对比。
  • 错误码分布,例如 401、429、500 各占多少。
  • 回退是否频繁发生,如果频繁发生,说明主模型稳定性或配置可能有问题。

不需要一开始就把所有指标做成仪表盘。先跑几天看日志,确认哪些接口被大量调用、哪些模型消费占比高,再决定是否需要成本告警和限流策略。

注意:网关缓存能降低重复成本,但它不能替代业务层的权限控制和数据脱敏。如果你的应用向模型发送用户隐私数据,仍然需要在业务侧做好授权和过滤。

5. 从“能跑”到“能用”的几个关键经验

5.1 流式输出不是越快越好

很多人在刚接入流式输出时会追求“字出得越快越好”。但这里有个容易忽略的点:流式输出只是把结果分块传输,它不等于最终回答时间变短。

如果模型本身响应很快,当然体验好。如果模型本身需要几十秒才能跑完,流式只是让用户更早看到第一个字,用户的等待感知确实会降低,但服务端处理时间并没有缩短。

真正影响体验的因素还包括:

  • 首字延迟:与模型服务商、网关转发耗时、输入内容长度有关。
  • 输出长度:长回答即使首字很快,也需要较长时间才能完整结束。
  • 前端渲染:如果你的页面在接收流式文本时做了重渲染,并且消息列表很长,可能出现输入卡顿。

所以判断“流式链路是否健康”,不要只看“字有没有蹦出来”,还要关注首字延迟、整体耗时和页面渲染性能。

5.2 错误处理不是只返回一个错误 JSON

把 AI SDK 接入网关后,常见的错误不止一种:

  • 网关拒绝了请求,返回 401 或 403。
  • 网关到达模型服务,但模型服务超时或限流。
  • 客户端在生成过程中主动停止。
  • 模型返回了格式正确但内容不符合预期的结果。

错误处理要分层去做。前端至少要有“加载中”“停止生成”“失败重试”的交互。服务端接口需要能捕获上游错误,并把可读信息返回给前端。网关层要记得设置超时和重试策略,不能无限等待上游模型。

我踩过的一个典型问题:前端点击生成后,useChat 弹了一个异常,但服务端日志里根本没有请求记录。原因是函数组件里某些变量为 undefined,请求还没发就已经抛错了。调试时先看浏览器 Network,再看服务端日志,不要上来就怀疑模型。

5.3 边界条件:切换模型、内容长度、超时都要单独测

不要把“一个模型测试通过”等同于“所有模型都能通过”。真实业务中不同模型的能力边界差异很大:

  • 上下文长度不同。你发给模型的 messages 如果非常大,超出上下文窗口的模型可能直接报错或被截断。
  • 支持的输入格式不同。有的模型擅长工具调用,有的模型擅长结构化输出,但同一个参数不一定能原样通用。
  • 审核策略不同。同样一段内容在模型 A 可以通过,在模型 B 可能被拦截。

建议在接网关时,把模型切换这个动作拆成一个个独立测试用例,不要只测“能否回复”。

我一般会准备几类测试输入:

  • 一句话提问,验证基础连通性。
  • 多轮对话,验证上下文传递。
  • 超长文本,验证上下文窗口和耗时。
  • 格式要求文本,例如“只输出 JSON”,验证结构化输出能力。
  • 空输入或异常格式,验证参数校验。

每一类输入都记录:是否成功、耗时多少、结果是否稳定、有没有触发缓存或回退。这样你在真正上线时对行为边界有把握。

6. 常见报错和排查顺序

6.1 看到 401、403 先查 baseURL 和 key

这类状态码通常不是模型能力问题,而是认证问题。排查顺序:

  1. 先确认页面请求到达了服务端接口。
  2. 再确认服务端读取环境变量的位置是否正确。
  3. 然后确认网关地址和网关 key 是否配对。
  4. 最后检查该网关 key 是否真的具备调用目标模型的权限。

非常常见的一种情况是:本地环境变量改动了,但本地开发服务没有重启,进程里还保留旧变量。改完.env.local后,务必重启开发服务。

如果你用的是 Windows 环境,还要注意环境变量字符串中是否有特殊字符,某些符号在 shell 中会被转义,导致 key 被截断。

6.2 页面能打开但回答不动,先分前端还是后端

这个问题几乎每轮测试都会遇到。我的排查顺序是这样的:

先看浏览器 Network 面板。如果 POST/api/chat请求为 404,说明路由文件路径不对或服务端没有正确导出。如果请求为 200 但一直没有数据,再往下查。

然后看服务端日志,确认 Route Handler 是否真的进入了执行逻辑。如果请求进来了但直接卡住,多半是模型调用超时。

如果服务端提示请求已经发送给网关,但网关日志没有记录,那就是应用到网关这一步的网络或地址配置有问题。此时不要继续分析 prompt,先把链路通断解决。

如果网关有记录,但模型响应报错,再把网关控制台里的回退状态、上游请求结果打开,看真实模型服务返回的错误信息。

6.3 网关缓存命中但结果不对时,清缓存并核对参数

带缓存的系统最容易出现一种“诡异问题”:明明代码已经改了 prompt,但返回内容还是旧版本。

先判断是不是缓存命中。如果连续几次请求完全一样,返回时间非常快,且网关日志显示 cache hit,那基本可以确认是缓存命中。

这时候需要看一下缓存 key 的设计。常见网关默认会组合请求参数生成 key。不同的参数组合会影响命中率。如果你的业务希望模型每次都重新输出,就需要在网关里关闭对应接口的缓存,或者手动清除缓存。

排查时要记得:缓存造成的错误不是“功能 bug”,而是配置与预期不一致。先不要改模型参数,先确认当前生效的缓存策略。

6.4 部署到 Vercel 后,访问、域名和资源限制要提前确认

本地跑通之后部署到 Vercel,通常会有几类新问题。

环境变量在本地正常,但部署后不生效,最常见原因是:在 Vercel 控制台的项目设置里没有添加生产环境变量,或者本地.env.local没有同步到平台。

默认域名访问不稳定,这不是代码逻辑问题。如果业务面向特定地区用户,建议把自定义域名、DNS 解析、部署区域这些事项一并规划。Vercel 提供的默认域名适合开发和演示,正式对外服务时要按你的目标用户网络环境选择合理部署方式。

部署后请求量上来,还需要注意平台的函数超时时间、免费额度内资源限制、日志保留时间等因素。不要等线上流量上来才发现服务被限流或日志找不到了。

我个人最后建议:学习这套组合时,先把“单条请求能通”当成第一里程碑;再开缓存和回退观察行为变化;最后才考虑多模型灰度、成本统计和团队协作。按这个顺序踩坑,你会少很多“不知道哪一层出问题”的时刻。

这套东西真正好玩的地方,不是某一个 API 多神奇,而是你亲手把“前端聊天体验、后端模型调用、中间网关容错”三层串起来之后,再换模型、做缓存、调策略时,改动成本明显变低。把基础链路跑稳,剩下的很多玩法都是在这个骨架上长出来的。

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

ZA-12 DW无线鼠标深度解析:经典模具的无线化,手感是否依旧?

很多人第一次看到“ZOWIE ZA-12 DW”这个型号&#xff0c;第一反应是“又一款无线电竞鼠标&#xff0c;和市面上的轻量化鼠标有什么区别&#xff1f;”真正用过 ZA 系列模具的玩家&#xff0c;则会先盯住一个问题&#xff1a;无线化之后&#xff0c;那个曾经让人又爱又恨的背部…

作者头像 李华
网站建设 2026/9/4 5:47:49

从Q-learning到DQN:深度强化学习核心改进与工程实践

简介&#xff1a;本资源是一套面向人工智能与机器人路径规划方向学习者和研究者的MATLAB强化学习实践方案&#xff0c;聚焦Q-learning算法的工程化改进与加速收敛优化。针对传统Q-learning在复杂环境中训练慢、策略震荡等问题&#xff0c;项目融合学习率衰减、动态ε-greedy探索…

作者头像 李华
网站建设 2026/9/4 5:46:26

Spring Boot房屋租赁系统:从业务建模到部署上线的全栈实践指南

简介&#xff1a;本资源是一套完整的基于SpringBoot开发的房屋租赁系统毕业设计资料&#xff0c;面向Java初学者与计算机专业本科生&#xff0c;解决传统租房信息分散、管理低效、流程不透明等实际问题&#xff0c;适用于课程设计、毕设开发及小型租赁平台原型构建。压缩包含91…

作者头像 李华
网站建设 2026/9/4 5:45:42

【 元脑服务器NF5280G7-NF5280M7技术规格分享】

##2U元脑NF5280G7 型号元脑NF5280G7分支型号NF5280-M7-A0-R0-00、NF5280-M7-C0-R0-00、NF5280-M7-A0-F0-001CPU类型2*第4/5代英特尔至强可扩展处理器(Sapphire Rapids/Emerald Rapids)内存插槽支持32*DDR5 RDIMM(5600MT/s1DPC&#xff0c;4400MT/s2DPC), 不支持3DS;NF5280-M7-…

作者头像 李华
网站建设 2026/9/4 5:42:50

ComfyUI视频生成优化:INT8量化与加速LoRA实战指南

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

作者头像 李华
网站建设 2026/9/4 5:42:49

Matlab实现贝叶斯全局优化:高斯过程与采集函数实战

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

作者头像 李华