Spring AI 实战:ChatClient 四步链式调用与自动装配入门指南

最近用 Spring AI 给内部工具加了一个自然语言查询入口,说实话,从加依赖到能聊天,花的时间比我预想中少太多。核心就两件事:yml 里把模型服务商的 key、base-url、模型名称配好,然后注入 ChatClient.Builder,用四步链式调用把提示词发出去、把结果接回来。整个过程没有手写 HTTP 请求、没有自己处理鉴权,也没有拼 JSON,Spring Boot 的自动装配直接帮你把客户端准备好了。这个项目的目标读者是那些想在业务里接大模型能力,但又不想引入太重框架的 Java 后端同学,也适合刚入门的 Spring 开发者抄一版作业。

1. 项目思路:Spring AI 到底解决了什么问题

1.1 如果不使用 Spring AI,你需要做哪些手工活

每个用过大模型 API 的人都知道,直接调 HTTP 接口并不复杂,复杂的是把它嵌进 Spring 生态里。你至少要处理四件事:

  • 把模型厂商的鉴权信息、模型名、接口地址写进配置,再手动封装一个 RestTemplate 或 WebClient 客户端;
  • 定义请求体和响应体对应的 Java 类,每次调用都要做一次 JSON 序列化和反序列化;
  • 处理鉴权头、HTTP 状态码、重试、超时、异常包装,否则生产环境一个网络抖动就能把服务打挂;
  • 如果要做多轮对话,还得自己在业务代码里拼接历史消息,长期维护成本不低。

这也是我看中 Spring AI 的直接原因。它不是要代替大模型厂商的 SDK,而是把“连接模型”这件事做成了 Spring 风格的自动配置。你只要把依赖引进去、把 yml 配好,容器启动后就会自动出现一个ChatClient.Builder。这个 Builder 背后已经帮你搞定了 HTTP 客户端、鉴权头、模型映射、异常处理、响应转换。业务代码里不需要关心模型厂商协议长什么样。

1.2 选型时的关键考量

选型那几天我其实认真纠结过。自己封装 RestTemplate 看起来可控,但代价是后续每个接入方都要重复造轮子。接一套自带监控、自带流式输出、自带结构化输出的框架,长期性价比更高。Spring AI 目前的最佳位置不是取代 LangChain 这类完整 Agent 框架,而是作为一个轻量的 Java 原生接入层。

  • 如果你的项目是纯 Spring Boot 技术栈,用 Spring AI 的成本最低,Bean 管理、配置体系、异常体系都是现成的;
  • 如果你需要的不只是文本聊天,还有图像生成、向量存储、结构化输出、Prompt Template、函数调用,Spring AI 把这些统一抽象成了同一套 API;
  • 如果你希望测试代码可控,它还提供了伪聊天客户端等测试工具,不过这个超出了这次体验的范围。

我最后选择先跑通一个最简链路:yml 配置 -> ChatClient -> 链式调用 -> 返回结果。不追求一步到位,先把基础链路吃透,后面替换模型、加多轮、加流式,都只是在同一套 API 上加参数。

1.3 从配置到结果返回的完整链路

从概念上看,完整调用链路可以想象成一条流水线:

  • application.yml中的配置项由 Spring Boot 绑定到自动配置类;
  • 自动配置类创建 HttpClient 相关 Bean,再生成ChatClient.Builder;
  • 业务代码通过构造器注入ChatClient.Builder,调用build()得到一个ChatClient实例;
  • 调用chatClient.prompt()开始组装请求,接着用.system(...)、.user(...)设置消息内容;
  • .call()发起同步调用,内部完成鉴权头拼接、JSON 序列化、HTTP 发送、响应解析;
  • .content()或.entity(...)从响应中取出文本或实体对象,交回业务代码。

也就是说,真正需要你手写的,只有“配置”和“链式调用”这两层。理解了这个链路,后面踩到任何问题都能顺着配置、自动装配、网络请求、响应解析这几个环节快速定位。

2. 准备阶段:依赖、yml 配置与自动装配

2.1 引入依赖的坑

Spring AI 的 starter 不像 Spring Web 那样一个依赖就完事,它通常需要结合 BOM 统一管理版本。我在pom.xml中做了两件事。第一步,在dependencyManagement里导入 BOM,避免自己和团队后续维护一堆版本号:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>你的兼容版本</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

第二步,引入服务商对应的 starter。这里的“xxx”需要替换成你实际使用的模型服务商前缀:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-xxx-spring-boot-starter</artifactId> </dependency>

这个写法能保证你依赖的各个子模块版本一致。我第一次没引 BOM,直接写了一个版本号,结果编译没问题,运行时报附件和 core 模块版本不一致。后面换回 BOM 管理,这类问题就再没出现过。

2.2 yml 配置文件的核心字段

“配好 yml 就能聊”这句话不夸张,但配置项要想清楚。我最简配置长这样:

spring: ai: xxx: api-key: ${AI_API_KEY} base-url: ${AI_BASE_URL} chat: options: model: ${AI_MODEL:default-chat-model} temperature: 0.7 max-tokens: 1024

这里的xxx必须是你引入的那个 starter 对应的前缀,每个服务商前缀不同。api-key、base-url、model 是三个绝对不能省的字段。api-key就是调用模型服务的密钥,base-url是 API 地址,model是模型名称。temperature和max-tokens不写也能跑,但建议第一次就设置好,因为它们直接影响模型输出的随机性和长度上限。

我用${AI_API_KEY}这种占位符而不是直接写死密钥,是为了避免把敏感信息提交到代码仓库。本地调试时可以在 IDEA 的运行配置里加环境变量,也可以在.env文件里维护。线上环境则放到配置中心或 CI/CD 变量中,安全性和可维护性都更好。

2.3 自动装配是怎么把 Bean 创建出来的

初学者最容易困惑的是:我没写 Bean,为什么能直接注入ChatClient.Builder?答案在 Spring Boot 的自动装配里。starter 被引入后,classpath 上出现了自动配置类。Spring Boot 启动时会读取META-INF下的配置信息,发现ChatClientAutoConfiguration一类的自动配置类。里面会用条件注解判断类路径中是否存在ChatClient和模型服务商相关的类,只要条件满足,就自动注册ChatClient.Builder和对应的模型工厂。

这个设计思路和 Spring Boot 的数据库自动配置完全一样。好处是业务代码里几乎零配置,坏处是一旦报错,新人很难理解“Bean 到底从哪里来”。遇到这种情况,我总结过一个简单排查法:启动日志里搜索ChatClient关键字,看自动配置条件是否满足;如果发现配置类没生效,先检查 starter 版本和 Spring Boot 版本是否匹配,再看 yml 前缀是否写对。

2.4 多环境配置怎么做

实际项目中不可能只有一个环境。开发环境、测试环境、生产环境的模型名称和 base-url 大概率不一样。我用 Spring Profile 拆了三份配置:

  • application.yml:公共配置,比如默认模型名称;
  • application-dev.yml:开发环境的 key 和 base-url;
  • application-prod.yml:生产环境的 key 和 base-url。

启动时通过SPRING_PROFILES_ACTIVE=prod指定环境,Spring Boot 会自动覆盖同名配置项。这样做的好处是,业务代码完全不用改,部署时只需切换环境变量。如果你有配置中心,也可以把spring.ai下的配置统一托管,效果一样。

3. ChatClient 四步链式调用:从零写一个能聊天的服务

3.1 入口:拿到 ChatClient.Builder 并 build

在 Spring Boot 项目里,你很少需要new ChatClient。正常写法是构造器注入ChatClient.Builder,然后调用build()得到一个 ChatClient 实例:

@Service public class ChatAssistant { private final ChatClient chatClient; public ChatAssistant(ChatClient.Builder builder) { this.chatClient = builder .defaultSystem("你是一个经验丰富的Java技术助手,回答力求准确、简洁。") .build(); } }

build()这一步会把默认参数固化进客户端。如果你希望所有请求默认都带一套系统提示词,就在build()之前用defaultSystem(...)设置。这样后续每一次链式调用都会自动带上这段默认系统提示词,不需要重复传。当然,链式调用里也可以临时覆盖。

3.2 四步链路的第一步:system 系统提示词

严格来说,一次完整的链式调用从chatClient.prompt()开始,但prompt()更像是打开一个 Prompt 构建器,真正的四步链式方法是system、user、call、content。第一步是设置系统提示词:

String result = chatClient.prompt() .system("你是一个答疑助手。请直接回答,不要输出额外解释。") .user(userMessage) .call() .content();

system(...)里的内容相当于给模型一个角色设定和行为准则。它不参与通常意义上的“用户问题”,但会深刻影响回答风格。我常用它来约束模型:禁止编造数据、遇到不确定的问题明确说不知道、返回 JSON 时要严格遵守字段格式。这些约束写在 system 里比写在 user 里更稳定,因为系统提示词在每次请求中优先级更高,也不容易被用户输入干扰。

实际使用时,system 提示词不要写得太长。模型对上下文的窗口有限,提示词太长会挤占真正的用户消息空间。我自己的习惯是 200 字以内,把最关键的约束写清楚就够了。

3.3 第二步:user 用户消息

user(...)这一步用来传递用户输入。它的参数可以是一个简单的字符串,也可以是一个带模板和参数的 Prompt 对象。

最简单的用法是直接传入文本:

.user("Spring AI 和普通 HTTP 调用有什么区别?请用三句话回答。")

多轮对话场景下,你需要自己维护历史消息列表,然后把历史记录和当前问题一起拼进 user 消息里。注意不要无限增长,否则很快会超出模型的 token 上限。一般我会滑动截取最近 5 到 10 条消息,控制 context 体积。

如果你想在请求里追加额外上下文,比如传入一个业务对象,可以在user(...)的另一个重载里传入对象和相关 PromptTemplate。但初体验阶段,直接传字符串已经够用。

3.4 第三步:call 同步调用

call()是链式调用的核心执行步骤。调用它之后,ChatClient 会执行以下动作:

  • 把 system 和 user 消息包装成请求体;
  • 拼接鉴权头;
  • 通过内置 HTTP 客户端把请求发给 yml 里配置的 base-url;
  • 接收响应并将模型返回的文本包进 ChatResponse。

它是同步操作,所以当前线程会阻塞直到拿到响应或抛出异常。对于平时接口耗时要求低、交互频率低的内部工具,同步调用完全够用。如果要做实时打字机效果,可以把call()换成stream(),返回的是一个Flux<String>流式结果。不过那是进阶玩法,这次先不展开。

3.5 第三步和第四步之间:content 与结构化返回

很多人会把四步理解成“取文本”和“取对象”两种结束方式。其实call()返回的对象上有多个方法,其中最常用的是content()和entity(Class)。

content()直接返回 String,适合普通问答、摘要生成等场景。entity()则会让模型输出按指定结构返回,并自动完成 JSON 到 Java 对象的反序列化。例如:

public record CodeReviewResult(String summary, Boolean hasBug, String suggestion) {} CodeReviewResult result = chatClient.prompt() .system("你是代码评审专家。请以JSON格式返回,字段为summary、hasBug、suggestion。") .user(codeSnippet) .call() .entity(CodeReviewResult.class);

使用entity(...)时,系统提示词必须明确告诉模型输出 JSON,并列出字段名,否则模型返回的文本很难被正确反序列化。这个功能非常适合做信息抽取、格式化输出、接口对接,能省去大量手工字符串解析。

3.6 完整四步链式调用示例

把上面的片段拼起来,就是一个可以放进 Controller 的最简聊天接口:

@RestController public class ChatController { private final ChatAssistant chatAssistant; public ChatController(ChatAssistant chatAssistant) { this.chatAssistant = chatAssistant; } @GetMapping("/chat") public String chat(@RequestParam(defaultValue = "你好,介绍一下你自己") String message) { return chatAssistant.ask(message); } } @Service public class ChatAssistant { private final ChatClient chatClient; public ChatAssistant(ChatClient.Builder builder) { this.chatClient = builder .defaultSystem("你是一个技术助手,回答尽量简洁、准确。") .build(); } public String ask(String userMessage) { return chatClient.prompt() .system("如果用户打招呼,请简单回应;如果用户问技术问题,请先给结论再补充细节。") .user(userMessage) .call() .content(); } }

启动应用后,访问http://localhost:8080/chat?message=你好,就能收到模型回复。这个示例虽然短,但已经完整覆盖了“配好 yml 就能聊”的全部环节。

4. 配置和调用中的常见问题与排查实录

4.1 401 鉴权失败

最常见的问题就是 HTTP 401。我遇到这个报错通常会按顺序排查:

  • 环境变量AI_API_KEY是否真的存在,IDEA 里配置的环境变量需要重启才生效;
  • yml 中的占位符是否写对了变量名;
  • API key 是否带有多余空格,复制的时候很容易混入换行符;
  • 如果报告无法读取 appender,一般是自动配置拿到的配置是空值。

如果本地选了application-dev.yml,还要确认 Profile 是否被加载。很多时候配置放到了默认application.yml,但实际运行的是prodProfile,配置项被覆盖成了空。

4.2 404 接口地址不对

base-url 写错会直接报 404。不同模型服务的接口路径习惯不太一样,有的要求带/v1,有的不带。我刚开始把/v1/chat/completions和/v1搞混过,导致请求打到了不存在的位置。

排查思路很简单:先查看 yml 里的 base-url,再对照模型服务商文档给出的请求地址。注意尾部的斜杠,多一个/少一个/都可能造成路径拼接错误。Spring AI 内部会拼接默认接口路径,所以尽量不要在 base-url 里写过于具体的路径,留给程序自己拼接通常更稳定。

4.3 超时和连接池问题

生产环境下,模型服务响应偶尔会慢。如果默认超时时间太短,请求频繁超时,业务方会以为接口挂了。Spring AI 支持在 yml 中配置连接和读取超时时间,不同服务商前缀下字段略有差异,但基本都有类似connect-timeout、read-timeout的场景,需要你看一眼当前版本提供哪些属性。

我踩过一个坑:内网通过代理访问模型服务,连接能建立,但读取响应超时。排查了很久才发现是网络代理和长连接复用互相干扰。后面把连接池的 maxIdle 调小,并让 HTTP 客户端忽略某些代理头,才恢复正常。

4.4 中文乱码与返回内容截断

中文乱码现在比较少见,因为现代 HTTP 客户端默认按 UTF-8 处理。如果出现乱码,优先检查服务端口和数据库连接串是否指定了 UTF-8 编码。文本截断则更常是因为模型上下文或输出长度限制,可以通过调大max-tokens解决。

但如果你的业务本身需要处理长文档,就不能只依赖参数调大。我建议在调用前对文本做分片,把长文本拆成适合模型窗口的段落,分段生成摘要或结果,最后再合并。这个过程可以借助 Spring AI 的 PromptTemplate,但核心仍然是“先分片,再处理”。

4.5 版本兼容性排查

Spring AI 迭代速度比较快,版本差异有时很大。项目里同时用了 Spring Boot 3.2 和某个 Spring AI 版本,启动时报方法找不到,是因为某个 API 在升级后改了签名。

遇到这类问题,我建议先看官方 release note,再搜索项目里是否用了ChatClient的旧方法。尤其是.user()的返回值类型、CallResponseSpec是否存在,不同版本可能有细微差别。为了保证长期稳定,尽量用 BOM 管理版本,并固定 Spring Boot 大版本,不要混着升。

5. 实操心得与几处值得注意的细节

5.1 密钥管理别偷懒

初体验阶段直接用固定 key 写 yml 可以,但上了团队项目,密钥管理必须认真对待。最好统一走环境变量或配置中心,禁止把真实 key 提交到 Git。我见过同事不小心把 key 推到私有仓库后,几小时就被外部扫描工具抓走,只能紧急轮换密钥。这种事情一次就能让人长记性。

5.2 把 ChatClient 封装成你的业务服务

直接在各处注入ChatClient虽然方便,但会让业务代码散落提示词、参数和异常处理逻辑。我更建议在 Service 层封装一层ChatAssistant,把 system 提示词、调用参数、结果类型统一收口。后续要加日志埋点、限流、缓存、多模型切换,都只改一个类。这个习惯对任何团队都值。

5.3 流式输出与结果解析是下一步优化方向

四步链式调用适合快速验证。真正面向用户的产品,通常需要流式输出和结构化输出配合使用。你可以把call()换成stream(),让打字机式回复实时展示;也可以像前面说的entity(...)那样,把模型输出直接映射成业务对象。初体验结束后,这两条路都值得深入,但核心链路和配置思路不会变。

5.4 最后分享一个调试技巧

如果你在本地想看到模型请求到底发到了哪里、返回了什么,可以打开 Spring AI 的 Debug 日志,观察请求和响应细节。我经常在配置里临时加:

logging: level: org.springframework.ai: DEBUG

跑一条请求,日志里能看到自动配置加载了哪些类、HTTP 请求最终打到了哪个地址、响应里包含哪些字段。排查了一个小时的问题,可能看完日志十秒就定位了。看完记得把日志级别调回 INFO,否则生产环境日志量会非常大。

从我的实际体验看,Spring AI 最大的价值不是省掉几行 HTTP 代码,而是把“连接大模型”变成了 Spring 项目里一件理所当然的事。配好 yml 之后,剩下的只是你如何设计提示词和封装业务逻辑。希望这篇初体验记录能帮正在尝试的你少走几步弯路。

相关新闻

pyautogui图像识别驱动的桌面GUI自动化测试实战

pyautogui图像识别驱动的桌面GUI自动化测试实战

做自动化测试这些年&#xff0c;我一直习惯拿到新工具先找个能“看得见摸得着”的场景去练手。上次把 Python 的 pyautogui 重新捡起来&#xff0c;顺手拿 4399 小游戏当靶场&#xff0c;写了一套完全靠图像识别驱动的自动化脚本。整个过程中不抓包、不改内存、不碰游戏协议&am…

2026/10/21 8:02:13 阅读更多 →
杰理方案I2S时钟配置详解:从频率换算到故障排查

杰理方案I2S时钟配置详解:从频率换算到故障排查

上个月帮一个朋友调杰理方案的蓝牙音箱&#xff0c;板子外挂了一颗I2S接口的DAC&#xff0c;音质指标看着很漂亮。结果上电放歌第一秒我就知道要熬夜了——声音忽快忽慢&#xff0c;像磁带被手指捏着卷&#xff0c;里面还垫着一层细细的“滋滋”声。我没有急着翻代码&#xff0…

2026/10/21 8:02:13 阅读更多 →
基于Spring AI和RAG构建带引用溯源的知识库问答助手

基于Spring AI和RAG构建带引用溯源的知识库问答助手

1. AI 总是自己编答案&#xff1f;先把幻觉的病根说清楚做知识库问答助手的时候&#xff0c;我最大的痛点是 AI 一本正经地胡说八道。明明知识库里没有这个数据&#xff0c;它也能给你编出个一二三四来&#xff0c;语气还特别笃定。为了解决这个毛病&#xff0c;我把 Spring AI…

2026/10/21 8:02:13 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年&#xff0c;我大部分时间都在跟浏览器标签页打交道&#xff0c;直到第一次把桌面应用跑起来&#xff0c;才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词&#xff0c;最开始是在某段时间频繁出现在同事的讨论里&#xff0c;一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档&#xff0c;主要面向智能锁安装人员、终端用户及售后维护者&#xff0c;帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件&#xff0c;大小4.37MB&#xff0c;内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统&#xff0c;整车要求空载跑到8m/s&#xff0c;电机转速已经拉到额定点&#xff0c;驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令&#xff0c;转速纹丝不动&#xff0c;三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年&#xff0c;用它写过嵌入式驱动、做过游戏服务端&#xff0c;也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词&#xff0c;我其实挺感慨的——这门语言的难点从来不是语法多&#xff0c;而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介&#xff1a;亚太天能T109滑盖智能锁产品说明书以PDF格式发布&#xff0c;适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开&#xff0c;先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能&#xff0c;再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历&#xff1a;翻开源码注释、论文摘要或者一份专业报告&#xff0c;每个字都认识&#xff0c;连在一起却像天书。想找人聊聊&#xff0c;身边要么没人懂&#xff0c;要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型&#xff0c;几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 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/21 21:50:56 阅读更多 →
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/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练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/21 16:24:48 阅读更多 →