跳到主要内容 Spring AI Alibaba + Ollama 实战:基于本地 Qwen3 的 Spring Boot 大模型应用 | 极客日志
Python
Spring AI Alibaba + Ollama 实战:基于本地 Qwen3 的 Spring Boot 大模型应用 在大模型快速演进的今天,Java 开发者同样希望'开箱即用'地接入各类模型服务。Spring 官方推出的 Spring AI,已经为 Java / Spring Boot 应用提供了一套统一、优雅的 AI 抽象;而在国内模型生态中,如何更好地对接阿里云通义(Qwen)与灵积平台(DashScope),则是 Spring AI Alibaba 重点解决的问题。 基于仓库中的 spring_ai_…
板砖工程师 发布于 2026/4/6 更新于 2026/4/13 42K 浏览在大模型快速演进的今天,Java 开发者同样希望'开箱即用'地接入各类模型服务。Spring 官方推出的 Spring AI,已经为 Java / Spring Boot 应用提供了一套统一、优雅的 AI 抽象;而在国内模型生态中,如何更好地对接阿里云通义(Qwen)与灵积平台(DashScope),则是 Spring AI Alibaba 重点解决的问题。
本文基于仓库中的 spring_ai_alibaba-demo 子项目,从真实代码 出发,带你一起拆解:如何用 Spring AI + Spring AI Alibaba 的生态,在本地通过 Ollama 跑 Qwen3 模型 ,并逐步扩展到 RAG、工具调用和 Graph 工作流。
GitHub 项目地址:https://github.com/zhouByte-hub/java-ai/tree/main/spring_ai_alibaba-demo
欢迎 Star、Fork 和关注!文中所有代码都可以在该子项目中找到,更适合边读边跑。
面向读者:
已有 Spring Boot 基础,希望快速接入大模型的后端开发;
计划在本地或内网环境使用 Qwen3 等模型(通过 Ollama),但又希望未来平滑切到阿里云 DashScope;
想了解 Spring AI Alibaba 在 Graph、RAG、工具调用等场景中的作用和优势。
一、项目概览:Spring AI + Spring AI Alibaba 在这个 Demo 里的分工
spring_ai_alibaba-demo 是一个多模块示例工程,核心模块包括:
根模块 spring_ai_alibaba-demo:
使用 Spring AI 的 spring-ai-starter-model-ollama 接入本地 Ollama 服务;
使用 spring-ai-starter-vector-store-pgvector 集成 PostgreSQL + PgVector 做向量检索;
通过 ChatModel / ChatClient 演示基础对话、RAG、工具调用和记忆;
通过依赖管理引入 spring-ai-alibaba-bom,为后续接入阿里云生态(包括 DashScope、Graph 等)奠定基础。
子模块 alibaba-graph:
使用 spring-ai-alibaba-graph-core 演示基于大模型的有状态流程(StateGraph),依然以 Ollama 的 Qwen3 作为底层模型;
子模块 alibaba-mcp-server / alibaba-mcp-client:
使用 Spring AI 的 MCP 能力演示模型调用外部工具 / 资源的模式。
换句话说:
当前 Demo 没有直接连阿里云 DashScope ,而是选择在本地通过 Ollama 运行 Qwen3 模型 ;
但项目在依赖管理和结构设计上,已经完全站在 Spring AI Alibaba 生态 之上,随时可以切换到阿里云在线服务。
接下来,我们按'从简单到复杂'的顺序,依次看看各个模块是怎么搭建的。
二、依赖与环境:本地 Qwen3 + PgVector
先看根模块 spring_ai_alibaba-demo/pom.xml 中的关键部分:
<properties > <java.version > 17</ > 1.1.0.0-M5 1.1.0 org.springframework.boot spring-boot-starter-web org.springframework.ai spring-ai-starter-model-ollama org.springframework.ai spring-ai-starter-vector-store-pgvector org.postgresql postgresql com.alibaba.cloud.ai spring-ai-alibaba-bom ${spring-ai-alibaba.version} pom import org.springframework.ai spring-ai-bom ${spring-ai.version} pom import
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown 转 HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
HTML 转 Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
java.version
<spring-ai-alibaba.version >
</spring-ai-alibaba.version >
<spring-ai.version >
</spring-ai.version >
</properties >
<dependencies >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
</dependency >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
</dependency >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
</dependency >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
</dependency >
</dependencies >
<dependencyManagement >
<dependencies >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
<version >
</version >
<type >
</type >
<scope >
</scope >
</dependency >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
<version >
</version >
<type >
</type >
<scope >
</scope >
</dependency >
</dependencies >
</dependencyManagement >
通过 BOM (spring-ai-alibaba-bom + spring-ai-bom)统一版本管理,避免各个 Starter 之间的版本地狱;
实际运行时模型选择 Ollama ,既方便本地开发调试,又可以在网络受限场景下顺畅运行;
未来如果要切到阿里云 DashScope,只需要:
打开已经写好的(但当前被注释掉的) spring-ai-alibaba-starter-dashscope 依赖;
在配置文件里增加 spring.ai.dashscope.* 对应配置,不需要改业务代码。
环境配置:Ollama + Qwen3 + PgVector spring_ai_alibaba-demo/src/main/resources/application.yaml 中:
server :port :8081 # 应用监听端口servlet :context-path : /alibaba-ai # 统一的服务前缀spring:ai:ollama:base-url: http:/ /localhost:11434# 本地 Ollama 服务地址chat:options:model: qwen3:0.6b # 聊天用的 Qwen3 模型名称temperature:0.8# 采样温度,越高回答越发散embedding:options:model: qwen3-embedding:0.6b # 用于向量化的 embedding 模型vectorstore:pgvector:dimensions:1024# 向量维度,需要与 embedding 模型输出一致distance-type: cosine_distance # 相似度度量方式initialize-schema:true# 启动时自动创建 PgVector 表结构datasource:url: jdbc:postgresql:/ /<your-host>:5432/ postgres?serverTimezone=Asia /Shanghai # PostgreSQL 连接串username : postgres password :****# 建议通过环境变量或配置中心注入
Ollama 在本机 11434 端口提供服务,加载的是 qwen3:0.6b 模型(本质上仍然是阿里云通义家族的模型,只是以本地方式运行);
Embedding 使用 qwen3-embedding:0.6b;
PgVector 存储维度设置为 1024,采用余弦相似度;
数据源配置指向 PostgreSQL,用于向量存储和(可选)对话记忆持久化。
三、基础对话:从 ChatModel 到 ChatClient Demo 中提供了两种对话方式:直接使用 ChatModel,以及通过 ChatClient 封装后的高级用法。
3.1 使用 ChatModel 流式返回 @RestController@RequestMapping("/chatModel" )publicclassChatModelController{// 注入由 Spring AI 自动装配的 Ollama ChatModelprivatefinalChatModel ollamaChatModel;publicChatModelController(ChatModel ollamaChatModel ){this.ollamaChatModel = ollamaChatModel;}@GetMapping("/chat" )publicFlux<String>chat(@RequestParam("message" )String message ){// message:用户输入的自然语言问题return ollamaChatModel.stream(newPrompt(message ) )// 以流式方式调用大模型.map(ChatResponse::getResult )// 提取每个增量响应的结果对象.mapNotNull(result -> result.getOutput( ).getText( ) );// 只保留最终输出的文本内容}}
ChatModel 由 spring-ai-starter-model-ollama 自动装配,底层指向本地 Qwen3 模型;
.stream(...) 返回的是一个 响应式 Flux ,可以在前端按 token/片段逐步渲染;
控制器本身和普通 Spring Web 控制器没有本质差别,学习成本非常低。
3.2 使用 ChatClient 提升可用性 @RestController@RequestMapping("/chatClient" )publicclassChatClientController{// 基于 ChatModel 封装的高级客户端,后续可以挂接 Adviser、工具等能力privatefinalChatClient ollamaChatClient;publicChatClientController(ChatClient ollamaChatClient ){this.ollamaChatClient = ollamaChatClient;}@GetMapping("/chat" )publicFlux<String>stream(@RequestParam("message" )String message ){// 使用最简单的 Prompt,直接将用户输入交给大模型,并以流式方式返回结果return ollamaChatClient .prompt(newPrompt(message ) )// 构造 Prompt 对象.stream()// 流式调用.content();// 提取文本内容}@GetMapping("/prompt" )publicFlux<String>prompt(){PromptTemplate template =PromptTemplate.builder().template("请用简短中文回答:{question}" )// 模板中定义占位符 {question}.variables(Map.of( ) )// 这里可以预先声明变量,也可以在 create 时传入.build();// 使用实际问题填充模板变量Prompt prompt = template.create(Map.of("question" ,"Spring AI Alibaba 有什么特点?" ) );return ollamaChatClient.prompt(prompt ).stream().content();}}
和 ChatModel 相比,ChatClient 的优势在于:
提供链式 API:.prompt().call()/stream(),更易读;
更容易挂接 Adviser(记忆、RAG、工具等),形成统一调用入口;
在需要多轮交互、上下文管理时更易扩展。
在 OllamaConfig 中,Demo 还展示了如何为 ChatClient 挂接记忆 Adviser,后面章节会展开。
四、对话记忆:内存版与可扩展版 实际业务中,一个'傻傻忘记前文'的大模型体验非常差。Demo 中给出了两种记忆实现方式。
4.1 简单内存记忆:SimpleMemories @ComponentpublicclassSimpleMemoriesimplementsChatMemory{privatestaticfinalMap<String,List<Message>> MEMORIES_CACHE =newHashMap<>();@Overridepublicvoidadd(String conversationId,List <Message> messages ){// conversationId:会话标识;messages:本轮新增的消息列表List<Message> memories = MEMORIES_CACHE.getOrDefault(conversationId,newArrayList<>( ) );if(messages !=null&&!messages.isEmpty( ) ){ memories.addAll(messages );} MEMORIES_CACHE.put(conversationId, memories );}@OverridepublicList<Message>get(String conversationId ){// 根据会话 ID 取出该会话的历史消息return MEMORIES_CACHE.getOrDefault(conversationId,newArrayList<>( ) );}@Overridepublicvoidclear(String conversationId ){// 清空某个会话的记忆List<Message> messages = MEMORIES_CACHE.get(conversationId );if(messages !=null ){ messages.clear();}}}
通过 conversationId 区分不同会话;
适合 Demo、PoC 或对可靠性要求不高的场景;
结合 MessageChatMemoryAdvisor 可以自动把历史消息注入到当前 Prompt 中。
4.2 Adviser 方式:MemoriesAdviser @ComponentpublicclassMemoriesAdviserimplementsBaseAdvisor{privatestaticfinalMap<String,List<Message>> MEMORIES =newHashMap<>();// 用于在 ChatClient 的上下文中标识当前会话 ID 的 keyprivatestaticfinalString CHAT_MEMORIES_SESSION_ID ="chat_memories_session_id" ;@OverridepublicChatClientRequestbefore(ChatClientRequest request,AdvisorChain chain ){// 从上下文中读取会话 ID,并取出其历史消息String sessionId = request.context().get(CHAT_MEMORIES_SESSION_ID ).toString();List<Message> messages = MEMORIES.getOrDefault(sessionId,newArrayList<>( ) );// 当前请求的消息放到历史消息后面,一起交给大模型 messages.addAll(request.prompt( ).getInstructions( ) );Prompt prompt = request.prompt().mutate().messages(messages ).build();return request.mutate().prompt(prompt ).build();}@OverridepublicChatClientResponseafter(ChatClientResponse response,AdvisorChain chain ){// 把本次大模型回复写回到对应会话的记忆中AssistantMessage output = response.chatResponse().getResult().getOutput();String sessionId = response.context().get(CHAT_MEMORIES_SESSION_ID ).toString();List<Message> messages = MEMORIES.getOrDefault(sessionId,newArrayList<>( ) ); messages.add(output ); MEMORIES.put(sessionId, messages );return response;}}
在 before 中把历史消息 + 当前消息拼成一个新的 Prompt;
在 after 中把模型回复写回内存;
通过在 ChatClient 构建时添加 defaultAdvisors(memoriesAdvisor),即可对所有请求启用记忆能力。
进一步,你可以把 DataBaseChatMemoryRepository 补充完整,将消息写入数据库,实现持久化对话记忆。
五、RAG:Qwen3 + PgVector 的检索增强 RAG(Retrieval Augmented Generation)是典型的企业级能力,本 Demo 通过 RagChatClientController 进行演示。
5.1 向量入库:TokenTextSplitter + PgVectorStore @RestController @RequestMapping ("/rag" )publicclassRagChatClientController{privatefinalChatClient ragChatClient ;privatefinalPgVectorStore pgVectorStore ;publicRagChatClientController (ChatClient ragChatClient,PgVectorStore pgVectorStore){this .ragChatClient = ragChatClient ;this .pgVectorStore = pgVectorStore ;}@GetMapping ("/embedding" )publicvoidembeddingContent (@RequestParam ("message" )String message){
TokenTextSplitter 基于 token 切分文档,避免切得过碎或过长;
PgVectorStore.add 将切分后的文档写入 PostgreSQL + PgVector;
真实项目中可把 /embedding 换成异步批处理任务。
5.2 RAG 对话:RetrievalAugmentationAdvisor @ConfigurationpublicclassVectorChatClientConfig{@Bean("ragChatClient" )publicChatClientragChatClient(ChatModel chatModel,VectorStore vectorStore ){VectorStoreDocumentRetriever retriever =VectorStoreDocumentRetriever.builder().vectorStore(vectorStore )// 具体使用的向量库实现,这里是 PgVector.topK(3 )// 每次检索返回相似度最高的前 3 条文档.similarityThreshold(0.5 )// 相似度阈值,小于该值的文档会被过滤掉.build();RetrievalAugmentationAdvisor advisor =RetrievalAugmentationAdvisor.builder().documentRetriever(retriever )// 指定文档检索器.order(0 )// Adviser 执行顺序,越小越先执行.build();returnChatClient.builder(chatModel ).defaultAdvisors(advisor )// 默认启用 RAG 能力.build();}}
RetrievalAugmentationAdvisor 会在每次请求前,先到向量库检索相关文档;
然后把检索结果作为'系统提示词'或'上下文'塞给 Qwen3 模型;
对你来说,只需调用 ragChatClient.prompt().user(question).call(),就能得到'带知识库'的回答。
六、工具调用:用 @Tool 让模型调用你的 Java 方法 在很多场景中,大模型需要调用业务系统的 API 才能完成任务。Spring AI 提供了 @Tool 注解,Demo 中的 ZoomTool 便是一个简单示例。
6.1 定义工具:ZoomTool @ComponentpublicclassZoomTool {@Tool (description ="通过时区 ID 获取当前时间" )publicStringgetTimeByZone (@ToolParam (description ="时区 ID,比如 Asia/Shanghai" )String zone){
6.2 将工具挂到 ChatClient 上 @ConfigurationpublicclassToolChatClientConfig{@Bean("toolChatClient" )publicChatClienttoolChatClient(ChatModel ollamaChatModel,ZoomTool zoomTool ){// ollamaChatModel:底层使用的 Qwen3 模型;zoomTool:提供获取时间的业务工具returnChatClient.builder(ollamaChatModel ).defaultSystem(this.systemPrompt( ) )// 设置默认的系统提示词,统一咖啡馆背景.defaultTools(zoomTool )// 将 ZoomTool 注册为可调用的工具.build();}privateStringsystemPrompt(){Map<String,Object> vars =newHashMap<>(); vars.put("AMERICAN" ,"1-3" );// 美式咖啡制作时间(分钟) vars.put("LATTE" ,"2" );// 拿铁咖啡制作时间(分钟) vars.put("TIME_ZONE" ,"Asia/Shanghai" );// 默认时区 IDSystemPromptTemplate tpl =SystemPromptTemplate.builder().template("欢迎光临 ZhouByte咖啡馆,... 默认时区:{TIME_ZONE}" )// 系统提示词模板.variables(vars )// 绑定上面的变量.build();return tpl.render();// 渲染出包含具体变量值的系统提示词}}
@RestController @RequestMapping ("/tool" )publicclassToolChatController{privatefinalChatClient toolChatClient ;publicToolChatController (ChatClient toolChatClient){this .toolChatClient = toolChatClient ;}@GetMapping ("/chat" )publicFlux <String >chat (@RequestParam ("message" )String message){return toolChatClient .prompt ()
模型可以在需要时自动调用 getTimeByZone,返回指定时区时间;
你只需要编写普通的 Java 方法,剩下的交给 Spring AI 的工具调用机制。
七、Alibaba Graph 子项目:有状态工作流编排 spring_ai_alibaba-demo/alibaba-graph 子项目使用 spring-ai-alibaba-graph-core 演示了如何构建大模型工作流。
7.1 定义 Graph:StateGraph + CompiledGraph @ConfigurationpublicclassGraphConfig{@Bean("quickStartGraph" )publicCompiledGraphquickStartGraph()throwsGraphStateException{// "quickStartGraph" :图名称;后面的 Map 用于定义状态 key 的合并策略StateGraph graph =newStateGraph("quickStartGraph" ,( )->Map.of("input" ,newReplaceStrategy( ),// 多次写入时,后写入的值覆盖之前的值"output" ,newReplaceStrategy( ) ) ); graph.addNode("node1" ,AsyncNodeAction.node_async(state ->{// node1:设置初始 input 和 outputreturnMap.of("input" ,"graphConfig_addNode" ,"output" ,"graphConfig_output" );} ) ); graph.addNode("node2" ,AsyncNodeAction.node_async(state ->{// node2:模拟业务处理,将 input 改为 ZhouBytereturnMap.of("input" ,"ZhouByte" ,"output" ,"EMPTY" );} ) );// 定义执行顺序:START -> node1 -> node2 -> END graph.addEdge(StateGraph.START,"node1" ).addEdge("node1" ,"node2" ).addEdge("node2" ,StateGraph.END );return graph.compile();}}
StateGraph 描述节点、边和状态合并策略;
AsyncNodeAction 封装每个节点的执行逻辑;
compile() 得到可执行的 CompiledGraph。
7.2 调用 Graph:WebFlux + 流式输出 @RestController @RequestMapping ("/v1" )publicclassGraphController{@ResourceprivateCompiledGraph quickStartGraph;@GetMapping ("/graph" )publicFlux<String>startGraph (){
使用 WebFlux + Flux<NodeOutput> 将节点执行结果流式返回;
通过 RunnableConfig.builder().threadId(conversationId) 还可以实现'带会话 ID 的工作流',类似有状态 Agent。
7.3 quickStartGraph 执行流程图 结合上面的 GraphConfig 和 GraphController,/v1/graph 接口整体执行流程可以用下面这张流程图来表示(以 GitHub 为例,可以直接渲染 Mermaid):
HTTP 请求:GET /alibaba-graph/v1/graphGraphController.startGraph()CompiledGraph.stream(Map.of())StateGraph.START节点 node1
input = graphConfig_addNode
output = graphConfig_output节点 node2
input = ZhouByte
output = EMPTYStateGraph.ENDFlux 流式返回
从 GraphController.startGraph() 开始,调用 CompiledGraph.stream(Map.of()) 启动图的执行;
图从 StateGraph.START 出发,依次流经 node1、node2,最终到达 StateGraph.END;
每个节点都会向全局状态写入 input / output 等字段,并以 Flux<NodeOutput> 的形式逐步返回给调用方。
7.4 多条件分支 Graph 示例(addConditionalEdges) 在实际业务中,Graph 往往不只是线性顺序,还会根据状态进行分支判断。spring-ai-alibaba-graph-core 提供了 addConditionalEdges,可以基于当前 OverAllState 计算「条件标签」,再根据标签跳转到不同节点。
下面是一个简化的「评分决策」示例,根据 score 分数分别走向通过 / 复核 / 拒绝三条路径:
@ConfigurationpublicclassConditionalGraphConfig{@Bean("scoreDecisionGraph" )publicCompiledGraphscoreDecisionGraph()throwsGraphStateException{StateGraph graph =newStateGraph("scoreDecisionGraph" ,( )->Map.of("score" ,newReplaceStrategy( ),// 保存当前评分"result" ,newReplaceStrategy( )// 保存决策结果 ) );// 读取或设置评分(示例中从 state 中读取,实际可由外部请求传入) graph.addNode("checkScore" ,AsyncNodeAction.node_async(state ->{Integer score =(Integer ) state.value("score" ).orElse(75 );// 默认 75 分returnMap.of("score" , score );} ) );// 三个业务分支节点:通过 / 复核 / 拒绝 graph.addNode("pass" ,AsyncNodeAction.node_async(state ->Map.of("result" ,"PASS" ) ) ); graph.addNode("review" ,AsyncNodeAction.node_async(state ->Map.of("result" ,"REVIEW" ) ) ); graph.addNode("reject" ,AsyncNodeAction.node_async(state ->Map.of("result" ,"REJECT" ) ) );// 起点先进入评分检查节点 graph.addEdge(StateGraph.START,"checkScore" );// 多条件边:根据 score 返回不同的'标签' ,再由 mappings 决定下一跳节点 graph.addConditionalEdges("checkScore" ,AsyncEdgeAction.edge_async(state ->{int score =(Integer ) state.value("score" ).orElse(0 );if (score >=80 ){return "PASS" ;}if (score >=60 ){return "REVIEW" ;}return "REJECT" ;} ),Map.of("PASS" ,"pass" ,"REVIEW" ,"review" ,"REJECT" ,"reject" ) );// 三个结果节点最终都指向 END graph.addEdge("pass" ,StateGraph.END ); graph.addEdge("review" ,StateGraph.END ); graph.addEdge("reject" ,StateGraph.END );return graph.compile();}}
这段代码中,addConditionalEdges 的三个参数含义是:
sourceId:条件边的源节点 ID,这里是 "checkScore";
AsyncEdgeAction:根据当前 OverAllState 计算条件标签,这里返回 "PASS" / "REVIEW" / "REJECT";
mappings:标签与目标节点 ID 的映射,例如 "PASS" -> "pass",即当标签为 "PASS" 时跳到 pass 节点。
在真实项目中,你可以把 score 换成「风控评分」「召回结果命中情况」「用户画像标签」等任意业务信号,通过 addConditionalEdges 把复杂分支逻辑从代码 if/else 中抽离出来,统一放在 Graph 层管理。
在这个子项目中,Graph 本身是'流程层',可以在节点里调用 Spring AI / Spring AI Alibaba 的各种模型与工具,实现复杂的多步推理与业务编排。
八、如何从本地 Ollama 平滑切到阿里云 DashScope 虽然当前 Demo 主要跑在本地 Ollama 上,但由于使用了 Spring AI + Spring AI Alibaba 的统一抽象,切换到阿里云 DashScope 十分简单:
在 pom.xml 中启用 DashScope Starter(示例中已给出注释代码):
<dependency > <groupId > com.alibaba.cloud.ai</groupId > <artifactId > spring-ai-alibaba-starter-dashscope</artifactId > <version > 1.1.0.0-M5</version > </dependency >
在配置文件中增加 DashScope 的配置(示例):
spring:ai:dashscope:api-key: ${DASHSCOPE_API_KEY} # 从环境变量或配置中心读取 DashScope 的 API Keyendpoint: https://dashscope.aliyuncs.com chat:options:model: qwen-plus
将原来的 ChatModel 注入点从 Ollama 替换为 DashScope 对应的 Bean(通常只需要调整配置,不改业务代码)。
开发阶段:本地跑 Qwen3(Ollama),成本低、调试快;
生产阶段:切到云上 DashScope(Qwen-Max / Qwen-Plus 等),享受更强算力和更高可用性;
中长期:在 Spring AI Alibaba 的生态内同时兼容多家国内模型厂商。
九、实践建议与最佳实践
配置管理
API Key 使用环境变量或配置中心(Nacos、KMS 等),避免硬编码;
Ollama、DashScope 的模型名称、温度等参数尽量抽到配置文件中。
错误处理与重试
针对网络异常、超时、限流等场景做兜底和重试策略;
对外暴露的接口统一封装错误返回,避免直接把底层错误抛给前端。
性能与成本
在高并发场景建议优先使用流式输出 + 前端增量渲染;
RAG 中控制 TopK、相似度阈值和切分策略,避免向量库'爆炸'。
代码结构
将 ChatClient 配置、Graph 配置等放在独立的 config 包中,业务层只关心接口调用;
工具方法使用 @Tool 暴露,便于模型统一管理和调用。
版本与升级
Spring AI Alibaba 当前仍以 Milestone 版本为主(如 1.1.0.0-M5),升级前建议阅读 release notes;
保持对 spring-ai-bom / spring-ai-alibaba-bom 的依赖,让升级尽量在 BOM 层完成。
十、总结与展望 基于 spring_ai_alibaba-demo 子项目,我们实际体验了一次:
如何用 Spring AI + Spring AI Alibaba BOM 快速接入本地 Qwen3(Ollama);
如何在同一套抽象下串联起对话、记忆、RAG、工具调用;
如何通过 spring-ai-alibaba-graph-core 构建基于大模型的有状态工作流;
以及如何在不改业务代码的前提下,为未来切换到阿里云 DashScope 留出空间。
对 Spring 开发者来说,这套体系最大的价值在于:
统一抽象:不同模型供应商之间切换成本极低;
生态完善:兼容 Spring Boot、WebFlux、向量库、MCP、Graph 等丰富组件;
本地 + 云端双模:既能在本地快速迭代,又能无缝迁移到云上生产环境。
再次附上示例子项目 GitHub 地址,欢迎你亲手跑一跑代码、提 Issue、点 Star: