From d3fb2d829aaf88eb05fd47df862d97a4ca76efe6 Mon Sep 17 00:00:00 2001 From: "Luke.Ye" Date: Tue, 8 Jul 2025 08:50:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96llm=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/LLMAppService.java | 4 +- .../service/LLMAppServiceImpl.java | 57 +++++++++++++++++-- .../infrastructure/config/ConstantConfig.java | 4 +- .../service/LLMAppServiceImplTest.java | 3 +- .../south/llm/AnythingLLMServiceTest.java | 2 +- 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/knowledge/base/application/service/LLMAppService.java b/src/main/java/com/knowledge/base/application/service/LLMAppService.java index 37efc5d..481174f 100644 --- a/src/main/java/com/knowledge/base/application/service/LLMAppService.java +++ b/src/main/java/com/knowledge/base/application/service/LLMAppService.java @@ -1,5 +1,6 @@ package com.knowledge.base.application.service; +import com.knowledge.base.infrastructure.north.dto.llm.WorkspaceAttachment; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.List; @@ -30,9 +31,10 @@ public interface LLMAppService { * @param llmToken LLM鉴权token * @param workspaceSlug 当前工作区slug * @param keywords 搜索关键词 + * @param question 当前的问题 * @return 本次被pin的docPath集合 */ - List pinDocsByKeywords(String llmToken, String workspaceSlug, List keywords); + List pinDocsByKeywords(String llmToken, String workspaceSlug, List keywords, String question); } diff --git a/src/main/java/com/knowledge/base/application/service/LLMAppServiceImpl.java b/src/main/java/com/knowledge/base/application/service/LLMAppServiceImpl.java index 957b1e1..ed2dcdb 100644 --- a/src/main/java/com/knowledge/base/application/service/LLMAppServiceImpl.java +++ b/src/main/java/com/knowledge/base/application/service/LLMAppServiceImpl.java @@ -22,6 +22,8 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; @Service @@ -37,6 +39,12 @@ public class LLMAppServiceImpl implements LLMAppService { private final FileElasticsearchGateway esGateway; + /** + * pin和unpin的并发控制 + */ + private static boolean PROCESSING = false; + private static String PROCESSING_QUESTION = StrUtil.EMPTY; + @Override public String getToken(String password) throws Exception { @@ -46,8 +54,7 @@ public class LLMAppServiceImpl implements LLMAppService { @Override public SseEmitter ask(String llmToken, String question, Map params) throws Exception { SseEmitter emitter = new SseEmitter(300 * 1000L); // 超时时间设为5分钟 - String keyword = (String) params.get("keyword"); - pinDocsByKeywords(llmToken, ConstantConfig.DEFAULT_SLUG_ID, Lists.newArrayList(keyword)); + List pinnedDocs = pinDocsByKeywords(llmToken, ConstantConfig.DEFAULT_SLUG_ID, extractKeywords(question), question); ThreadPoolUtil.execute(() -> { try { @@ -63,6 +70,16 @@ public class LLMAppServiceImpl implements LLMAppService { } catch (IOException ioException) { log.warn("SSE发送错误信息失败", ioException); } + } finally { + // 问题结束后,需要unpin掉 + pinnedDocs.forEach(doc -> { + try { + anythingLLMService.updatePin(llmToken, ConstantConfig.DEFAULT_SLUG_ID, doc.getDocpath(), false); + } catch (Exception e) { + log.warn("unpin失败: {}", doc, e); + } + }); + PROCESSING = false; } }, ThreadPoolConfig.SSE_POOL); @@ -70,11 +87,16 @@ public class LLMAppServiceImpl implements LLMAppService { } @Override - public List pinDocsByKeywords(String llmToken, String workspaceSlug, List keywords) { + public List pinDocsByKeywords(String llmToken, String workspaceSlug, List keywords, String question) { if(CollectionUtil.isEmpty(keywords)) { return Collections.emptyList(); } + if(PROCESSING) { + log.info("有问题正在处理,请稍等,question: {}", PROCESSING_QUESTION); + return Collections.emptyList(); + } + List fileEsModels = esGateway.searchByKeywords(keywords); Set esFilepaths = fileEsModels.stream() .map(FileEsModel::getFilepath) @@ -86,6 +108,9 @@ public class LLMAppServiceImpl implements LLMAppService { return Lists.newArrayList(); } + // 将当前问题标注为处理中 + markQuestionProcessing(question); + // 2. 获取当前工作区所有附件(docPath -> url) List attachments = anythingLLMService.fetchAttachments(llmToken, workspaceSlug); if (attachments == null || attachments.isEmpty()) { @@ -94,9 +119,9 @@ public class LLMAppServiceImpl implements LLMAppService { } // 3. 找到需要pin的docPath集合 - Set toPinDocs = attachments.stream() + List toPinDocs = attachments.stream() .filter(att -> esFilepaths.stream().anyMatch(f -> att.getUrl().endsWith(f))) - .collect(Collectors.toSet()); + .collect(Collectors.toList()); // 4. 只unpin之前已pin的文档 attachments.stream() @@ -121,6 +146,26 @@ public class LLMAppServiceImpl implements LLMAppService { log.info("[pinDocsByKeywords] 共pin住文档{}条: {}", toPinDocs.size(), JSONUtil.toJsonStr(toPinDocs)); - return toPinDocs.stream().map(WorkspaceAttachment::getUrl).collect(Collectors.toList()); + return toPinDocs; } + + /** + * 提取所有被LEFT & RIGHT 包裹的子串,返回数组 + */ + public static List extractKeywords(String text) { + List keywords = new ArrayList<>(); + Pattern pattern = Pattern.compile(ConstantConfig.KEYWORD_PATTERN_LEFT + "(.*?)" + ConstantConfig.KEYWORD_PATTERN_RIGHT); + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + keywords.add(matcher.group(1)); + } + return keywords; + } + + public static void markQuestionProcessing(String question) { + PROCESSING = true; + PROCESSING_QUESTION = question; + } + + } diff --git a/src/main/java/com/knowledge/base/infrastructure/config/ConstantConfig.java b/src/main/java/com/knowledge/base/infrastructure/config/ConstantConfig.java index 0c44082..fd979d9 100644 --- a/src/main/java/com/knowledge/base/infrastructure/config/ConstantConfig.java +++ b/src/main/java/com/knowledge/base/infrastructure/config/ConstantConfig.java @@ -34,6 +34,6 @@ public class ConstantConfig { * 以下是AnythingLLM相关 */ public static final String DEFAULT_SLUG_ID = "87e14982-a821-48d8-9c6b-3557d0bb2f96"; - - + public static final String KEYWORD_PATTERN_LEFT = "["; + public static final String KEYWORD_PATTERN_RIGHT = "]"; } diff --git a/src/test/java/com/knowledge/base/application/service/LLMAppServiceImplTest.java b/src/test/java/com/knowledge/base/application/service/LLMAppServiceImplTest.java index e0f920a..05fded0 100644 --- a/src/test/java/com/knowledge/base/application/service/LLMAppServiceImplTest.java +++ b/src/test/java/com/knowledge/base/application/service/LLMAppServiceImplTest.java @@ -4,6 +4,7 @@ import cn.hutool.json.JSONUtil; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.knowledge.base.infrastructure.config.ConstantConfig; +import com.knowledge.base.infrastructure.north.dto.llm.WorkspaceAttachment; import com.knowledge.base.infrastructure.south.llm.AnythingLLMServiceImpl; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; @@ -46,7 +47,7 @@ public class LLMAppServiceImplTest { @Test public void testPinDocsByKeywords() throws Exception { try { - List result = llmAppService.pinDocsByKeywords(token, getSlug(), Lists.newArrayList("商业")); + List result = llmAppService.pinDocsByKeywords(token, getSlug(), Lists.newArrayList("商业"), "[[商业]] 的本质是什么?"); log.info("result: {}", JSONUtil.toJsonStr(result)); } catch (Exception e) { log.warn("测试 pinDocsByKeywords 失败!: {}", e.getMessage(), e); diff --git a/src/test/java/com/knowledge/base/infrastructure/south/llm/AnythingLLMServiceTest.java b/src/test/java/com/knowledge/base/infrastructure/south/llm/AnythingLLMServiceTest.java index 66131fd..315605a 100644 --- a/src/test/java/com/knowledge/base/infrastructure/south/llm/AnythingLLMServiceTest.java +++ b/src/test/java/com/knowledge/base/infrastructure/south/llm/AnythingLLMServiceTest.java @@ -149,7 +149,7 @@ public class AnythingLLMServiceTest { Map params = new HashMap<>(); params.put("wsName", "部门知识库"); - llmService.streamAnswer(token, "介绍一下黄金圈法则", params, writer); +// llmService.streamAnswer(token, "介绍一下黄金圈法则", params, writer); } catch (Exception e) { log.warn("测试 streamAnswer 异常", e); }