【原】无脑操作:IDEA + maven + SpringAI + 讯飞星火大模型实现简单智能对话
1、实现效果

2、设置pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <parent> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-parent</artifactId> 8 <version>3.4.4</version> 9 <relativePath/> <!-- lookup parent from repository --> 10 </parent> 11 <groupId>cn.temptation</groupId> 12 <artifactId>studyAI</artifactId> 13 <version>0.0.1-SNAPSHOT</version> 14 <name>studyAI</name> 15 <description>studyAI</description> 16 <url/> 17 18 <properties> 19 <java.version>17</java.version> 20 <spring-ai.version>1.0.3</spring-ai.version> 21 </properties> 22 23 <dependencyManagement> 24 <dependencies> 25 <dependency> 26 <groupId>org.springframework.ai</groupId> 27 <artifactId>spring-ai-bom</artifactId> 28 <version>1.0.3</version> 29 <type>pom</type> 30 <scope>import</scope> 31 </dependency> 32 </dependencies> 33 </dependencyManagement> 34 35 <dependencies> 36 <!-- Lombok --> 37 <dependency> 38 <groupId>org.projectlombok</groupId> 39 <artifactId>lombok</artifactId> 40 <version>1.18.22</version> 41 </dependency> 42 <!-- web --> 43 <dependency> 44 <groupId>org.springframework.boot</groupId> 45 <artifactId>spring-boot-starter-web</artifactId> 46 </dependency> 47 <!-- thymeleaf --> 48 <dependency> 49 <groupId>org.springframework.boot</groupId> 50 <artifactId>spring-boot-starter-thymeleaf</artifactId> 51 </dependency> 52 <!-- spark4j --> 53 <dependency> 54 <groupId>io.github.briqt</groupId> 55 <artifactId>xunfei-spark4j</artifactId> 56 <version>1.3.0</version> 57 </dependency> 58 <!-- 热部署 --> 59 <dependency> 60 <groupId>org.springframework.boot</groupId> 61 <artifactId>spring-boot-devtools</artifactId> 62 <optional>true</optional> 63 </dependency> 64 </dependencies> 65 66 <build> 67 <plugins> 68 <plugin> 69 <groupId>org.springframework.boot</groupId> 70 <artifactId>spring-boot-maven-plugin</artifactId> 71 </plugin> 72 </plugins> 73 </build> 74 75 </project>
3、设置application.yaml


1 spring: 2 application: 3 name: iflytek-ai 4 thymeleaf: 5 cache: false 6 7 xunfei: 8 client: 9 appId: 从讯飞开放平台上找到自己的应用的APPID 10 apiSecret: 从讯飞开放平台上找到自己的应用的APISecret 11 apiKey: 从讯飞开放平台上找到自己的应用的APIKey 12 13 server: 14 port: 80
4、整个程序结构如下图所示:

5、编写SparkConfig.java配置类
1 package cn.temptation.config; 2 3 import io.github.briqt.spark4j.SparkClient; 4 import lombok.Data; 5 import org.springframework.boot.context.properties.ConfigurationProperties; 6 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Configuration; 8 9 @Configuration 10 @ConfigurationProperties(prefix = "xunfei.client") 11 @Data 12 public class SparkConfig { 13 private String appid; 14 private String apiSecret; 15 private String apiKey; 16 17 @Bean 18 public SparkClient sparkClient() { 19 SparkClient sparkClient = new SparkClient(); 20 sparkClient.appid = this.appid; 21 sparkClient.apiSecret = this.apiSecret; 22 sparkClient.apiKey = this.apiKey; 23 24 return sparkClient; 25 } 26 }
6、使用xunfei-spark4j时,想使用免费的Spark Lite时,有一个坑,因为SparkApiVersion枚举的结构如下:
1 public enum SparkApiVersion { 2 V1_5("v1.1", "https://spark-api.xf-yun.com/v1.1/chat", "general"), 3 V2_0("v2.1", "https://spark-api.xf-yun.com/v2.1/chat", "generalv2"), 4 V3_0("v3.1", "https://spark-api.xf-yun.com/v3.1/chat", "generalv3"), 5 V3_5("v3.5", "https://spark-api.xf-yun.com/v3.5/chat", "generalv3.5"), 6 V4_0("v4.0", "https://spark-api.xf-yun.com/v4.0/chat", "4.0Ultra"); 7 8 private final String version; 9 private final String url; 10 private final String domain; 11 12 private SparkApiVersion(String version, String url, String domain) { 13 this.version = version; 14 this.url = url; 15 this.domain = domain; 16 } 17 18 public String getVersion() { 19 return this.version; 20 } 21 22 public String getUrl() { 23 return this.url; 24 } 25 26 public String getDomain() { 27 return this.domain; 28 } 29 }
这里找不到免费的Spark Lite,所以需要自己修改。编写EnumReflectionUtil.java反射工具类。
1 public class EnumReflectionUtil { 2 public static void setEnumField(Enum<?> enumConstant, String fieldName, Object newValue) throws Exception { 3 Field field = enumConstant.getClass().getDeclaredField(fieldName); 4 field.setAccessible(true); 5 field.set(enumConstant, newValue); 6 } 7 }
7、编写聊天处理类ChatWithController.java
1 @Controller 2 public class ChatWithController { 3 // 初始化客户端 4 @Resource 5 private SparkClient sparkClient; 6 7 // 通过反射类,在使用大模型时,指定使用免费的Spark Lite大模型 8 @PostConstruct 9 public void init() throws Exception { 10 // 修改 V1_5 的版本信息 11 EnumReflectionUtil.setEnumField(SparkApiVersion.V1_5, "version", "v1.1"); 12 EnumReflectionUtil.setEnumField(SparkApiVersion.V1_5, "url", "https://spark-api.xf-yun.com/v1.1/chat"); 13 EnumReflectionUtil.setEnumField(SparkApiVersion.V1_5, "domain", "lite"); 14 } 15 16 // AI预设System角色的条件 17 public static final String PRECONDITION = "你是 iflytek"; 18 19 // 跳转前端页面 20 @RequestMapping("/") 21 public String index() { 22 return "index"; 23 } 24 25 // 和讯飞星火大模型Spark Lite对话 26 @RequestMapping(value = "/chat", produces = "application/json") 27 public ResponseEntity<?> sendHttpToSpark(@RequestBody Map<String, String> map) { 28 // 消息列表 29 List<SparkMessage> messages = new ArrayList<>(); 30 // 设置System角色,则使用下句 31 // messages.add(SparkMessage.systemContent(PRECONDITION)); 32 // 获取前端输入的对话内容,设置User角色 33 messages.add(SparkMessage.userContent(map.get("message"))); 34 35 // 构造请求 36 SparkRequest sparkRequest = SparkRequest.builder() 37 .messages(messages) 38 .apiVersion(SparkApiVersion.V1_5) 39 .build(); 40 41 SparkSyncChatResponse chatResponse = sparkClient.chatSync(sparkRequest); 42 43 String responseContent = chatResponse.getContent(); 44 45 Map<String, Object> response = new HashMap<>(); 46 response.put("response", responseContent); 47 48 return ResponseEntity.ok(response); 49 } 50 }
8、编写前端交互页面index.html
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>AI 智能助手</title> 7 <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> 8 <style> 9 :root { 10 --primary-color: #4a90e2; 11 --secondary-color: #f5f5f5; 12 --success-color: #34d399; 13 --error-color: #ef4444; 14 } 15 16 * { 17 box-sizing: border-box; 18 margin: 0; 19 padding: 0; 20 } 21 22 body { 23 font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; 24 background: #f0f2f5; 25 min-height: 100vh; 26 } 27 28 .container { 29 max-width: 1200px; 30 margin: 0 auto; 31 padding: 20px; 32 } 33 34 .chat-container { 35 background: white; 36 border-radius: 12px; 37 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 38 height: 80vh; 39 display: flex; 40 flex-direction: column; 41 } 42 43 #chat-box { 44 flex: 1; 45 overflow-y: auto; 46 padding: 20px; 47 scroll-behavior: smooth; 48 } 49 50 .message { 51 display: flex; 52 gap: 12px; 53 margin-bottom: 16px; 54 } 55 56 .message.user { 57 flex-direction: row-reverse; 58 } 59 60 .avatar { 61 width: 40px; 62 height: 40px; 63 border-radius: 50%; 64 background: var(--secondary-color); 65 display: flex; 66 align-items: center; 67 justify-content: center; 68 flex-shrink: 0; 69 } 70 71 .message-content { 72 max-width: 70%; 73 padding: 12px 16px; 74 border-radius: 12px; 75 position: relative; 76 } 77 78 .user .message-content { 79 background: var(--primary-color); 80 color: white; 81 border-radius: 12px 12px 0 12px; 82 } 83 84 .bot .message-content { 85 background: var(--secondary-color); 86 border-radius: 12px 12px 12px 0; 87 } 88 89 .input-area { 90 border-top: 1px solid #eee; 91 padding: 16px; 92 display: flex; 93 gap: 12px; 94 background: white; 95 } 96 97 #user-input { 98 flex: 1; 99 padding: 12px; 100 border: 1px solid #e2e8f0; 101 border-radius: 8px; 102 font-size: 16px; 103 transition: all 0.3s ease; 104 } 105 106 #user-input:focus { 107 outline: none; 108 border-color: var(--primary-color); 109 box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2); 110 } 111 112 button { 113 padding: 12px 24px; 114 background: var(--primary-color); 115 color: white; 116 border: none; 117 border-radius: 8px; 118 cursor: pointer; 119 transition: all 0.2s ease; 120 display: flex; 121 align-items: center; 122 gap: 8px; 123 } 124 125 button:hover { 126 background: #357abd; 127 transform: translateY(-1px); 128 } 129 130 button:disabled { 131 background: #94a3b8; 132 cursor: not-allowed; 133 } 134 135 @media (max-width: 768px) { 136 .container { 137 padding: 10px; 138 } 139 140 .message-content { 141 max-width: 85%; 142 } 143 } 144 </style> 145 </head> 146 <body> 147 <div class="container"> 148 <h1 style="margin-bottom: 20px; color: #1e293b;">AI 智能助手 <i class="fas fa-robot"></i></h1> 149 150 <div class="chat-container"> 151 <div id="chat-box"> 152 <div class="message bot"> 153 <div class="avatar"><i class="fas fa-robot"></i></div> 154 <div class="message-content">您好!我是智能助手,可以回答各种问题...</div> 155 </div> 156 </div> 157 158 <div class="input-area"> 159 <input type="text" id="user-input" placeholder="输入消息..."> 160 <button onclick="sendMessage()" id="send-btn"> 161 <i class="fas fa-paper-plane"></i> 发送 162 </button> 163 </div> 164 </div> 165 </div> 166 167 <script> 168 let isSending = false 169 170 // 消息处理 171 async function sendMessage() { 172 if (isSending) return 173 174 const input = document.getElementById('user-input') 175 const message = input.value.trim() 176 if (!message) return 177 178 isSending = true 179 input.disabled = true 180 document.getElementById('send-btn').disabled = true 181 182 try { 183 appendMessage(message, 'user') 184 input.value = '' 185 186 const response = await fetch('/chat', { 187 method: 'POST', 188 headers: {'Content-Type': 'application/json'}, 189 body: JSON.stringify({message}) 190 }) 191 192 const data = await response.json() 193 if (response.ok) { 194 appendMessage(data.response, 'bot') 195 } else { 196 appendMessage(`错误:${data.error}`, 'bot') 197 } 198 } catch (error) { 199 appendMessage('网络请求失败', 'bot') 200 } finally { 201 isSending = false 202 input.disabled = false 203 document.getElementById('send-btn').disabled = false 204 input.focus() 205 } 206 } 207 208 function appendMessage(message, sender) { 209 const chatBox = document.getElementById('chat-box') 210 const isUser = sender === 'user' 211 212 const messageEl = document.createElement('div') 213 messageEl.className = `message ${isUser ? 'user' : 'bot'}` 214 messageEl.innerHTML = ` 215 <div class="avatar"> 216 ${isUser ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>'} 217 </div> 218 <div class="message-content">${message}</div> 219 ` 220 221 chatBox.appendChild(messageEl) 222 chatBox.scrollTop = chatBox.scrollHeight 223 } 224 225 // 回车发送 226 document.getElementById('user-input').addEventListener('keypress', (e) => { 227 if (e.key === 'Enter' && !e.shiftKey) { 228 e.preventDefault() 229 sendMessage() 230 } 231 }) 232 </script> 233 </body> 234 </html>
本文编写过程中参考了网络上一些大佬的经验,在此一并感谢。
抛砖引玉,期待更多人研究讯飞星火大模型...

浙公网安备 33010602011771号