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

---
 yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/MarkdownQaSplitter.java |  264 ++++++----------------------------------------------
 1 files changed, 34 insertions(+), 230 deletions(-)

diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/MarkdownQaSplitter.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/MarkdownQaSplitter.java
index 1fbf4f2..6239602 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/MarkdownQaSplitter.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/splitter/MarkdownQaSplitter.java
@@ -5,7 +5,6 @@
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.ai.transformer.splitter.TextSplitter;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -14,44 +13,16 @@
 
 /**
  * Markdown QA 鏍煎紡涓撶敤鍒囩墖鍣�
- *
- * <p>鍔熻兘鐗圭偣锛�
- * <ul>
- *   <li>璇嗗埆浜岀骇鏍囬锛�## 锛変綔涓洪棶棰樻爣璁�</li>
- *   <li>鐭� QA 瀵逛繚鎸佸畬鏁达紙涓嶈秴杩� Token 闄愬埗锛�</li>
- *   <li>闀跨瓟妗堟櫤鑳藉垏鍒嗭紝姣忎釜鐗囨淇濈暀瀹屾暣闂浣滀负涓婁笅鏂�</li>
- *   <li>鏀寔鑷畾涔� Token 浼扮畻鍣�</li>
- * </ul>
- *
- * @author runzhen
+ * 璇嗗埆浜岀骇鏍囬锛�## 锛変綔涓洪棶棰樻爣璁帮紝淇濇寔闂瓟瀵瑰畬鏁存��
  */
 @Slf4j
-@SuppressWarnings("SizeReplaceableByIsEmpty")
 public class MarkdownQaSplitter extends TextSplitter {
 
-    /**
-     * 浜岀骇鏍囬姝e垯锛氬尮閰� "## " 寮�澶寸殑琛�
-     */
     private static final Pattern H2_PATTERN = Pattern.compile("^##\\s+(.+)$", Pattern.MULTILINE);
-
-    /**
-     * 娈佃惤鍒嗛殧绗︼細鍙屾崲琛�
-     */
     private static final String PARAGRAPH_SEPARATOR = "\n\n";
-
-    /**
-     * 鍙ュ瓙鍒嗛殧绗�
-     */
     private static final Pattern SENTENCE_PATTERN = Pattern.compile("[銆傦紒锛�.!?]\\s*");
 
-    /**
-     * 鍒嗘鐨勬渶澶� Token 鏁�
-     */
     private final int chunkSize;
-
-    /**
-     * Token 浼扮畻鍣紙绠�鍗曞疄鐜帮細涓枃鎸夊瓧绗︽暟锛岃嫳鏂囨寜鍗曡瘝鏁扮殑 1.3 鍊嶏級
-     */
     private final TokenEstimator tokenEstimator;
 
     public MarkdownQaSplitter(int chunkSize) {
@@ -61,282 +32,115 @@
 
     @Override
     protected List<String> splitText(String text) {
-        if (StrUtil.isEmpty(text)) {
-            return Collections.emptyList();
-        }
-
-        // 瑙f瀽 QA 瀵�
+        if (StrUtil.isEmpty(text)) return Collections.emptyList();
         List<QaPair> qaPairs = parseQaPairs(text);
-        if (CollUtil.isEmpty(qaPairs)) {
-            // 濡傛灉娌℃湁璇嗗埆鍒� QA 鏍煎紡锛屾寜娈佃惤鍒囧垎
-            return fallbackSplit(text);
-        }
-
-        // 澶勭悊姣忎釜 QA 瀵�
+        if (CollUtil.isEmpty(qaPairs)) return fallbackSplit(text);
         List<String> result = new ArrayList<>();
-        for (QaPair qaPair : qaPairs) {
-            result.addAll(splitQaPair(qaPair));
-        }
+        for (QaPair qaPair : qaPairs) result.addAll(splitQaPair(qaPair));
         return result;
     }
 
-    /**
-     * 瑙f瀽 Markdown QA 瀵�
-     *
-     * @param content 鏂囨湰鍐呭
-     * @return QA 瀵瑰垪琛�
-     */
     private List<QaPair> parseQaPairs(String content) {
-        // 鎵惧埌鎵�鏈変簩绾ф爣棰樹綅缃�
         List<QaPair> qaPairs = new ArrayList<>();
         List<Integer> headingPositions = new ArrayList<>();
         List<String> questions = new ArrayList<>();
         Matcher matcher = H2_PATTERN.matcher(content);
-        while (matcher.find()) {
-            headingPositions.add(matcher.start());
-            questions.add(matcher.group(1).trim());
-        }
-        if (CollUtil.isEmpty(headingPositions)) {
-            return qaPairs;
-        }
-
-        // 鎻愬彇姣忎釜 QA 瀵�
+        while (matcher.find()) { headingPositions.add(matcher.start()); questions.add(matcher.group(1).trim()); }
+        if (CollUtil.isEmpty(headingPositions)) return qaPairs;
         for (int i = 0; i < headingPositions.size(); i++) {
             int start = headingPositions.get(i);
-            int end = (i + 1 < headingPositions.size())
-                    ? headingPositions.get(i + 1)
-                    : content.length();
-            String qaText = content.substring(start, end).trim();
+            int end = (i + 1 < headingPositions.size()) ? headingPositions.get(i + 1) : content.length();
+            String fullText = content.substring(start, end).trim();
             String question = questions.get(i);
-            // 鎻愬彇绛旀閮ㄥ垎锛堝幓鎺夐棶棰樻爣棰橈級
-            String answer = qaText.substring(qaText.indexOf('\n') + 1).trim();
-            qaPairs.add(new QaPair(question, answer, qaText));
+            String answer = fullText.substring(fullText.indexOf('\n') + 1).trim();
+            qaPairs.add(new QaPair(question, answer, fullText));
         }
         return qaPairs;
     }
 
-    /**
-     * 鍒囧垎鍗曚釜 QA 瀵�
-     *
-     * @param qaPair QA 瀵�
-     * @return 鍒囧垎鍚庣殑鏂囨湰鐗囨鍒楄〃
-     */
     private List<String> splitQaPair(QaPair qaPair) {
-        // 濡傛灉鏁翠釜 QA 瀵逛笉瓒呰繃闄愬埗锛屼繚鎸佸畬鏁�
+        int qaTokens = tokenEstimator.estimate(qaPair.fullText);
+        if (qaTokens <= chunkSize) return Collections.singletonList(qaPair.fullText);
         List<String> chunks = new ArrayList<>();
-        String fullQa = qaPair.fullText;
-        int qaTokens = tokenEstimator.estimate(fullQa);
-        if (qaTokens <= chunkSize) {
-            chunks.add(fullQa);
-            return chunks;
-        }
-
-        // 闀跨瓟妗堥渶瑕佸垏鍒�
-        log.debug("QA 瀵硅秴杩� Token 闄愬埗 ({} > {})锛屽紑濮嬫櫤鑳藉垏鍒�: {}", qaTokens, chunkSize, qaPair.question);
         List<String> answerChunks = splitLongAnswer(qaPair.answer, qaPair.question);
         for (String answerChunk : answerChunks) {
-            // 姣忎釜鐗囨閮藉寘鍚畬鏁撮棶棰�
-            String chunkText = "## " + qaPair.question + "\n" + answerChunk;
-            chunks.add(chunkText);
+            chunks.add("## " + qaPair.question + "\n" + answerChunk);
         }
         return chunks;
     }
 
-    /**
-     * 鍒囧垎闀跨瓟妗�
-     *
-     * @param answer 绛旀鏂囨湰
-     * @param question 闂鏂囨湰
-     * @return 鍒囧垎鍚庣殑绛旀鐗囨鍒楄〃
-     */
     private List<String> splitLongAnswer(String answer, String question) {
         List<String> chunks = new ArrayList<>();
-        // 棰勭暀闂鐨� Token 绌洪棿
         String questionHeader = "## " + question + "\n";
         int questionTokens = tokenEstimator.estimate(questionHeader);
-        int availableTokens = chunkSize - questionTokens - 10; // 棰勭暀 10 涓� Token 鐨勭紦鍐�
-
-        // 鍏堟寜娈佃惤鍒囧垎
+        int availableTokens = chunkSize - questionTokens - 10;
         String[] paragraphs = answer.split(PARAGRAPH_SEPARATOR);
         StringBuilder currentChunk = new StringBuilder();
         int currentTokens = 0;
         for (String paragraph : paragraphs) {
-            if (StrUtil.isEmpty(paragraph)) {
-                continue;
-            }
+            if (StrUtil.isEmpty(paragraph)) continue;
             int paragraphTokens = tokenEstimator.estimate(paragraph);
-            // 濡傛灉鍗曚釜娈佃惤灏辫秴杩囬檺鍒讹紝闇�瑕佹寜鍙ュ瓙鍒囧垎
             if (paragraphTokens > availableTokens) {
-                // 鍏堜繚瀛樺綋鍓嶅潡
-                if (currentChunk.length() > 0) {
-                    chunks.add(currentChunk.toString().trim());
-                    currentChunk = new StringBuilder();
-                    currentTokens = 0;
-                }
-                // 鎸夊彞瀛愬垏鍒嗛暱娈佃惤
+                if (currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; }
                 chunks.addAll(splitLongParagraph(paragraph, availableTokens));
                 continue;
             }
-            // 濡傛灉鍔犱笂杩欎釜娈佃惤浼氳秴杩囬檺鍒�
-            if (currentTokens + paragraphTokens > availableTokens && currentChunk.length() > 0) {
-                chunks.add(currentChunk.toString().trim());
-                currentChunk = new StringBuilder();
-                currentTokens = 0;
-            }
-            if (currentChunk.length() > 0) {
-                currentChunk.append("\n\n");
-            }
-            // 娣诲姞娈佃惤
+            if (currentTokens + paragraphTokens > availableTokens && currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; }
+            if (currentChunk.length() > 0) currentChunk.append("\n\n");
             currentChunk.append(paragraph);
             currentTokens += paragraphTokens;
         }
-
-        // 娣诲姞鏈�鍚庝竴鍧�
-        if (currentChunk.length() > 0) {
-            chunks.add(currentChunk.toString().trim());
-        }
+        if (currentChunk.length() > 0) chunks.add(currentChunk.toString().trim());
         return CollUtil.isEmpty(chunks) ? Collections.singletonList(answer) : chunks;
     }
 
-    /**
-     * 鍒囧垎闀挎钀斤紙鎸夊彞瀛愶級
-     *
-     * @param paragraph 娈佃惤鏂囨湰
-     * @param availableTokens 鍙敤鐨� Token 鏁�
-     * @return 鍒囧垎鍚庣殑鏂囨湰鐗囨鍒楄〃
-     */
     private List<String> splitLongParagraph(String paragraph, int availableTokens) {
-        // 鎸夊彞瀛愬垏鍒�
         List<String> chunks = new ArrayList<>();
         String[] sentences = SENTENCE_PATTERN.split(paragraph);
-
-        // 鎸夊彞瀛愮疮绉垏鍒�
         StringBuilder currentChunk = new StringBuilder();
         int currentTokens = 0;
         for (String sentence : sentences) {
-            if (StrUtil.isEmpty(sentence)) {
-                continue;
-            }
+            if (StrUtil.isEmpty(sentence)) continue;
             int sentenceTokens = tokenEstimator.estimate(sentence);
-            // 濡傛灉鍗曚釜鍙ュ瓙灏辫秴杩囬檺鍒讹紝寮哄埗鍒囧垎
-            if (sentenceTokens > availableTokens) {
-                if (currentChunk.length() > 0) {
-                    chunks.add(currentChunk.toString().trim());
-                    currentChunk = new StringBuilder();
-                    currentTokens = 0;
-                }
-                chunks.add(sentence.trim());
-                continue;
-            }
-            // 濡傛灉鍔犱笂杩欎釜鍙ュ瓙浼氳秴杩囬檺鍒�
-            if (currentTokens + sentenceTokens > availableTokens && currentChunk.length() > 0) {
-                chunks.add(currentChunk.toString().trim());
-                currentChunk = new StringBuilder();
-                currentTokens = 0;
-            }
-            // 娣诲姞鍙ュ瓙
+            if (sentenceTokens > availableTokens) { if (currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; } chunks.add(sentence.trim()); continue; }
+            if (currentTokens + sentenceTokens > availableTokens && currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; }
             currentChunk.append(sentence);
             currentTokens += sentenceTokens;
         }
-
-        // 娣诲姞鏈�鍚庝竴鍧�
-        if (currentChunk.length() > 0) {
-            chunks.add(currentChunk.toString().trim());
-        }
+        if (currentChunk.length() > 0) chunks.add(currentChunk.toString().trim());
         return chunks.isEmpty() ? Collections.singletonList(paragraph) : chunks;
     }
 
-    /**
-     * 闄嶇骇鍒囧垎绛栫暐锛堝綋鏈瘑鍒埌 QA 鏍煎紡鏃讹級
-     *
-     * @param content 鏂囨湰鍐呭
-     * @return 鍒囧垎鍚庣殑鏂囨湰鐗囨鍒楄〃
-     */
     private List<String> fallbackSplit(String content) {
-        // 鎸夋钀藉垏鍒�
         List<String> chunks = new ArrayList<>();
         String[] paragraphs = content.split(PARAGRAPH_SEPARATOR);
-
-        // 鎸夋钀界疮绉垏鍒�
         StringBuilder currentChunk = new StringBuilder();
         int currentTokens = 0;
         for (String paragraph : paragraphs) {
-            if (StrUtil.isEmpty(paragraph)) {
-                continue;
-            }
+            if (StrUtil.isEmpty(paragraph)) continue;
             int paragraphTokens = tokenEstimator.estimate(paragraph);
-            // 濡傛灉鍔犱笂杩欎釜娈佃惤浼氳秴杩囬檺鍒�
-            if (currentTokens + paragraphTokens > chunkSize && currentChunk.length() > 0) {
-                chunks.add(currentChunk.toString().trim());
-                currentChunk = new StringBuilder();
-                currentTokens = 0;
-            }
-            // 娣诲姞娈佃惤
-            if (currentChunk.length() > 0) {
-                currentChunk.append("\n\n");
-            }
+            if (currentTokens + paragraphTokens > chunkSize && currentChunk.length() > 0) { chunks.add(currentChunk.toString().trim()); currentChunk = new StringBuilder(); currentTokens = 0; }
+            if (currentChunk.length() > 0) currentChunk.append("\n\n");
             currentChunk.append(paragraph);
             currentTokens += paragraphTokens;
         }
-
-        // 娣诲姞鏈�鍚庝竴鍧�
-        if (currentChunk.length() > 0) {
-            chunks.add(currentChunk.toString().trim());
-        }
+        if (currentChunk.length() > 0) chunks.add(currentChunk.toString().trim());
         return chunks.isEmpty() ? Collections.singletonList(content) : chunks;
     }
 
-    /**
-     * QA 瀵规暟鎹粨鏋�
-     */
     @AllArgsConstructor
-    private static class QaPair {
+    private static class QaPair { String question; String answer; String fullText; }
 
-        String question;
-        String answer;
-        String fullText;
+    public interface TokenEstimator { int estimate(String text); }
 
-    }
-
-    /**
-     * Token 浼扮畻鍣ㄦ帴鍙�
-     */
-    public interface TokenEstimator {
-
-        int estimate(String text);
-
-    }
-
-    /**
-     * 绠�鍗曠殑 Token 浼扮畻鍣ㄥ疄鐜�
-     * 涓枃锛�1 瀛楃 鈮� 1 Token
-     * 鑻辨枃锛�1 鍗曡瘝 鈮� 1.3 Token
-     */
     private static class SimpleTokenEstimator implements 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