diff --git a/README.md b/README.md index 5f2aca5ccd711691cb996cf70e305583a512643e..893248faa51051ecd835ef20e2015d1a3619af58 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ * SpringBoot2->SpringBoot3 ## 增加内容 -* 集成Ollama+Deepseek +* 集成Langchain+Ollama+Deepseek +* 集成redis-stack向量数据库 ## 平台简介 -一直想升级若依框架到JDK17,使用SpringBoot3框架去集成Ollama+DeepSeek,希望能有更多的小伙伴参与到开源项目来交流分享,共同为国产软件出一份力! +一直想升级若依框架到JDK17,使用SpringBoot3框架去集成DeepSeek,希望能有更多的小伙伴参与到开源项目来交流分享,共同为国产软件出一份力! ## 内置功能 @@ -42,7 +43,6 @@ ## 演示图 - @@ -50,14 +50,12 @@
微信图片 20250206221105
- - ## RuoYi-SpringBoot3(Deepseek)交流群 -![输入图片说明](sql/IMG_6235.jpeg) +b31ccebd5c8418efb3d7e425e5e4212 + ## 最后 作者咖啡瘾爆棚哈哈,你们的支持是作者持续更新的动力,谢谢各位大哥! - diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index b2c218ea66cadc8eecc27b7e9c7cad56a219fe3b..65390b7f37481b3f9a071056febb78cfae0a4067 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -73,7 +73,7 @@ spring: # redis 配置 redis: # 地址 - host: localhost + host: 192.168.48.129 # 端口,默认为6379 port: 6379 # 数据库索引 diff --git a/ruoyi-ai/pom.xml b/ruoyi-ai/pom.xml index ef11f0ae99d9da8ff144026e1bfa20399e9c9818..5a7fd6dcc87312670d348d3340aa3de61795932b 100644 --- a/ruoyi-ai/pom.xml +++ b/ruoyi-ai/pom.xml @@ -55,10 +55,15 @@ 0.35.0 - com.google.protobuf - protobuf-java - 3.19.4 + dev.langchain4j + langchain4j-redis + 0.35.0 + + + com.ruoyi + ruoyi-framework + diff --git a/ruoyi-ai/src/main/java/com/ruoyi/ai/config/BaseConfig.java b/ruoyi-ai/src/main/java/com/ruoyi/ai/config/BaseConfig.java index 35cf24c88c4c671dd70f44a4406542bcb72771ee..3f7e0c182da86d0f9622b677c832bcbe91542633 100644 --- a/ruoyi-ai/src/main/java/com/ruoyi/ai/config/BaseConfig.java +++ b/ruoyi-ai/src/main/java/com/ruoyi/ai/config/BaseConfig.java @@ -13,9 +13,12 @@ import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.ollama.OllamaChatModel; import dev.langchain4j.model.ollama.OllamaEmbeddingModel; import dev.langchain4j.model.ollama.OllamaStreamingChatModel; +import dev.langchain4j.rag.content.retriever.ContentRetriever; +import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; import dev.langchain4j.service.AiServices; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore; +import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore; import io.qdrant.client.QdrantClient; import io.qdrant.client.QdrantGrpcClient; import org.springframework.beans.factory.annotation.Autowired; @@ -86,10 +89,10 @@ public class BaseConfig { @Bean public EmbeddingStore embeddingStore() { - return QdrantEmbeddingStore.builder() - .host("192.168.48.128") - .port(6334) - .collectionName("测试数据库") + return RedisEmbeddingStore.builder() + .host("192.168.48.129") + .port(6379) + .dimension(1536) //维度,需要与计算结果保持⼀致。如果使⽤其他的模型,可能会有不同的结果。 .build(); } } diff --git a/ruoyi-ai/src/main/java/com/ruoyi/ai/controller/LangchainController.java b/ruoyi-ai/src/main/java/com/ruoyi/ai/controller/LangchainController.java index 3462cadd6b36e46a2adade88b354e6886ec374c9..9cdd5fd902108c5bbf61293792069e2f03c16c77 100644 --- a/ruoyi-ai/src/main/java/com/ruoyi/ai/controller/LangchainController.java +++ b/ruoyi-ai/src/main/java/com/ruoyi/ai/controller/LangchainController.java @@ -1,12 +1,23 @@ package com.ruoyi.ai.controller; -import com.ruoyi.ai.service.ChatAssistant; + import com.ruoyi.ai.service.impl.LangchainServiceImpl; +import com.ruoyi.ai.utils.RuoyiDocumentSplitter; + import com.ruoyi.common.core.domain.AjaxResult; +import dev.langchain4j.data.document.Document; +import dev.langchain4j.data.document.DocumentParser; +import dev.langchain4j.data.document.DocumentSplitter; +import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; +import dev.langchain4j.data.document.parser.TextDocumentParser; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.ollama.OllamaEmbeddingModel; +import dev.langchain4j.rag.content.Content; +import dev.langchain4j.rag.content.retriever.ContentRetriever; +import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever; +import dev.langchain4j.rag.query.Query; import dev.langchain4j.store.embedding.EmbeddingStore; import io.qdrant.client.QdrantClient; import io.qdrant.client.grpc.Collections; @@ -17,7 +28,12 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; +import java.util.List; @RestController @RequestMapping("/langchain") @@ -36,6 +52,7 @@ public class LangchainController { private EmbeddingStore embeddingStore; + /** * 普通消息 */ @@ -70,7 +87,7 @@ public class LangchainController { .setDistance(Collections.Distance.Cosine) .setSize(1024) .build(); - qdrantClient.createCollectionAsync(collectionName, vectorParams); + qdrantClient.createCollectionAsync(collectionName, vectorParams); return AjaxResult.success(); } @@ -86,4 +103,52 @@ public class LangchainController { return AjaxResult.success(); } + + /** + * Redis向量库 + */ + @GetMapping(value = "/withTextStorageByRedis") + public AjaxResult withTextStorageByRedis(@RequestParam("content") String content){ + TextSegment textSegment = TextSegment.from(content); + textSegment.metadata().put("author", "欧阳日峰"); + Embedding embedding = ollamaEmbeddingModel.embed(textSegment).content(); + embeddingStore.add(embedding, textSegment); + return AjaxResult.success(); + } + + /** + * Redis文本向量化 + */ + @GetMapping(value = "/withDocumentStorageByRedis") + public AjaxResult withDocumentStorageByRedis(){ + Path documentPath = null; + try { + documentPath = Paths.get(LangchainController.class.getClassLoader().getResource("Java题目.txt").toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + DocumentParser documentParser = new TextDocumentParser(); + Document document = FileSystemDocumentLoader.loadDocument(documentPath,documentParser); + DocumentSplitter splitter = new RuoyiDocumentSplitter(); + List segments = splitter.split(document); + List embeddings = ollamaEmbeddingModel.embedAll(segments).content(); + embeddingStore.addAll(embeddings, segments); + return AjaxResult.success(); + } + + /** + * Redis文本检索 + */ + @GetMapping(value = "/retrieveInformation") + public AjaxResult retrieveInformation(@RequestParam("question") String question){ + ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder() + .embeddingStore(embeddingStore) //向量存储模型 + .embeddingModel(ollamaEmbeddingModel) //向量模型 + .maxResults(5) // 最相似的5个结果 + .minScore(0.8) // 只找相似度在0.8以上的内容 + .build(); + Query query = new Query(question); + List contentList = contentRetriever.retrieve(query); + return AjaxResult.success(contentList); + } } diff --git a/ruoyi-ai/src/main/java/com/ruoyi/ai/controller/OllamaController.java b/ruoyi-ai/src/main/java/com/ruoyi/ai/controller/OllamaController.java index 1a9450a67bd8d2fd4130d5301f756e396f210c5e..e4e24ada264ebf17d6a6bf42a60b9d911c6ce162 100644 --- a/ruoyi-ai/src/main/java/com/ruoyi/ai/controller/OllamaController.java +++ b/ruoyi-ai/src/main/java/com/ruoyi/ai/controller/OllamaController.java @@ -3,9 +3,6 @@ package com.ruoyi.ai.controller; import com.ruoyi.ai.service.impl.OllamaServiceImpl; import com.ruoyi.common.core.domain.AjaxResult; 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.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; diff --git a/ruoyi-ai/src/main/java/com/ruoyi/ai/utils/RuoyiDocumentSplitter.java b/ruoyi-ai/src/main/java/com/ruoyi/ai/utils/RuoyiDocumentSplitter.java new file mode 100644 index 0000000000000000000000000000000000000000..b914972488551fa01d593b9513afac7de9459ad5 --- /dev/null +++ b/ruoyi-ai/src/main/java/com/ruoyi/ai/utils/RuoyiDocumentSplitter.java @@ -0,0 +1,26 @@ +package com.ruoyi.ai.utils; + + +import dev.langchain4j.data.document.Document; +import dev.langchain4j.data.document.DocumentSplitter; +import dev.langchain4j.data.segment.TextSegment; + +import java.util.ArrayList; +import java.util.List; + +/** + * Author: 欧阳日峰 + * Description:切分文件工具 + **/ +public class RuoyiDocumentSplitter implements DocumentSplitter { + public static final String SPLIT_EXP="\\s*\\R\\s*\\R\\s*"; + @Override + public List split(Document document) { + List segments = new ArrayList<>(); + String[] parts = document.text().split(SPLIT_EXP); + for (String part : parts) { + segments.add(TextSegment.from(part)); + } + return segments; + } +} diff --git "a/ruoyi-ai/src/main/resources/Java\351\242\230\347\233\256.txt" "b/ruoyi-ai/src/main/resources/Java\351\242\230\347\233\256.txt" new file mode 100644 index 0000000000000000000000000000000000000000..d6f543cb37928dd498e24de812aa158e0cb72f6b --- /dev/null +++ "b/ruoyi-ai/src/main/resources/Java\351\242\230\347\233\256.txt" @@ -0,0 +1,77 @@ +Q: CMS分为哪几个阶段? +CMS已经弃用。生活美好,时间有限,不建议再深入研究了。如果碰到问题,直接祭出回收过程即可。 +1、 初始标记 +2、 并发标记 +3、 并发预清理 +4、 并发可取消的预清理 +5、 重新标记 +6、 并发清理 +由于《深入理解java虚拟机》一书的流行,面试时省略3、4步一般也是没问题的。 + +Q: Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别? +sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第66题中的线程状态转换图)。wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。 +补充:可能不少人对什么是进程,什么是线程还比较模糊,对于为什么需要多线程编程也不是特别理解。简单的说:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位;线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。线程的划分尺度小于进程,这使得多线程程序的并发性高;进程在执行时通常拥有独立的内存单元,而线程之间可以共享内存。使用多线程的编程通常能够带来更好的性能和用户体验,但是多线程的程序对于其他程序是不友好的,因为它可能占用了更多的CPU资源。当然,也不是线程越多,程序的性能就越好,因为线程之间的调度和切换也会浪费CPU时间。时下很时髦的Node.js就采用了单线程异步I/O的工作模式。 + +Q: 请解释如何配置Tomcat来使用IIS和NTLM ? +必须遵循isapi_redirector.dll的标准指令 +配置IIS使用“集成windows验证” +确保在服务器.xml中您已经禁用了tomcat身份验证 + +Q: Java中的继承是单继承还是多继承 +Java中既有单继承,又有多继承。对于java类来说只能有一个父类,对于接口来说可以同时继承多个接口 + +Q: 事务的使用场景在什么地方? +但一个业务逻辑包括多个数据库操作的时候,而且需要保证每个数据表操作都执行的成功进行下一个操作,这个时候可以使用事务 + +Q: 字节流与字符流的区别 +1、 以字节为单位输入输出数据,字节流按照8位传输 +2、 以字符为单位输入输出数据,字符流按照16位传输 + +Q: Java 堆的结构是什么样子的?什么是堆中的永久代(Perm Gen space) +JVM 的堆是运行时数据区,所有类的实例和数组都是在堆上分配内存。它在 JVM 启动的时候被创建。对象所占的堆内存是由自动内存管理系统也就是垃圾收集器回收。 +堆内存是由存活和死亡的对象组成的。存活的对象是应用可以访问的,不会被垃圾回收。死亡的对象是应用不可访问尚且还没有被垃圾收集器回收掉的对象。一直到垃圾收集器把这些 对象回收掉之前,他们会一直占据堆内存空间。 + +Q: Spring中自动装配的方式有哪些? +1、 no:不进行自动装配,手动设置Bean的依赖关系。 +2、 byName:根据Bean的名字进行自动装配。 +3、 byType:根据Bean的类型进行自动装配。 +4、 constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。 +5、 autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配。 + +Q: 栈帧里面包含哪些东西? +局部变量表、操作数栈、动态连接、返回地址等 + +Q: 你是如何调用 wait() 方法的?使用 if 块还是循环?为什么? +处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。 +wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码: +synchronized (monitor) { // 判断条件谓词是否得到满足 while(!locked) { // 等待唤醒 monitor.wait(); } // 处理其他的业务逻辑 } + +Q: ArrayList与LinkedList有什么区别? +1、 ArrayList与LinkedList都实现了List接口。 +2、 ArrayList是线性表,底层是使用数组实现的,它在尾端插入和访问数据时效率较高, +3、 Linked是双向链表,他在中间插入或者头部插入时效率较高,在访问数据时效率较低 + +Q: Super与this表示什么? +1、 Super表示当前类的父类对象 +2、 This表示当前类的对象 + +Q: 简述Java的对象结构 +Java对象由三个部分组成:对象头、实例数据、对齐填充。 +对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。 +实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的) +对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐 ) + +Q: Java 虚拟机栈的作用? +Java 虚拟机栈来描述 Java 方法的内存模型。每当有新线程创建时就会分配一个栈空间,线程结束后栈空间被回收,栈与线程拥有相同的生命周期。栈中元素用于支持虚拟机进行方法调用,每个方法在执行时都会创建一个栈帧存储方法的局部变量表、操作栈、动态链接和方法出口等信息。每个方法从调用到执行完成,就是栈帧从入栈到出栈的过程。 +有两类异常:① 线程请求的栈深度大于虚拟机允许的深度抛出 StackOverflowError。② 如果 JVM 栈容量可以动态扩展,栈扩展无法申请足够内存抛出 OutOfMemoryError(HotSpot 不可动态扩展,不存在此问题)。 + +Q: 实际开发中应用场景哪里用到了模板方法 +其实很多框架中都有用到了模板方法模式 +例如: +数据库访问的封装、Junit单元测试、servlet中关于doGet/doPost方法的调用等等 + +Q: import java和javax有什么区别 +tech.souyunku.com/EasonJim/p/… + +Q: 构造器(constructor)是否可被重写(override)? +构造器不能被继承,因此不能被重写,但可以被重载。 \ No newline at end of file
6ca95e2c724da1d77543c9f2a18b9d6