mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 23:23:55 +08:00
fix: 修复PR审查意见 - 新增单文档删除接口、删除BotController、优化imports、修复i18n
# 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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
-148
@@ -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>
|
||||
// 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<String, Object> 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<String, Object> 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;
|
||||
// }
|
||||
//}
|
||||
+2
-1
@@ -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 = "分页查询知识库列表")
|
||||
|
||||
+14
@@ -126,6 +126,20 @@ public class KnowledgeFilesController {
|
||||
return new Result<>();
|
||||
}
|
||||
|
||||
@DeleteMapping("/documents/{document_id}")
|
||||
@Operation(summary = "删除单个文档")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> 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")
|
||||
|
||||
+10
-7
@@ -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<String> onData);
|
||||
public abstract void postStream(String endpoint, Object body, Consumer<String> onData);
|
||||
|
||||
/**
|
||||
* SearchBot 提问
|
||||
@@ -197,7 +200,7 @@ public abstract class KnowledgeBaseAdapter {
|
||||
* @return 响应对象
|
||||
*/
|
||||
public abstract Object postSearchBotAsk(Map<String, Object> config, Object body,
|
||||
java.util.function.Consumer<String> onData);
|
||||
Consumer<String> onData);
|
||||
|
||||
/**
|
||||
* AgentBot 对话
|
||||
@@ -208,5 +211,5 @@ public abstract class KnowledgeBaseAdapter {
|
||||
* @param onData 数据回调
|
||||
*/
|
||||
public abstract void postAgentBotCompletion(Map<String, Object> config, String agentId, Object body,
|
||||
java.util.function.Consumer<String> onData);
|
||||
Consumer<String> onData);
|
||||
}
|
||||
@@ -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<String> onData) {
|
||||
public void postStream(String endpoint, Object body, Consumer<String> 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()) {
|
||||
|
||||
+4
-3
@@ -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<String> onData) {
|
||||
public void postStream(String endpoint, Object body, Consumer<String> onData) {
|
||||
try {
|
||||
getClient().postStream(endpoint, body, onData);
|
||||
} catch (Exception e) {
|
||||
@@ -526,7 +527,7 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
||||
|
||||
@Override
|
||||
public Object postSearchBotAsk(Map<String, Object> config, Object body,
|
||||
java.util.function.Consumer<String> onData) {
|
||||
Consumer<String> 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<String, Object> config, String agentId, Object body,
|
||||
java.util.function.Consumer<String> onData) {
|
||||
Consumer<String> onData) {
|
||||
// AgentBot 对应 /api/v1/agentbots/{id}/completions
|
||||
try {
|
||||
getClient().postStream("/api/v1/agentbots/" + agentId + "/completions", body, onData);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user