目录1.版本选择2.完整代码实现3.效果1.版本选择当前Spring AI 最新正式版本为1.1.2我们使用这个版本对应的springboot版本Spring Boot 3.5.0 and 4.0.02.完整代码实现这里我们使用ollama部署的本地模型ollama部署可以参考之前的文章二1.1 ollama本地快速部署deepseek后端pom.xml?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.5.9/version /parent groupIdcom.haylee/groupId artifactIdspring-ai-agent/artifactId version1.0-SNAPSHOT/version namespring-ai-agent/name properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding maven.compiler.release17/maven.compiler.release spring-ai-version1.1.2/spring-ai-version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai-version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-ollama/artifactId /dependency /dependencies build /build /projectapplication.ymlspring: thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html encoding: UTF-8 ai: ollama: base-url: http://localhost:11434 chat: options: model: qwen3:4b temperature: 0.6 # 值越小会降低随机性保证一致性 init: # 不自动下载模型 pull-model-strategy: neverIndexControllerpackage com.haylee.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; Controller public class IndexController { GetMapping(/) public String streamIndexPage() { return stream-index; // 返回模板名称 } }OllamaChatControllerpackage com.haylee.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.api.OllamaChatOptions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; RestController RequestMapping(/ollama) public class OllamaChatController { private Logger logger LoggerFactory.getLogger(OllamaChatController.class); Autowired private OllamaChatModel ollamaChatModel; /** * 模型 * param prompt * return */ GetMapping(/call) public String call(RequestParam(prompt) String prompt) { Prompt pt new Prompt(prompt, OllamaChatOptions.builder() .enableThinking() .build()); ChatResponse response ollamaChatModel.call(pt); String thinking response.getResult().getMetadata().get(thinking); logger.info([Thinking] thinking); String answer response.getResult().getOutput().getText(); logger.info([Response] answer); return answer; } /** * 模型streamspringboot reactive stream * param prompt * return */ GetMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestParam(prompt) String prompt) { Prompt pt new Prompt(prompt, OllamaChatOptions.builder() .enableThinking() .build()); FluxChatResponse result ollamaChatModel.stream(pt); // result.subscribe(response - { // String thinking response.getResult().getMetadata().get(thinking); // String content response.getResult().getOutput().getText(); // if (thinking ! null !thinking.isEmpty()) { // System.out.println([Thinking] thinking); // } // if (content ! null !content.isEmpty()) { // System.out.println([Response] content); // } // }); return result.map(response - response.getResult().getOutput().getText() ). concatWith(Flux.just([DONE])). doOnComplete(() - logger.info(Stream completed)); } }前端resources/templates/stream-index.html!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleSpring AI 流式输出/title style body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } .input-section { margin-bottom: 20px; } #prompt-input { width: 70%; padding: 10px; font-size: 16px; } button { padding: 10px 20px; font-size: 16px; margin-left: 10px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #0056b3; } button:disabled { background-color: #6c757d; cursor: not-allowed; } #response-container { border: 1px solid #ddd; padding: 15px; min-height: 200px; max-height: 400px; overflow-y: auto; background-color: #f9f9f9; white-space: pre-wrap; font-family: monospace; line-height: 1.5; } .thinking { color: #666; font-style: italic; } .output { color: #000; } .status { margin-top: 10px; padding: 5px; color: #28a745; } /style /head body h1Spring AI 流式输出/h1 div classinput-section input typetext idprompt-input placeholder请输入您的问题... / button idsend-btn发送/button button idclear-btn清空/button /div div idresponse-container等待输入.../div div idstatus classstatus/div script document.addEventListener(DOMContentLoaded, function() { const promptInput document.getElementById(prompt-input); const sendBtn document.getElementById(send-btn); const clearBtn document.getElementById(clear-btn); const responseContainer document.getElementById(response-container); const statusDiv document.getElementById(status); sendBtn.addEventListener(click, function() { const prompt promptInput.value.trim(); if (prompt) { responseContainer.innerHTML ; // 创建新的 EventSource const eventSource new EventSource(/ollama/stream?prompt encodeURIComponent(prompt)); sendBtn.disabled true; sendBtn.textContent 响应中...; eventSource.onmessage function(event) { if (event.data [DONE]) { eventSource.close(); // 关闭连接 sendBtn.disabled false; sendBtn.textContent 发送; return; } responseContainer.textContent event.data; responseContainer.scrollTop responseContainer.scrollHeight; }; // 监听错误事件并关闭连接 eventSource.onerror function(err) { console.error(SSE Error:, err); eventSource.close(); // 关闭连接 sendBtn.disabled false; sendBtn.textContent 发送; }; // 监听完成事件需要服务器发送完成信号 eventSource.addEventListener(complete, function() { eventSource.close(); // 手动关闭连接 sendBtn.disabled false; sendBtn.textContent 发送; }); } else { alert(请输入提示内容); } }); // 支持回车键发送 promptInput.addEventListener(keypress, function(e) { if (e.key Enter) { sendBtn.click(); } }); // 清空按钮 clearBtn.addEventListener(click, function() { responseContainer.textContent 等待输入...; promptInput.value ; statusDiv.textContent ; }); }); /script /body /html3.效果*****************想要spring AI 完整代码(12306mcp、自定义mcp、rag等)关注顶部公众号发送 sai免费领取*****************这里使用MCP服务工具参考AI大模型三3.2 Spring AI实现Agent大模型相关课程1一1.大模型的发展与局限性2二1.1 ollama本地快速部署deepseek31.2 linux本地部署deepseek千问蒸馏版web对话聊天41.3 linux本地部署通义万相2.1deepseek视频生成51.4 Qwen2.5-Omni全模态大模型部署61.5 Stable Diffusion中文文生图模型部署71.6 DeepSeek-OCR部署尝鲜82.1 从零训练自己的大模型概述92.2 分词器102.3 预训练自己的模型112.4 微调自己的模型122.5 人类对齐训练自己的模型133.1 微调训练详解143.2 Llama-Factory微调训练deepseek-r1实践153.3 transformLoRA代码微调deepseek实践164.1 文生图Text-to-Image模型发展史174.2 文生图GUI训练实践-真人写实生成184.3 文生图代码训练实践-真人写实生成195.1 文生视频Text-to-Video模型发展史205.2 文生视频Text-to-Video模型训练实践216.1 目标检测模型的发展史226.2 YOLO模型训练实践及目标跟踪23三1.1 Dify介绍241.2 Dify安装251.3 Dify文本生成快速搭建旅游助手261.4 Dify聊天助手快速搭建智能淘宝店小二271.5 Dify agent快速搭建爬虫助手281.6 Dify工作流快速搭建数据可视化助手291.7 Dify chatflow快速搭建数据查询智能助手302.1 RAG介绍312.2 Spring AI-手动实现RAG322.3 Spring AI-开箱即用完整实践RAG332.4 LlamaIndex实现RAG342.5 LlamaIndex构建RAG优化与实践352.6 LangChain实现RAG企业知识问答助手362.7 LangChain构建RAG企业知识问答助手实践373.1 agent核心功能与概念383.2 Spring AI实现Agent