本节需求
将 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: 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: 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;
@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())
.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;
@Data @ConfigurationProperties(prefix = "redis.sdk.config", ignoreInvalidFields = true) public class RedisClientConfigProperties {
private String host; private int port; private String password; private int poolSize = 64; private int minIdleSize = 10; private int idleTimeout = 10000; private int connectTimeout = 10000; private int retryAttempts = 3; private int retryInterval = 1000; private int pingInterval = 0; 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;
@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;
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;
@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 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) { TikaDocumentReader documentReader = new TikaDocumentReader(file.getResource()); List<Document> documents = documentReader.get(); List<Document> documentSplitterList = tokenTextSplitter.apply(documents);
documents.forEach(doc -> doc.getMetadata().put("knowledge", ragTag)); documentSplitterList.forEach(doc -> doc.getMetadata().put("knowledge", ragTag));
pgVectorStore.accept(documentSplitterList);
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中也可以查看到标签!