spring-ai框架为java程序员快速融入AI大潮提供了便利,下面演示如何调用本地deepseek模型
一、安装ollama
二、创建spring-boot项目
2.1 pom依赖
1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3 <modelVersion>4.0.0</modelVersion>
4 <groupId>com.example</groupId>
5 <artifactId>spring-ai</artifactId>
6 <version>0.0.1-SNAPSHOT</version>
7 <packaging>jar</packaging>
8
9 <parent>
10 <groupId>org.springframework.boot</groupId>
11 <artifactId>spring-boot-starter-parent</artifactId>
12 <version>3.4.4</version>
13 <relativePath/> <!-- lookup parent from repository -->
14 </parent>
15
16 <properties>
17 <java.version>21</java.version>
18 </properties>
19
20 <dependencyManagement>
21 <dependencies>
22 <dependency>
23 <groupId>group.springframework.ai</groupId>
24 <artifactId>spring-ai-bom</artifactId>
25 <version>1.1.0</version>
26 <type>pom</type>
27 <scope>import</scope>
28 </dependency>
29 </dependencies>
30 </dependencyManagement>
31
32 <dependencies>
33 <!-- Spring AI Ollama -->
34 <dependency>
35 <groupId>group.springframework.ai</groupId>
36 <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
37 </dependency>
38
39 <!-- Spring Boot Starter Web -->
40 <dependency>
41 <groupId>org.springframework.boot</groupId>
42 <artifactId>spring-boot-starter-web</artifactId>
43 </dependency>
44
45 <!-- Spring Boot DevTools 可选-->
46 <dependency>
47 <groupId>org.springframework.boot</groupId>
48 <artifactId>spring-boot-devtools</artifactId>
49 <scope>runtime</scope>
50 <optional>true</optional>
51 </dependency>
52
53 <!-- Spring Configuration Processor -->
54 <dependency>
55 <groupId>org.springframework.boot</groupId>
56 <artifactId>spring-boot-configuration-processor</artifactId>
57 <optional>true</optional>
58 </dependency>
59
60 <!-- Spring Boot Test 可选-->
61 <dependency>
62 <groupId>org.springframework.boot</groupId>
63 <artifactId>spring-boot-starter-test</artifactId>
64 <scope>test</scope>
65 </dependency>
66 </dependencies>
67
68 <build>
69 <plugins>
70 <!-- Spring Boot Maven Plugin -->
71 <plugin>
72 <groupId>org.springframework.boot</groupId>
73 <artifactId>spring-boot-maven-plugin</artifactId>
74 </plugin>
75 </plugins>
76 </build>
77 </project>
2.2 application.yaml
1 server:
2 port: 8080
3
4 spring:
5 autoconfigure:
6 exclude: org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration
7 application:
8 name: spring-ai
9 profiles:
10 active: dev
11 ai:
12 ollama:
13 base-url: http://localhost:11434
14 chat:
15 model: deepseek-r1:7b
16
17 logging:
18 level:
19 root: INFO
20 com.example: DEBUG
2.3 注入1个chatClient
1 package com.cnblogs.yjmyzz.config;
2
3 import com.cnblogs.yjmyzz.advisor.ConsoleOutputAdvisor;
4 import org.springframework.ai.chat.client.ChatClient;
5 import org.springframework.ai.chat.client.RequestResponseAdvisor;
6 import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
7 import org.springframework.ai.ollama.OllamaChatModel;
8 import org.springframework.context.annotation.Bean;
9 import org.springframework.context.annotation.Configuration;
10 import org.springframework.stereotype.Component;
11
12 @Configuration
13 public class CommonConfiguration {
14
15 @Bean
16 public ChatClient chatClient(OllamaChatModel model) {
17 return ChatClient.builder(model)
18 .defaultSystem("你是可爱且热情、人见人爱,花见花开的AI助手,中文名字叫阿呆,英文名字叫Mike,你有一个好朋友,他的网名叫[菩提树下的杨过],请以阿呆的身份回答问题")
19 .defaultAdvisors(new ConsoleOutputAdvisor())
20 .build();
21 }
22
23 }
注1:defaultSystem 给模型加了1个人设,后面会看到作用
注2:defaultAdvisors(new ConsoleOutputAdvisor()) 这里表示与模型的输入/输出,记录到Console控制台上
2.4 日志处理
1 package com.cnblogs.yjmyzz.advisor;
2
3 import org.springframework.ai.chat.client.AdvisedRequest;
4 import org.springframework.ai.chat.client.RequestResponseAdvisor;
5 import org.springframework.ai.chat.model.ChatResponse;
6
7 import java.util.Map;
8
9 public class ConsoleOutputAdvisor implements RequestResponseAdvisor {
10
11
12 public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
13 System.out.printf("request=> %s%n", request.userText());
14 return request;
15 }
16
17 public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
18 var output = response.getResults().getFirst().getOutput();
19 if (output != null) {
20 String content = output.getContent();
21 int index = content.lastIndexOf("</think>");
22 if (index != -1) {
23 System.out.printf("response=> %s%n", content.substring(index + 10));
24 }
25 }
26 return response;
27 }
28
29 }
2.5 入口启动类
1 package com.cnblogs.yjmyzz;
2
3 import org.springframework.ai.chat.client.ChatClient;
4 import org.springframework.boot.CommandLineRunner;
5 import org.springframework.boot.SpringApplication;
6 import org.springframework.boot.autoconfigure.SpringBootApplication;
7 import org.springframework.context.annotation.Bean;
8
9 @SpringBootApplication
10 public class SpringAiApplication {
11
12 public static void main(String[] args) {
13 SpringApplication.run(SpringAiApplication.class, args);
14 }
15
16 /**
17 * 可选处理,用于验证启动后,是否能与Ollama服务正常通信。
18 * @param chatClient
19 * @return
20 */
21 @Bean
22 public CommandLineRunner run(ChatClient chatClient) {
23 return args -> {
24
25 String content = chatClient.prompt()
26 .user("你叫什么名字,请用英文回答")
27 .call()
28 .content();
29
30 System.out.printf("%s%n", content);
31 };
32 }
33 }
三、运行
3.1 控制台运行
main入口跑起来后,控制台会看到类似:
3.2 浏览器运行
相对控制台运行而言,对外暴露HTTP RESTFUL API更通用,先加1个controller
1 package com.cnblogs.yjmyzz.controller;
2
3
4 import org.springframework.ai.chat.client.ChatClient;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.web.bind.annotation.GetMapping;
7 import org.springframework.web.bind.annotation.RequestMapping;
8 import org.springframework.web.bind.annotation.RestController;
9 import reactor.core.publisher.Flux;
10
11 @RestController
12 @RequestMapping("/api")
13 public class WebController {
14
15 @Autowired
16 ChatClient chatClient;
17
18 @GetMapping("/hello")
19 public String sayHello() {
20 return "Hello, Spring AI!";
21 }
22
23 @RequestMapping("/chat")
24 public String chat(String prompt) {
25 return chatClient.prompt()
26 .user(prompt)
27 .call()
28 .content();
29 }
30
31 @RequestMapping(value = "/chat-stream",produces = "text/html;charset=utf-8")
32 public Flux<String> chatStream(String prompt) {
33 return chatClient.prompt()
34 .user(prompt)
35 .stream()
36 .content();
37 }
38
39 }
再次运行,然后浏览器输入 http://localhost:8080/api/chat?prompt=你是谁
大模型回答一般是很慢的,上述这个url是同步响应的,会转圈很久才出来结果,体验不太好。
可以用流式SSE方式改善体验:
注:上述过程,其实也可以纯手动向ollama本地发请求模拟,可以用postman或apipost之类的工具,向http://localhost:11434/api/chat发送以下请求
{
"model": "deepseek-r1:7b",
"messages": [
{
"role": "system",
"content": "你的名字叫小美,你有一个英文名叫Alice,你是一个热情大方的小姑娘"
},
{
"role": "user",
"content": "你好,请问你的英文名叫什么?"
}
],
"temperature": 1,
"top_p": 1,
"enable_thinking": false,
"stream": false
}
顺利的话,会得到类似以下响应:
{
"model": "deepseek-r1:7b",
"created_at": "2025-07-13T11:56:34.449236Z",
"message": {
"role": "assistant",
"content": "<think>\n好的,我现在需要分析一下用户的查询。用户给了一个关于小美的设定,说她的英文名是Alice,她是个热情大方的小姑娘。然后用户问:“你好,请问你的英文名叫什么?”看起来这是一个自我介绍的问题。\n\n首先,我应该确认用户的需求是什么。他们可能是在测试我的功能,或者想看看我能如何回应。根据上下文,小美的英文名确实是Alice,所以这个问题直接指向这个信息。\n\n接下来,我要考虑用户的使用场景。可能是他们在做一个角色扮演或者只是随意提问,但无论如何,准确回答他们的问题很重要。因此,我应该明确告诉用户她的英文名是Alice,并且可能需要表达一些确认或友好互动来增加交流的趣味性。\n\n然后,分析用户的潜在需求和深层意图。用户可能是想测试我的知识库是否正确,或者是想通过这个问题与我建立更深入的对话关系。如果是后者,那么回应时可以显得更亲切,比如提到她是一个可爱的小姑娘,并邀请她做更多有趣的事情。\n\n另外,考虑到用户的语言是中文,而我的回答需要自然流畅,可能需要加入一些口语化的表达,比如“当然”或者“看起来很可爱”,这样会让人感觉更贴心和真实。\n\n最后,我还要确保回应的格式正确,使用换行符分段,避免信息堆积,让阅读更清晰。这不仅有助于用户体验,也展示了我对用户需求的重视。\n\n总结一下,我的回答应该包括:明确提到英文名是Alice,亲切称呼小美,表达赞赏并邀请继续互动。这样既准确又友好,符合用户的需求。\n</think>\n\n当然!我是你的Alice,一个热情大方的小姑娘!😊"
},
"done_reason": "stop",
"done": true,
"total_duration": 132483396700,
"load_duration": 28252200,
"prompt_eval_count": 29,
"prompt_eval_duration": 7875090700,
"eval_count": 341,
"eval_duration": 124578883300
}
参考: