ftp wordpress 搬站,新闻源软文发布平台,长沙搜索排名优化公司,吃什么补肾吗Spring AI之Java经典面试题智能小助手 前言一、准备面试题二、搭建工程三、文件读取与解析四、Markdown文件解析五、问题搜索六、自定义EmbeddingClient七、定义请求Controller 前言
通过Ollama在本地部署了Llama3大模型#xff0c;这篇来基于Llama3和Spring AI#xff0c;以… Spring AI之Java经典面试题智能小助手 前言一、准备面试题二、搭建工程三、文件读取与解析四、Markdown文件解析五、问题搜索六、自定义EmbeddingClient七、定义请求Controller 前言
通过Ollama在本地部署了Llama3大模型这篇来基于Llama3和Spring AI以及ChatGPT Web来实现一个Java经典面试题智能小助手。 私有化部署大模型最佳解决方案 Ollama
一、准备面试题
建议优先使用marddown或txt等文本格式因为经过尝试如果导出为PDF格式会出现格式错乱。 ThreadLocal和InheritableThreadLocal的区别 ThreadLocal和InheritableThreadLocal都可以用通过线程来共享数据区别在于当前线程在InheritableThreadLocal中设置的值可以被子线程继承并且是复制也就是子线程和父线程一开始InheritableThreadLocal中的值时一致的但是后续的修改互不影响而当前线程在ThreadLocal中设置的值不会被子线程所继承。 如何理解Java中的装箱与拆箱 装箱就是int类型包装为Integer类型拆箱就是反过来因为Java中支持8种基本数据类型每种基本类型都有对应的包装类型装箱会调用valueOf()方法传入基本类型返回包装类型这个方法中通常会有一个缓存比如用来缓存数字1对应的Integer对象拆箱会调用intValue()方法返回基本类型不要过多的进行装箱和拆箱毕竟是在调方法是消耗性能的。 Java中为什么要有基础类型 Java是面向对象的一切都是对象但是像字符、数字这些常用类型每次用的时候也去new对象就会比较费性能和内存了所以Java设计了8种基础类型在使用基础类型时对应的内存空间是直接分配在栈上的而不是分配在堆上这样性能也更好。 说说进程和线程的区别 一个操作系统上会运行很多个程序这些程序都有自己的代码,以及都要用内存来存代码和代码运行过程中产生的数据进程就是用来隔离各个程序的内存空间的使得程序之间互不干扰还是这多个程序为了让它们能同时运行CPU就需要先执行这个程序的几条指令然后切换到另外一个程序去执行然后再切回来就像同时在运行多条指令流水线而这个流水线就是线程是CPU调度的最小单位 为什么Java不支持多继承 首先思考这么一种场景假如现在A类继承了B类和C类并且B类和C类中都存在test()方法那么当A类对象调用test()方法时该调用B类的test()呢还是C类的test()呢是没有答案的所以Java中不允许多继承。 String、StringBuffer、StringBuilder的区别 String是不可变的如果尝试去修改会新生成一个字符串对象StringBuffer和StringBuilder是可变的StringBuffer是线程安全的StringBuilder是线程不安全的所以在单线程环境下StringBuilder效率会更高 二、搭建工程
引入SpringBoot
parentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.2.1/version
/parent引入Spring AI
dependencyManagementdependenciesdependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-bom/artifactIdversion0.8.1-SNAPSHOT/versiontypepom/typescopeimport/scope/dependency/dependencies
/dependencyManagement引入spring web
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId
/dependency引入Ollama
dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-ollama-spring-boot-starter/artifactId
/dependency引入Markdown解析器
dependencygroupIdcom.vladsch.flexmark/groupIdartifactIdflexmark/artifactIdversion0.42.14/version
/dependency引入Redis向量数据库相关
dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-redis/artifactId
/dependencydependencygroupIdredis.clients/groupIdartifactIdjedis/artifactIdversion5.1.0/version
/dependency指定仓库
repositoriesrepositoryidspring-milestones/idnameSpring Milestones/nameurlhttps://repo.spring.io/milestone/urlsnapshotsenabledfalse/enabled/snapshots/repositoryrepositoryidspring-snapshots/idnameSpring Snapshots/nameurlhttps://repo.spring.io/snapshot/urlreleasesenabledfalse/enabled/releases/repository
/repositories三、文件读取与解析
新建InterviewService提供向量存储、向量搜索功能
Bean
public RedisVectorStore vectorStore(EmbeddingClient embeddingClient) {RedisVectorStore.RedisVectorStoreConfig config RedisVectorStore.RedisVectorStoreConfig.builder().withURI(redis://localhost:6379).withIndexName(interview-assistant-index).withMetadataFields(RedisVectorStore.MetadataField.text(filename)).build();return new RedisVectorStore(config, embeddingClient);
}package com.qjc.demo.service;import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
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.core.io.Resource;
import org.springframework.stereotype.Component;import java.util.List;/**** projectName spring-ollama-demo* packageName com.qjc.demo.service* author qjc* description TODO* Email qjc1024aliyun.com* date 2024-10-18 10:23**/Component
public class InterviewService {Value(classpath:Java基础面试题.md)private Resource resource;Autowiredprivate VectorStore vectorStore;public ListDocument loadText() {// 读取文件内容TextReader textReader new TextReader(resource);ListDocument documents textReader.get();// 解析文件内容MarkdownSplitter textSplitter new MarkdownSplitter();ListDocument list textSplitter.apply(documents);// 将问题提取出来存入Metadatalist.forEach(document - {String title document.getContent().split(title)[0];String replace title.replace(##, );document.getMetadata().put(question, replace.trim());});// 向量化以及向量存储vectorStore.add(list);return list;}public ListDocument search(String message){// ...}
}
四、Markdown文件解析
思路是通过解析文件中的二级标题和标题下的内容得到一个Document标题和内容直接用title分割。
package com.com.qjc.demo.utils;import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.collection.iteration.ReversiblePeekingIterator;
import org.springframework.ai.transformer.splitter.TextSplitter;import java.util.ArrayList;
import java.util.List;
/**** projectName spring-ollama-demo* packageName com.qjc.demo.utils* author qjc* description TODO* Email qjc1024aliyun.com* date 2024-10-18 10:23**/
public class MarkdownSplitter extends TextSplitter {Overrideprotected ListString splitText(String text) {Parser parser Parser.builder().build();Document markdownDocument parser.parse(text);ListString result new ArrayList();ReversiblePeekingIteratorNode iterator markdownDocument.getChildren().iterator();StringBuilder builder new StringBuilder();while (iterator.hasNext()) {Node node iterator.next();// 如果是二级标题if (node instanceof Heading ((Heading) node).getLevel() 2) {if (!builder.isEmpty()) {result.add(builder.toString());}builder.delete(0, builder.length());builder.append(node.getChars());builder.append(title);} else {builder.append(node.getChars());}}if (!builder.isEmpty()) {result.add(builder.toString());}return result;}
}
五、问题搜索
public ListDocument search(String question){// 先查元数据SearchRequest metaSearchRequest SearchRequest.query(question).withTopK(3).withSimilarityThreshold(0.9).withFilterExpression(String.format(question in [%s], question));ListDocument metaDocuments vectorStore.similaritySearch(metaSearchRequest);if (!CollectionUtils.isEmpty(metaDocuments)) {return metaDocuments;}// 元数据没查到在相似搜索SearchRequest searchRequest SearchRequest.query(question).withTopK(3).withSimilarityThreshold(0.9);return vectorStore.similaritySearch(searchRequest);}六、自定义EmbeddingClient
默认情况下是对问题和答案同时进行向量化如果只想对问题进行向量化则需要自定义EmbeddingClient
package com.qjc.demo.config;import org.springframework.ai.document.Document;
import org.springframework.ai.ollama.OllamaEmbeddingClient;
import org.springframework.ai.ollama.api.OllamaApi;import java.util.List;/**** projectName spring-ollama-demo* packageName com.qjc.demo.config* author qjc* description TODO* Email qjc1024aliyun.com* date 2024-10-18 10:23**/
public class QjcOllamaEmbeddingClient extends OllamaEmbeddingClient {public QjcOllamaEmbeddingClient (OllamaApi ollamaApi) {super(ollamaApi);}Overridepublic ListDouble embed(Document document) // 单独对问题进行向量化String question (String) document.getMetadata().get(question);return this.embed(question);}
}
Beanpublic QjcOllamaEmbeddingClient ollamaEmbeddingClient(OllamaApi ollamaApi, OllamaEmbeddingProperties properties) {QjcOllamaEmbeddingClient qjcOllamaEmbeddingClient new QjcOllamaEmbeddingClient (ollamaApi);qjcOllamaEmbeddingClient.withModel(nomic-embed-text:v1.5);return qjcOllamaEmbeddingClient;}七、定义请求Controller
package com.qjc.demo.controller;import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.StreamingChatClient;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.document.Document;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import reactor.core.publisher.Flux;import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/**** projectName spring-ollama-demo* packageName com.qjc.demo.controller* author qjc* description TODO* Email qjc1024aliyun.com* date 2024-10-18 10:23**/
RestController
public class ChatController {Autowiredprivate StreamingChatClient chatClient;Autowiredprivate InterviewService interviewService;GetMapping(/document)public ListDocument document() {return interviewService.loadText();}GetMapping(/documentSearch)public ListDocument documentSearch(RequestParam String message) {return interviewService.search(message);}PostMapping(value /v1/chat/completions, produces MediaType.TEXT_EVENT_STREAM_VALUE)public FluxOpenAiApi.ChatCompletionChunk interview(RequestBody OpenAiApi.ChatCompletionRequest request) {String question request.messages().get(1).content();// 向量搜索ListDocument documentList interviewService.search(question);// 提示词模板PromptTemplate promptTemplate new PromptTemplate({userMessage}\n\n 用中文并根据以下信息回答问题:\n {contents});// 组装提示词Prompt prompt promptTemplate.create(Map.of(userMessage, question, contents, documentList));// 调用大模型FluxChatResponse stream chatClient.stream(prompt);return stream.map(chatResponse - {String content chatResponse.getResult().getOutput().getContent();// 需要优化OpenAiApi.ChatCompletionChunk chatCompletionChunk new OpenAiApi.ChatCompletionChunk(1,List.of(new OpenAiApi.ChatCompletionChunk.ChunkChoice(OpenAiApi.ChatCompletionFinishReason.STOP,1,new OpenAiApi.ChatCompletionMessage(content,OpenAiApi.ChatCompletionMessage.Role.ASSISTANT), new OpenAiApi.LogProbs(null))),null, null, null, null);return chatCompletionChunk;});}
}