淺析如何利用Spring AI構(gòu)建一個(gè)簡(jiǎn)單的問答系統(tǒng)
1. 引言
隨著大語(yǔ)言模型(LLM)技術(shù)的不斷發(fā)展,將AI能力集成到企業(yè)應(yīng)用中變得越來(lái)越重要。Spring AI是Spring生態(tài)系統(tǒng)的最新成員,旨在簡(jiǎn)化AI服務(wù)與Spring應(yīng)用的集成過(guò)程。
本文將詳細(xì)介紹如何利用Spring AI構(gòu)建一個(gè)簡(jiǎn)單的問答系統(tǒng),幫助開發(fā)者快速入門AI應(yīng)用開發(fā)。
2. 環(huán)境準(zhǔn)備
2.1 項(xiàng)目依賴
首先,創(chuàng)建一個(gè)Spring Boot項(xiàng)目,并添加必要的依賴
<?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"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>spring-ai-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <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> <repository> <name>Central Portal Snapshots</name> <id>central-portal-snapshots</id> <url>https://central.sonatype.com/repository/maven-snapshots/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-core</artifactId> <version>1.0.0-M6</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0-M6</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.25</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>21</source> <target>21</target> <encoding>utf-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.2.0</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
2.2 配置API密鑰
在application.yml
中配置API密鑰
server: port: 5555 spring: ai: openai: api-key: sk-xxxxx # 需要替換為上圖所示的硅基流動(dòng)API密鑰 base-url: https://api.siliconflow.cn/ embedding: options: model: BAAI/bge-m3 chat: options: model: deepseek-ai/DeepSeek-V3
注意:為了安全起見,建議通過(guò)環(huán)境變量注入API密鑰,而不是直接硬編碼在配置文件中。
3. 核心代碼實(shí)現(xiàn)
3.1 主應(yīng)用類
創(chuàng)建Spring Boot應(yīng)用的入口類:
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.vectorstore.SimpleVectorStore; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import java.util.List; @SpringBootApplication public class QaApplication { public static void main(String[] args) { SpringApplication.run(QaApplication.class, args); } @Bean public ChatClient chatClient(OpenAiChatModel model){ return ChatClient .builder(model) .build(); } @Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { VectorStore vectorStore = SimpleVectorStore.builder(embeddingModel).build(); // 構(gòu)建測(cè)試數(shù)據(jù) List<Document> documents = List.of(new Document("Hello Spring AI"), new Document("Hello Spring Boot")); // 添加到向量數(shù)據(jù)庫(kù) vectorStore.add(documents); return vectorStore; } }
3.2 請(qǐng)求模型
創(chuàng)建一個(gè)簡(jiǎn)單的模型類來(lái)封裝問題請(qǐng)求:
import lombok.Data; @Data public class QuestionRequest { private String question; private String sessionId; }
3.3 問答服務(wù)
實(shí)現(xiàn)問答核心服務(wù):
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; @Service public class QaService { private final ChatClient chatClient; private final PromptTemplate promptTemplate; @Autowired public QaService(ChatClient chatClient) { this.chatClient = chatClient; // 創(chuàng)建一個(gè)提示模板,指導(dǎo)AI如何回答問題 this.promptTemplate = new PromptTemplate(""" 你是一個(gè)智能問答助手,請(qǐng)簡(jiǎn)潔、準(zhǔn)確地回答用戶的問題。 如果你不知道答案,請(qǐng)直接說(shuō)不知道,不要編造信息。 用戶問題: {question} 回答: """); } public String getAnswer(String question) { // 準(zhǔn)備模板參數(shù) Map<String, Object> parameters = new HashMap<>(); parameters.put("question", question); // 創(chuàng)建提示 Prompt prompt = promptTemplate.create(parameters); // 調(diào)用AI獲取回答 return chatClient.prompt(prompt).call().content(); } }
3.4 REST控制器
創(chuàng)建REST API接口,處理問題請(qǐng)求:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/qa") public class QaController { private final QaService qaService; private final ConversationService conversationService; private final KnowledgeBaseQaService knowledgeBaseQaService; @Autowired public QaController(QaService qaService, ConversationService conversationService, KnowledgeBaseQaService knowledgeBaseQaService ) { this.qaService = qaService; this.conversationService = conversationService; this.knowledgeBaseQaService = knowledgeBaseQaService; } @PostMapping("/ask") public Map<String, String> askQuestion(@RequestBody QuestionRequest request) { String answer = qaService.getAnswer(request.getQuestion()); Map<String, String> response = new HashMap<>(); response.put("question", request.getQuestion()); response.put("answer", answer); return response; } @PostMapping("/ask-session") public Map<String, String> askSession(@RequestBody QuestionRequest request) { String answer = conversationService.chat(request.getSessionId(),request.getQuestion()); Map<String, String> response = new HashMap<>(); response.put("question", request.getQuestion()); response.put("answer", answer); return response; } @PostMapping("/ask-knowledge") public Map<String, String> askKnowledge(@RequestBody QuestionRequest request) { String answer = knowledgeBaseQaService.getAnswerWithKnowledgeBase(request.getQuestion()); Map<String, String> response = new HashMap<>(); response.put("question", request.getQuestion()); response.put("answer", answer); return response; } }
3.5 簡(jiǎn)單HTML前端
在src/main/resources/static
目錄下創(chuàng)建一個(gè)簡(jiǎn)單的HTML頁(yè)面(qa.html):
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>AI問答系統(tǒng)</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; } .container { border: 1px solid #ddd; border-radius: 5px; padding: 20px; margin-top: 20px; } .question-form { margin-bottom: 20px; } #question { width: 100%; padding: 10px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px; } button { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #45a049; } .answer { margin-top: 20px; padding: 15px; background-color: #f9f9f9; border-radius: 4px; white-space: pre-wrap; } .loading { color: #888; font-style: italic; display: none; } </style> </head> <body> <h1>AI問答系統(tǒng)</h1> <div class="container"> <div class="question-form"> <h2>請(qǐng)輸入您的問題</h2> <textarea id="question" rows="4" placeholder="例如:什么是Spring AI?"></textarea> <button id="ask-button">提問</button> <p class="loading" id="loading">AI正在思考中,請(qǐng)稍候...</p> </div> <div class="answer" id="answer-container" style="display:none;"> <h2>回答</h2> <div id="answer-text"></div> </div> </div> <script> document.getElementById('ask-button').addEventListener('click', async function() { const question = document.getElementById('question').value.trim(); if (!question) { alert('請(qǐng)輸入問題'); return; } // 顯示加載狀態(tài) document.getElementById('loading').style.display = 'block'; document.getElementById('answer-container').style.display = 'none'; try { // 普通模式 /api/qa/ask // 會(huì)話模式 /api/qa/ask-session // 知識(shí)庫(kù)模式 /api/qa/ask-knowledge const response = await fetch('/api/qa/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question: question, sessionId: '12345' }) }); if (!response.ok) { throw new Error('服務(wù)器錯(cuò)誤'); } const data = await response.json(); // 顯示回答 document.getElementById('answer-text').textContent = data.answer; document.getElementById('answer-container').style.display = 'block'; } catch (error) { console.error('Error:', error); document.getElementById('answer-text').textContent = '發(fā)生錯(cuò)誤: ' + error.message; document.getElementById('answer-container').style.display = 'block'; } finally { // 隱藏加載狀態(tài) document.getElementById('loading').style.display = 'none'; } }); </script> </body> </html>
4. 運(yùn)行與測(cè)試
完成上述代碼后,運(yùn)行Spring Boot應(yīng)用:
mvn spring-boot:run
或者使用IDE直接運(yùn)行QaApplication
類。
啟動(dòng)后,訪問http://localhost:5555/qa.html
,即可使用問答系統(tǒng)。在文本框中輸入問題,點(diǎn)擊"提問"按鈕后,系統(tǒng)會(huì)將問題發(fā)送給AI,并展示回答結(jié)果。
5. 功能擴(kuò)展
這個(gè)基礎(chǔ)的問答系統(tǒng)可以通過(guò)以下方式進(jìn)行擴(kuò)展
5.1 添加對(duì)話歷史
改進(jìn)服務(wù),支持多輪對(duì)話
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Service public class ConversationService { private final ChatClient chatClient; // TODO 此處僅為簡(jiǎn)單模擬,實(shí)際應(yīng)為數(shù)據(jù)庫(kù)或其他存儲(chǔ)方式 private final Map<String, List<Message>> conversations = new ConcurrentHashMap<>(); @Autowired public ConversationService(ChatClient chatClient) { this.chatClient = chatClient; } public String chat(String sessionId, String userMessage) { // 獲取或創(chuàng)建會(huì)話歷史 List<Message> messages = conversations.computeIfAbsent(sessionId, k -> new ArrayList<>()); // 添加用戶消息 messages.add(new UserMessage(userMessage)); // 創(chuàng)建帶有歷史上下文的提示 Prompt prompt = new Prompt(messages); // 調(diào)用AI String response = chatClient.prompt(prompt).call().content(); // 保存AI回復(fù) messages.add(new AssistantMessage(response)); // 管理會(huì)話長(zhǎng)度,避免超出Token限制 if (messages.size() > 10) { messages = messages.subList(messages.size() - 10, messages.size()); conversations.put(sessionId, messages); } return response; } public void clearConversation(String sessionId) { conversations.remove(sessionId); } }
5.2 添加知識(shí)庫(kù)集成
使用向量存儲(chǔ)和檢索增強(qiáng)生成(RAG)
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.Embedding; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; import java.util.Map; @Service public class KnowledgeBaseQaService { private final ChatClient chatClient; private final VectorStore vectorStore; @Autowired public KnowledgeBaseQaService( ChatClient chatClient, VectorStore vectorStore) { this.chatClient = chatClient; this.vectorStore = vectorStore; } public String getAnswerWithKnowledgeBase(String question) { // 在知識(shí)庫(kù)中搜索相關(guān)文檔 List<Document> relevantDocs = vectorStore.similaritySearch(question); // 構(gòu)建上下文 StringBuilder context = new StringBuilder(); for (Document doc : relevantDocs) { context.append(doc.getText()).append("\n\n"); } // 創(chuàng)建提示模板 PromptTemplate promptTemplate = new PromptTemplate(""" 你是一個(gè)智能問答助手。請(qǐng)根據(jù)以下提供的信息回答用戶問題。 如果無(wú)法從提供的信息中找到答案,請(qǐng)基于你的知識(shí)謹(jǐn)慎回答,并明確指出這是你的一般性了解。 參考信息: {context} 用戶問題: {question} 回答: """); // 準(zhǔn)備參數(shù) Map<String, Object> parameters = new HashMap<>(); parameters.put("context", context.toString()); parameters.put("question", question); // 創(chuàng)建提示并調(diào)用AI Prompt prompt = promptTemplate.create(parameters); return chatClient.prompt(prompt).call().content(); } }
6. 總結(jié)
本文詳細(xì)介紹了如何使用Spring AI創(chuàng)建一個(gè)簡(jiǎn)單的問答系統(tǒng)。通過(guò)Spring AI提供的抽象層,我們能夠輕松地集成大語(yǔ)言模型,無(wú)需深入了解底層API細(xì)節(jié)。這種方式可以讓開發(fā)者專注于業(yè)務(wù)邏輯,同時(shí)保持了Spring生態(tài)系統(tǒng)的一致性。。
到此這篇關(guān)于淺析如何利用Spring AI構(gòu)建一個(gè)簡(jiǎn)單的問答系統(tǒng)的文章就介紹到這了,更多相關(guān)Spring AI構(gòu)建問答系統(tǒng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java?http請(qǐng)求獲取圖片并返回文件流給前端的方法步驟
作為一名Java后端開發(fā)者,掌握如何從后端返回文件流至前端是基本技能之一,這篇文章主要介紹了java?http請(qǐng)求獲取圖片并返回文件流給前端的方法步驟,需要的朋友可以參考下2024-09-09Java控制臺(tái)版五子棋的簡(jiǎn)單實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Java控制臺(tái)版五子棋的簡(jiǎn)單實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Java實(shí)現(xiàn)手機(jī)號(hào)碼歸屬地查詢
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)手機(jī)號(hào)碼歸屬地查詢功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12Java生成隨機(jī)數(shù)之Random與ThreadLocalRandom性能比較詳解
大家項(xiàng)目中如果有生成隨機(jī)數(shù)的需求,我想大多都會(huì)選擇使用Random來(lái)實(shí)現(xiàn),它內(nèi)部使用了CAS來(lái)實(shí)現(xiàn)。?實(shí)際上,JDK1.7之后,提供了另外一個(gè)生成隨機(jī)數(shù)的類ThreadLocalRandom,那么他們二者之間的性能是怎么樣的呢?本文就來(lái)詳細(xì)說(shuō)說(shuō)2022-12-12Springboot注解@Value讀取配置文件參數(shù)詳解
Spring Boot提供了靈活的配置文件讀取機(jī)制,主要有兩種方式,第一種是使用@Value注解直接在類屬性上讀取application.yml文件中的配置,這種方式簡(jiǎn)單直接,但需要為每個(gè)配置項(xiàng)單獨(dú)設(shè)置屬性,第二種方式是通過(guò)@PropertySource注解讀取自定義的Properties文件2024-11-11重新對(duì)Java的類加載器的學(xué)習(xí)方式
這篇文章主要介紹了重新對(duì)Java的類加載器的學(xué)習(xí)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05Javaweb項(xiàng)目啟動(dòng)Tomcat常見的報(bào)錯(cuò)解決方案
Java Web項(xiàng)目啟動(dòng)Tomcat時(shí)可能會(huì)遇到各種錯(cuò)誤,本文就來(lái)介紹一下Javaweb項(xiàng)目啟動(dòng)Tomcat常見的報(bào)錯(cuò)解決方案,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02