From 50879bb28a51ae8c21cd00d273d106431e1d2c8f Mon Sep 17 00:00:00 2001
From: 陈昶聿 <chychen@nbjetron.com>
Date: 星期一, 22 六月 2026 16:55:44 +0800
Subject: [PATCH] 【市一】大模型
---
smartor/src/main/java/com/smartor/common/QwenLLMUtil.java | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 221 insertions(+), 0 deletions(-)
diff --git a/smartor/src/main/java/com/smartor/common/QwenLLMUtil.java b/smartor/src/main/java/com/smartor/common/QwenLLMUtil.java
new file mode 100644
index 0000000..1421f5a
--- /dev/null
+++ b/smartor/src/main/java/com/smartor/common/QwenLLMUtil.java
@@ -0,0 +1,221 @@
+package com.smartor.common;
+
+import com.alibaba.fastjson2.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import com.ruoyi.common.utils.http.HttpUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 閫氫箟鍗冮棶锛圦wen锛夊ぇ妯″瀷宸ュ叿绫汇��
+ * <p>
+ * 鍩轰簬闃块噷浜戠櫨鐐� DashScope 鐨� OpenAI 鍏煎鎺ュ彛锛坽@code /compatible-mode/v1/chat/completions}锛夛紝
+ * 涓昏鐢ㄤ簬璇箟鍖归厤锛氭妸璇煶璇嗗埆寰楀埌鐨勮嚜鐢辨枃鏈紝褰掍竴鍖栧埌涓�缁勯璁鹃�夐」涓渶鎺ヨ繎鐨勪竴涓��
+ * <p>
+ * 閰嶇疆椤癸紙application.yml锛夛細
+ * <pre>
+ * qwen:
+ * api-key: sk-xxxxxxxx # 鐧剧偧 API Key锛屽繀濉�
+ * model: qwen-plus # 妯″瀷鍚嶇О锛岄粯璁� qwen-plus
+ * url: https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
+ * </pre>
+ */
+@Slf4j
+@Component
+public class QwenLLMUtil {
+
+ /** 鐧剧偧 API Key */
+ @Value("${qwen.api-key:}")
+ private static String apiKey = "sk-712da9346f0940ff909b40dce17579b1";
+
+ /** 妯″瀷鍚嶇О */
+ @Value("${qwen.model:qwen-plus}")
+ private static String model = "qwen-plus";
+
+ /** 鎺ュ彛鍦板潃锛圤penAI 鍏煎妯″紡锛� */
+ @Value("${qwen.url:https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions}")
+ private static String url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
+
+ /**
+ * 鍒ゆ柇璇煶鏂囨湰鏈�鎺ヨ繎鍝釜閫夐」銆�
+ *
+ * @param voiceText 璇煶璇嗗埆寰楀埌鐨勬枃鏈�
+ * @param options 鍊欓�夐�夐」鍒楄〃
+ * @return 鍛戒腑鐨勯�夐」鍘熸枃锛涙棤娉曞尮閰嶄换涓�閫夐」鏃惰繑鍥� {@code null}
+ */
+ public String matchOption(String voiceText, List<String> options) {
+ int index = matchOptionIndex(voiceText, options);
+ return index < 0 ? null : options.get(index);
+ }
+
+ /**
+ * 鍒ゆ柇璇煶鏂囨湰鏈�鎺ヨ繎鍝釜閫夐」锛岃繑鍥為�夐」鍦ㄥ垪琛ㄤ腑鐨勪笅鏍囥��
+ *
+ * @param voiceText 璇煶璇嗗埆寰楀埌鐨勬枃鏈�
+ * @param options 鍊欓�夐�夐」鍒楄〃
+ * @return 鍛戒腑閫夐」鐨勪笅鏍囷紙浠� 0 寮�濮嬶級锛涙棤娉曞尮閰嶄换涓�閫夐」鏃惰繑鍥� {@code -1}
+ */
+ public int matchOptionIndex(String voiceText, List<String> options) {
+ if (StringUtils.isBlank(voiceText) || options == null || options.isEmpty()) {
+ return -1;
+ }
+ // 鍙湁涓�涓�夐」鏃舵棤闇�璋冪敤妯″瀷
+ if (options.size() == 1) {
+ return 0;
+ }
+
+ StringBuilder optionText = new StringBuilder();
+ for (int i = 0; i < options.size(); i++) {
+ optionText.append(i + 1).append(". ").append(options.get(i)).append('\n');
+ }
+
+ String systemPrompt = "浣犳槸涓�涓涔夊尮閰嶅姪鎵嬨�傜敤鎴蜂細缁欏嚭涓�娈佃闊宠瘑鍒枃鏈拰鑻ュ共涓甫缂栧彿鐨勯�夐」锛�"
+ + "璇峰垽鏂繖娈垫枃鏈湪璇箟涓婃渶鎺ヨ繎鍝竴涓�夐」銆傚彧鍏佽浠庣粰瀹氶�夐」涓�夋嫨锛�"
+ + "涓嶈鍋氫换浣曡В閲娿�傜洿鎺ヨ緭鍑烘渶鍖归厤閫夐」鐨勭紪鍙锋暟瀛楋紱鑻ユ病鏈変换浣曢�夐」涓庢枃鏈浉鍏筹紝鍒欒緭鍑� 0銆�";
+ String userPrompt = "璇煶鏂囨湰锛�" + voiceText + "\n\n閫夐」锛歕n" + optionText
+ + "\n璇峰彧杈撳嚭涓�涓暟瀛楋紙鏈�鍖归厤閫夐」鐨勭紪鍙凤紝娌℃湁鍖归厤鍒欒緭鍑� 0锛夈��";
+
+ String content = chat(systemPrompt, userPrompt);
+ if (StringUtils.isBlank(content)) {
+ return -1;
+ }
+
+ Integer number = extractFirstNumber(content);
+ if (number == null || number <= 0 || number > options.size()) {
+ log.warn("Qwen 閫夐」鍖归厤鏈懡涓紝voiceText={}, options={}, modelReturn={}", voiceText, options, content);
+ return -1;
+ }
+ return number - 1;
+ }
+
+ /**
+ * 鍒ゆ柇璇煶鏂囨湰鏄惁绗﹀悎杩欎釜鎰忔��
+ * @param questionText
+ * @param voiceText 璇煶璇嗗埆寰楀埌鐨勬枃鏈�
+ * @return 鍛戒腑鐨勯�夐」鍘熸枃锛涙棤娉曞尮閰嶄换涓�閫夐」鏃惰繑鍥� {@code null}
+ */
+ public static int matchRegex(String questionText, String voiceText, String value, String regexText) {
+ if (StringUtils.isBlank(voiceText) || regexText == null || regexText.isEmpty()) {
+ return -1;
+ }
+
+ String systemPrompt = "浣犳槸涓�涓笓涓氱殑璇煶璇嗗埆鏂囨湰璇箟鍖归厤鍔╂墜銆備綘鐨勪换鍔℃槸鍒ゆ柇鐢ㄦ埛鐨勮闊虫枃鏈槸鍚﹀湪璇箟涓婄鍚堢粰瀹氱殑<姝e垯鍖归厤瑙勫垯>鎴�<瀵瑰簲鎸囨爣鍊�>銆�"
+ + "鐢变簬姝e垯琛ㄨ揪寮忔棤娉曡鐩栨墍鏈夎嚜鐒惰瑷�鐨勫悓涔夎〃杈撅紝浣犻渶瑕佷綔涓鸿涔夊厹搴曟満鍒讹紝鍒ゆ柇璇煶鏂囨湰鏄惁琛ㄨ揪浜嗕笌姝e垯瑙勫垯鎴栨寚鏍囧�肩浉鍚屾垨鐩歌繎鐨勬牳蹇冩剰鍥俱��"
+ + "銆愭牳蹇冭鍒欍��"
+ + "1. 濡傛灉璇煶鏂囨湰鍦ㄥ瓧闈笂鍖归厤浜嗘鍒欙紝鎴栬�呭湪璇箟涓婅〃杈句簡姝e垯/鎸囨爣鍊肩殑鎰忔�濓紝璇疯緭鍑猴細1銆�"
+ + "2. 濡傛灉璇煶鏂囨湰涓庢鍒�/鎸囨爣鍊肩殑鎰忔�濆畬鍏ㄦ棤鍏炽�佹剰鎬濈浉鍙嶆垨鏃犳硶鎺ㄦ柇鍑虹浉鍏虫剰鍥撅紝璇疯緭鍑猴細0銆�"
+ + "3. 缁濆绂佹杈撳嚭浠讳綍瑙i噴銆佹爣鐐圭鍙枫�佹崲琛岀鎴栧叾浠栨棤鍏冲瓧绗︺�備綘鐨勬渶缁堝洖澶嶅彧鑳芥槸涓�涓暟瀛楋紙1 鎴� 0锛夈��"
+ ;
+ String userPrompt = "璇锋牴鎹互涓嬩俊鎭繘琛岃涔夊尮閰嶅垽鏂細\n" +
+ "- 闂鏂囨湰锛�" + questionText + "\n\n"
+ + "- 璇煶璇嗗埆鏂囨湰锛�" + voiceText + "\n\n"
+ + "- 姝e垯鍖归厤鏂囨湰锛歕n" + regexText + "\n\n"
+ + "- 瀵瑰簲鎸囨爣鍊硷細\n" + value
+ + "\n璇峰垽鏂繖娈佃闊虫枃鏈槸鍚︽帴杩戞鍒欏尮閰嶈鍒欐垨鑰呭搴旀寚鏍囧�肩殑鎰忔�濄�傝嫢鏈夌浉鍏虫剰鎬濄�佽兘鍖归厤寰椾笂锛岀洿鎺ヨ緭鍑� 1锛涜嫢涓庢枃鏈剰鎬濆畬鍏ㄤ笉鐩稿叧锛屽垯杈撳嚭 0銆�";
+
+ String content = chat(systemPrompt, userPrompt);
+ if (StringUtils.isBlank(content)) {
+ return -1;
+ }
+
+ Integer number = extractFirstNumber(content);
+ if (number == null || number <= 0) {
+ log.warn("Qwen 閫夐」鍖归厤鏈懡涓紝voiceText={}, regexText={}, modelReturn={}", voiceText, regexText, content);
+ return -1;
+ }
+ return number - 1;
+ }
+
+ /**
+ * 閫氱敤瀵硅瘽璋冪敤锛岃繑鍥炴ā鍨嬪洖澶嶇殑鏂囨湰鍐呭銆�
+ *
+ * @param systemPrompt 绯荤粺鎻愮ず璇嶏紝鍙负绌�
+ * @param userPrompt 鐢ㄦ埛鎻愮ず璇�
+ * @return 妯″瀷鍥炲姝f枃锛涜皟鐢ㄥけ璐ヨ繑鍥� {@code null}
+ */
+ public static String chat(String systemPrompt, String userPrompt) {
+ if (StringUtils.isBlank(apiKey)) {
+ throw new IllegalStateException("閫氫箟鍗冮棶 API Key 鏈厤缃紙qwen.api-key锛�");
+ }
+ if (StringUtils.isBlank(userPrompt)) {
+ throw new IllegalArgumentException("userPrompt 涓嶈兘涓虹┖");
+ }
+
+ JSONArray messages = new JSONArray();
+ if (StringUtils.isNotBlank(systemPrompt)) {
+ messages.add(message("system", systemPrompt));
+ }
+ messages.add(message("user", userPrompt));
+
+ JSONObject body = new JSONObject();
+ body.put("model", model);
+ body.put("messages", messages);
+ // 鍖归厤鍦烘櫙闇�瑕佺ǔ瀹氱粨鏋滐紝娓╁害璋冧綆
+ body.put("temperature", 0.01);
+
+ Map<String, String> headers = new HashMap<>();
+ headers.put("Authorization", "Bearer " + apiKey);
+ headers.put("Content-Type", "application/json");
+
+ String response = HttpUtils.sendPostByHeader(url, body.toJSONString(), headers);
+ if (StringUtils.isBlank(response)) {
+ log.error("閫氫箟鍗冮棶杩斿洖涓虹┖锛寀rl={}, body={}", url, body.toJSONString());
+ return null;
+ }
+
+ try {
+ JSONObject json = JSONObject.parseObject(response);
+ JSONArray choices = json.getJSONArray("choices");
+ if (choices == null || choices.isEmpty()) {
+ log.error("閫氫箟鍗冮棶鍝嶅簲鏃� choices锛宺esponse={}", response);
+ return null;
+ }
+ JSONObject msg = choices.getJSONObject(0).getJSONObject("message");
+ return msg == null ? null : StringUtils.trim(msg.getString("content"));
+ } catch (Exception e) {
+ log.error("瑙f瀽閫氫箟鍗冮棶鍝嶅簲澶辫触锛宺esponse={}", response, e);
+ return null;
+ }
+ }
+
+ private static JSONObject message(String role, String content) {
+ JSONObject msg = new JSONObject();
+ msg.put("role", role);
+ msg.put("content", content);
+ return msg;
+ }
+
+ /**
+ * 浠庢ā鍨嬪洖澶嶄腑鎻愬彇绗竴涓暣鏁般�傛ā鍨嬪伓灏斾細鍥炲 鈥滈�夐」2鈥� 鈥�2銆傗�� 涔嬬被锛屽仛涓�娆″厹搴曡В鏋愩��
+ */
+ private static Integer extractFirstNumber(String text) {
+ List<Character> digits = new ArrayList<>();
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ if (c >= '0' && c <= '9') {
+ digits.add(c);
+ } else if (!digits.isEmpty()) {
+ break;
+ }
+ }
+ if (digits.isEmpty()) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (char c : digits) {
+ sb.append(c);
+ }
+ try {
+ return Integer.parseInt(sb.toString());
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+}
--
Gitblit v1.9.3