Ollama流式应答接口实现

本节需求

引入 Spring AI 框架组件,对接 Ollama DeepSeek 提供服务接口。包括:普通应答接口和流式接口。

普通请求(同步请求)

普通请求是指客户端一次性发送请求,服务端处理完毕后一次性返回完整的结果。
例如:

流式请求

流式请求是指客户端发送请求后,服务端会将结果分批次,逐步推送给客户端,客户端可以边接收边处理。
例如:

功能实现

1. 工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ai-rag-knowledge/
├── xfg-dev-tech-api/ # API 接口层
│ └── IAiService.java # AI 服务接口定义
├── xfg-dev-tech-app/ # 应用层
│ ├── Application.java # Spring Boot 启动类
│ ├── config/ # 配置类
│ │ ├── OllamaConfig.java
│ │ ├── RedisClientConfig.java
│ │ └── RedisClientConfigProperties.java
│ └── resources/ # 配置文件
│ ├── application.yml
│ ├── application-dev.yml
│ └── logback-spring.xml
└── xfg-dev-tech-trigger/ # 触发器层
└── OllamaController.java # HTTP 控制器

2. 依赖管理

当前步骤需要ollama的依赖,在app模块的POM.xml文件添加依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama</artifactId>
</dependency>

3. 配置管理

配置信息需根据开发与部署环境灵活调整。开发过程中,我在 Windows PC 上进行 Java 开发,OllamaDeepSeek 部署于云服务器的 Docker 环境中,配置文件如下所示:

application.yml:

1
2
3
4
5
6
spring:
application:
name: ai-rag-knowledge
profiles:
active: dev

application-dev.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
server:
port: 8080

spring:
ai:
ollama:
# 使用云服务器的 Ollama 服务,将 IP 更换为你自己云服务器的公网 IP
# 本地部署的就改为localhost:11434
base-url: http://<部署了Ollama的公网IP>:11434

# Redis 配置
redis:
sdk:
config:
host: localhost
port: 6379
pool-size: 5
min-idle-size: 2
idle-timeout: 30000
connect-timeout: 5000
retry-attempts: 3
retry-interval: 1000
ping-interval: 60000
keep-alive: true

logging:
level:
root: debug
cn.bugstack.xfg.dev.tech: debug
config: classpath:logback-spring.xml

其他配置文件暂且不考虑

4. 代码实现

AI服务接口(IAiService.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.bugstack.xfg.dev.tech.api;

import org.springframework.ai.chat.ChatResponse;
import reactor.core.publisher.Flux;

public interface IAiService {

ChatResponse generate(String model, String message);

Flux<ChatResponse> generateStream(String model, String message);

}

Ollama 配置类 (OllamaConfig.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.bugstack.xfg.dev.tech.config;

import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OllamaConfig {

@Bean
public OllamaApi ollamaApi(@Value("${spring.ai.ollama.base-url}") String baseUrl) {
return new OllamaApi(baseUrl);
}

@Bean
public OllamaChatClient ollamaChatClient(OllamaApi ollamaApi) {
return new OllamaChatClient(ollamaApi);
}

}

HTTP 控制器 (OllamaController.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package cn.bugstack.xfg.dev.tech.trigger.http;

import cn.bugstack.xfg.dev.tech.api.IAiService;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatClient;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

@RestController()
@CrossOrigin("*")
@RequestMapping("/api/v1/ollama/")
public class OllamaController implements IAiService {

@Resource
private OllamaChatClient chatClient;

/**
* 同步调用接口
* curl http://localhost:8080/api/v1/ollama/generate?model=deepseek-r1:1.5b&message=1+1
*/
@RequestMapping(value = "generate", method = RequestMethod.GET)
@Override
public ChatResponse generate(@RequestParam String model, @RequestParam String message) {
return chatClient.call(new Prompt(message, OllamaOptions.create().withModel(model)));
}

/**
* 流式调用接口
* curl http://localhost:8080/api/v1/ollama/generate_stream?model=deepseek-r1:1.5b&message=hi
*/
@RequestMapping(value = "generate_stream", method = RequestMethod.GET)
@Override
public Flux<ChatResponse> generateStream(@RequestParam String model, @RequestParam String message) {
return chatClient.stream(new Prompt(message, OllamaOptions.create().withModel(model)));
}
}

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.bugstack.xfg.dev.tech;

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Configurable
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class);
}

}

功能测试

1. 普通请求测试

启动项目,确保云服务器的ollama已经启动后,浏览器地址栏输入:http://localhost:8080/api/v1/ollama/generate?model=deepseek-r1:1.5b&message=1+1,得到结果:

2. 流式请求测试

地址栏输入http://localhost:8080/api/v1/ollama/generate_stream?model=deepseek-r1:1.5b&message=你好啊

将流式请求的文本拼凑得到的响应结果为:

1
2
3
4
5
<think>

</think>

你好!很高兴见到你,有什么我可以帮忙的吗?无论是问题、建议还是闲聊,我都在这儿呢!😊

小结

本节基于 Spring AI 对接了 Ollama(以 DeepSeek 模型为例),分别实现了普通应答与流式应答两类接口:

如果不能出现最终的响应结果,请依次检查:

  1. 云服务器防火墙是否开放11434端口
  2. 云服务器中ollama是否成功运行
  3. application-dev.ymlbase-url是否更改为云服务器的公网IP
  4. application-dev.ymlport是否为8080