Ollama RAG 知识库接口服务实现

本节需求

将 RAG 知识库上传、选择和使用以接口方式提供

功能实现

1. 工程结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ai-rag-knowledge/
├── xfg-dev-tech-api/ # API 接口层(服务契约)
│ ├── IRAGService.java # RAG 服务接口定义
│ └── IAiService.java # AI 服务接口定义
├── xfg-dev-tech-app/ # 应用层(启动 + 基础设施装配)
│ ├── Application.java # Spring Boot 启动类
│ ├── config/
│ │ ├── OllamaConfig.java
│ │ ├── RedisClientConfig.java
│ │ └── RedisClientConfigProperties.java
│ └── test/
│ └── RagPipelineTest.java # 单元测试
└── xfg-dev-tech-trigger/ # 触发器层(对外接口)
├── OllamaController.java # Ollama HTTP控制器
└── RAGController.java # RAG HTTP控制器

2. 依赖管理

trigger模块下引入:

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store</artifactId>
</dependency>

从这里开始就需要使用Redisson了,需要检查根pom,trigger以及app模块都引入了redisson

1
2
3
4
5
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.44.0</version>
</dependency>

3. 配置管理

这里需要配置redis的链接地址,IP、端口等信息:
本地:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Redis 配置
redis:
sdk:
config:
host: localhost
port: 6379
password: <你的密码> # 没有就删除这一行
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

云服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Redis 配置
redis:
sdk:
config:
host: <部署了redis的公网IP>
port: 16379
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

4. 代码实现

Redisson配置(RedisClientConfig.java,RedisClientConfigProperties.java)

如果设置了密码,就把.setPassword(properties.getPassword())的注释取消

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
40
package cn.bugstack.xfg.dev.tech.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Redis 客户端,使用 Redisson <a href="https://github.com/redisson/redisson">Redisson</a>
*/
@Configuration
@EnableConfigurationProperties(RedisClientConfigProperties.class)
public class RedisClientConfig {

@Bean("redissonClient")
public RedissonClient redissonClient(ConfigurableApplicationContext applicationContext, RedisClientConfigProperties properties) {
Config config = new Config();
config.setCodec(JsonJacksonCodec.INSTANCE);

config.useSingleServer()
.setAddress("redis://" + properties.getHost() + ":" + properties.getPort())
// .setPassword(properties.getPassword())
.setConnectionPoolSize(properties.getPoolSize())
.setConnectionMinimumIdleSize(properties.getMinIdleSize())
.setIdleConnectionTimeout(properties.getIdleTimeout())
.setConnectTimeout(properties.getConnectTimeout())
.setRetryAttempts(properties.getRetryAttempts())
.setRetryInterval(properties.getRetryInterval())
.setPingConnectionInterval(properties.getPingInterval())
.setKeepAlive(properties.isKeepAlive())
;

return Redisson.create(config);
}

}
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
package cn.bugstack.xfg.dev.tech.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* Redis 连接配置 <a href="https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter">redisson-spring-boot-starter</a>
*/
@Data
@ConfigurationProperties(prefix = "redis.sdk.config", ignoreInvalidFields = true)
public class RedisClientConfigProperties {

/** host:ip */
private String host;
/** 端口 */
private int port;
/** 账密 */
private String password;
/** 设置连接池的大小,默认为64 */
private int poolSize = 64;
/** 设置连接池的最小空闲连接数,默认为10 */
private int minIdleSize = 10;
/** 设置连接的最大空闲时间(单位:毫秒),超过该时间的空闲连接将被关闭,默认为10000 */
private int idleTimeout = 10000;
/** 设置连接超时时间(单位:毫秒),默认为10000 */
private int connectTimeout = 10000;
/** 设置连接重试次数,默认为3 */
private int retryAttempts = 3;
/** 设置连接重试的间隔时间(单位:毫秒),默认为1000 */
private int retryInterval = 1000;
/** 设置定期检查连接是否可用的时间间隔(单位:毫秒),默认为0,表示不进行定期检查 */
private int pingInterval = 0;
/** 设置是否保持长连接,默认为true */
private boolean keepAlive = true;

}
统一响应类实现(Response.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
package cn.bugstack.xfg.dev.tech.api.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
* @author 渣渣熊
* @description 统一响应类
* @create 2025-10-18 15:25
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Response<T> implements Serializable {

private String code;
private String info;
private T data;

}

RAG服务接口定义(IRAGService.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.bugstack.xfg.dev.tech.api;

import cn.bugstack.xfg.dev.tech.api.response.Response;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

/**
* @author 渣渣熊
* @description RAG服务接口定义
* @create 2025-10-18 15:24
*/
public interface IRAGService {

Response<List<String>> queryRagTagList();

Response<String> uploadFile(String ragTag, List<MultipartFile> files);
}
RAG HTTP控制器(RAGController)
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package cn.bugstack.xfg.dev.tech.trigger.http;

import cn.bugstack.xfg.dev.tech.api.IRAGService;
import cn.bugstack.xfg.dev.tech.api.response.Response;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.PgVectorStore;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

/**
* @author 渣渣熊
* @description RAG HTTP控制器
* @create 2025-10-18 15:23
*/
@Slf4j
@RestController()
@CrossOrigin("*")
@RequestMapping("/api/v1/rag/")
public class RAGController implements IRAGService {

@Resource
private TokenTextSplitter tokenTextSplitter;
@Resource
private PgVectorStore pgVectorStore;
@Resource
private RedissonClient redissonClient;

@RequestMapping(value = "query_rag_tag_list", method = RequestMethod.GET)
@Override
// 从Redis获取所有知识库标签
public Response<List<String>> queryRagTagList() {
RList<String> elements = redissonClient.getList("ragTag");
return Response.<List<String>>builder()
.code("0000")
.info("调用成功")
.data(elements)
.build();
}

@RequestMapping(value = "file/upload", method = RequestMethod.POST, headers = "content-type=multipart/form-data")
@Override
public Response<String> uploadFile(@RequestParam String ragTag, @RequestParam("file") List<MultipartFile> files) {
log.info("上传知识库开始 {}", ragTag);
for (MultipartFile file : files) {
// 1. 文档读取
TikaDocumentReader documentReader = new TikaDocumentReader(file.getResource());
List<Document> documents = documentReader.get();
// 2. 文本分割
List<Document> documentSplitterList = tokenTextSplitter.apply(documents);

// 3. 添加知识库标签元数据
documents.forEach(doc -> doc.getMetadata().put("knowledge", ragTag));
documentSplitterList.forEach(doc -> doc.getMetadata().put("knowledge", ragTag));

// 4. 向量化存储到PostgreSQL
pgVectorStore.accept(documentSplitterList);

// 5. 更新标签列表
RList<String> elements = redissonClient.getList("ragTag");
if (!elements.contains(ragTag)){
elements.add(ragTag);
}
}

log.info("上传知识库完成 {}", ragTag);
return Response.<String>builder().code("0000").info("调用成功").build();
}

}

功能测试

1. 文件上传测试

请求路径:http://localhost:8080/api/v1/rag/file/upload

  • Headers
    • Content-Type:multipart/form-data
  • Body:
    • ragTag:<标签名>
    • file:<文件>(这里要将参数类型也改成file,然后上传文件),这里我写了个test.txt

显示调用成功!

2. 获取知识库标签测试

请求路径:http://localhost:8080/api/v1/rag/query_rag_tag_list

可以看到我们刚刚上传的"测试"标签!

redis中也可以查看到标签!