From fb5dcaeb2ab91d0f9ffea26fd15ddcbbe5d36bb9 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 31 七月 2026 17:57:27 +0800
Subject: [PATCH] feat(aftersales): 售后工单新增问题类型和严重程度字段

---
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/SemanticTextSplitter.java |  227 ++++++--------------------------------------------------
 1 files changed, 27 insertions(+), 200 deletions(-)

diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/SemanticTextSplitter.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/SemanticTextSplitter.java
index 4c7112e..401dc64 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/SemanticTextSplitter.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/SemanticTextSplitter.java
@@ -3,7 +3,6 @@
 import cn.hutool.core.util.StrUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.ai.transformer.splitter.TextSplitter;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -13,289 +12,117 @@
 
 /**
  * 璇箟鍖栨枃鏈垏鐗囧櫒
- *
- * <p>鍔熻兘鐗圭偣锛�
- * <ul>
- *   <li>浼樺厛鍦ㄦ钀借竟鐣岋紙鍙屾崲琛岋級澶勫垏鍒�</li>
- *   <li>鍏舵鍦ㄥ彞瀛愯竟鐣岋紙鍙ュ彿銆侀棶鍙枫�佹劅鍙瑰彿锛夊鍒囧垎</li>
- *   <li>閬垮厤鍦ㄥ彞瀛愪腑闂存埅鏂紝淇濇寔璇箟瀹屾暣鎬�</li>
- *   <li>鏀寔涓嫳鏂囨爣鐐圭鍙疯瘑鍒�</li>
- * </ul>
- *
- * @author runzhen
+ * 浼樺厛鍦ㄦ钀借竟鐣屽鍒囧垎锛屽叾娆″湪鍙ュ瓙杈圭晫澶勫垏鍒嗭紝淇濇寔璇箟瀹屾暣鎬�
  */
 @Slf4j
 public class SemanticTextSplitter extends TextSplitter {
 
-    /**
-     * 鍒嗘鐨勬渶澶� Token 鏁�
-     */
     private final int chunkSize;
-
-    /**
-     * 娈佃惤閲嶅彔澶у皬锛堢敤浜庝繚鎸佷笂涓嬫枃杩炶疮鎬э級
-     */
     private final int chunkOverlap;
-
-    /**
-     * 娈佃惤鍒嗛殧绗︼紙鎸変紭鍏堢骇鎺掑簭锛�
-     */
-    private static final List<String> PARAGRAPH_SEPARATORS = Arrays.asList(
-            "\n\n\n",    // 涓変釜鎹㈣
-            "\n\n",      // 鍙屾崲琛�
-            "\n"         // 鍗曟崲琛�
-    );
-
-    /**
-     * 鍙ュ瓙缁撴潫鏍囪锛堜腑鑻辨枃鏍囩偣锛�
-     */
-    private static final Pattern SENTENCE_END_PATTERN = Pattern.compile(
-            "[銆傦紒锛�.!?]+[\\s\"'锛�)銆慭\]]*"
-    );
-
-    /**
-     * Token 浼扮畻鍣�
-     */
+    private static final List<String> PARAGRAPH_SEPARATORS = Arrays.asList("\n\n\n", "\n\n", "\n");
+    private static final Pattern SENTENCE_END_PATTERN = Pattern.compile("[銆傦紒锛�.!?]+[\\s\"'锛�)銆慭\]]*");
     private final MarkdownQaSplitter.TokenEstimator tokenEstimator;
 
     public SemanticTextSplitter(int chunkSize, int chunkOverlap) {
         this.chunkSize = chunkSize;
-        this.chunkOverlap = Math.min(chunkOverlap, chunkSize / 2); // 閲嶅彔涓嶈秴杩囦竴鍗�
+        this.chunkOverlap = Math.min(chunkOverlap, chunkSize / 2);
         this.tokenEstimator = new SimpleTokenEstimator();
     }
 
-    public SemanticTextSplitter(int chunkSize) {
-        this(chunkSize, 50); // 榛樿閲嶅彔 50 涓� Token
-    }
+    public SemanticTextSplitter(int chunkSize) { this(chunkSize, 50); }
 
     @Override
     protected List<String> splitText(String text) {
-        if (StrUtil.isEmpty(text)) {
-            return Collections.emptyList();
-        }
+        if (StrUtil.isEmpty(text)) return Collections.emptyList();
         return splitTextRecursive(text);
     }
 
-    /**
-     * 鍒囧垎鏂囨湰锛堥�掑綊绛栫暐锛�
-     *
-     * @param text 寰呭垏鍒嗘枃鏈�
-     * @return 鍒囧垎鍚庣殑鏂囨湰鍧楀垪琛�
-     */
     private List<String> splitTextRecursive(String text) {
-        List<String> chunks = new ArrayList<>();
-
-        // 濡傛灉鏂囨湰涓嶈秴杩囬檺鍒讹紝鐩存帴杩斿洖
         int textTokens = tokenEstimator.estimate(text);
-        if (textTokens <= chunkSize) {
-            chunks.add(text.trim());
-            return chunks;
-        }
+        if (textTokens <= chunkSize) return Collections.singletonList(text.trim());
 
-        // 灏濊瘯鎸変笉鍚屽垎闅旂鍒囧垎
         List<String> splits = null;
         String usedSeparator = null;
         for (String separator : PARAGRAPH_SEPARATORS) {
-            if (text.contains(separator)) {
-                splits = Arrays.asList(text.split(Pattern.quote(separator)));
-                usedSeparator = separator;
-                break;
-            }
+            if (text.contains(separator)) { splits = Arrays.asList(text.split(Pattern.quote(separator))); usedSeparator = separator; break; }
         }
-
-        // 濡傛灉娌℃湁鎵惧埌娈佃惤鍒嗛殧绗︼紝鎸夊彞瀛愬垏鍒�
-        if (splits == null || splits.size() == 1) {
-            splits = splitBySentences(text);
-            usedSeparator = ""; // 鍙ュ瓙鍒囧垎涓嶉渶瑕佸垎闅旂
-        }
-
-        // 鍚堝苟灏忕墖娈�
-        chunks = mergeSplits(splits, usedSeparator);
-        return chunks;
+        if (splits == null || splits.size() == 1) { splits = splitBySentences(text); usedSeparator = ""; }
+        return mergeSplits(splits, usedSeparator);
     }
 
-    /**
-     * 鎸夊彞瀛愬垏鍒�
-     *
-     * @param text 寰呭垏鍒嗘枃鏈�
-     * @return 鍙ュ瓙鍒楄〃
-     */
     private List<String> splitBySentences(String text) {
-        // 浣跨敤姝e垯琛ㄨ揪寮忓尮閰嶅彞瀛愮粨鏉熶綅缃�
         List<String> sentences = new ArrayList<>();
         int lastEnd = 0;
         Matcher matcher = SENTENCE_END_PATTERN.matcher(text);
         while (matcher.find()) {
             String sentence = text.substring(lastEnd, matcher.end()).trim();
-            if (StrUtil.isNotEmpty(sentence)) {
-                sentences.add(sentence);
-            }
+            if (StrUtil.isNotEmpty(sentence)) sentences.add(sentence);
             lastEnd = matcher.end();
         }
-
-        // 娣诲姞鍓╀綑閮ㄥ垎
-        if (lastEnd < text.length()) {
-            String remaining = text.substring(lastEnd).trim();
-            if (StrUtil.isNotEmpty(remaining)) {
-                sentences.add(remaining);
-            }
-        }
+        if (lastEnd < text.length()) { String remaining = text.substring(lastEnd).trim(); if (StrUtil.isNotEmpty(remaining)) sentences.add(remaining); }
         return sentences.isEmpty() ? Collections.singletonList(text) : sentences;
     }
 
-    /**
-     * 鍚堝苟鍒囧垎鍚庣殑灏忕墖娈�
-     *
-     * @param splits 鍒囧垎鍚庣殑鐗囨鍒楄〃
-     * @param separator 鐗囨闂寸殑鍒嗛殧绗�
-     * @return 鍚堝苟鍚庣殑鏂囨湰鍧楀垪琛�
-     */
     private List<String> mergeSplits(List<String> splits, String separator) {
         List<String> chunks = new ArrayList<>();
         List<String> currentChunks = new ArrayList<>();
         int currentLength = 0;
-
         for (String split : splits) {
-            if (StrUtil.isEmpty(split)) {
-                continue;
-            }
+            if (StrUtil.isEmpty(split)) continue;
             int splitTokens = tokenEstimator.estimate(split);
-            // 濡傛灉鍗曚釜鐗囨灏辫秴杩囬檺鍒讹紝杩涗竴姝ラ�掑綊鍒囧垎
             if (splitTokens > chunkSize) {
-                // 鍏堜繚瀛樺綋鍓嶇疮绉殑鍧�
-                if (!currentChunks.isEmpty()) {
-                    String chunkText = String.join(separator, currentChunks);
-                    chunks.add(chunkText.trim());
-                    currentChunks.clear();
-                    currentLength = 0;
-                }
-                // 閫掑綊鍒囧垎澶х墖娈�
-                if (!separator.isEmpty()) {
-                    // 濡傛灉鏄钀藉垎闅旂锛屽皾璇曟寜鍙ュ瓙鍒囧垎
-                    chunks.addAll(splitTextRecursive(split));
-                } else {
-                    // 濡傛灉宸茬粡鏄彞瀛愮骇鍒紝寮哄埗鎸夊瓧绗﹀垏鍒�
-                    chunks.addAll(forceSplitLongText(split));
-                }
+                if (!currentChunks.isEmpty()) { chunks.add(String.join(separator, currentChunks).trim()); currentChunks.clear(); currentLength = 0; }
+                if (!separator.isEmpty()) { chunks.addAll(splitTextRecursive(split)); } else { chunks.addAll(forceSplitLongText(split)); }
                 continue;
             }
-            // 璁$畻鍔犱笂鍒嗛殧绗︾殑 Token 鏁�
             int separatorTokens = StrUtil.isEmpty(separator) ? 0 : tokenEstimator.estimate(separator);
-            // 濡傛灉鍔犱笂杩欎釜鐗囨浼氳秴杩囬檺鍒�
             if (!currentChunks.isEmpty() && currentLength + splitTokens + separatorTokens > chunkSize) {
-                // 淇濆瓨褰撳墠鍧�
-                String chunkText = String.join(separator, currentChunks);
-                chunks.add(chunkText.trim());
-
-                // 澶勭悊閲嶅彔锛氫繚鐣欐渶鍚庡嚑涓墖娈�
+                chunks.add(String.join(separator, currentChunks).trim());
                 currentChunks = getOverlappingChunks(currentChunks, separator);
                 currentLength = estimateTokens(currentChunks, separator);
             }
-            // 娣诲姞褰撳墠鐗囨
             currentChunks.add(split);
             currentLength += splitTokens + separatorTokens;
         }
-
-        // 娣诲姞鏈�鍚庝竴鍧�
-        if (!currentChunks.isEmpty()) {
-            String chunkText = String.join(separator, currentChunks);
-            chunks.add(chunkText.trim());
-        }
+        if (!currentChunks.isEmpty()) chunks.add(String.join(separator, currentChunks).trim());
         return chunks;
     }
 
-    /**
-     * 鑾峰彇閲嶅彔鐨勭墖娈碉紙鐢ㄤ簬淇濇寔涓婁笅鏂囷級
-     *
-     * @param chunks 褰撳墠鐗囨鍒楄〃
-     * @param separator 鐗囨闂寸殑鍒嗛殧绗�
-     * @return 閲嶅彔鐨勭墖娈靛垪琛�
-     */
     private List<String> getOverlappingChunks(List<String> chunks, String separator) {
-        if (chunkOverlap == 0 || chunks.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        // 浠庡悗寰�鍓嶅彇鐗囨锛岀洿鍒拌揪鍒伴噸鍙犲ぇ灏�
+        if (chunkOverlap == 0 || chunks.isEmpty()) return new ArrayList<>();
         List<String> overlapping = new ArrayList<>();
         int tokens = 0;
         for (int i = chunks.size() - 1; i >= 0; i--) {
             String chunk = chunks.get(i);
             int chunkTokens = tokenEstimator.estimate(chunk);
-            if (tokens + chunkTokens > chunkOverlap) {
-                break;
-            }
-            // 娣诲姞鍒伴噸鍙犲垪琛ㄥ墠绔�
+            if (tokens + chunkTokens > chunkOverlap) break;
             overlapping.add(0, chunk);
             tokens += chunkTokens + (StrUtil.isEmpty(separator) ? 0 : tokenEstimator.estimate(separator));
         }
         return overlapping;
     }
 
-    /**
-     * 浼扮畻鐗囨鍒楄〃鐨勬�� Token 鏁�
-     *
-     * @param chunks 鐗囨鍒楄〃
-     * @param separator 鐗囨闂寸殑鍒嗛殧绗�
-     * @return 鎬� Token 鏁�
-     */
     private int estimateTokens(List<String> chunks, String separator) {
         int total = 0;
-        for (int i = 0; i < chunks.size(); i++) {
-            total += tokenEstimator.estimate(chunks.get(i));
-            if (i < chunks.size() - 1 && StrUtil.isNotEmpty(separator)) {
-                total += tokenEstimator.estimate(separator);
-            }
-        }
+        for (int i = 0; i < chunks.size(); i++) { total += tokenEstimator.estimate(chunks.get(i)); if (i < chunks.size() - 1 && StrUtil.isNotEmpty(separator)) total += tokenEstimator.estimate(separator); }
         return total;
     }
 
-    /**
-     * 寮哄埗鍒囧垎闀挎枃鏈紙褰撹涔夊垏鍒嗗け璐ユ椂锛�
-     *
-     * @param text 寰呭垏鍒嗘枃鏈�
-     * @return 鍒囧垎鍚庣殑鏂囨湰鍧楀垪琛�
-     */
     private List<String> forceSplitLongText(String text) {
         List<String> chunks = new ArrayList<>();
-        int charsPerChunk = (int) (chunkSize * 0.8); // 淇濆畧浼拌
-        for (int i = 0; i < text.length(); i += charsPerChunk) {
-            int end = Math.min(i + charsPerChunk, text.length());
-            String chunk = text.substring(i, end);
-            chunks.add(chunk.trim());
-        }
-        log.warn("鏂囨湰杩囬暱锛屽凡寮哄埗鎸夊瓧绗﹀垏鍒嗭紝鍙兘褰卞搷璇箟瀹屾暣鎬�");
+        int charsPerChunk = (int)(chunkSize * 0.8);
+        for (int i = 0; i < text.length(); i += charsPerChunk) { int end = Math.min(i + charsPerChunk, text.length()); chunks.add(text.substring(i, end).trim()); }
         return chunks;
     }
 
-    /**
-     * 绠�鍗曠殑 Token 浼扮畻鍣ㄥ疄鐜�
-     */
     private static class SimpleTokenEstimator implements MarkdownQaSplitter.TokenEstimator {
-
         @Override
         public int estimate(String text) {
-            if (StrUtil.isEmpty(text)) {
-                return 0;
-            }
-
-            int chineseChars = 0;
-            int englishWords = 0;
-            // 绠�鍗曠粺璁′腑鑻辨枃
-            for (char c : text.toCharArray()) {
-                if (c >= 0x4E00 && c <= 0x9FA5) {
-                    chineseChars++;
-                }
-            }
-            // 鑻辨枃鍗曡瘝浼扮畻
-            String[] words = text.split("\\s+");
-            for (String word : words) {
-                if (word.matches(".*[a-zA-Z].*")) {
-                    englishWords++;
-                }
-            }
-            return chineseChars + (int) (englishWords * 1.3);
+            if (StrUtil.isEmpty(text)) return 0;
+            int chineseChars = 0, englishWords = 0;
+            for (char c : text.toCharArray()) { if (c >= 0x4E00 && c <= 0x9FA5) chineseChars++; }
+            for (String word : text.split("\\s+")) { if (word.matches(".*[a-zA-Z].*")) englishWords++; }
+            return chineseChars + (int)(englishWords * 1.3);
         }
     }
-
 }

--
Gitblit v1.9.3