SpringBoot集成Redis向量數據庫實現相似性搜索功能
1.什么是Redis向量數據庫?
Redis 是一個開源(BSD 許可)的內存數據結構存儲,用作數據庫、緩存、消息代理和流式處理引擎。Redis 提供數據結構,例如字符串、哈希、列表、集合、帶范圍查詢的有序集合、位圖、超對數日志、地理空間索引和流。
Redis 搜索和查詢 擴展了 Redis OSS 的核心功能,并允許您將 Redis 用作向量數據庫
- 在哈希或 JSON 文檔中存儲向量和關聯(lián)的元數據
- 檢索向量
- 執(zhí)行向量搜索
2.向量檢索(Vector Search)的核心原理
向量檢索(Vector Search)的核心原理是通過將文本或數據表示為高維向量,并在查詢時根據向量的相似度進行搜索。在你的代碼中,向量檢索過程涉及以下幾步:
匹配的原理:
- 檢索的核心是將文本或數據轉換成向量,在高維向量空間中查找與查詢最相似的向量。
- 在存儲數據時將指定的字段通過嵌入模型生成了向量。
- 在檢索時,查詢文本被向量化,然后與 Redis 中存儲的向量進行相似度比較,找到相似度最高的向量(即相關的文檔)。
關鍵點:
- 嵌入模型 將文本轉換成向量。
- 相似度計算 通過余弦相似度或歐幾里得距離來度量相似性。
- Top K 返回相似度最高的 K 個文檔。
具體過程
1. 向量化數據:
當你將 JSON 中的字段存入 Redis 時,向量化工具(例如 vectorStore
)會將指定的字段轉換為高維向量。每個字段的內容會通過某種嵌入模型(如 Word2Vec、BERT、OpenAI Embeddings 等)轉換成向量表示。每個向量表示的是該字段內容的語義特征。
2. 搜索時的向量生成:
當執(zhí)行 SearchRequest.query(message)
時,系統(tǒng)會將輸入的 message
轉換為一個查詢向量。這一步是通過同樣的嵌入模型,將查詢文本轉換為與存儲在 Redis 中相同維度的向量。
3. 相似度匹配:
vectorStore.similaritySearch(request)
函數使用了一個向量相似度計算方法來查找最相似的向量。這通常是通過 余弦相似度 或 歐幾里得距離 來度量查詢向量和存儲向量之間的距離。然后返回與查詢最相似的前 K
個文檔,即 withTopK(topK)
所指定的 K
個最相關的結果。
4. 返回匹配的文檔:
匹配的結果是根據相似度得分排序的 List<Document>
。這些文檔是你最初存儲在 Redis 中的記錄,包含了 JSON 中指定的字段。
3.環(huán)境搭建
version: '3' services: redis-stack: image: redis/redis-stack ports: - 6379:6379 redis-insight: image: redislabs/redisinsight:latest ports: - 5540:5540
Run following command:
docker-compose up -d
訪問 http://localhost:5540
4.代碼工程
實驗目標
實現文件數據向量化到redis,并進行相似性搜索
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.1</version> <relativePath /> <!-- lookup parent from repository --> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>RedisVectorStore</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <spring-ai.version>0.8.1</spring-ai.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-transformers-spring-boot-starter</artifactId> <version>${spring-ai.version}</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-redis-spring-boot-starter</artifactId> <version>${spring-ai.version}</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>5.1.0</version> </dependency> </dependencies> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </pluginRepository> </pluginRepositories> </project>
controller
package com.et.controller; import com.et.service.SearchService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.et.service.SearchService; import java.util.HashMap; import java.util.Map; @RestController public class HelloWorldController { @Autowired SearchService searchService; @RequestMapping("/hello") public Map<String, Object> showHelloWorld(){ Map<String, Object> map = new HashMap<>(); map.put("msg", searchService.retrieve("beer")); return map; } }
configuration
加載文件數據到并將數據向量化到redis
JsonReader loader = new JsonReader(file, KEYS);
JsonReader
和 VectorStore
實現是將 KEYS
中指定的多個字段拼接在一起,生成一個統(tǒng)一的文本表示,然后通過嵌入模型將這些字段的組合文本轉換為一個單一的向量,那么這里就是將多個字段組合成 一個綜合向量。并將其處理后存入 Redis。
package com.et.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; import org.springframework.ai.reader.JsonReader; import org.springframework.ai.vectorstore.RedisVectorStore; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import java.util.Map; import java.util.zip.GZIPInputStream; @Component public class DataLoader implements ApplicationRunner { private static final Logger logger = LoggerFactory.getLogger(DataLoader.class); private static final String[] KEYS = { "name", "abv", "ibu", "description" }; @Value("classpath:/data/beers.json.gz") private Resource data; private final RedisVectorStore vectorStore; private final RedisVectorStoreProperties properties; public DataLoader(RedisVectorStore vectorStore, RedisVectorStoreProperties properties) { this.vectorStore = vectorStore; this.properties = properties; } @Override public void run(ApplicationArguments args) throws Exception { Map<String, Object> indexInfo = vectorStore.getJedis().ftInfo(properties.getIndex()); Long sss= (Long) indexInfo.getOrDefault("num_docs", "0"); int numDocs=sss.intValue(); if (numDocs > 20000) { logger.info("Embeddings already loaded. Skipping"); return; } Resource file = data; if (data.getFilename().endsWith(".gz")) { GZIPInputStream inputStream = new GZIPInputStream(data.getInputStream()); file = new InputStreamResource(inputStream, "beers.json.gz"); } logger.info("Creating Embeddings..."); // tag::loader[] // Create a JSON reader with fields relevant to our use case JsonReader loader = new JsonReader(file, KEYS); // Use the autowired VectorStore to insert the documents into Redis vectorStore.add(loader.get()); // end::loader[] logger.info("Embeddings created."); } }
配置redis vectorStore
package com.et.config; import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties; import org.springframework.ai.chat.ChatClient; import org.springframework.ai.document.MetadataMode; import org.springframework.ai.transformers.TransformersEmbeddingClient; import org.springframework.ai.vectorstore.RedisVectorStore; import org.springframework.ai.vectorstore.RedisVectorStore.RedisVectorStoreConfig; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RedisConfiguration { @Bean TransformersEmbeddingClient transformersEmbeddingClient() { return new TransformersEmbeddingClient(MetadataMode.EMBED); } @Bean VectorStore vectorStore(TransformersEmbeddingClient embeddingClient, RedisVectorStoreProperties properties) { var config = RedisVectorStoreConfig.builder().withURI(properties.getUri()).withIndexName(properties.getIndex()) .withPrefix(properties.getPrefix()).build(); RedisVectorStore vectorStore = new RedisVectorStore(config, embeddingClient); vectorStore.afterPropertiesSet(); return vectorStore; } }
service
查詢時,查詢文本也會生成一個整體向量,與存儲的綜合向量進行匹配。
package com.et.service; import org.springframework.ai.document.Document; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.List; @Service public class SearchService { @Value("${topk:10}") private int topK; @Autowired private VectorStore vectorStore; public List<Document> retrieve(String message) { SearchRequest request = SearchRequest.query(message).withTopK(topK); // Query Redis for the top K documents most relevant to the input message List<Document> docs = vectorStore.similaritySearch(request); return docs; } }
只是一些關鍵代碼,所有代碼請參見下面代碼倉庫
代碼倉庫
- https://github.com/Harries/springboot-demo(RedisVectorStore)
5.測試
啟動Spring Boot應用程序,查看日志
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.2.1) 2024-09-24T14:03:48.217+08:00 INFO 23996 --- [ main] com.et.DemoApplication : Starting DemoApplication using Java 17.0.9 with PID 23996 (D:\IdeaProjects\ETFramework\RedisVectorStore\target\classes started by Dell in D:\IdeaProjects\ETFramework) 2024-09-24T14:03:48.221+08:00 INFO 23996 --- [ main] com.et.DemoApplication : No active profile set, falling back to 1 default profile: "default" 2024-09-24T14:03:49.186+08:00 INFO 23996 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8088 (http) 2024-09-24T14:03:49.199+08:00 INFO 23996 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2024-09-24T14:03:49.199+08:00 INFO 23996 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.17] 2024-09-24T14:03:49.289+08:00 INFO 23996 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2024-09-24T14:03:49.290+08:00 INFO 23996 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1033 ms 2024-09-24T14:03:49.406+08:00 WARN 23996 --- [ main] ai.djl.util.cuda.CudaUtils : Failed to detect GPU count: CUDA driver version is insufficient for CUDA runtime version (35) 2024-09-24T14:03:49.407+08:00 WARN 23996 --- [ main] ai.djl.util.cuda.CudaUtils : Failed to detect GPU count: CUDA driver version is insufficient for CUDA runtime version (35) 2024-09-24T14:03:49.408+08:00 INFO 23996 --- [ main] ai.djl.util.Platform : Found matching platform from: jar:file:/D:/jar_repository/ai/djl/huggingface/tokenizers/0.26.0/tokenizers-0.26.0.jar!/native/lib/tokenizers.properties 2024-09-24T14:03:49.867+08:00 INFO 23996 --- [ main] o.s.a.t.TransformersEmbeddingClient : Model input names: input_ids, attention_mask, token_type_ids 2024-09-24T14:03:49.867+08:00 INFO 23996 --- [ main] o.s.a.t.TransformersEmbeddingClient : Model output names: last_hidden_state 2024-09-24T14:03:50.346+08:00 INFO 23996 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8088 (http) with context path '' 2024-09-24T14:03:50.354+08:00 INFO 23996 --- [ main] com.et.DemoApplication : Started DemoApplication in 2.522 seconds (process running for 2.933) 2024-09-24T14:03:50.364+08:00 INFO 23996 --- [ main] com.et.config.DataLoader : Creating Embeddings... 2024-09-24T14:03:51.493+08:00 WARN 23996 --- [ main] ai.djl.util.cuda.CudaUtils : Failed to detect GPU count: CUDA driver version is insufficient for CUDA runtime version (35) 2024-09-24T14:03:51.800+08:00 INFO 23996 --- [ main] ai.djl.pytorch.engine.PtEngine : PyTorch graph executor optimizer is enabled, this may impact your inference latency and throughput. See: https://docs.djl.ai/docs/development/inference_performance_optimization.html#graph-executor-optimization 2024-09-24T14:03:51.802+08:00 INFO 23996 --- [ main] ai.djl.pytorch.engine.PtEngine : Number of inter-op threads is 6 2024-09-24T14:03:51.802+08:00 INFO 23996 --- [ main] ai.djl.pytorch.engine.PtEngine : Number of intra-op threads is 6 2024-09-24T14:04:26.212+08:00 INFO 23996 --- [nio-8088-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2024-09-24T14:04:26.213+08:00 INFO 23996 --- [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2024-09-24T14:04:26.215+08:00 INFO 23996 --- [nio-8088-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms 2024-09-24T14:09:48.846+08:00 INFO 23996 --- [ main] com.et.config.DataLoader : Embeddings created.
查看redis是否存在向量化的數據
訪問http://127.0.0.1:8088/hello 進行0 相似度搜索(top 10),返回得分前10的數據
以上就是SpringBoot集成Redis向量數據庫實現相似性搜索功能的詳細內容,更多關于SpringBoot Redis相似性搜索的資料請關注腳本之家其它相關文章!
相關文章
Java使用ByteBuffer進行多文件合并和拆分的代碼實現
因為驗證證書的需要,需要把證書文件和公鑰給到客戶,考慮到多個文件交互的不便性,所以決定將2個文件合并成一個文件交互給客戶,但是由于是加密文件,采用字符串形式合并后,拆分后文件不可用,本文給大家介紹了Java使用ByteBuffer進行多文件合并和拆分,需要的朋友可以參考下2024-09-09Alibaba?Nacos配置中心動態(tài)感知原理示例解析
這篇文章主要介紹了Alibaba?Nacos配置中心動態(tài)感知原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08SpringBoot項目從18.18M瘦身到0.18M的實現
本文主要介紹了SpringBoot項目從18.18M瘦身到0.18M的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-01-01Springboot使用異步請求提高系統(tǒng)的吞吐量詳解
這篇文章主要介紹了Springboot使用異步請求提高系統(tǒng)的吞吐量詳解,和同步請求相對,異步不需要等待響應,隨時可以發(fā)送下一次請求,如果是同步請求,需要將信息填寫完整,再發(fā)送請求,服務器響應填寫是否正確,再做修改,需要的朋友可以參考下2023-08-08