news 2026/10/21 14:41:26

Spring AI + 千问3.5接入实战:Java服务一行代码调用大模型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI + 千问3.5接入实战:Java服务一行代码调用大模型

如果你是个写 Java 的后端,最近刷到 AI 相关的东西肯定心痒过——别人五分钟接个 GPT,写个 Python 脚本就满屏跑 Demo,我们自己还在用 RestTemplate 跟各种模型平台拉扯 HTTP 响应。靠 Java 接入大模型,真没想象中那么痛苦,Spring AI 的出现把这条路基本铺平了,配合千问 3.5 这类成熟的中文模型,几行配置加一行调用,就能给你原来的 Spring Boot 服务开一个“AI 接口”。

这篇文章不写那些抄来抄去的官方文档,就从一个实际接项目的角度,把 Spring AI + 千问 3.5 集成的完整思路、代码、参数和踩坑记录全摊开讲。适合已经会写 Spring Boot、想快速让业务拥有对话、文案生成、结构化抽取能力的人,也适合纯 Java 项目想低成本试水 AI 的团队。

1. 项目概述与核心思路拆解

1.1 为什么说 Java 接入大模型是个“脏活累活”

很多人觉得 Java 接大模型难,难在哪?先看两类现状。

第一类是完全没有 AI 经验的团队。他们以为大模型接口无非就是“发个 HTTP 请求,把返回的 JSON 拼出来”,真上手才发现要处理的事情一大堆:认证怎么带、错误码怎么识别、流式返回怎么解析、上下文怎么传、重试怎么做、并发会不会被限流。这些事每一项都不难,但合在一起就得写小几百行工具代码,还得反复跟对方的接口文档对。

第二类是已经有 AI 经验、但一直用 Python 的团队。Python 生态里的大模型 SDK 实在太多了,封装也成熟,拿到 key 就能跑。Java 这边虽然也有个别模型厂商提供的 SDK,但不同平台一套风格,换一个模型就得重新学一套 API。等你写完接 A 厂的代码,又听说 B 厂模型更便宜更好用,想换?改动范围不是几行配置能解决的。

说白了,Java 生态缺的不是“能不能接”,缺的是一个统一抽象层。就像当年 JDBC 解决了数据库方言问题、Spring Data 解决了数据访问模板问题,AI 领域也需要一个类似的规范,把不同模型平台的差异挡住。

Spring AI 做的就是这件事。它把当前主流的对话模型、向量模型、图片模型全部收拢到一套 Spring 风格 API 里,你面向的是ChatClient这个接口,背后接的是千问、通义、文心还是国外某个模型,业务代码基本可以不改。

1.2 Spring AI 到底帮你做了什么

Spring AI 是 Spring 生态推出的 AI 应用开发框架,思路和 Spring Data 一脉相承:你只定义要“做什么”,至于“怎么做”交给自动配置去完成。

它的核心组件叫ChatClient。你可以理解成:以前我们调数据库用JdbcTemplate,调 Redis 用RedisTemplate,现在调大模型用ChatClient。这个对象由框架自动装配,你只要往上下文里注入ChatClient.Builder,就能生成一个可用的客户端。

具体来说,它帮我们处理了几层事情:

  • 统一的请求模型。不管底层是千问还是别的模型平台,你只需要给ChatClient传user内容、system提示词、options参数,其余按框架统一的模型对象执行。
  • 自动配置。引入对应模型的 starter 依赖后,Spring Boot 会在启动时读取application.yml里的配置,自动创建ChatClient,不需要你自己 new。
  • 流式响应支持。大模型天然适合流式输出,Spring AI 把响应封装成Flux,前端可以直接走 SSE 协议。
  • 结构化输出。返回 JSON 可以自动映射到 Java 对象,这是日常业务里最实用的能力。
  • 工具函数调用和 RAG 等高级能力。这些对老手扩展来说很舒服,后面我会提到。

它的价值不是让你少写一个工具类,而是让整个 AI 集成的“硬编码链路”透明化。你甚至不需要关心 HTTP 请求细节,模型平台的接口变了,可能是框架顺手帮你适配,而不是你熬夜改代码。

1.3 为什么拿千问 3.5 来打样

选千问 3.5 作为示例,原因很现实。

首先,千问系列模型对中文任务的处理能力在国产模型里是第一梯队,回答质量稳定,尤其适合内容生成、客服问答、信息抽取这类业务场景。作为团队接 AI 的第一个模型,它的综合表现不太容易“劝退”。

其次,它的 API 风格比较标准,Spring AI 官方也提供了对应的 starter,集成路径最短。我见过很多项目一开始就挑冷门模型,结果官方 SDK 都没有,只能自己写 HTTP client,硬生生把一个月耗在适配层上。用千问打样,可以快速验证整个链路。

最后是成本和合规性。中文业务的数据敏感性不用多说,模型部署在国内,调用延迟和审批顾虑会更少。当你验证完 Spring AI 这套架构之后,模型切换成本也很低,后面真因为业务需求要换别的模型,只需要换依赖、改配置,业务代码依然是一行chatClient.call()。

2. 环境准备与配置细节

2.1 版本选择:Spring Boot 和 Spring AI 别瞎配

我接这个项目的时候第一件事就是查版本兼容性,因为 Spring AI 这个框架目前迭代非常快,版本之间 API 差异不小。如果你用最新版 Spring Boot 加旧版 Spring AI,往往会出现ChatClient类找不到、配置属性不生效这种很尴尬的问题。

我的建议是无脑选当前 Spring Boot 3.x 的稳定版本,JDK 至少 17 往上。Spring AI 的版本以官方发布为准,尽量别用快照版本,虽然快照有最新功能,但 API 说变就变,写出来的代码过两周可能就废了。

实际项目里,我用的是 Maven 管理依赖。先把 Spring AI 的 BOM 引入进来,避免自己手动控制每个模块的版本:

<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

这里版本号只是一个示例,你实际使用时应该去 start.spring.io 或者官方仓库确认当前最新稳定版。版本对齐的最大好处是:Spring AI 内部各模块之间的兼容性由 BOM 维护,你不需要自己操心。

2.2 Maven 依赖与仓库配置

接着引入核心 starter。如果你使用的是 Spring AI 对千问模型的专门集成,依赖大致长这样:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-qwen</artifactId> </dependency>

引入这个依赖后,Spring Boot 的自动配置会自动识别千问模型相关的配置项,并生成ChatModel、ChatClient等 Bean。后面你只管注入使用,不用写一行工厂代码。

有一点需要特别提醒:Spring AI 的部分模块在早期版本里不是放在 Maven 中央仓库的,而是在 Spring 自己的里程碑仓库里。如果直接引入依赖后下载失败,就需要在pom.xml里把仓库地址加上:

<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>

仓库地址这种事,官方文档里写得清楚,但很多新手就是会漏。你漏了也分不清是版本问题还是依赖问题,排查起来浪费时间。

2.3 API Key 的配置姿势:从硬编码到环境变量

每个大模型平台都会给你一个 API Key,这相当于你的账号密码。我见过有人图省事直接写在application.yml里,还顺手传到 Git 仓库。这里必须敲个警钟:Key 一旦泄露,等于别人拿着你的账单疯狂调模型,刷几百上千块只是一瞬间的事。

正确做法是通过环境变量注入,然后在配置里引用:

spring: ai: model: chat: qwen: api-key: ${QWEN_API_KEY} model: qwen3.5

没有环境变量的话,也可以用配置中心或者启动参数传。

在这个配置里,model字段填的是模型在服务商那边的标识,比如qwen3.5或者你在控制台开通的具体版本型号。每个人开通的资源不一样,模型名并不通用,最稳妥的方式是登录控制台确认自己的模型标识,而不是照抄网上的案例。

这一段配置配好之后,核心工作就完成了大半。启动服务后,Spring AI 会读取配置帮你创建客户端,我们接下来见到的“一行代码”,全靠这一步撑着。

3. 一行代码接入的背后

3.1 先说结论:这一行代码写了什么

在 Spring AI 里,最基础的对话调用确实只有一行:

String answer = chatClient.call("请用三句话介绍你的功能");

就这么简单。chatClient.call()会同步等待模型返回完整结果,然后把文本内容直接给你。如果你也想快速做接口验证,把它丢进 Controller 就能跑通全流程。

但这行代码能成立,不是 Spring AI 会魔法,而是前面这些事都已经被提前干完了:

  • 依赖里已经引入了千问的 starter;
  • application.yml已经把 key 和模型名配好;
  • Spring Boot 启动时自动实例化了ChatClient;
  • chatClient底层封装了网络请求、认证、响应解析等逻辑。

所以你在业务代码里看到的“一行”,其实是框架把复杂度咽下去了。这也提醒我们,永远不要真把大模型接入当成“一行代码”的工程量,该配置的、该测试的一点都不能省。

3.2 最简调用:不写一行样板,直接出文案

实际在 Spring Boot 项目里,我会写一个最简单的 Controller:

@RestController public class AiController { private final ChatClient chatClient; public AiController(ChatClient.Builder builder) { this.chatClient = builder.build(); } @GetMapping("/ai/chat") public String chat(@RequestParam(defaultValue = "你好") String message) { return chatClient.call(message); } }

启动项目,浏览器访问/ai/chat?message=介绍一下Java,就能看到模型返回结果。这个接口虽然简陋,但证明了整条链路是通的,后面所有复杂功能都在这条链路上扩展。

这里有个很小的细节:注入的是ChatClient.Builder,不是直接注入ChatClient。因为框架希望你在构建时能额外设置系统提示词、默认参数等,而不是拿着一个固定实例到处用。如果你直接注入ChatClient也可能可以,但遇到多租户、多模型场景时不好扩展。

3.3 把“超能力”封装成自己的工具类

Controller 里直接裸露调用虽然 demo 很爽,但真实项目不能这么干。模型调用是资源操作,应该抽到 Service 层统一管理。

我会定义一个AiAssistant,把业务语义都收进来:

@Service public class AiAssistant { private final ChatClient chatClient; public AiAssistant(ChatClient.Builder builder) { this.chatClient = builder .defaultSystem("你是一个严谨的技术写作助手,回答要简洁、准确、有条理。") .build(); } public String generateArticleOutline(String topic) { String prompt = """ 请为下面的主题生成一份文章大纲,包含 3 个章节。 主题:%s """.formatted(topic); return chatClient.call(prompt); } public String summarize(String content) { String prompt = """ 请用 100 字以内总结下面的内容: %s """.formatted(content); return chatClient.call(prompt); } }

这个类把“角色设定”集中管理。以后要换提示词,或者调整模型参数,都只改这里,不会污染 Controller。

加粗提示一下:在真实项目中,ChatClient是线程安全的,可以全局复用。不要每次请求都new一个客户端,那是白烧性能。

3.4 流式输出:聊天框里的打字机效果

如果只做一次性问答,同步的call()就够用了。但你要是做聊天机器人、智能助手,用户绝对忍受不了转圈等一大段文字一次性蹦出来。这时候要用流式输出。

Spring AI 对流的支持非常自然:

@GetMapping(value = "/ai/stream", produces = "text/event-stream") public Flux<String> streamChat(@RequestParam String message) { return chatClient.prompt() .user(message) .stream() .content(); }

前端用EventSource或fetch的 ReadableStream 就能实时接收内容,体验和国内主流聊天产品差不多。这里要注意两点:

  • 返回类型是Flux<String>,需要引入响应式依赖,但 Spring Boot WebFlux 下天然支持;
  • 接口的produces要写成text/event-stream,否则浏览器不按流式处理。

流式模式并不是所有模型平台都支持得很好,千问 3.5 这代模型的流式体验已经相当成熟,断句和响应速度都跟得上。

4. 参数调优与实际应用

4.1 温度、最大 Token 这些参数怎么调

大模型不是“给的提示词越多越好”,很多参数是决定输出质量的关键,最重要的两个就是temperature和max-tokens。

temperature控制随机性。取值一般在 0 到 1 之间,值越低越保守,越高越发散。做客服机器人和信息抽取,我建议 0.2 到 0.4,保证答案稳定;做文案创作、头脑风暴,可以拉到 0.8 以上,让模型更有“灵感”。

max-tokens直接限制最大回复长度。它既影响质量,也直接影响账单。你需要估算业务里最长回答可能超过多少 token,不能无脑设成 4096 或 8192。比如一个商品简介生成场景,设成 512 完全够了;写长文才需要 2048 以上。

在配置文件里这样设置:

spring: ai: model: chat: qwen: options: temperature: 0.4 max-tokens: 1024

如果你希望不同接口使用不同参数,可以在调用时通过Prompt单独覆盖:

Prompt prompt = new Prompt( "写一篇产品推广文案", QwenChatOptions.builder() .withTemperature(0.9) .withMaxTokens(512) .build() ); String result = chatClient.call(prompt);

在未完全确认参数效果前,不要“感觉差不多就行”。我建议把自己常见的问题整理成一组测试集,跑对比,挑出最合适的参数组合,固化到配置里。

4.2 多轮会话:别让 AI 失去上下文

默认情况下,ChatClient每次调用都是独立的,模型不记得你上一句说了什么。要做客服、助手这类多轮对话,就必须手动把历史消息一起传进去。

Spring AI 里多轮对话可以像拼消息列表一样写:

String result = chatClient.prompt() .system("你是客服助手,回答要耐心、简洁。") .user(history) .user("现在帮我查一下订单状态") .call() .getContent();

你可以把最近几轮用户消息和助手消息拼接成一个上下文文本,或者使用框架提供的消息接口构建列表。关键是:上下文太长会占用大量 token,还会拖慢响应。我一般只保留最近 6 到 8 轮对话,超过就截断,效果基本够用。

自己维护对话历史的方案适合中小项目。真正到了多用户、高并发场景,应该把会话数据丢进 Redis 或数据库,而不是放在单机内存里,否则服务一重启全部丢失。

4.3 结构化输出:让 AI 直接返回 Java 对象

这个功能特别适合做信息抽取和表单填充。比如你让模型从一段简历里提取姓名、技能、工作经验,以前你得自己写 JSON 解析,现在可以直接让 Spring AI 帮你映射成 POJO。

record PersonInfo(String name, List<String> skills, int years) {} PersonInfo person = chatClient.prompt() .user("从这段简历里提取人员信息:...") .call() .entity(PersonInfo.class);

这里 Spring AI 会在内部请求模型返回 JSON,然后反序列化成PersonInfo。注意 record 的字段名要和模型返回的 JSON key 一致,否则映射不出来。如果需要国内字段名,可以在提示词里明确指定返回结构。

结构化输出最大的价值在降本增效,一段 500 字的简历,人工录入至少要三分钟,模型三秒就给出结构化结果。只要做好字段校验,这条路非常稳。

5. 踩坑记录与问题排查

5.1 依赖下载失败、版本冲突

我在项目里遇到最多的报错就是类找不到。比如NoClassDefFoundError: ChatClient,基本就是版本冲突。

先做三件事:

  • 检查 Maven 是否成功拉取了所有依赖;
  • 看mvn dependency:tree里有没有旧版本 Spring AI 被传递进来;
  • 确认项目里没有同时引入多个模型 starter。

同时引入两个模型 starter 的问题很隐蔽,因为它们各自的自动配置都会尝试创建ChatModelBean,轻则启动有警告,重则直接启动失败。一个项目接入多个模型时,最好明确限定各自 Bean 名称。

5.2 模型名报错:404、400 怎么判断

大模型接口报 404 或 400,大概率不是网络问题,而是模型名没对上。

排查思路很简单:登录模型服务商控制台,查看你当前账号能访问的模型列表。不同账号能用的模型不一样,你在网上看到的教程模型名,在自己项目里不一定有效。

如果你确认模型名没错,再看配置里的 key 是否有权限。很多新手把测试环境的 key 配到线上,返回 401/403 也是一脸懵。

5.3 超时问题:从 5 秒到 60 秒

默认情况下,Spring Boot 内置的 HTTP 客户端超时时间可能很短,长一点的生成任务很容易就断了。最典型的症状就是:请求发了,模型也接了,但前端 5 秒后就收到超时,钱还被扣了。

解决思路是定制RestClient的超时参数:

  • connectTimeout:连接建立超时,一般 5 到 10 秒;
  • readTimeout:读取响应超时,建议 60 秒以上,复杂任务甚至 120 秒。

不同 Spring AI 版本的配置方式有差异,核心是提供一个修改过超时配置的RestClient.BuilderBean,让自动配置使用它。调整之后,长文生成就不会半路断开了。

5.4 并发场景下会有哪些坑

大模型服务商普遍有 QPS 限制。你的服务并发一上来,下一秒就会收到限流错误,返回 429 或类似提示。

应对办法,一是做好业务层的限流,比如按用户维度限制请求频率;二是在调用 AI 的链路上做简单重试。重试时注意指数退避,不要等几十个线程一起重试,那不是解决问题,是放大大问题。

另外,模型响应耗时不固定,如果这是在 Web 请求线程里同步调用,线程池很容易被占满。建议把 AI 调用放到独立的线程池,或者用响应式编程方式,避免把整个服务的线程资源拖垮。

5.5 一个不算 bug 的坑:提示词才是天花板

很多团队换了大模型之后,发现效果并没有明显提升,就开始怀疑模型能力有问题。实际上,一半以上的情况是提示词没写对。

同样一个任务,你用“帮我写个文案”和“你是一名资深品牌策划,结合产品卖点和目标用户画像,输出 3 条不同风格的推广短文案,每条不超过 50 字”,效果天差地别。Spring AI 的优势在于它把提示词和代码分离得很干净,你用PromptTemplate统一管理提示词,才是后续持续调优的正路。

我在实际项目中体会最深的一件事是:Spring AI 最大的价值不是省了那几行代码,而是逼着整个 Java 团队用统一的模式去思考 AI 集成。以前每人一套接入方式,现在大家共用一套抽象,后续换模型、加功能、做监控都清晰很多。如果你正准备把 AI 能力接进 Java 服务,别一上来就研究底层协议,先把 Spring AI 和千问 3.5 这条链路跑通,再针对业务做提示词和参数优化,你会发现所谓“风口”落地起来比自己想象中稳得多。

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

Rayleigh-Ritz法与Galerkin法:有限元求解核心原理与工程实现解析

有限元方法的名字很多人听过&#xff0c;但真要动手推导或者自己写个求解器&#xff0c;八成都会卡在两个名字上&#xff1a;Rayleigh-Ritz法和Galerkin法。这俩货经常被放在一起讲&#xff0c;教材里的推导一页接一页&#xff0c;符号满天飞&#xff0c;但很少有人能说清楚它们…

作者头像 李华
网站建设 2026/10/21 14:40:36

Rayleigh-Ritz法与Galerkin法深度对比:从变分原理到有限元实现

如果你学有限元、做结构仿真&#xff0c;或者刚开始啃数值方法的教材&#xff0c;那“Rayleigh-Ritz法”和“Galerkin法”这两个名字一定绕不开。它们一个是变分法的代表&#xff0c;靠“能量极值”找近似解&#xff1b;一个是加权残值法的代表&#xff0c;靠“残差投影”求近似…

作者头像 李华
网站建设 2026/10/21 14:39:57

基于Dify企业版的企业AI落地实践:从数据到智能决策链路

1. 项目定位与整体建设思路 1.1 这个项目到底在解决什么问题 这两年我接触了不少处在数字化转型中途的企业&#xff0c;大家的处境其实都差不多&#xff1a;系统上了几十套&#xff0c;ERP、CRM、OA、HR&#xff0c;数据堆了一堆&#xff0c;但管理层做决策仍然靠Excel和拍脑袋…

作者头像 李华
网站建设 2026/10/21 14:37:50

REA业务建模实战:用资源、事件、参与者重构数据模型

你知道REA吗&#xff1f;我第一次看到这个词的时候&#xff0c;以为是“React”的拼写错误&#xff0c;后来才意识到说的是企业建模里的 REA&#xff08;Resource-Event-Agent&#xff0c;资源-事件-参与者&#xff09;参考模型。做业务系统的这些年&#xff0c;我见过太多团队…

作者头像 李华
网站建设 2026/10/21 14:37:04

技能管理实战:从技能点到技能链的拆解与组合方法

1. 从“skills”这个词说起&#xff1a;它到底指什么“skills”这个词看起来简单&#xff0c;但放在不同语境里&#xff0c;指向的东西差别很大。有人看到它想到的是简历上那一栏“专业技能”&#xff0c;有人想到的是游戏里的技能树&#xff0c;还有人想到的是团队协作中每个人…

作者头像 李华
网站建设 2026/10/21 14:36:19

Dell R730 安装 Windows Server 2008 R2 驱动全攻略:顺序、避坑与验证

简介&#xff1a;这份驱动包面向需要在 Dell PowerEdge R730 服务器上部署 Windows Server 2008 R2 64 位系统的运维与装机人员&#xff0c;用于解决服务器重装或系统迁移后网卡、芯片组、磁盘阵列卡、显卡等设备缺少官方驱动、无法正常识别的问题。包内共 96 个文件&#xff0…

作者头像 李华