From f7bf29a5e465758aeafbf1505903342b7faaf61d Mon Sep 17 00:00:00 2001 From: GZH <1206563805@qq.com> Date: Wed, 4 Mar 2026 17:29:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DPR=E5=AE=A1=E6=9F=A5?= =?UTF-8?q?=E6=84=8F=E8=A7=81=20-=20=E6=96=B0=E5=A2=9E=E5=8D=95=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=88=A0=E9=99=A4=E6=8E=A5=E5=8F=A3=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4BotController=E3=80=81=E4=BC=98=E5=8C=96imports?= =?UTF-8?q?=E3=80=81=E4=BF=AE=E5=A4=8Di18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Conflicts: # main/manager-api/src/main/resources/i18n/messages_de_DE.properties # main/manager-api/src/main/resources/i18n/messages_en_US.properties # main/manager-api/src/main/resources/i18n/messages_vi_VN.properties --- .../common/config/RestTemplateConfig.java | 6 +- .../knowledge/controller/BotController.java | 148 ------------------ .../controller/KnowledgeBaseController.java | 3 +- .../controller/KnowledgeFilesController.java | 14 ++ .../knowledge/rag/KnowledgeBaseAdapter.java | 17 +- .../modules/knowledge/rag/RAGFlowClient.java | 43 +++-- .../knowledge/rag/impl/RAGFlowAdapter.java | 7 +- .../resources/i18n/messages_vi_VN.properties | 1 + 8 files changed, 64 insertions(+), 175 deletions(-) delete mode 100644 main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/BotController.java diff --git a/main/manager-api/src/main/java/xiaozhi/common/config/RestTemplateConfig.java b/main/manager-api/src/main/java/xiaozhi/common/config/RestTemplateConfig.java index 6c8b7c34..6608032b 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/config/RestTemplateConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/common/config/RestTemplateConfig.java @@ -2,7 +2,9 @@ package xiaozhi.common.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.JdkClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; +import java.time.Duration; /** * RestTemplate配置 @@ -12,8 +14,8 @@ public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { - org.springframework.http.client.JdkClientHttpRequestFactory factory = new org.springframework.http.client.JdkClientHttpRequestFactory(); - factory.setReadTimeout(java.time.Duration.ofSeconds(30)); + JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory(); + factory.setReadTimeout(Duration.ofSeconds(30)); return new RestTemplate(factory); } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/BotController.java b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/BotController.java deleted file mode 100644 index 0696ab1c..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/BotController.java +++ /dev/null @@ -1,148 +0,0 @@ -//package xiaozhi.modules.knowledge.controller; -// -//import java.util.HashMap; -//import java.util.Map; -// -//import org.apache.commons.lang3.StringUtils; -//import org.springframework.http.MediaType; -//import org.springframework.web.bind.annotation.PathVariable; -//import org.springframework.web.bind.annotation.PostMapping; -//import org.springframework.web.bind.annotation.RequestBody; -//import org.springframework.web.bind.annotation.RequestMapping; -//import org.springframework.web.bind.annotation.RestController; -//import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -// -//import io.swagger.v3.oas.annotations.Operation; -//import io.swagger.v3.oas.annotations.tags.Tag; -//import lombok.RequiredArgsConstructor; -//import lombok.extern.slf4j.Slf4j; -//import xiaozhi.common.exception.ErrorCode; -//import xiaozhi.common.exception.RenException; -//import xiaozhi.modules.agent.entity.AgentEntity; -//import xiaozhi.modules.agent.service.AgentService; -//import xiaozhi.modules.knowledge.dto.bot.BotDTO; -//import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapter; -//import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapterFactory; -//import xiaozhi.modules.knowledge.service.KnowledgeBaseService; -//import xiaozhi.modules.model.entity.ModelConfigEntity; -//import xiaozhi.modules.model.service.ModelConfigService; -// -//@Tag(name = "外部机器人服务") -//@RestController -//@RequestMapping("/api/v1") // 保持与外部 API 一致的风格,或者映射到 RAGFlow 风格 -//@RequiredArgsConstructor -//@Slf4j -//public class BotController {//todo:这个类还有待完成,待完善。这里的agent和其他模块的agent不一样,这里的agent是存在于ragflow中等agent/工作流,不独立存在,需要借助rag作为平台进行交流交互 -// -// private final AgentService agentService; -// private final ModelConfigService modelConfigService; -// private final KnowledgeBaseService knowledgeBaseService; -// -// // SearchBot (暂未完全实现,预留接口) -// @Operation(summary = "SearchBot 提问") -// @PostMapping(value = "/searchbots/ask", produces = MediaType.TEXT_EVENT_STREAM_VALUE) -// public SseEmitter searchBotAsk(@RequestBody BotDTO.SearchAskReq request) { -// // TODO: SearchBot 通常需要指定 Dataset 或 KnowledgeBase -// // 这里需要明确 SearchBot 的 ID 来源。 -// // 暂时返回未实现 -// throw new RenException("SearchBot API 暂未开放"); -// } -// -// // AgentBot -// @Operation(summary = "AgentBot 对话 (流式)") -// @PostMapping(value = "/agentbots/{id}/completions", produces = MediaType.TEXT_EVENT_STREAM_VALUE) -// public SseEmitter agentBotCompletion(@PathVariable("id") String agentId, -// @RequestBody BotDTO.AgentCompletionReq request) { -// SseEmitter emitter = new SseEmitter(5 * 60 * 1000L); // 5 min timeout -// -// new Thread(() -> { -// try { -// // 1. 获取本地 Agent -// // 注意:这里的 id 是本地 AgentId -// AgentEntity agent = agentService.getAgentById(agentId); // getAgentById actually returns VO but we need -// // Entity fields -// // 直接查 DB 或用 Service -// // Service returns AgentInfoVO, let's better use getById from BaseService (which -// // returns Entity) if exposed -// // AgentService extends BaseService -// AgentEntity entity = agentService.selectById(agentId); -// -// if (entity == null) { -// throw new RenException(ErrorCode.AGENT_NOT_FOUND); -// } -// -// // 2. 获取关联的 RAG 配置 -// String llmModelId = entity.getLlmModelId(); -// if (StringUtils.isBlank(llmModelId)) { -// throw new RenException("Agent未配置LLM模型"); -// } -// -// // 3. 所有的 AgentBot 请求都通过关联的 RAGConfig 下发 -// Map ragConfig = knowledgeBaseService.getRAGConfig(llmModelId); -// String adapterType = (String) ragConfig.getOrDefault("type", "ragflow"); -// KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig); -// -// // 4. 发起请求 -// // 注意:这里需要传递给 RAGFlow 的 Agent ID 可能是 config 中的某些字段, -// // 或者我们这里构建的 Agent 实际上是 RAGFlow 中的 Agent? -// // 根据 Deep Dive,ChatService 是自己组装 prompt 调用 chat/completions。 -// // 而 BotController 似乎旨在暴露 RAGFlow 的 AgentBot 能力。 -// // 如果我们是“Java主导”,那么其实这里的逻辑应该和 ChatService 类似: -// // 即:我们自己组装参数,调用 RAGFlow 的 Chat/Completion 接口,而不是调用 AgentBot 接口。 -// // 除非 RAGFlow 侧真的有一个 "Agent" 对象对应我们的 Agent。 -// // 鉴于 Step 8 中我们决定 "Java-Side Master",且 RAGFlow 是推理引擎, -// // 我们应该复用 ChatService 的逻辑,或者调用 POST /chat/completions (RAGFlow) -// -// // 但为了符合 BotDTO 的定义 (/agentbots/{id}/completions),我们这里模拟这个行为 -// // 且尽量复用 ChatService 的流式能力。 -// -// // 这里的 adapter.postAgentBotCompletion 实现的是调用 /api/v1/agentbots/{id}/completions -// // 这要求 RAGFlow 侧必须存在这个 ID。 -// // 但我们的 Agent 是本地的。 -// // **修正策略**: -// // 如果 RAGFlow 侧没有对应 Agent,我们不能调这个接口。 -// // 我们应该降级为调用 Chat 接口 (/chat/completions),带上 system prompt。 -// -// // 出于本步骤目标 "Bot Service (Public Access)",我们为了兼容外部 Bot 协议, -// // 应该要把本地请求转换为 RAGFlow 的 Chat 请求。 -// -// Map chatBody = new HashMap<>(); -// chatBody.put("messages", java.util.List.of( -// Map.of("role", "user", "content", request.getQuestion()))); -// chatBody.put("stream", request.getStream()); -// chatBody.put("model", entity.getAgentName()); // Mock name -// if (StringUtils.isNotBlank(entity.getSystemPrompt())) { -// chatBody.put("system_prompt", entity.getSystemPrompt()); -// } -// -// adapter.postStream("/api/v1/chat/completions", chatBody, line -> { -// try { -// if (line.startsWith("data:")) { -// String dataContent = line.substring(5).trim(); -// if (!"[DONE]".equals(dataContent)) { -// emitter.send(SseEmitter.event().data(dataContent)); -// } -// } -// } catch (Exception e) { -// log.error("SSE Error", e); -// throw new RuntimeException(e); -// } -// }); -// -// emitter.send(SseEmitter.event().data("[DONE]")); -// emitter.complete(); -// -// } catch (Exception e) { -// log.error("AgentBot Error", e); -// try { -// emitter.send(SseEmitter.event().name("error").data(e.getMessage())); -// emitter.completeWithError(e); -// } catch (Exception ex) { -// // ignore -// } -// } -// }).start(); -// -// return emitter; -// } -//} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/KnowledgeBaseController.java b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/KnowledgeBaseController.java index f1008a28..10aafdae 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/KnowledgeBaseController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/KnowledgeBaseController.java @@ -26,6 +26,7 @@ import xiaozhi.common.utils.Result; import xiaozhi.common.utils.ToolUtil; import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO; import xiaozhi.modules.knowledge.service.KnowledgeBaseService; +import xiaozhi.modules.knowledge.service.KnowledgeManagerService; import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.security.user.SecurityUser; @@ -36,7 +37,7 @@ import xiaozhi.modules.security.user.SecurityUser; public class KnowledgeBaseController { private final KnowledgeBaseService knowledgeBaseService; - private final xiaozhi.modules.knowledge.service.KnowledgeManagerService knowledgeManagerService; + private final KnowledgeManagerService knowledgeManagerService; @GetMapping @Operation(summary = "分页查询知识库列表") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/KnowledgeFilesController.java b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/KnowledgeFilesController.java index 475aadba..22d2f3d2 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/KnowledgeFilesController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/controller/KnowledgeFilesController.java @@ -126,6 +126,20 @@ public class KnowledgeFilesController { return new Result<>(); } + @DeleteMapping("/documents/{document_id}") + @Operation(summary = "删除单个文档") + @RequiresPermissions("sys:role:normal") + public Result deleteSingle(@PathVariable("dataset_id") String datasetId, + @PathVariable("document_id") String documentId) { + // 验证知识库权限 + validateKnowledgeBasePermission(datasetId); + + DocumentDTO.BatchIdReq req = new DocumentDTO.BatchIdReq(); + req.setIds(java.util.Collections.singletonList(documentId)); + knowledgeFilesService.deleteDocuments(datasetId, req); + return new Result<>(); + } + @PostMapping("/chunks") @Operation(summary = "解析文档(切块)") @RequiresPermissions("sys:role:normal") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/KnowledgeBaseAdapter.java b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/KnowledgeBaseAdapter.java index 8143fea2..9e6314e4 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/KnowledgeBaseAdapter.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/KnowledgeBaseAdapter.java @@ -8,6 +8,9 @@ import xiaozhi.modules.knowledge.dto.dataset.DatasetDTO; import xiaozhi.common.page.PageData; import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO; import xiaozhi.modules.knowledge.dto.document.DocumentDTO; +import xiaozhi.modules.knowledge.dto.document.ChunkDTO; +import xiaozhi.modules.knowledge.dto.document.RetrievalDTO; +import java.util.function.Consumer; /** * 知识库API适配器抽象基类 @@ -105,9 +108,9 @@ public abstract class KnowledgeBaseAdapter { * @param req 列表请求参数 (分页、关键词等) * @return 切片列表VO */ - public abstract xiaozhi.modules.knowledge.dto.document.ChunkDTO.ListVO listChunks(String datasetId, + public abstract ChunkDTO.ListVO listChunks(String datasetId, String documentId, - xiaozhi.modules.knowledge.dto.document.ChunkDTO.ListReq req); + ChunkDTO.ListReq req); /** * 召回测试 - 从知识库中检索相关切片 @@ -115,8 +118,8 @@ public abstract class KnowledgeBaseAdapter { * @param req 检索测试请求参数 * @return 召回测试结果 */ - public abstract xiaozhi.modules.knowledge.dto.document.RetrievalDTO.ResultVO retrievalTest( - xiaozhi.modules.knowledge.dto.document.RetrievalDTO.TestReq req); + public abstract RetrievalDTO.ResultVO retrievalTest( + RetrievalDTO.TestReq req); /** * 测试连接 @@ -186,7 +189,7 @@ public abstract class KnowledgeBaseAdapter { * @param body 请求体 * @param onData 数据回调 */ - public abstract void postStream(String endpoint, Object body, java.util.function.Consumer onData); + public abstract void postStream(String endpoint, Object body, Consumer onData); /** * SearchBot 提问 @@ -197,7 +200,7 @@ public abstract class KnowledgeBaseAdapter { * @return 响应对象 */ public abstract Object postSearchBotAsk(Map config, Object body, - java.util.function.Consumer onData); + Consumer onData); /** * AgentBot 对话 @@ -208,5 +211,5 @@ public abstract class KnowledgeBaseAdapter { * @param onData 数据回调 */ public abstract void postAgentBotCompletion(Map config, String agentId, Object body, - java.util.function.Consumer onData); + Consumer onData); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/RAGFlowClient.java b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/RAGFlowClient.java index bcc39ff1..9f204e98 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/RAGFlowClient.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/RAGFlowClient.java @@ -13,6 +13,21 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; +import org.springframework.http.client.SimpleClientHttpRequestFactory; + +import java.text.SimpleDateFormat; +import java.util.TimeZone; +import java.util.Locale; +import java.net.URLEncoder; +import java.io.UnsupportedEncodingException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.URI; +import java.io.OutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.function.Consumer; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,8 +60,8 @@ public class RAGFlowClient { this.objectMapper = new ObjectMapper(); // [Reinforce] 兼容 RAGFlow 返回的 RFC 1123 日期格式 (如: Tue, 10 Feb 2026 10:27:35 GMT) this.objectMapper - .setDateFormat(new java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", java.util.Locale.US)); - this.objectMapper.setTimeZone(java.util.TimeZone.getTimeZone("GMT")); + .setDateFormat(new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US)); + this.objectMapper.setTimeZone(TimeZone.getTimeZone("GMT")); // 优先从 Spring 上下文中获取池化的 RestTemplate Bean (Issue 3: 连接池化) RestTemplate pooledTemplate = null; @@ -62,7 +77,7 @@ public class RAGFlowClient { } else { // 兜底方案:配置超时并创建简单 RestTemplate log.info("RAGFlowClient 初始化: 使用独立 RestTemplate (Debug Mode)"); - org.springframework.http.client.SimpleClientHttpRequestFactory factory = new org.springframework.http.client.SimpleClientHttpRequestFactory(); + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(timeoutSeconds * 1000); factory.setReadTimeout(timeoutSeconds * 1000); this.restTemplate = new RestTemplate(factory); @@ -194,10 +209,10 @@ public class RAGFlowClient { if (v != null) { try { sb.append(k).append("=") - .append(java.net.URLEncoder.encode(v.toString(), + .append(URLEncoder.encode(v.toString(), StandardCharsets.UTF_8.name())) .append("&"); - } catch (java.io.UnsupportedEncodingException e) { + } catch (UnsupportedEncodingException e) { log.warn("参数编码失败: k={}, v={}", k, v); sb.append(k).append("=").append(v).append("&"); } @@ -217,32 +232,32 @@ public class RAGFlowClient { * @param body 请求体 * @param onData 数据回调(每收到一行数据调用一次) */ - public void postStream(String endpoint, Object body, java.util.function.Consumer onData) { + public void postStream(String endpoint, Object body, Consumer onData) { try { String url = buildUrl(endpoint, null); log.debug("POST STREAM {}", url); String jsonBody = objectMapper.writeValueAsString(body); - java.net.http.HttpClient httpClient = java.net.http.HttpClient.newBuilder() + HttpClient httpClient = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(DEFAULT_TIMEOUT)) .build(); - java.net.http.HttpRequest request = java.net.http.HttpRequest.newBuilder() - .uri(java.net.URI.create(url)) + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + apiKey) - .POST(java.net.http.HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8)) + .POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8)) .build(); // 发送请求并处理流式响应 - httpClient.send(request, java.net.http.HttpResponse.BodyHandlers.ofInputStream()) + httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()) .body() - .transferTo(new java.io.OutputStream() { - private final java.io.ByteArrayOutputStream buffer = new java.io.ByteArrayOutputStream(); + .transferTo(new OutputStream() { + private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); @Override - public void write(int b) throws java.io.IOException { + public void write(int b) throws IOException { if (b == '\n') { String line = buffer.toString(StandardCharsets.UTF_8); if (!line.trim().isEmpty()) { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/impl/RAGFlowAdapter.java b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/impl/RAGFlowAdapter.java index a2b6a243..32cbc64b 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/impl/RAGFlowAdapter.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/knowledge/rag/impl/RAGFlowAdapter.java @@ -8,6 +8,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.apache.commons.lang3.StringUtils; import org.springframework.core.io.AbstractResource; @@ -515,7 +516,7 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter { } @Override - public void postStream(String endpoint, Object body, java.util.function.Consumer onData) { + public void postStream(String endpoint, Object body, Consumer onData) { try { getClient().postStream(endpoint, body, onData); } catch (Exception e) { @@ -526,7 +527,7 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter { @Override public Object postSearchBotAsk(Map config, Object body, - java.util.function.Consumer onData) { + Consumer onData) { // SearchBot 实际上是 Dataset 检索的一种封装,或者是未公开的 API? // 假设 RAGFlow 没有显式的 /searchbots 接口供 SDK 调用,而是 Dataset Retrieval 或者 Chat。 // 但根据 BotDTO,它是 /api/v1/searchbots/ask (假设) @@ -547,7 +548,7 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter { @Override public void postAgentBotCompletion(Map config, String agentId, Object body, - java.util.function.Consumer onData) { + Consumer onData) { // AgentBot 对应 /api/v1/agentbots/{id}/completions try { getClient().postStream("/api/v1/agentbots/" + agentId + "/completions", body, onData); diff --git a/main/manager-api/src/main/resources/i18n/messages_vi_VN.properties b/main/manager-api/src/main/resources/i18n/messages_vi_VN.properties index 5dad10c7..566ae4d4 100644 --- a/main/manager-api/src/main/resources/i18n/messages_vi_VN.properties +++ b/main/manager-api/src/main/resources/i18n/messages_vi_VN.properties @@ -206,3 +206,4 @@ 10197=T\u00EAn th\u1EB9\uFFFD kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng 10198=Kh\u00F4ng t\u00ECm th\u1EA5y th\u1EB9\uFFFD 10199=T\u1EC7p \u0111ang \u0111\u01B0\u1EE3c ph\u00E2n t\u00EDch, thao t\u00E1c n\u00E0y kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3 +