From b624a829c3b5b4ee3288610489363f7f1866f3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=98=BF=E6=98=9F=E4=B8=8D=E6=98=AF=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E5=91=98?= <1031900093@qq.com> Date: Fri, 3 Jul 2026 18:03:10 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/Mysql/create_table_mysql.sql | 54 +++- .../controller/BusinessChatController.java | 6 + .../data/SuperAgentChatDialogue.java | 9 + .../data/SuperAgentChatExchange.java | 12 + .../ai/chatagent/dto/ChatRequestDto.java | 6 + .../model/ConversationExchangeView.java | 4 + .../model/ConversationSessionView.java | 3 + .../model/KnowledgeDocumentOptionView.java | 6 +- .../ai/chatagent/model/SearchReference.java | 10 +- .../rag/model/ConversationExecutionPlan.java | 15 + .../rag/model/RagRuntimeOptions.java | 117 +++++++ .../channel/GraphRagRetrievalChannel.java | 43 ++- .../channel/KeywordRetrievalChannel.java | 5 +- .../channel/RaptorRetrievalChannel.java | 41 ++- .../channel/TableRetrievalChannel.java | 32 +- .../channel/VectorRetrievalChannel.java | 3 +- .../service/ChatPreparationOrchestrator.java | 118 +++++-- .../DocumentRetrieveRequestFactory.java | 14 +- .../service/DocumentTableQueryPlanner.java | 91 +++++- .../KnowledgeBaseRuntimeConfigResolver.java | 253 +++++++++++++++ .../rag/service/RagCitationRepairService.java | 2 - .../rag/service/RagRetrievalEngine.java | 74 ++--- .../rag/support/SearchReferenceMapper.java | 5 +- .../service/BusinessChatService.java | 79 ++++- .../service/ConversationArchiveStore.java | 10 +- .../MybatisConversationArchiveStore.java | 78 ++++- .../chatagent/service/StreamLaunchPlan.java | 3 + .../javaup/ai/chatagent/service/TaskInfo.java | 8 + ...DocumentElasticsearchIndexInitializer.java | 6 - ...dgeRouteElasticsearchIndexInitializer.java | 1 - .../KnowledgeBaseManageController.java | 56 ++++ .../controller/KnowledgeManageController.java | 5 +- .../ai/manage/data/SuperAgentDocument.java | 8 +- .../manage/data/SuperAgentKnowledgeBase.java | 41 +++ .../data/SuperAgentKnowledgeRouteTrace.java | 8 + .../data/SuperAgentKnowledgeScopeNode.java | 2 + .../data/SuperAgentKnowledgeTopicNode.java | 2 + .../data/SuperAgentTopicDocumentRelation.java | 2 + .../ai/manage/dto/DocumentUploadDto.java | 8 +- .../dto/KnowledgeBaseConfigUpdateDto.java | 19 ++ .../ai/manage/dto/KnowledgeBaseDeleteDto.java | 9 + .../ai/manage/dto/KnowledgeBaseDetailDto.java | 9 + .../ai/manage/dto/KnowledgeBaseSaveDto.java | 31 ++ .../manage/dto/KnowledgeScopeDeleteDto.java | 2 + .../ai/manage/dto/KnowledgeScopeQueryDto.java | 9 + .../ai/manage/dto/KnowledgeScopeSaveDto.java | 2 + .../manage/dto/KnowledgeTopicDeleteDto.java | 2 + .../ai/manage/dto/KnowledgeTopicQueryDto.java | 2 + .../ai/manage/dto/KnowledgeTopicSaveDto.java | 2 + .../TopicDocumentRelationListQueryDto.java | 2 + .../dto/TopicDocumentRelationRemoveDto.java | 2 + .../dto/TopicDocumentRelationSaveDto.java | 2 + .../mapper/SuperAgentKnowledgeBaseMapper.java | 9 + .../manage/model/DocumentRetrieveFilters.java | 8 - .../model/KnowledgeBaseSelectionSnapshot.java | 49 +++ .../model/KnowledgeDocumentDescriptor.java | 8 +- .../model/es/DocumentKeywordIndexRecord.java | 9 - .../model/es/KnowledgeRouteIndexRecord.java | 4 +- .../model/route/DocumentRouteCandidate.java | 8 - .../model/route/KnowledgeRouteContext.java | 37 +++ .../service/DocumentKnowledgeService.java | 3 + .../service/KnowledgeBaseManageService.java | 33 ++ .../KnowledgeBaseRetrievalScopeService.java | 14 + .../service/KnowledgeManageService.java | 3 +- .../service/KnowledgeRouteIndexService.java | 4 +- .../manage/service/KnowledgeRouteService.java | 9 +- .../ai/manage/service/RaptorBuildService.java | 2 +- .../impl/DocumentAsyncProcessServiceImpl.java | 142 --------- .../impl/DocumentKnowledgeServiceImpl.java | 39 ++- .../impl/DocumentManageServiceImpl.java | 20 +- .../impl/DocumentProfileServiceImpl.java | 125 +------- ...asticsearchKnowledgeRouteIndexService.java | 63 ++-- ...GraphRagCrossDocumentIndexServiceImpl.java | 99 +++++- .../impl/KnowledgeBaseManageServiceImpl.java | 291 ++++++++++++++++++ ...nowledgeBaseRetrievalScopeServiceImpl.java | 136 ++++++++ .../impl/KnowledgeManageServiceImpl.java | 66 +++- .../impl/KnowledgeRouteServiceImpl.java | 211 +++++++------ .../service/impl/RaptorBuildServiceImpl.java | 46 ++- ...ticsearchDocumentKeywordSearchGateway.java | 38 +-- .../DocumentKnowledgeMetadataKeys.java | 7 +- .../ai/manage/support/RaptorScopeSupport.java | 18 +- .../ai/manage/vo/DocumentListItemVo.java | 8 +- .../ai/manage/vo/KnowledgeBaseItemVo.java | 37 +++ .../ai/manage/vo/KnowledgeBaseOptionVo.java | 23 ++ .../ai/manage/vo/KnowledgeScopeItemVo.java | 2 + .../ai/manage/vo/KnowledgeTopicItemVo.java | 2 + .../vo/TopicDocumentRelationItemVo.java | 10 +- .../DocumentScopeRetrievalChannelTest.java | 5 + .../channel/GraphRagRetrievalChannelTest.java | 18 +- .../channel/TableRetrievalChannelTest.java | 12 +- .../ChatPreparationOrchestratorTest.java | 33 +- ...nowledgeBaseRuntimeConfigResolverTest.java | 95 ++++++ .../rag/service/RagRetrievalEngineTest.java | 7 +- ...hRagCrossDocumentIndexServiceImplTest.java | 90 ++++-- .../support/RaptorScopeSupportTest.java | 22 +- .../enums/KnowledgeBaseSelectionMode.java | 31 ++ vue/src/api/api.js | 53 +++- vue/src/router/index.js | 8 + vue/src/views/BusinessChatView.vue | 215 +++++++++++-- vue/src/views/admin/AdminDocumentListView.vue | 65 ++-- .../views/admin/AdminKnowledgeBaseView.vue | 229 ++++++++++++++ .../admin/AdminKnowledgeRouteTraceView.vue | 4 +- .../views/admin/AdminKnowledgeRouteView.vue | 143 ++++++--- vue/src/views/admin/AdminLayoutView.vue | 2 + .../admin/AdminObservabilityDetailView.vue | 10 + 105 files changed, 3093 insertions(+), 865 deletions(-) create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/model/RagRuntimeOptions.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/KnowledgeBaseRuntimeConfigResolver.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/controller/KnowledgeBaseManageController.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeBase.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseConfigUpdateDto.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseDeleteDto.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseDetailDto.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseSaveDto.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeQueryDto.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/mapper/SuperAgentKnowledgeBaseMapper.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/KnowledgeBaseSelectionSnapshot.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/route/KnowledgeRouteContext.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeBaseManageService.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeBaseRetrievalScopeService.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeBaseManageServiceImpl.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeBaseRetrievalScopeServiceImpl.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeBaseItemVo.java create mode 100644 super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeBaseOptionVo.java create mode 100644 super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/KnowledgeBaseRuntimeConfigResolverTest.java create mode 100644 super-agent-common/super-agent-common-frame/src/main/java/org/javaup/enums/KnowledgeBaseSelectionMode.java create mode 100644 vue/src/views/admin/AdminKnowledgeBaseView.vue diff --git a/sql/Mysql/create_table_mysql.sql b/sql/Mysql/create_table_mysql.sql index 31548bb..7c6e511 100644 --- a/sql/Mysql/create_table_mysql.sql +++ b/sql/Mysql/create_table_mysql.sql @@ -5,6 +5,9 @@ CREATE TABLE IF NOT EXISTS super_agent_chat_dialogue ( chat_mode TINYINT(1) NOT NULL DEFAULT '1' COMMENT '1:当前文档问答 2:开放式提问', selected_document_id BIGINT DEFAULT NULL COMMENT '当前会话显式锁定的提问文档id', selected_document_name VARCHAR(255) DEFAULT NULL COMMENT '当前会话显式锁定的提问文档名称', + knowledge_base_selection_mode VARCHAR(16) NOT NULL DEFAULT 'NONE' COMMENT '当前会话知识库选择模式 NONE/ALL/SELECTED', + selected_knowledge_base_ids_json JSON DEFAULT NULL COMMENT '当前会话已选知识库id快照', + selected_knowledge_base_names_json JSON DEFAULT NULL COMMENT '当前会话已选知识库名称快照', create_time DATETIME DEFAULT NULL COMMENT '创建时间', edit_time DATETIME DEFAULT NULL COMMENT '编辑时间', status TINYINT(1) DEFAULT '1' COMMENT '1:正常 0:删除', @@ -28,6 +31,10 @@ CREATE TABLE IF NOT EXISTS super_agent_chat_exchange ( finish_note TEXT DEFAULT NULL COMMENT '失败或终止说明', first_token_latency_ms BIGINT DEFAULT NULL COMMENT '首包耗时,毫秒', total_latency_ms BIGINT DEFAULT NULL COMMENT '总耗时,毫秒', + knowledge_base_selection_mode VARCHAR(16) NOT NULL DEFAULT 'NONE' COMMENT '当轮知识库选择模式 NONE/ALL/SELECTED', + selected_knowledge_base_ids_json JSON DEFAULT NULL COMMENT '当轮已选知识库id快照', + selected_knowledge_base_names_json JSON DEFAULT NULL COMMENT '当轮已选知识库名称快照', + retrieval_config_snapshot_json JSON DEFAULT NULL COMMENT '当轮生效RAG检索配置快照', create_time DATETIME DEFAULT NULL COMMENT '创建时间', edit_time DATETIME DEFAULT NULL COMMENT '编辑时间', status TINYINT(1) DEFAULT '1' COMMENT '1:正常 0:删除', @@ -104,6 +111,28 @@ CREATE TABLE IF NOT EXISTS GRAPH_CHECKPOINT ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Spring AI Alibaba Graph checkpoint 表'; +CREATE TABLE IF NOT EXISTS `super_agent_knowledge_base` ( + `id` bigint NOT NULL COMMENT '主键id', + `base_code` varchar(64) NOT NULL COMMENT '知识库编码', + `base_name` varchar(128) NOT NULL COMMENT '知识库名称', + `description` varchar(1024) DEFAULT NULL COMMENT '知识库描述', + `embedding_model` varchar(128) DEFAULT NULL COMMENT '向量模型快照', + `retrieval_config_json` JSON DEFAULT NULL COMMENT '检索配置JSON', + `graph_rag_config_json` JSON DEFAULT NULL COMMENT 'GraphRAG配置JSON', + `raptor_config_json` JSON DEFAULT NULL COMMENT 'RAPTOR配置JSON', + `metadata_filter_json` JSON DEFAULT NULL COMMENT '元数据过滤配置JSON', + `is_default` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否默认知识库 1:是 0:否', + `sort_order` int DEFAULT '0' COMMENT '排序值', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `edit_time` datetime DEFAULT NULL COMMENT '编辑时间', + `status` tinyint(1) DEFAULT '1' COMMENT '1:正常 0:删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_knowledge_base_code` (`base_code`), + KEY `idx_knowledge_base_default` (`is_default`, `status`), + KEY `idx_knowledge_base_sort` (`sort_order`, `id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='轻量知识库表'; + + CREATE TABLE IF NOT EXISTS `super_agent_document` ( `id` bigint NOT NULL COMMENT '主键id', `document_name` varchar(255) NOT NULL COMMENT '文档名称', @@ -124,10 +153,9 @@ CREATE TABLE IF NOT EXISTS `super_agent_document` ( `content_quality_level` tinyint DEFAULT '0' COMMENT '内容质量 0:未知 1:低 2:中 3:高', `parse_text_path` varchar(512) DEFAULT NULL COMMENT '解析文本存储路径', `parse_error_msg` varchar(1000) DEFAULT NULL COMMENT '解析失败原因', - `knowledge_scope_code` varchar(64) DEFAULT NULL COMMENT '业务知识域编码,例如 oa / crm / finance', - `knowledge_scope_name` varchar(128) DEFAULT NULL COMMENT '业务知识域名称,例如 OA系统 / CRM系统', - `business_category` varchar(128) DEFAULT NULL COMMENT '业务分类,例如 流程 / 规则 / 操作手册', - `document_tags` varchar(512) DEFAULT NULL COMMENT '逗号分隔标签快照', + `knowledge_base_id` bigint NOT NULL COMMENT '所属知识库id', + `knowledge_base_code` varchar(64) NOT NULL COMMENT '所属知识库编码快照', + `knowledge_base_name` varchar(128) NOT NULL COMMENT '所属知识库名称快照', `current_plan_id` bigint DEFAULT NULL COMMENT '当前策略方案id', `last_parse_task_id` bigint DEFAULT NULL COMMENT '最近一次成功解析任务id', `structure_node_count` int DEFAULT '0' COMMENT '最近一次结构化解析生成的节点数', @@ -140,7 +168,7 @@ CREATE TABLE IF NOT EXISTS `super_agent_document` ( KEY `idx_parse_status` (`parse_status`), KEY `idx_strategy_status` (`strategy_status`), KEY `idx_index_status` (`index_status`), - KEY `idx_knowledge_scope_code` (`knowledge_scope_code`), + KEY `idx_knowledge_base_id` (`knowledge_base_id`), KEY `idx_current_plan_id` (`current_plan_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文档表'; @@ -716,6 +744,7 @@ CREATE TABLE IF NOT EXISTS `super_agent_raptor_node` ( CREATE TABLE IF NOT EXISTS `super_agent_knowledge_scope_node` ( `id` bigint NOT NULL COMMENT '主键id', + `knowledge_base_id` bigint NOT NULL COMMENT '所属知识库id', `scope_code` varchar(64) NOT NULL COMMENT '知识范围编码', `scope_name` varchar(128) NOT NULL COMMENT '知识范围名称', `parent_scope_code` varchar(64) DEFAULT NULL COMMENT '父级知识范围编码', @@ -727,7 +756,8 @@ CREATE TABLE IF NOT EXISTS `super_agent_knowledge_scope_node` ( `edit_time` datetime DEFAULT NULL COMMENT '编辑时间', `status` tinyint(1) DEFAULT '1' COMMENT '1:正常 0:删除', PRIMARY KEY (`id`), - UNIQUE KEY `uk_scope_code` (`scope_code`), + UNIQUE KEY `uk_scope_base_code` (`knowledge_base_id`, `scope_code`), + KEY `idx_knowledge_base_id` (`knowledge_base_id`), KEY `idx_parent_scope_code` (`parent_scope_code`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识范围节点表'; @@ -735,6 +765,7 @@ CREATE TABLE IF NOT EXISTS `super_agent_knowledge_scope_node` ( CREATE TABLE IF NOT EXISTS `super_agent_knowledge_topic_node` ( `id` bigint NOT NULL COMMENT '主键id', + `knowledge_base_id` bigint NOT NULL COMMENT '所属知识库id', `topic_code` varchar(64) NOT NULL COMMENT '主题编码', `topic_name` varchar(128) NOT NULL COMMENT '主题名称', `scope_code` varchar(64) NOT NULL COMMENT '所属知识范围编码', @@ -748,7 +779,8 @@ CREATE TABLE IF NOT EXISTS `super_agent_knowledge_topic_node` ( `edit_time` datetime DEFAULT NULL COMMENT '编辑时间', `status` tinyint(1) DEFAULT '1' COMMENT '1:正常 0:删除', PRIMARY KEY (`id`), - UNIQUE KEY `uk_topic_code` (`topic_code`), + UNIQUE KEY `uk_topic_base_code` (`knowledge_base_id`, `topic_code`), + KEY `idx_knowledge_base_id` (`knowledge_base_id`), KEY `idx_scope_code` (`scope_code`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识主题节点表'; @@ -782,6 +814,7 @@ CREATE TABLE IF NOT EXISTS `super_agent_document_profile` ( CREATE TABLE IF NOT EXISTS `super_agent_topic_document_relation` ( `id` bigint NOT NULL COMMENT '主键id', + `knowledge_base_id` bigint NOT NULL COMMENT '所属知识库id', `topic_code` varchar(64) NOT NULL COMMENT '主题编码', `document_id` bigint NOT NULL COMMENT '文档id', `relation_score` decimal(8,4) DEFAULT '0.0000' COMMENT '关联分数', @@ -791,7 +824,8 @@ CREATE TABLE IF NOT EXISTS `super_agent_topic_document_relation` ( `edit_time` datetime DEFAULT NULL COMMENT '编辑时间', `status` tinyint(1) DEFAULT '1' COMMENT '1:正常 0:删除', PRIMARY KEY (`id`), - UNIQUE KEY `uk_topic_document` (`topic_code`, `document_id`), + UNIQUE KEY `uk_base_topic_document` (`knowledge_base_id`, `topic_code`, `document_id`), + KEY `idx_knowledge_base_id` (`knowledge_base_id`), KEY `idx_document_id` (`document_id`), KEY `idx_topic_code` (`topic_code`), KEY `idx_status` (`status`) @@ -805,6 +839,10 @@ CREATE TABLE IF NOT EXISTS `super_agent_knowledge_route_trace` ( `question` text COMMENT '原始问题', `rewrite_question` text COMMENT '改写问题', `mode` varchar(32) DEFAULT NULL COMMENT '运行模式 shadow/auto', + `knowledge_base_selection_mode` varchar(16) DEFAULT NULL COMMENT '知识库选择模式 NONE/ALL/SELECTED', + `selected_knowledge_base_ids_json` JSON DEFAULT NULL COMMENT '已选知识库id快照', + `selected_knowledge_base_names_json` JSON DEFAULT NULL COMMENT '已选知识库名称快照', + `allowed_document_ids_json` JSON DEFAULT NULL COMMENT '知识库硬边界允许检索的文档id快照', `top_scopes_json` text COMMENT '候选知识范围 JSON', `top_topics_json` text COMMENT '候选主题 JSON', `top_documents_json` text COMMENT '候选文档 JSON', diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/controller/BusinessChatController.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/controller/BusinessChatController.java index dd57f71..5ab87cd 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/controller/BusinessChatController.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/controller/BusinessChatController.java @@ -15,6 +15,7 @@ import org.javaup.ai.chatagent.model.KnowledgeDocumentOptionView; import org.javaup.ai.chatagent.model.RetrievalResultView; import org.javaup.ai.chatagent.model.StageBenchmarkView; import org.javaup.ai.chatagent.service.BusinessChatService; +import org.javaup.ai.manage.vo.KnowledgeBaseOptionVo; import org.javaup.ai.chatagent.vo.ConversationResetVo; import org.javaup.ai.chatagent.vo.ConversationSessionListVo; import org.javaup.ai.chatagent.vo.ConversationStopVo; @@ -49,6 +50,11 @@ public class BusinessChatController { return ApiResponse.ok(businessChatService.listKnowledgeDocumentOptions()); } + @PostMapping("/knowledge-base/options") + public ApiResponse> knowledgeBaseOptions() { + return ApiResponse.ok(businessChatService.listKnowledgeBaseOptions()); + } + @PostMapping("/session/stop") public ApiResponse stop(@Valid @RequestBody ConversationIdentityDto dto) { return ApiResponse.ok(businessChatService.stopConversation(dto.getConversationId())); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/data/SuperAgentChatDialogue.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/data/SuperAgentChatDialogue.java index b0b18da..03f21bb 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/data/SuperAgentChatDialogue.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/data/SuperAgentChatDialogue.java @@ -40,4 +40,13 @@ public class SuperAgentChatDialogue extends BaseTableData { @TableField("selected_document_name") private String selectedDocumentName; + + @TableField("knowledge_base_selection_mode") + private String knowledgeBaseSelectionMode; + + @TableField("selected_knowledge_base_ids_json") + private String selectedKnowledgeBaseIdsJson; + + @TableField("selected_knowledge_base_names_json") + private String selectedKnowledgeBaseNamesJson; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/data/SuperAgentChatExchange.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/data/SuperAgentChatExchange.java index 3e5b4cb..ea5b3cd 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/data/SuperAgentChatExchange.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/data/SuperAgentChatExchange.java @@ -61,4 +61,16 @@ public class SuperAgentChatExchange extends BaseTableData { @TableField("total_latency_ms") private Long totalResponseTimeMs; + + @TableField("knowledge_base_selection_mode") + private String knowledgeBaseSelectionMode; + + @TableField("selected_knowledge_base_ids_json") + private String selectedKnowledgeBaseIdsJson; + + @TableField("selected_knowledge_base_names_json") + private String selectedKnowledgeBaseNamesJson; + + @TableField("retrieval_config_snapshot_json") + private String retrievalConfigSnapshotJson; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/dto/ChatRequestDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/dto/ChatRequestDto.java index f96b2f5..360ae78 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/dto/ChatRequestDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/dto/ChatRequestDto.java @@ -5,6 +5,8 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + /** * @program: 企业级别深度设计 AI Agent。添加 阿星不是程序员 微信,添加时备注 super 来获取项目的完整资料 * @description: 数据传输对象 @@ -24,4 +26,8 @@ public class ChatRequestDto { private String chatMode; private String selectedDocumentId; + + private String knowledgeBaseSelectionMode; + + private List selectedKnowledgeBaseIds; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/ConversationExchangeView.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/ConversationExchangeView.java index baefc60..e9f07e1 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/ConversationExchangeView.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/ConversationExchangeView.java @@ -32,6 +32,10 @@ public class ConversationExchangeView { private String errorMessage; private Long firstResponseTimeMs; private Long totalResponseTimeMs; + private String knowledgeBaseSelectionMode; + private List selectedKnowledgeBaseIds; + private List selectedKnowledgeBaseNames; + private String retrievalConfigSnapshotJson; private Date createTime; private Date editTime; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/ConversationSessionView.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/ConversationSessionView.java index e489145..7713804 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/ConversationSessionView.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/ConversationSessionView.java @@ -31,6 +31,9 @@ public class ConversationSessionView { private ChatQueryMode chatMode; private String selectedDocumentId; private String selectedDocumentName; + private String knowledgeBaseSelectionMode; + private List selectedKnowledgeBaseIds; + private List selectedKnowledgeBaseNames; private Instant createdAt; private Instant updatedAt; private List exchanges; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/KnowledgeDocumentOptionView.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/KnowledgeDocumentOptionView.java index 358f8b3..350ab95 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/KnowledgeDocumentOptionView.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/KnowledgeDocumentOptionView.java @@ -17,7 +17,7 @@ public class KnowledgeDocumentOptionView { private String documentId; private String documentName; - private String knowledgeScopeName; - private String businessCategory; - private String documentTags; + private String knowledgeBaseId; + private String knowledgeBaseCode; + private String knowledgeBaseName; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/SearchReference.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/SearchReference.java index 727afb8..bcf1013 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/SearchReference.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/model/SearchReference.java @@ -29,6 +29,12 @@ public class SearchReference { private String documentName; + private Long knowledgeBaseId; + + private String knowledgeBaseCode; + + private String knowledgeBaseName; + private Long chunkId; private Long parentBlockId; @@ -57,10 +63,6 @@ public class SearchReference { private String toolName; - private String knowledgeScopeCode; - - private String knowledgeScopeName; - private Integer pageNo; private String pageRange; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/model/ConversationExecutionPlan.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/model/ConversationExecutionPlan.java index 2528b9f..3c05e37 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/model/ConversationExecutionPlan.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/model/ConversationExecutionPlan.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.javaup.enums.ChatQueryMode; +import org.javaup.enums.KnowledgeBaseSelectionMode; import java.time.LocalDate; import java.util.ArrayList; @@ -88,6 +89,20 @@ public class ConversationExecutionPlan { @Builder.Default private List retrievalTaskIds = new ArrayList<>(); + @Builder.Default + private KnowledgeBaseSelectionMode knowledgeBaseSelectionMode = KnowledgeBaseSelectionMode.NONE; + + @Builder.Default + private List selectedKnowledgeBaseIds = new ArrayList<>(); + + @Builder.Default + private List selectedKnowledgeBaseNames = new ArrayList<>(); + + @Builder.Default + private List allowedKnowledgeBaseDocumentIds = new ArrayList<>(); + + private RagRuntimeOptions ragRuntimeOptions; + private String clarificationReply; @Builder.Default diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/model/RagRuntimeOptions.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/model/RagRuntimeOptions.java new file mode 100644 index 0000000..50e56fc --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/model/RagRuntimeOptions.java @@ -0,0 +1,117 @@ +package org.javaup.ai.chatagent.rag.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.javaup.ai.chatagent.rag.config.ChatRagProperties; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RagRuntimeOptions { + + private int vectorTopK; + + private int keywordTopK; + + private int graphRagTopK; + + private int graphRagMaxHops; + + private int raptorTopK; + + private int raptorSourceChunkTopK; + + private int candidateTopK; + + private int finalTopK; + + private double minVectorSimilarity; + + private double keywordRelativeScoreFloor; + + private boolean keywordChannelEnabled; + + private boolean tableChannelEnabled; + + private boolean graphRagChannelEnabled; + + private boolean raptorChannelEnabled; + + private HybridOptions hybrid; + + @Builder.Default + private List kbConfigConflictFields = new ArrayList<>(); + + public static RagRuntimeOptions from(ChatRagProperties properties) { + ChatRagProperties.HybridProperties hybridProperties = properties == null ? null : properties.getHybrid(); + return RagRuntimeOptions.builder() + .vectorTopK(properties == null ? 8 : properties.getVectorTopK()) + .keywordTopK(properties == null ? 8 : properties.getKeywordTopK()) + .graphRagTopK(properties == null ? 5 : properties.getGraphRagTopK()) + .graphRagMaxHops(properties == null ? 2 : properties.getGraphRagMaxHops()) + .raptorTopK(properties == null ? 5 : properties.getRaptorTopK()) + .raptorSourceChunkTopK(properties == null ? 3 : properties.getRaptorSourceChunkTopK()) + .candidateTopK(properties == null ? 10 : properties.getCandidateTopK()) + .finalTopK(properties == null ? 5 : properties.getFinalTopK()) + .minVectorSimilarity(properties == null ? 0.45D : properties.getMinVectorSimilarity()) + .keywordRelativeScoreFloor(properties == null ? 0.35D : properties.getKeywordRelativeScoreFloor()) + .keywordChannelEnabled(properties == null || properties.isKeywordChannelEnabled()) + .tableChannelEnabled(properties == null || properties.isTableChannelEnabled()) + .graphRagChannelEnabled(properties == null || properties.isGraphRagChannelEnabled()) + .raptorChannelEnabled(properties == null || properties.isRaptorChannelEnabled()) + .hybrid(HybridOptions.from(hybridProperties)) + .kbConfigConflictFields(new ArrayList<>()) + .build(); + } + + public static RagRuntimeOptions resolve(ConversationExecutionPlan plan, ChatRagProperties properties) { + return plan == null || plan.getRagRuntimeOptions() == null + ? from(properties) + : plan.getRagRuntimeOptions(); + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class HybridOptions { + + private double vectorWeight; + + private double keywordWeight; + + private double tableWeight; + + private double graphRagWeight; + + private double raptorWeight; + + private double rankWeight; + + private double originalScoreWeight; + + private double metadataBoostWeight; + + private double maxMetadataBoost; + + public static HybridOptions from(ChatRagProperties.HybridProperties properties) { + return HybridOptions.builder() + .vectorWeight(properties == null ? 1.0D : properties.getVectorWeight()) + .keywordWeight(properties == null ? 1.0D : properties.getKeywordWeight()) + .tableWeight(properties == null ? 1.2D : properties.getTableWeight()) + .graphRagWeight(properties == null ? 1.1D : properties.getGraphRagWeight()) + .raptorWeight(properties == null ? 1.05D : properties.getRaptorWeight()) + .rankWeight(properties == null ? 1.0D : properties.getRankWeight()) + .originalScoreWeight(properties == null ? 0.08D : properties.getOriginalScoreWeight()) + .metadataBoostWeight(properties == null ? 0.04D : properties.getMetadataBoostWeight()) + .maxMetadataBoost(properties == null ? 1.0D : properties.getMaxMetadataBoost()) + .build(); + } + } +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/GraphRagRetrievalChannel.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/GraphRagRetrievalChannel.java index ae646ec..10a6f2b 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/GraphRagRetrievalChannel.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/GraphRagRetrievalChannel.java @@ -3,6 +3,7 @@ package org.javaup.ai.chatagent.rag.retrieve.channel; import cn.hutool.core.util.StrUtil; import org.javaup.ai.chatagent.rag.config.ChatRagProperties; import org.javaup.ai.chatagent.rag.model.ConversationExecutionPlan; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; import org.javaup.ai.chatagent.rag.service.DocumentRetrieveRequestFactory; import org.javaup.ai.manage.model.DocumentRetrieveRequest; import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; @@ -52,33 +53,37 @@ public class GraphRagRetrievalChannel implements RetrievalChannel { @Override public boolean supports(ConversationExecutionPlan plan) { return plan != null - && properties.isGraphRagChannelEnabled() + && RagRuntimeOptions.resolve(plan, properties).isGraphRagChannelEnabled() && !resolvedDocumentIds(plan).isEmpty(); } @Override public RetrievalChannelResult retrieve(String subQuestion, ConversationExecutionPlan plan) { - DocumentRetrieveRequest request = documentRetrieveRequestFactory.build(subQuestion, plan, properties.getGraphRagTopK()); + RagRuntimeOptions options = RagRuntimeOptions.resolve(plan, properties); + DocumentRetrieveRequest request = documentRetrieveRequestFactory.build(subQuestion, plan, options.getGraphRagTopK()); List results = graphRagSearchService.search( StrUtil.blankToDefault(request.getRetrievalQuery(), subQuestion), request.resolvedDocumentIds(), request.resolvedTaskIds(), - properties.getGraphRagTopK(), - properties.getGraphRagMaxHops() + options.getGraphRagTopK(), + options.getGraphRagMaxHops() ); if (results.isEmpty()) { return new RetrievalChannelResult(channelName(), List.of()); } - Map documentNames = resolveDocumentNames(); + Map documentDescriptors = resolveDocumentDescriptors(plan); List documents = results.stream() - .map(result -> toDocument(subQuestion, result, documentNames)) + .map(result -> toDocument(subQuestion, result, documentDescriptors)) .toList(); return new RetrievalChannelResult(channelName(), documents); } - private Document toDocument(String subQuestion, GraphRagSearchResult result, Map documentNames) { - String documentName = StrUtil.blankToDefault(documentNames.get(result.getDocumentId()), "文档图谱"); + private Document toDocument(String subQuestion, + GraphRagSearchResult result, + Map documentDescriptors) { + KnowledgeDocumentDescriptor descriptor = documentDescriptors.get(result.getDocumentId()); + String documentName = StrUtil.blankToDefault(descriptor == null ? null : descriptor.getDocumentName(), "文档图谱"); String text = renderEvidenceText(subQuestion, result); Map metadata = new LinkedHashMap<>(); metadata.put(DocumentKnowledgeMetadataKeys.SOURCE_TYPE, SOURCE_TYPE); @@ -86,6 +91,11 @@ public class GraphRagRetrievalChannel implements RetrievalChannel { metadata.put(DocumentKnowledgeMetadataKeys.SCORE, result.getScore()); putIfNotNull(metadata, DocumentKnowledgeMetadataKeys.DOCUMENT_ID, result.getDocumentId()); metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_NAME, documentName); + if (descriptor != null) { + putIfNotNull(metadata, DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_ID, descriptor.getKnowledgeBaseId()); + metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_CODE, StrUtil.blankToDefault(descriptor.getKnowledgeBaseCode(), "")); + metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_NAME, StrUtil.blankToDefault(descriptor.getKnowledgeBaseName(), "")); + } putIfNotNull(metadata, DocumentKnowledgeMetadataKeys.TASK_ID, result.getTaskId()); if (!isCommunityReportResult(result)) { putIfNotNull(metadata, DocumentKnowledgeMetadataKeys.PARENT_BLOCK_ID, result.getParentBlockId()); @@ -249,14 +259,21 @@ public class GraphRagRetrievalChannel implements RetrievalChannel { return plan.getSelectedTaskId() == null ? List.of() : List.of(plan.getSelectedTaskId()); } - private Map resolveDocumentNames() { - Map documentNames = new LinkedHashMap<>(); - for (KnowledgeDocumentDescriptor descriptor : documentKnowledgeService.listRetrievableDocuments()) { + private Map resolveDocumentDescriptors(ConversationExecutionPlan plan) { + Map documentDescriptors = new LinkedHashMap<>(); + List documentIds = resolvedDocumentIds(plan); + List descriptors = plan == null + || plan.getSelectedKnowledgeBaseIds() == null + || plan.getSelectedKnowledgeBaseIds().isEmpty() + ? documentKnowledgeService.listRetrievableDocuments() + : documentKnowledgeService.listRetrievableDocumentsByKnowledgeBaseIds(plan.getSelectedKnowledgeBaseIds()); + for (KnowledgeDocumentDescriptor descriptor : descriptors) { if (descriptor.getDocumentId() != null) { - documentNames.put(descriptor.getDocumentId(), descriptor.getDocumentName()); + documentDescriptors.put(descriptor.getDocumentId(), descriptor); } } - return documentNames; + documentDescriptors.keySet().retainAll(documentIds); + return documentDescriptors; } private void putIfNotNull(Map metadata, String key, Object value) { diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/KeywordRetrievalChannel.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/KeywordRetrievalChannel.java index 7a5433d..806e8e2 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/KeywordRetrievalChannel.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/KeywordRetrievalChannel.java @@ -3,6 +3,7 @@ package org.javaup.ai.chatagent.rag.retrieve.channel; import cn.hutool.core.collection.CollectionUtil; import org.javaup.ai.chatagent.rag.config.ChatRagProperties; import org.javaup.ai.chatagent.rag.model.ConversationExecutionPlan; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; import org.javaup.ai.chatagent.rag.service.DocumentRetrieveRequestFactory; import org.javaup.ai.manage.service.DocumentKnowledgeService; import org.javaup.enums.RetrievalChannelEnum; @@ -40,14 +41,14 @@ public class KeywordRetrievalChannel implements RetrievalChannel { @Override public boolean supports(ConversationExecutionPlan plan) { - return properties.isKeywordChannelEnabled() + return RagRuntimeOptions.resolve(plan, properties).isKeywordChannelEnabled() && hasDocumentScope(plan); } @Override public RetrievalChannelResult retrieve(String subQuestion, ConversationExecutionPlan plan) { List documentList = documentKnowledgeService.keywordSearch( - documentRetrieveRequestFactory.build(subQuestion, plan, properties.getKeywordTopK()) + documentRetrieveRequestFactory.build(subQuestion, plan, RagRuntimeOptions.resolve(plan, properties).getKeywordTopK()) ); return new RetrievalChannelResult( diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/RaptorRetrievalChannel.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/RaptorRetrievalChannel.java index e6f0679..152bfea 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/RaptorRetrievalChannel.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/RaptorRetrievalChannel.java @@ -3,6 +3,7 @@ package org.javaup.ai.chatagent.rag.retrieve.channel; import cn.hutool.core.util.StrUtil; import org.javaup.ai.chatagent.rag.config.ChatRagProperties; import org.javaup.ai.chatagent.rag.model.ConversationExecutionPlan; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; import org.javaup.ai.manage.model.raptor.RaptorSearchResult; import org.javaup.ai.manage.service.DocumentKnowledgeService; @@ -41,32 +42,36 @@ public class RaptorRetrievalChannel implements RetrievalChannel { @Override public boolean supports(ConversationExecutionPlan plan) { return plan != null - && properties.isRaptorChannelEnabled() + && RagRuntimeOptions.resolve(plan, properties).isRaptorChannelEnabled() && !resolvedDocumentIds(plan).isEmpty(); } @Override public RetrievalChannelResult retrieve(String subQuestion, ConversationExecutionPlan plan) { + RagRuntimeOptions options = RagRuntimeOptions.resolve(plan, properties); List results = raptorSearchService.search( subQuestion, resolvedDocumentIds(plan), resolvedTaskIds(plan), - properties.getRaptorTopK(), - properties.getRaptorSourceChunkTopK() + options.getRaptorTopK(), + options.getRaptorSourceChunkTopK() ); if (results.isEmpty()) { return new RetrievalChannelResult(channelName(), List.of()); } - Map documentNames = resolveDocumentNames(); + Map documentDescriptors = resolveDocumentDescriptors(plan); List documents = results.stream() - .map(result -> toDocument(subQuestion, result, documentNames)) + .map(result -> toDocument(subQuestion, result, documentDescriptors)) .toList(); return new RetrievalChannelResult(channelName(), documents); } - private Document toDocument(String subQuestion, RaptorSearchResult result, Map documentNames) { - String documentName = StrUtil.blankToDefault(documentNames.get(result.getDocumentId()), "文档摘要树"); + private Document toDocument(String subQuestion, + RaptorSearchResult result, + Map documentDescriptors) { + KnowledgeDocumentDescriptor descriptor = documentDescriptors.get(result.getDocumentId()); + String documentName = StrUtil.blankToDefault(descriptor == null ? null : descriptor.getDocumentName(), "文档摘要树"); String text = renderEvidenceText(subQuestion, result); Map metadata = new LinkedHashMap<>(); metadata.put(DocumentKnowledgeMetadataKeys.SOURCE_TYPE, SOURCE_TYPE); @@ -74,6 +79,11 @@ public class RaptorRetrievalChannel implements RetrievalChannel { metadata.put(DocumentKnowledgeMetadataKeys.SCORE, result.getScore()); metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_ID, result.getDocumentId()); metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_NAME, documentName); + if (descriptor != null) { + putIfNotNull(metadata, DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_ID, descriptor.getKnowledgeBaseId()); + metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_CODE, StrUtil.blankToDefault(descriptor.getKnowledgeBaseCode(), "")); + metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_NAME, StrUtil.blankToDefault(descriptor.getKnowledgeBaseName(), "")); + } metadata.put(DocumentKnowledgeMetadataKeys.TASK_ID, result.getTaskId()); metadata.put(DocumentKnowledgeMetadataKeys.PARENT_BLOCK_ID, result.getParentBlockId()); metadata.put(DocumentKnowledgeMetadataKeys.CHUNK_ID, result.getChunkId()); @@ -130,14 +140,21 @@ public class RaptorRetrievalChannel implements RetrievalChannel { return plan.getSelectedTaskId() == null ? List.of() : List.of(plan.getSelectedTaskId()); } - private Map resolveDocumentNames() { - Map documentNames = new LinkedHashMap<>(); - for (KnowledgeDocumentDescriptor descriptor : documentKnowledgeService.listRetrievableDocuments()) { + private Map resolveDocumentDescriptors(ConversationExecutionPlan plan) { + Map documentDescriptors = new LinkedHashMap<>(); + List documentIds = resolvedDocumentIds(plan); + List descriptors = plan == null + || plan.getSelectedKnowledgeBaseIds() == null + || plan.getSelectedKnowledgeBaseIds().isEmpty() + ? documentKnowledgeService.listRetrievableDocuments() + : documentKnowledgeService.listRetrievableDocumentsByKnowledgeBaseIds(plan.getSelectedKnowledgeBaseIds()); + for (KnowledgeDocumentDescriptor descriptor : descriptors) { if (descriptor.getDocumentId() != null) { - documentNames.put(descriptor.getDocumentId(), descriptor.getDocumentName()); + documentDescriptors.put(descriptor.getDocumentId(), descriptor); } } - return documentNames; + documentDescriptors.keySet().retainAll(documentIds); + return documentDescriptors; } private void putIfNotNull(Map metadata, String key, Object value) { diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/TableRetrievalChannel.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/TableRetrievalChannel.java index 9aebd14..e992d75 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/TableRetrievalChannel.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/TableRetrievalChannel.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil; import org.javaup.ai.chatagent.rag.model.ConversationExecutionPlan; import org.javaup.ai.chatagent.rag.model.DocumentTableQueryPlan; import org.javaup.ai.chatagent.rag.config.ChatRagProperties; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; import org.javaup.ai.chatagent.rag.service.DocumentTableQueryPlanner; import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; import org.javaup.ai.manage.model.table.DocumentTableDescriptor; @@ -50,7 +51,7 @@ public class TableRetrievalChannel implements RetrievalChannel { @Override public boolean supports(ConversationExecutionPlan plan) { return plan != null - && properties.isTableChannelEnabled() + && RagRuntimeOptions.resolve(plan, properties).isTableChannelEnabled() && !resolvedDocumentIds(plan).isEmpty(); } @@ -67,15 +68,16 @@ public class TableRetrievalChannel implements RetrievalChannel { DocumentTableQueryPlan planned = queryPlan.get(); DocumentTableQueryResult result = tableStructureService.query(planned.getQuery()); - Document document = buildEvidenceDocument(subQuestion, planned, result, resolveDocumentNames()); + Document document = buildEvidenceDocument(subQuestion, planned, result, resolveDocumentDescriptors(plan)); return new RetrievalChannelResult(channelName(), List.of(document)); } private Document buildEvidenceDocument(String subQuestion, DocumentTableQueryPlan queryPlan, DocumentTableQueryResult result, - Map documentNames) { - String documentName = StrUtil.blankToDefault(documentNames.get(result.getDocumentId()), "文档表格"); + Map documentDescriptors) { + KnowledgeDocumentDescriptor descriptor = documentDescriptors.get(result.getDocumentId()); + String documentName = StrUtil.blankToDefault(descriptor == null ? null : descriptor.getDocumentName(), "文档表格"); String text = renderEvidenceText(subQuestion, queryPlan, result); Map metadata = new LinkedHashMap<>(); metadata.put(DocumentKnowledgeMetadataKeys.SOURCE_TYPE, SOURCE_TYPE); @@ -83,6 +85,11 @@ public class TableRetrievalChannel implements RetrievalChannel { metadata.put(DocumentKnowledgeMetadataKeys.SCORE, TABLE_QUERY_SCORE); metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_ID, result.getDocumentId()); metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_NAME, documentName); + if (descriptor != null) { + putIfNotNull(metadata, DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_ID, descriptor.getKnowledgeBaseId()); + metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_CODE, StrUtil.blankToDefault(descriptor.getKnowledgeBaseCode(), "")); + metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_NAME, StrUtil.blankToDefault(descriptor.getKnowledgeBaseName(), "")); + } metadata.put(DocumentKnowledgeMetadataKeys.TASK_ID, result.getTaskId()); metadata.put(DocumentKnowledgeMetadataKeys.SECTION_PATH, StrUtil.blankToDefault(result.getSectionPath(), "")); putIfNotNull(metadata, DocumentKnowledgeMetadataKeys.PAGE_NO, result.getPageNo()); @@ -165,14 +172,21 @@ public class TableRetrievalChannel implements RetrievalChannel { return plan.getSelectedTaskId() == null ? List.of() : List.of(plan.getSelectedTaskId()); } - private Map resolveDocumentNames() { - Map documentNames = new LinkedHashMap<>(); - for (KnowledgeDocumentDescriptor descriptor : documentKnowledgeService.listRetrievableDocuments()) { + private Map resolveDocumentDescriptors(ConversationExecutionPlan plan) { + Map documentDescriptors = new LinkedHashMap<>(); + List documentIds = resolvedDocumentIds(plan); + List descriptors = plan == null + || plan.getSelectedKnowledgeBaseIds() == null + || plan.getSelectedKnowledgeBaseIds().isEmpty() + ? documentKnowledgeService.listRetrievableDocuments() + : documentKnowledgeService.listRetrievableDocumentsByKnowledgeBaseIds(plan.getSelectedKnowledgeBaseIds()); + for (KnowledgeDocumentDescriptor descriptor : descriptors) { if (descriptor.getDocumentId() != null) { - documentNames.put(descriptor.getDocumentId(), descriptor.getDocumentName()); + documentDescriptors.put(descriptor.getDocumentId(), descriptor); } } - return documentNames; + documentDescriptors.keySet().retainAll(documentIds); + return documentDescriptors; } private void putIfNotNull(Map metadata, String key, Object value) { diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/VectorRetrievalChannel.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/VectorRetrievalChannel.java index 7c3adfd..fa4e83e 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/VectorRetrievalChannel.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/retrieve/channel/VectorRetrievalChannel.java @@ -3,6 +3,7 @@ package org.javaup.ai.chatagent.rag.retrieve.channel; import cn.hutool.core.collection.CollectionUtil; import org.javaup.ai.chatagent.rag.config.ChatRagProperties; import org.javaup.ai.chatagent.rag.model.ConversationExecutionPlan; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; import org.javaup.ai.chatagent.rag.service.DocumentRetrieveRequestFactory; import org.javaup.ai.manage.service.DocumentKnowledgeService; import org.javaup.enums.RetrievalChannelEnum; @@ -47,7 +48,7 @@ public class VectorRetrievalChannel implements RetrievalChannel { public RetrievalChannelResult retrieve(String subQuestion, ConversationExecutionPlan plan) { List documentList = documentKnowledgeService.vectorSearch( - documentRetrieveRequestFactory.build(subQuestion, plan, properties.getVectorTopK()) + documentRetrieveRequestFactory.build(subQuestion, plan, RagRuntimeOptions.resolve(plan, properties).getVectorTopK()) ); return new RetrievalChannelResult( channelName(), documentList diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/ChatPreparationOrchestrator.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/ChatPreparationOrchestrator.java index 0e2b20a..38564f5 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/ChatPreparationOrchestrator.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/ChatPreparationOrchestrator.java @@ -20,11 +20,14 @@ import org.javaup.ai.chatagent.service.ConversationTraceRecorder; import org.javaup.ai.chatagent.service.TaskInfo; import org.javaup.ai.chatagent.support.TimeSensitiveQueryHelper; import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; import org.javaup.ai.manage.model.route.DocumentRouteCandidate; +import org.javaup.ai.manage.model.route.KnowledgeRouteContext; import org.javaup.ai.manage.model.route.KnowledgeRouteDecision; import org.javaup.ai.manage.service.DocumentKnowledgeService; import org.javaup.ai.manage.service.KnowledgeRouteService; import org.javaup.enums.ChatQueryMode; +import org.javaup.enums.KnowledgeBaseSelectionMode; import org.springframework.stereotype.Service; import java.math.BigDecimal; @@ -80,6 +83,7 @@ public class ChatPreparationOrchestrator { Long selectedDocumentId = taskInfo.selectedDocumentId(); String selectedDocumentName = taskInfo.selectedDocumentName(); Long selectedTaskId = taskInfo.selectedTaskId(); + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection = taskInfo.knowledgeBaseSelectionSnapshot(); LocalDate currentDate = taskInfo.currentDate(); String currentDateText = taskInfo.currentDateText(); ConversationTraceRecorder traceRecorder = taskInfo.traceRecorder(); @@ -125,7 +129,7 @@ public class ChatPreparationOrchestrator { if (chatMode == ChatQueryMode.OPEN_CHAT) { ConversationExecutionPlan plan = basePlan(question, chatMode, memoryContext, historyPlanningContext, historySummary, answerHistoryContext, currentDate, currentDateText, - requiresCurrentDateAnchoring, requiresFreshSearch) + requiresCurrentDateAnchoring, requiresFreshSearch, knowledgeBaseSelection) .mode(ExecutionMode.REACT_AGENT) .build(); if (traceRecorder != null) { @@ -139,6 +143,12 @@ public class ChatPreparationOrchestrator { } return plan; } + if (selectionMode(knowledgeBaseSelection) == KnowledgeBaseSelectionMode.NONE) { + return basePlan(question, ChatQueryMode.OPEN_CHAT, memoryContext, historyPlanningContext, historySummary, answerHistoryContext, currentDate, currentDateText, + requiresCurrentDateAnchoring, requiresFreshSearch, knowledgeBaseSelection) + .mode(ExecutionMode.REACT_AGENT) + .build(); + } if (!properties.isEnabled()) { throw new IllegalStateException("当前文档问答模式未启用,请先开启聊天侧 RAG 编排"); @@ -185,9 +195,10 @@ public class ChatPreparationOrchestrator { List routedDocumentIds = routedDocumentId == null ? List.of() : List.of(routedDocumentId); List routedTaskIds = routedTaskId == null ? List.of() : List.of(routedTaskId); if (chatMode == ChatQueryMode.AUTO_DOCUMENT) { - KnowledgeRouteDecision routeDecision = knowledgeRouteService.route(question, rewriteQuestion); - knowledgeRouteService.recordAutoRoute(conversationId, taskInfo.exchangeId(), question, rewriteQuestion, routeDecision); - List candidateDocuments = selectAutoCandidates(routeDecision, question, rewriteQuestion); + KnowledgeRouteContext routeContext = buildRouteContext(question, rewriteQuestion, knowledgeBaseSelection); + KnowledgeRouteDecision routeDecision = knowledgeRouteService.route(routeContext); + knowledgeRouteService.recordAutoRoute(conversationId, taskInfo.exchangeId(), routeContext, routeDecision); + List candidateDocuments = selectAutoCandidates(routeDecision, question, rewriteQuestion, allowedDocuments(knowledgeBaseSelection)); boolean lowConfidenceMultiDocumentRetrieval = shouldAllowLowConfidenceMultiDocumentRetrieval(routeDecision, candidateDocuments); if (lowConfidenceMultiDocumentRetrieval) { log.info("自动知识路由低置信多文档候选进入检索: conversationId={}, confidence={}, candidateDocumentCount={}, threshold=[{}, {})", @@ -200,7 +211,7 @@ public class ChatPreparationOrchestrator { if (shouldAskClarification(routeDecision, candidateDocuments, lowConfidenceMultiDocumentRetrieval)) { recordAutoDocumentRouteTrace(traceRecorder, routeDecision, candidateDocuments, true, false, null); return basePlan(question, chatMode, memoryContext, historyPlanningContext, historySummary, answerHistoryContext, currentDate, currentDateText, - requiresCurrentDateAnchoring, requiresFreshSearch) + requiresCurrentDateAnchoring, requiresFreshSearch, knowledgeBaseSelection) .mode(ExecutionMode.CLARIFICATION) .rewriteQuestion(rewriteQuestion) .rewriteSubQuestions(rewriteSubQuestions) @@ -249,7 +260,8 @@ public class ChatPreparationOrchestrator { lowConfidenceMultiDocumentRetrieval); } else if (chatMode == ChatQueryMode.DOCUMENT) { - knowledgeRouteService.recordShadowRoute(conversationId, taskInfo.exchangeId(), selectedDocumentId, question, rewriteQuestion); + knowledgeRouteService.recordShadowRoute(conversationId, taskInfo.exchangeId(), selectedDocumentId, + buildRouteContext(question, rewriteQuestion, knowledgeBaseSelection)); } ConversationTraceRecorder.StageHandle routeStage = traceRecorder == null @@ -318,7 +330,7 @@ public class ChatPreparationOrchestrator { navigationDecision == null || navigationDecision.getStructureAnchor() == null ? "" : safeText(navigationDecision.getStructureAnchor().getTargetSectionHint())); return basePlan(question, chatMode, memoryContext, historyPlanningContext, historySummary, routedAnswerHistoryContext, currentDate, currentDateText, - requiresCurrentDateAnchoring, requiresFreshSearch) + requiresCurrentDateAnchoring, requiresFreshSearch, knowledgeBaseSelection) .mode(executionMode) .navigationDecision(navigationDecision) .queryUnderstanding(queryUnderstanding) @@ -362,7 +374,8 @@ public class ChatPreparationOrchestrator { LocalDate currentDate, String currentDateText, boolean requiresCurrentDateAnchoring, - boolean requiresFreshSearch) { + boolean requiresFreshSearch, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { return ConversationExecutionPlan.builder() .chatMode(chatMode) .originalQuestion(question) @@ -385,6 +398,11 @@ public class ChatPreparationOrchestrator { .currentDateText(currentDateText) .requiresCurrentDateAnchoring(requiresCurrentDateAnchoring) .requiresFreshSearch(requiresFreshSearch) + .knowledgeBaseSelectionMode(selectionMode(knowledgeBaseSelection)) + .selectedKnowledgeBaseIds(selectedKnowledgeBaseIds(knowledgeBaseSelection)) + .selectedKnowledgeBaseNames(selectedKnowledgeBaseNames(knowledgeBaseSelection)) + .allowedKnowledgeBaseDocumentIds(allowedDocumentIds(knowledgeBaseSelection)) + .ragRuntimeOptions(knowledgeBaseSelection == null ? null : knowledgeBaseSelection.getRagRuntimeOptions()) .noEvidenceReply(properties.getNoEvidenceReply()); } @@ -506,10 +524,6 @@ public class ChatPreparationOrchestrator { item.put("documentId", StrUtil.blankToDefault(document.getDocumentId(), "")); item.put("documentName", StrUtil.blankToDefault(document.getDocumentName(), "")); item.put("lastIndexTaskId", StrUtil.blankToDefault(document.getLastIndexTaskId(), "")); - item.put("knowledgeScopeCode", StrUtil.blankToDefault(document.getKnowledgeScopeCode(), "")); - item.put("knowledgeScopeName", StrUtil.blankToDefault(document.getKnowledgeScopeName(), "")); - item.put("businessCategory", StrUtil.blankToDefault(document.getBusinessCategory(), "")); - item.put("documentTags", StrUtil.blankToDefault(document.getDocumentTags(), "")); item.put("score", document.getScore() == null ? "" : document.getScore().toPlainString()); item.put("reason", StrUtil.blankToDefault(document.getReason(), "")); return item; @@ -657,28 +671,36 @@ public class ChatPreparationOrchestrator { private List selectAutoCandidates(KnowledgeRouteDecision routeDecision, String question, - String rewriteQuestion) { + String rewriteQuestion, + List allowedDocuments) { if (routeDecision == null || routeDecision.getDocuments() == null || routeDecision.getDocuments().isEmpty()) { - return expandCandidatesByDocumentProfile(question, rewriteQuestion, 5); + return expandCandidatesByDocumentProfile(question, rewriteQuestion, allowedDocuments, 5); } int candidateLimit = routeDecision.getConfidence() != null && routeDecision.getConfidence().doubleValue() >= 0.80D ? 3 : 5; + List allowedDocumentIds = allowedDocuments == null + ? List.of() + : allowedDocuments.stream().map(KnowledgeDocumentDescriptor::getDocumentId).filter(Objects::nonNull).toList(); List candidates = routeDecision.getDocuments().stream() .filter(item -> StrUtil.isNotBlank(item.getDocumentId()) && StrUtil.isNotBlank(item.getLastIndexTaskId())) + .filter(item -> allowedDocumentIds.isEmpty() || allowedDocumentIds.contains(Long.valueOf(item.getDocumentId()))) .limit(candidateLimit) .toList(); if (candidates.isEmpty()) { - return expandCandidatesByDocumentProfile(question, rewriteQuestion, candidateLimit); + return expandCandidatesByDocumentProfile(question, rewriteQuestion, allowedDocuments, candidateLimit); } if (routeDecision.getConfidence() != null && routeDecision.getConfidence().doubleValue() < confidentDocumentThreshold()) { - return mergeCandidates(candidates, expandCandidatesByDocumentProfile(question, rewriteQuestion, candidateLimit), candidateLimit); + return mergeCandidates(candidates, expandCandidatesByDocumentProfile(question, rewriteQuestion, allowedDocuments, candidateLimit), candidateLimit); } return candidates; } private List expandCandidatesByDocumentProfile(String question, String rewriteQuestion, + List allowedDocuments, int limit) { - List descriptors = documentKnowledgeService.listRetrievableDocuments(); + List descriptors = allowedDocuments == null || allowedDocuments.isEmpty() + ? List.of() + : allowedDocuments; if (descriptors == null || descriptors.isEmpty()) { return List.of(); } @@ -693,16 +715,56 @@ public class ChatPreparationOrchestrator { String.valueOf(item.getDocumentId()), item.getDocumentName(), item.getLastIndexTaskId() == null ? "" : String.valueOf(item.getLastIndexTaskId()), - StrUtil.blankToDefault(item.getKnowledgeScopeCode(), ""), - StrUtil.blankToDefault(item.getKnowledgeScopeName(), ""), - StrUtil.blankToDefault(item.getBusinessCategory(), ""), - StrUtil.blankToDefault(item.getDocumentTags(), ""), BigDecimal.valueOf(descriptorRouteScore(item, queryTerms)).setScale(4, RoundingMode.HALF_UP), "低置信度时基于文档画像扩展候选范围" )) .toList(); } + private KnowledgeRouteContext buildRouteContext(String question, + String rewriteQuestion, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { + return KnowledgeRouteContext.builder() + .question(question) + .rewriteQuestion(rewriteQuestion) + .knowledgeBaseSelectionMode(selectionMode(knowledgeBaseSelection)) + .selectedKnowledgeBaseIds(selectedKnowledgeBaseIds(knowledgeBaseSelection)) + .selectedKnowledgeBaseNames(selectedKnowledgeBaseNames(knowledgeBaseSelection)) + .allowedDocuments(allowedDocuments(knowledgeBaseSelection)) + .allowedDocumentIds(allowedDocumentIds(knowledgeBaseSelection)) + .build(); + } + + private KnowledgeBaseSelectionMode selectionMode(KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { + return knowledgeBaseSelection == null || knowledgeBaseSelection.getSelectionMode() == null + ? KnowledgeBaseSelectionMode.NONE + : knowledgeBaseSelection.getSelectionMode(); + } + + private List selectedKnowledgeBaseIds(KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { + return knowledgeBaseSelection == null || knowledgeBaseSelection.getSelectedKnowledgeBaseIds() == null + ? List.of() + : knowledgeBaseSelection.getSelectedKnowledgeBaseIds(); + } + + private List selectedKnowledgeBaseNames(KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { + return knowledgeBaseSelection == null || knowledgeBaseSelection.getSelectedKnowledgeBaseNames() == null + ? List.of() + : knowledgeBaseSelection.getSelectedKnowledgeBaseNames(); + } + + private List allowedDocuments(KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { + return knowledgeBaseSelection == null || knowledgeBaseSelection.getAllowedDocuments() == null + ? List.of() + : knowledgeBaseSelection.getAllowedDocuments(); + } + + private List allowedDocumentIds(KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { + return knowledgeBaseSelection == null || knowledgeBaseSelection.getAllowedDocumentIds() == null + ? List.of() + : knowledgeBaseSelection.getAllowedDocumentIds(); + } + private List mergeCandidates(List primary, List secondary, int limit) { @@ -735,8 +797,7 @@ public class ChatPreparationOrchestrator { if (topScore == null || secondScore == null) { return false; } - return topScore.subtract(secondScore).doubleValue() <= 3D - && !Objects.equals(candidateDocuments.get(0).getKnowledgeScopeCode(), candidateDocuments.get(1).getKnowledgeScopeCode()); + return topScore.subtract(secondScore).doubleValue() <= 3D; } private boolean shouldAllowLowConfidenceMultiDocumentRetrieval(KnowledgeRouteDecision routeDecision, @@ -790,11 +851,6 @@ public class ChatPreparationOrchestrator { .append(". 《") .append(StrUtil.blankToDefault(item.getDocumentName(), item.getDocumentId())) .append("》"); - if (StrUtil.isNotBlank(item.getKnowledgeScopeName()) || StrUtil.isNotBlank(item.getKnowledgeScopeCode())) { - builder.append("(") - .append(StrUtil.blankToDefault(item.getKnowledgeScopeName(), item.getKnowledgeScopeCode())) - .append(")"); - } builder.append('\n'); } builder.append("你可以直接回复文档名,或者改用“当前文档问答”模式明确指定文档。"); @@ -844,10 +900,8 @@ public class ChatPreparationOrchestrator { private double descriptorRouteScore(KnowledgeDocumentDescriptor descriptor, List queryTerms) { String content = normalizeRouteExpansionText(String.join(" ", StrUtil.blankToDefault(descriptor.getDocumentName(), ""), - StrUtil.blankToDefault(descriptor.getKnowledgeScopeCode(), ""), - StrUtil.blankToDefault(descriptor.getKnowledgeScopeName(), ""), - StrUtil.blankToDefault(descriptor.getBusinessCategory(), ""), - StrUtil.blankToDefault(descriptor.getDocumentTags(), "") + StrUtil.blankToDefault(descriptor.getKnowledgeBaseCode(), ""), + StrUtil.blankToDefault(descriptor.getKnowledgeBaseName(), "") )); if (queryTerms == null || queryTerms.isEmpty() || content.isBlank()) { return 0D; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/DocumentRetrieveRequestFactory.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/DocumentRetrieveRequestFactory.java index d91d30d..3295c6a 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/DocumentRetrieveRequestFactory.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/DocumentRetrieveRequestFactory.java @@ -114,8 +114,6 @@ public class DocumentRetrieveRequestFactory { return DocumentRetrieveFilters.builder().build(); } LinkedHashSet documentNameHints = new LinkedHashSet<>(); - LinkedHashSet businessCategoryHints = new LinkedHashSet<>(); - LinkedHashSet documentTagHints = new LinkedHashSet<>(); LinkedHashSet sectionPathHints = new LinkedHashSet<>(); LinkedHashSet yearHints = new LinkedHashSet<>(); @@ -131,12 +129,10 @@ public class DocumentRetrieveRequestFactory { } } - collectQueryUnderstandingHints(queryUnderstanding, documentNameHints, businessCategoryHints, documentTagHints, sectionPathHints); + collectQueryUnderstandingHints(queryUnderstanding, documentNameHints, sectionPathHints); return DocumentRetrieveFilters.builder() .documentNameHints(new ArrayList<>(documentNameHints)) - .businessCategoryHints(new ArrayList<>(businessCategoryHints)) - .documentTagHints(new ArrayList<>(documentTagHints)) .sectionPathHints(new ArrayList<>(sectionPathHints)) .yearHints(new ArrayList<>(yearHints)) .build(); @@ -144,8 +140,6 @@ public class DocumentRetrieveRequestFactory { private void collectQueryUnderstandingHints(QueryUnderstandingResult queryUnderstanding, LinkedHashSet documentNameHints, - LinkedHashSet businessCategoryHints, - LinkedHashSet documentTagHints, LinkedHashSet sectionPathHints) { if (queryUnderstanding == null) { return; @@ -155,11 +149,7 @@ public class DocumentRetrieveRequestFactory { .filter(StrUtil::isNotBlank) .map(String::trim) .limit(8) - .forEach(entity -> { - documentNameHints.add(entity); - businessCategoryHints.add(entity); - documentTagHints.add(entity); - }); + .forEach(documentNameHints::add); } if (queryUnderstanding.getSectionAnchors() != null) { queryUnderstanding.getSectionAnchors().stream() diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/DocumentTableQueryPlanner.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/DocumentTableQueryPlanner.java index fe1d876..6c170dc 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/DocumentTableQueryPlanner.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/DocumentTableQueryPlanner.java @@ -300,8 +300,20 @@ public class DocumentTableQueryPlanner { } DocumentTableQuery.Operation operation = DocumentTableQuery.Operation.COUNT; Optional metricColumn = resolveMetricColumn(question, table); - Optional groupColumn = Optional.empty(); - List filters = List.of(); + Optional groupColumn = resolveGroupColumn(question, table, metricColumn.orElse(null)); + List filters = resolveFilters(question, table); + + String normalizedQuestion = normalize(question); + boolean aggregateCue = containsAny(normalizedQuestion, List.of("合计", "总计", "求和", "总和", "sum")); + if (aggregateCue && metricColumn.isPresent()) { + operation = DocumentTableQuery.Operation.SUM; + } + if (groupColumn.isPresent() && aggregateCue && metricColumn.isPresent()) { + operation = DocumentTableQuery.Operation.GROUP_SUM; + } + else if (groupColumn.isPresent() && containsAny(normalizedQuestion, List.of("数量", "个数", "多少", "统计"))) { + operation = DocumentTableQuery.Operation.GROUP_COUNT; + } if (operation == DocumentTableQuery.Operation.COUNT && groupColumn.isPresent()) { operation = DocumentTableQuery.Operation.GROUP_COUNT; @@ -335,7 +347,9 @@ public class DocumentTableQueryPlanner { } private boolean mentionsKnownTableSignal(String question, List tables) { - return tables.stream() + String normalizedQuestion = normalize(question); + boolean operationCue = containsAny(normalizedQuestion, List.of("合计", "总计", "求和", "总和", "统计", "数量", "个数", "多少", "最大", "最小", "sum", "count")); + return operationCue && tables.stream() .filter(table -> table != null) .anyMatch(table -> textMentionScore(question, table.getTitle()) > 0 || textMentionScore(question, table.getSectionPath()) > 0 @@ -355,6 +369,77 @@ public class DocumentTableQueryPlanner { .findFirst()); } + private Optional resolveGroupColumn(String question, + DocumentTableDescriptor table, + DocumentTableDescriptor.Column metricColumn) { + String normalizedQuestion = normalize(question); + if (!containsAny(normalizedQuestion, List.of("按", "分别", "分组", "各", "每"))) { + return Optional.empty(); + } + return table.getColumns().stream() + .filter(column -> metricColumn == null || !StrUtil.equals(column.getColumnName(), metricColumn.getColumnName())) + .filter(column -> !"NUMBER".equalsIgnoreCase(StrUtil.blankToDefault(column.getValueType(), ""))) + .map(column -> new ColumnScore(column, columnMentionScore(question, column))) + .filter(item -> item.score() > 0) + .max(Comparator.comparingInt(ColumnScore::score)) + .map(ColumnScore::column); + } + + private List resolveFilters(String question, DocumentTableDescriptor table) { + String normalizedQuestion = normalize(question); + List filters = new ArrayList<>(); + for (DocumentTableDescriptor.Column column : table.getColumns()) { + if (column == null || StrUtil.isBlank(column.getColumnName())) { + continue; + } + String normalizedColumn = normalize(column.getColumnName()); + if (normalizedColumn.isBlank()) { + continue; + } + for (String marker : List.of("为", "是", "等于")) { + int start = normalizedQuestion.indexOf(normalizedColumn + marker); + if (start < 0) { + continue; + } + int valueStart = start + normalizedColumn.length() + normalize(marker).length(); + String value = extractFilterValue(normalizedQuestion.substring(valueStart), table); + if (StrUtil.isNotBlank(value)) { + filters.add(DocumentTableQuery.Filter.builder() + .column(column.getColumnName()) + .operator(DocumentTableQuery.Operator.EQ) + .value(value) + .build()); + break; + } + } + } + return filters; + } + + private String extractFilterValue(String normalizedRemainder, DocumentTableDescriptor table) { + if (StrUtil.isBlank(normalizedRemainder)) { + return ""; + } + String value = normalizedRemainder; + for (DocumentTableDescriptor.Column column : table.getColumns()) { + String normalizedColumn = normalize(column.getColumnName()); + if (StrUtil.isBlank(normalizedColumn)) { + continue; + } + int index = value.indexOf(normalizedColumn); + if (index > 0) { + value = value.substring(0, index); + } + } + for (String cue : List.of("的", "数量", "个数", "多少", "合计", "总计", "求和", "总和", "统计", "sum", "count")) { + int index = value.indexOf(normalize(cue)); + if (index > 0) { + value = value.substring(0, index); + } + } + return value.trim(); + } + private int scorePlan(String question, DocumentTableDescriptor table, DocumentTableQuery query) { int score = 0; score += textMentionScore(question, table.getTitle()) * 3; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/KnowledgeBaseRuntimeConfigResolver.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/KnowledgeBaseRuntimeConfigResolver.java new file mode 100644 index 0000000..df6194e --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/KnowledgeBaseRuntimeConfigResolver.java @@ -0,0 +1,253 @@ +package org.javaup.ai.chatagent.rag.service; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.javaup.ai.chatagent.rag.config.ChatRagProperties; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; +import org.javaup.ai.manage.data.SuperAgentKnowledgeBase; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +@Slf4j +@Service +public class KnowledgeBaseRuntimeConfigResolver { + + private final ChatRagProperties properties; + private final ObjectMapper objectMapper; + + public KnowledgeBaseRuntimeConfigResolver(ChatRagProperties properties) { + this.properties = properties; + this.objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public RagRuntimeOptions resolve(List knowledgeBases) { + RagRuntimeOptions options = RagRuntimeOptions.from(properties); + List configs = knowledgeBases == null + ? List.of() + : knowledgeBases.stream() + .filter(Objects::nonNull) + .map(this::parseConfig) + .toList(); + if (configs.isEmpty()) { + return options; + } + if (configs.size() == 1) { + applySingle(options, configs.get(0)); + return options; + } + applyMerged(options, configs); + return options; + } + + private void applySingle(RagRuntimeOptions options, RuntimeConfig config) { + applyIfPresent(config.getVectorTopK(), options::setVectorTopK); + applyIfPresent(config.getKeywordTopK(), options::setKeywordTopK); + applyIfPresent(config.getGraphRagTopK(), options::setGraphRagTopK); + applyIfPresent(config.getGraphRagMaxHops(), options::setGraphRagMaxHops); + applyIfPresent(config.getRaptorTopK(), options::setRaptorTopK); + applyIfPresent(config.getRaptorSourceChunkTopK(), options::setRaptorSourceChunkTopK); + applyIfPresent(config.getCandidateTopK(), options::setCandidateTopK); + applyIfPresent(config.getFinalTopK(), options::setFinalTopK); + applyIfPresent(config.getMinVectorSimilarity(), options::setMinVectorSimilarity); + applyIfPresent(config.getKeywordRelativeScoreFloor(), options::setKeywordRelativeScoreFloor); + applyIfPresent(config.getKeywordChannelEnabled(), options::setKeywordChannelEnabled); + applyIfPresent(config.getTableChannelEnabled(), options::setTableChannelEnabled); + applyIfPresent(config.getGraphRagChannelEnabled(), options::setGraphRagChannelEnabled); + applyIfPresent(config.getRaptorChannelEnabled(), options::setRaptorChannelEnabled); + applySingleHybrid(options.getHybrid(), config.getHybrid()); + } + + private void applySingleHybrid(RagRuntimeOptions.HybridOptions options, HybridConfig config) { + if (config == null || options == null) { + return; + } + applyIfPresent(config.getVectorWeight(), options::setVectorWeight); + applyIfPresent(config.getKeywordWeight(), options::setKeywordWeight); + applyIfPresent(config.getTableWeight(), options::setTableWeight); + applyIfPresent(config.getGraphRagWeight(), options::setGraphRagWeight); + applyIfPresent(config.getRaptorWeight(), options::setRaptorWeight); + applyIfPresent(config.getRankWeight(), options::setRankWeight); + applyIfPresent(config.getOriginalScoreWeight(), options::setOriginalScoreWeight); + applyIfPresent(config.getMetadataBoostWeight(), options::setMetadataBoostWeight); + applyIfPresent(config.getMaxMetadataBoost(), options::setMaxMetadataBoost); + } + + private void applyMerged(RagRuntimeOptions options, List configs) { + List conflicts = new ArrayList<>(); + mergeField(configs, RuntimeConfig::getVectorTopK, options::setVectorTopK, "vectorTopK", conflicts); + mergeField(configs, RuntimeConfig::getKeywordTopK, options::setKeywordTopK, "keywordTopK", conflicts); + mergeField(configs, RuntimeConfig::getGraphRagTopK, options::setGraphRagTopK, "graphRagTopK", conflicts); + mergeField(configs, RuntimeConfig::getGraphRagMaxHops, options::setGraphRagMaxHops, "graphRagMaxHops", conflicts); + mergeField(configs, RuntimeConfig::getRaptorTopK, options::setRaptorTopK, "raptorTopK", conflicts); + mergeField(configs, RuntimeConfig::getRaptorSourceChunkTopK, options::setRaptorSourceChunkTopK, "raptorSourceChunkTopK", conflicts); + mergeField(configs, RuntimeConfig::getCandidateTopK, options::setCandidateTopK, "candidateTopK", conflicts); + mergeField(configs, RuntimeConfig::getFinalTopK, options::setFinalTopK, "finalTopK", conflicts); + mergeField(configs, RuntimeConfig::getMinVectorSimilarity, options::setMinVectorSimilarity, "minVectorSimilarity", conflicts); + mergeField(configs, RuntimeConfig::getKeywordRelativeScoreFloor, options::setKeywordRelativeScoreFloor, "keywordRelativeScoreFloor", conflicts); + mergeField(configs, RuntimeConfig::getKeywordChannelEnabled, options::setKeywordChannelEnabled, "keywordChannelEnabled", conflicts); + mergeField(configs, RuntimeConfig::getTableChannelEnabled, options::setTableChannelEnabled, "tableChannelEnabled", conflicts); + mergeField(configs, RuntimeConfig::getGraphRagChannelEnabled, options::setGraphRagChannelEnabled, "graphRagChannelEnabled", conflicts); + mergeField(configs, RuntimeConfig::getRaptorChannelEnabled, options::setRaptorChannelEnabled, "raptorChannelEnabled", conflicts); + + mergeHybridField(configs, HybridConfig::getVectorWeight, options.getHybrid()::setVectorWeight, "hybrid.vectorWeight", conflicts); + mergeHybridField(configs, HybridConfig::getKeywordWeight, options.getHybrid()::setKeywordWeight, "hybrid.keywordWeight", conflicts); + mergeHybridField(configs, HybridConfig::getTableWeight, options.getHybrid()::setTableWeight, "hybrid.tableWeight", conflicts); + mergeHybridField(configs, HybridConfig::getGraphRagWeight, options.getHybrid()::setGraphRagWeight, "hybrid.graphRagWeight", conflicts); + mergeHybridField(configs, HybridConfig::getRaptorWeight, options.getHybrid()::setRaptorWeight, "hybrid.raptorWeight", conflicts); + mergeHybridField(configs, HybridConfig::getRankWeight, options.getHybrid()::setRankWeight, "hybrid.rankWeight", conflicts); + mergeHybridField(configs, HybridConfig::getOriginalScoreWeight, options.getHybrid()::setOriginalScoreWeight, "hybrid.originalScoreWeight", conflicts); + mergeHybridField(configs, HybridConfig::getMetadataBoostWeight, options.getHybrid()::setMetadataBoostWeight, "hybrid.metadataBoostWeight", conflicts); + mergeHybridField(configs, HybridConfig::getMaxMetadataBoost, options.getHybrid()::setMaxMetadataBoost, "hybrid.maxMetadataBoost", conflicts); + + options.setKbConfigConflictFields(new ArrayList<>(new LinkedHashSet<>(conflicts))); + } + + private void mergeHybridField(List configs, + Function getter, + java.util.function.Consumer setter, + String field, + List conflicts) { + mergeField(configs, + config -> config.getHybrid() == null ? null : getter.apply(config.getHybrid()), + setter, + field, + conflicts); + } + + private void mergeField(List configs, + Function getter, + java.util.function.Consumer setter, + String field, + List conflicts) { + List values = configs.stream().map(getter).toList(); + if (values.stream().allMatch(Objects::isNull)) { + return; + } + if (values.stream().anyMatch(Objects::isNull)) { + conflicts.add(field); + return; + } + T first = values.get(0); + boolean same = values.stream().allMatch(value -> Objects.equals(first, value)); + if (same) { + setter.accept(first); + } + else { + conflicts.add(field); + } + } + + private RuntimeConfig parseConfig(SuperAgentKnowledgeBase knowledgeBase) { + RuntimeConfig merged = new RuntimeConfig(); + mergeInto(merged, parseJson(knowledgeBase.getRetrievalConfigJson(), knowledgeBase)); + mergeInto(merged, parseJson(knowledgeBase.getGraphRagConfigJson(), knowledgeBase)); + mergeInto(merged, parseJson(knowledgeBase.getRaptorConfigJson(), knowledgeBase)); + return merged; + } + + private RuntimeConfig parseJson(String rawJson, SuperAgentKnowledgeBase knowledgeBase) { + if (StrUtil.isBlank(rawJson)) { + return new RuntimeConfig(); + } + try { + return objectMapper.readValue(rawJson, RuntimeConfig.class); + } + catch (JsonProcessingException | RuntimeException exception) { + log.warn("知识库 RAG 配置 JSON 解析失败,将忽略该段配置: knowledgeBaseId={}, knowledgeBaseName={}", + knowledgeBase == null ? null : knowledgeBase.getId(), + knowledgeBase == null ? "" : knowledgeBase.getBaseName(), + exception); + return new RuntimeConfig(); + } + } + + private void mergeInto(RuntimeConfig target, RuntimeConfig source) { + if (source == null) { + return; + } + copyIfPresent(source.getVectorTopK(), target::setVectorTopK); + copyIfPresent(source.getKeywordTopK(), target::setKeywordTopK); + copyIfPresent(source.getGraphRagTopK(), target::setGraphRagTopK); + copyIfPresent(source.getGraphRagMaxHops(), target::setGraphRagMaxHops); + copyIfPresent(source.getRaptorTopK(), target::setRaptorTopK); + copyIfPresent(source.getRaptorSourceChunkTopK(), target::setRaptorSourceChunkTopK); + copyIfPresent(source.getCandidateTopK(), target::setCandidateTopK); + copyIfPresent(source.getFinalTopK(), target::setFinalTopK); + copyIfPresent(source.getMinVectorSimilarity(), target::setMinVectorSimilarity); + copyIfPresent(source.getKeywordRelativeScoreFloor(), target::setKeywordRelativeScoreFloor); + copyIfPresent(source.getKeywordChannelEnabled(), target::setKeywordChannelEnabled); + copyIfPresent(source.getTableChannelEnabled(), target::setTableChannelEnabled); + copyIfPresent(source.getGraphRagChannelEnabled(), target::setGraphRagChannelEnabled); + copyIfPresent(source.getRaptorChannelEnabled(), target::setRaptorChannelEnabled); + if (source.getHybrid() != null) { + if (target.getHybrid() == null) { + target.setHybrid(new HybridConfig()); + } + mergeHybridInto(target.getHybrid(), source.getHybrid()); + } + } + + private void mergeHybridInto(HybridConfig target, HybridConfig source) { + copyIfPresent(source.getVectorWeight(), target::setVectorWeight); + copyIfPresent(source.getKeywordWeight(), target::setKeywordWeight); + copyIfPresent(source.getTableWeight(), target::setTableWeight); + copyIfPresent(source.getGraphRagWeight(), target::setGraphRagWeight); + copyIfPresent(source.getRaptorWeight(), target::setRaptorWeight); + copyIfPresent(source.getRankWeight(), target::setRankWeight); + copyIfPresent(source.getOriginalScoreWeight(), target::setOriginalScoreWeight); + copyIfPresent(source.getMetadataBoostWeight(), target::setMetadataBoostWeight); + copyIfPresent(source.getMaxMetadataBoost(), target::setMaxMetadataBoost); + } + + private void applyIfPresent(T value, java.util.function.Consumer setter) { + copyIfPresent(value, setter); + } + + private void copyIfPresent(T value, java.util.function.Consumer setter) { + if (value != null) { + setter.accept(value); + } + } + + @Data + public static class RuntimeConfig { + private Integer vectorTopK; + private Integer keywordTopK; + private Integer graphRagTopK; + private Integer graphRagMaxHops; + private Integer raptorTopK; + private Integer raptorSourceChunkTopK; + private Integer candidateTopK; + private Integer finalTopK; + private Double minVectorSimilarity; + private Double keywordRelativeScoreFloor; + private Boolean keywordChannelEnabled; + private Boolean tableChannelEnabled; + private Boolean graphRagChannelEnabled; + private Boolean raptorChannelEnabled; + private HybridConfig hybrid; + } + + @Data + public static class HybridConfig { + private Double vectorWeight; + private Double keywordWeight; + private Double tableWeight; + private Double graphRagWeight; + private Double raptorWeight; + private Double rankWeight; + private Double originalScoreWeight; + private Double metadataBoostWeight; + private Double maxMetadataBoost; + } +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/RagCitationRepairService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/RagCitationRepairService.java index eef7b70..a79e415 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/RagCitationRepairService.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/RagCitationRepairService.java @@ -170,8 +170,6 @@ public class RagCitationRepairService { metadata.put("chunkNo", reference.getChunkNo()); metadata.put("parentBlockNo", reference.getParentBlockNo()); metadata.put("sourceBlockIds", StrUtil.blankToDefault(reference.getSourceBlockIds(), "")); - metadata.put("knowledgeScopeCode", StrUtil.blankToDefault(reference.getKnowledgeScopeCode(), "")); - metadata.put("knowledgeScopeName", StrUtil.blankToDefault(reference.getKnowledgeScopeName(), "")); metadata.put("channel", StrUtil.blankToDefault(reference.getChannel(), "")); metadata.put("tableId", reference.getTableId()); metadata.put("tableNo", reference.getTableNo()); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/RagRetrievalEngine.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/RagRetrievalEngine.java index 11f7000..71df4cb 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/RagRetrievalEngine.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/service/RagRetrievalEngine.java @@ -8,6 +8,7 @@ import org.javaup.ai.chatagent.rag.config.ChatRagProperties; import org.javaup.ai.chatagent.rag.model.ConversationExecutionPlan; import org.javaup.ai.chatagent.rag.model.QueryType; import org.javaup.ai.chatagent.rag.model.QueryUnderstandingResult; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; import org.javaup.ai.chatagent.rag.model.RagRetrievalContext; import org.javaup.ai.chatagent.rag.model.RetrievalIntent; import org.javaup.ai.chatagent.rag.model.SubQuestionChannelTrace; @@ -169,7 +170,7 @@ public class RagRetrievalEngine { .filter(result -> result.getDocuments() != null) .toList(); List channelResults = rawChannelResults.stream() - .map(this::applyEvidenceGate) + .map(result -> applyEvidenceGate(result, plan)) .toList(); List channelTraces = buildChannelTraces(rawChannelResults, channelResults, plan); @@ -197,7 +198,7 @@ public class RagRetrievalEngine { recordChannelObservations(traceRecorder, subQuestionIndex, subQuestion, rawChannelResults, channelResults, channelTraces, finalDocuments); recordRetrievalResultObservations(traceRecorder, subQuestionIndex, subQuestion, - rawChannelResults, channelResults, mergedCandidates, rerankedCandidates, finalDocuments); + rawChannelResults, channelResults, mergedCandidates, rerankedCandidates, finalDocuments, plan); } catch (RuntimeException exception) { log.warn("记录检索观测数据失败, subQuestionIndex={}", subQuestionIndex, exception); } @@ -219,7 +220,7 @@ public class RagRetrievalEngine { if (rerankedCandidates == null || rerankedCandidates.isEmpty()) { return List.of(); } - int finalTopK = Math.max(properties.getFinalTopK(), 0); + int finalTopK = Math.max(runtimeOptions(plan).getFinalTopK(), 0); if (finalTopK <= 0 || rerankedCandidates.size() <= finalTopK) { return rerankedCandidates.stream() .limit(finalTopK) @@ -293,30 +294,31 @@ public class RagRetrievalEngine { return replaceIndex >= 0 ? replaceIndex : selected.size() - 1; } - private RetrievalChannelResult applyEvidenceGate(RetrievalChannelResult result) { + private RetrievalChannelResult applyEvidenceGate(RetrievalChannelResult result, ConversationExecutionPlan plan) { if (result == null || result.getDocuments() == null || result.getDocuments().isEmpty()) { return result; } List documents = switch (result.getChannelName()) { - case "vector" -> filterVectorCandidates(result.getDocuments()); - case "keyword" -> filterKeywordCandidates(result.getDocuments()); + case "vector" -> filterVectorCandidates(result.getDocuments(), plan); + case "keyword" -> filterKeywordCandidates(result.getDocuments(), plan); default -> result.getDocuments(); }; return new RetrievalChannelResult(result.getChannelName(), documents); } - private List filterVectorCandidates(List documents) { + private List filterVectorCandidates(List documents, ConversationExecutionPlan plan) { + double minSimilarity = runtimeOptions(plan).getMinVectorSimilarity(); return documents.stream() .filter(document -> { Double score = resolveScore(document); - return score != null && score >= properties.getMinVectorSimilarity(); + return score != null && score >= minSimilarity; }) .toList(); } - private List filterKeywordCandidates(List documents) { + private List filterKeywordCandidates(List documents, ConversationExecutionPlan plan) { Double topScore = documents.stream() .map(this::resolveScore) .filter(Objects::nonNull) @@ -326,7 +328,7 @@ public class RagRetrievalEngine { return documents; } - double acceptedFloor = topScore * Math.max(0D, properties.getKeywordRelativeScoreFloor()); + double acceptedFloor = topScore * Math.max(0D, runtimeOptions(plan).getKeywordRelativeScoreFloor()); return documents.stream() .filter(document -> { Double score = resolveScore(document); @@ -347,7 +349,7 @@ public class RagRetrievalEngine { } List sortedHolders = holders.values().stream() - .peek(this::finishHybridScore) + .peek(holder -> finishHybridScore(holder, plan)) .peek(this::writeHybridMetadata) .sorted((left, right) -> Double.compare(right.score, left.score)) .toList(); @@ -361,7 +363,7 @@ public class RagRetrievalEngine { if (sortedHolders == null || sortedHolders.isEmpty()) { return List.of(); } - int candidateTopK = Math.max(properties.getCandidateTopK(), 0); + int candidateTopK = Math.max(runtimeOptions(plan).getCandidateTopK(), 0); if (candidateTopK <= 0 || sortedHolders.size() <= candidateTopK) { return sortedHolders.stream() .limit(candidateTopK) @@ -479,9 +481,9 @@ public class RagRetrievalEngine { CandidateHolder holder = holders.computeIfAbsent(documentId, ignored -> new CandidateHolder(document)); mergeGraphRagMetadata(holder, document); holder.rrfScore += rrfScore; - holder.rankScore += channelWeight * hybridRankWeight() * normalizedRankScore; - holder.originalScore += channelWeight * hybridOriginalScoreWeight() * normalizedOriginalScore; - holder.metadataBoost = Math.max(holder.metadataBoost, calculateMetadataBoost(document, metadataBoostTerms)); + holder.rankScore += channelWeight * hybridRankWeight(plan) * normalizedRankScore; + holder.originalScore += channelWeight * hybridOriginalScoreWeight(plan) * normalizedOriginalScore; + holder.metadataBoost = Math.max(holder.metadataBoost, calculateMetadataBoost(document, metadataBoostTerms, plan)); holder.channels.add(channelResult.getChannelName()); if (RetrievalChannelEnum.VECTOR.getName().equals(channelResult.getChannelName()) && originalScore != null) { holder.vectorScore = originalScore; @@ -492,10 +494,10 @@ public class RagRetrievalEngine { } } - private void finishHybridScore(CandidateHolder holder) { + private void finishHybridScore(CandidateHolder holder, ConversationExecutionPlan plan) { holder.score = holder.rankScore + holder.originalScore - + hybridMetadataBoostWeight() * Math.min(holder.metadataBoost, hybridMaxMetadataBoost()); + + hybridMetadataBoostWeight(plan) * Math.min(holder.metadataBoost, hybridMaxMetadataBoost(plan)); } private double normalizeOriginalScore(Double originalScore, double channelMaxScore) { @@ -506,7 +508,7 @@ public class RagRetrievalEngine { } private double resolveChannelWeight(String channelName, ConversationExecutionPlan plan) { - ChatRagProperties.HybridProperties hybrid = properties.getHybrid(); + RagRuntimeOptions.HybridOptions hybrid = runtimeOptions(plan).getHybrid(); double baseWeight; if (RetrievalChannelEnum.VECTOR.getName().equals(channelName)) { baseWeight = hybrid == null ? 1D : Math.max(0D, hybrid.getVectorWeight()); @@ -659,23 +661,27 @@ public class RagRetrievalEngine { return plan == null || plan.getRetrievalIntent() == null ? RetrievalIntent.GENERAL : plan.getRetrievalIntent(); } - private double hybridRankWeight() { - ChatRagProperties.HybridProperties hybrid = properties.getHybrid(); + private RagRuntimeOptions runtimeOptions(ConversationExecutionPlan plan) { + return RagRuntimeOptions.resolve(plan, properties); + } + + private double hybridRankWeight(ConversationExecutionPlan plan) { + RagRuntimeOptions.HybridOptions hybrid = runtimeOptions(plan).getHybrid(); return hybrid == null ? 1D : Math.max(0D, hybrid.getRankWeight()); } - private double hybridOriginalScoreWeight() { - ChatRagProperties.HybridProperties hybrid = properties.getHybrid(); + private double hybridOriginalScoreWeight(ConversationExecutionPlan plan) { + RagRuntimeOptions.HybridOptions hybrid = runtimeOptions(plan).getHybrid(); return hybrid == null ? 0.08D : Math.max(0D, hybrid.getOriginalScoreWeight()); } - private double hybridMetadataBoostWeight() { - ChatRagProperties.HybridProperties hybrid = properties.getHybrid(); + private double hybridMetadataBoostWeight(ConversationExecutionPlan plan) { + RagRuntimeOptions.HybridOptions hybrid = runtimeOptions(plan).getHybrid(); return hybrid == null ? 0.04D : Math.max(0D, hybrid.getMetadataBoostWeight()); } - private double hybridMaxMetadataBoost() { - ChatRagProperties.HybridProperties hybrid = properties.getHybrid(); + private double hybridMaxMetadataBoost(ConversationExecutionPlan plan) { + RagRuntimeOptions.HybridOptions hybrid = runtimeOptions(plan).getHybrid(); return hybrid == null ? 1D : Math.max(0D, hybrid.getMaxMetadataBoost()); } @@ -724,7 +730,7 @@ public class RagRetrievalEngine { } } - private double calculateMetadataBoost(Document document, List terms) { + private double calculateMetadataBoost(Document document, List terms, ConversationExecutionPlan plan) { if (document == null || document.getMetadata() == null || terms == null || terms.isEmpty()) { return 0D; } @@ -734,12 +740,9 @@ public class RagRetrievalEngine { boost += containsAnyMetadataTerm(document, terms, DocumentKnowledgeMetadataKeys.KEYWORDS) ? 0.18D : 0D; boost += containsAnyMetadataTerm(document, terms, DocumentKnowledgeMetadataKeys.QUESTIONS) ? 0.14D : 0D; boost += containsAnyMetadataTerm(document, terms, DocumentKnowledgeMetadataKeys.DOCUMENT_NAME) ? 0.10D : 0D; - boost += containsAnyMetadataTerm(document, terms, DocumentKnowledgeMetadataKeys.KNOWLEDGE_SCOPE_NAME) ? 0.08D : 0D; - boost += containsAnyMetadataTerm(document, terms, DocumentKnowledgeMetadataKeys.BUSINESS_CATEGORY) ? 0.06D : 0D; - boost += containsAnyMetadataTerm(document, terms, DocumentKnowledgeMetadataKeys.DOCUMENT_TAGS) ? 0.06D : 0D; boost += chunkTypeBoost(document); boost += graphRankMetadataBoost(document); - return Math.min(boost, hybridMaxMetadataBoost()); + return Math.min(boost, hybridMaxMetadataBoost(plan)); } private boolean containsAnyMetadataTerm(Document document, List terms, String metadataKey) { @@ -1371,7 +1374,8 @@ public class RagRetrievalEngine { List filteredResults, List mergedCandidates, List rerankedCandidates, - List finalDocuments) { + List finalDocuments, + ConversationExecutionPlan plan) { List results = new ArrayList<>(); Map finalRankMap = new LinkedHashMap<>(); Map mergedCandidateMap = new LinkedHashMap<>(); @@ -1516,18 +1520,18 @@ public class RagRetrievalEngine { if ("vector".equals(channelName)) { view.setSelectionReason(String.format( "向量闸门过滤:分数 %.4f < 阈值 %.4f", - score, properties.getMinVectorSimilarity() + score, runtimeOptions(plan).getMinVectorSimilarity() )); } else if ("keyword".equals(channelName)) { view.setSelectionReason(String.format( "关键词闸门过滤:分数 %.4f 低于相对阈值(floor=%.2f)", - score, properties.getKeywordRelativeScoreFloor() + score, runtimeOptions(plan).getKeywordRelativeScoreFloor() )); } else { view.setSelectionReason("闸门过滤:分数 " + String.format("%.4f", score)); } } else { - view.setSelectionReason("超出 finalTopK 限制(topK=" + properties.getFinalTopK() + ")"); + view.setSelectionReason("超出 finalTopK 限制(topK=" + runtimeOptions(plan).getFinalTopK() + ")"); } results.add(view); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/support/SearchReferenceMapper.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/support/SearchReferenceMapper.java index 80425d9..de7134b 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/support/SearchReferenceMapper.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/rag/support/SearchReferenceMapper.java @@ -44,6 +44,9 @@ public final class SearchReferenceMapper { reference.setTitle(asText(metadata.get(DocumentKnowledgeMetadataKeys.DOCUMENT_NAME), "文档片段")); reference.setDocumentId(asLong(metadata.get(DocumentKnowledgeMetadataKeys.DOCUMENT_ID))); reference.setDocumentName(asText(metadata.get(DocumentKnowledgeMetadataKeys.DOCUMENT_NAME), "")); + reference.setKnowledgeBaseId(asLong(metadata.get(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_ID))); + reference.setKnowledgeBaseCode(asText(metadata.get(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_CODE), "")); + reference.setKnowledgeBaseName(asText(metadata.get(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_NAME), "")); reference.setParentBlockId(asLong(metadata.get(DocumentKnowledgeMetadataKeys.PARENT_BLOCK_ID))); reference.setParentBlockNo(asInteger(metadata.get(DocumentKnowledgeMetadataKeys.PARENT_BLOCK_NO))); reference.setChunkId(asLong(metadata.get(DocumentKnowledgeMetadataKeys.CHUNK_ID))); @@ -53,8 +56,6 @@ public final class SearchReferenceMapper { reference.setStructureNodeType(asInteger(metadata.get(DocumentKnowledgeMetadataKeys.STRUCTURE_NODE_TYPE))); reference.setCanonicalPath(asText(metadata.get(DocumentKnowledgeMetadataKeys.CANONICAL_PATH), "")); reference.setItemIndex(asInteger(metadata.get(DocumentKnowledgeMetadataKeys.ITEM_INDEX))); - reference.setKnowledgeScopeCode(asText(metadata.get(DocumentKnowledgeMetadataKeys.KNOWLEDGE_SCOPE_CODE), "")); - reference.setKnowledgeScopeName(asText(metadata.get(DocumentKnowledgeMetadataKeys.KNOWLEDGE_SCOPE_NAME), "")); reference.setPageNo(asInteger(metadata.get(DocumentKnowledgeMetadataKeys.PAGE_NO))); reference.setPageRange(asText(metadata.get(DocumentKnowledgeMetadataKeys.PAGE_RANGE), "")); reference.setBboxJson(asText(metadata.get(DocumentKnowledgeMetadataKeys.BBOX_JSON), "")); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/BusinessChatService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/BusinessChatService.java index 04bd8be..3f94180 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/BusinessChatService.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/BusinessChatService.java @@ -37,10 +37,15 @@ import org.javaup.ai.chatagent.vo.ConversationSessionListVo; import org.javaup.ai.chatagent.vo.ConversationStopVo; import org.javaup.ai.prompt.PromptTemplateNames; import org.javaup.ai.prompt.PromptTemplateService; +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; import org.javaup.enums.ChatTurnStatus; import org.javaup.enums.ChatQueryMode; +import org.javaup.enums.KnowledgeBaseSelectionMode; import org.javaup.exception.SuperAgentFrameException; import org.javaup.lease.RedisLeaseManager; +import org.javaup.ai.manage.service.KnowledgeBaseManageService; +import org.javaup.ai.manage.service.KnowledgeBaseRetrievalScopeService; +import org.javaup.ai.manage.vo.KnowledgeBaseOptionVo; import org.springframework.ai.chat.messages.AbstractMessage; import org.springframework.ai.chat.messages.MessageType; import org.springframework.stereotype.Service; @@ -99,6 +104,8 @@ public class BusinessChatService { private final StageBenchmarkService stageBenchmarkService; private final PromptTemplateService promptTemplateService; private final RagCitationRepairService ragCitationRepairService; + private final KnowledgeBaseRetrievalScopeService knowledgeBaseRetrievalScopeService; + private final KnowledgeBaseManageService knowledgeBaseManageService; public Flux openConversationStream(ChatRequestDto request) { @@ -151,7 +158,8 @@ public class BusinessChatService { launchPlan.getQuestion(), launchPlan.getChatMode(), launchPlan.getSelectedDocumentId(), - launchPlan.getSelectedDocumentName() + launchPlan.getSelectedDocumentName(), + launchPlan.getKnowledgeBaseSelectionSnapshot() ); TaskInfo taskInfo = createTaskInfo(launchPlan, exchangeView); @@ -229,6 +237,7 @@ public class BusinessChatService { launchPlan.getSelectedDocumentId(), launchPlan.getSelectedDocumentName(), launchPlan.getSelectedTaskId(), + launchPlan.getKnowledgeBaseSelectionSnapshot(), launchPlan.getCurrentDate(), launchPlan.getCurrentDateText(), null, @@ -306,8 +315,15 @@ public class BusinessChatService { String conversationId = normalizeConversationId(request.getConversationId()); ChatQueryMode chatMode = parseRequiredChatMode(request.getChatMode()); + KnowledgeBaseSelectionMode selectionMode = parseKnowledgeBaseSelectionMode(request.getKnowledgeBaseSelectionMode()); + validateChatModeAndKnowledgeBaseSelection(chatMode, selectionMode); - KnowledgeDocumentDescriptor selectedDocument = resolveSelectedDocument(chatMode, request.getSelectedDocumentId()); + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection = knowledgeBaseRetrievalScopeService.resolve( + chatMode, + selectionMode, + request.getSelectedKnowledgeBaseIds() + ); + KnowledgeDocumentDescriptor selectedDocument = resolveSelectedDocument(chatMode, request.getSelectedDocumentId(), knowledgeBaseSelection); LocalDate currentDate = LocalDate.now(CHAT_ZONE_ID); String currentDateText = formatCurrentDate(currentDate); @@ -318,6 +334,7 @@ public class BusinessChatService { selectedDocument == null ? null : selectedDocument.getDocumentId(), selectedDocument == null ? "" : selectedDocument.getDocumentName(), selectedDocument == null ? null : selectedDocument.getLastIndexTaskId(), + knowledgeBaseSelection, buildChatLeaseKey(conversationId), @@ -520,6 +537,10 @@ public class BusinessChatService { .toList(); } + public List listKnowledgeBaseOptions() { + return knowledgeBaseManageService.listOptions(); + } + public ConversationMemorySummaryView rebuildConversationSummary(String conversationId) { return conversationMemoryService.rebuildConversationSummary(conversationId); } @@ -933,7 +954,8 @@ public class BusinessChatService { taskInfo.conversationId(), executionPlan.getChatMode(), executionPlan.getSelectedDocumentId(), - executionPlan.getSelectedDocumentName() + executionPlan.getSelectedDocumentName(), + taskInfo.knowledgeBaseSelectionSnapshot() ); putContextIfNotNull(taskInfo.runnableConfig(), ChatContextKeys.SELECTED_DOCUMENT_ID, executionPlan.getSelectedDocumentId()); putContextIfNotBlank(taskInfo.runnableConfig(), ChatContextKeys.SELECTED_DOCUMENT_NAME, executionPlan.getSelectedDocumentName()); @@ -980,6 +1002,9 @@ public class BusinessChatService { archiveRecord.chatMode(), archiveRecord.selectedDocumentId() == null ? "" : String.valueOf(archiveRecord.selectedDocumentId()), archiveRecord.selectedDocumentName(), + archiveRecord.knowledgeBaseSelectionMode(), + archiveRecord.selectedKnowledgeBaseIds(), + archiveRecord.selectedKnowledgeBaseNames(), archiveRecord.createdAt(), archiveRecord.updatedAt(), exchanges, @@ -1040,12 +1065,18 @@ public class BusinessChatService { exchange.getErrorMessage(), toNullable(taskInfo.firstResponseTimeMs().get()), System.currentTimeMillis() - taskInfo.startTime(), + exchange.getKnowledgeBaseSelectionMode(), + exchange.getSelectedKnowledgeBaseIds(), + exchange.getSelectedKnowledgeBaseNames(), + exchange.getRetrievalConfigSnapshotJson(), exchange.getCreateTime(), exchange.getEditTime() ); } - private KnowledgeDocumentDescriptor resolveSelectedDocument(ChatQueryMode chatMode, String selectedDocumentId) { + private KnowledgeDocumentDescriptor resolveSelectedDocument(ChatQueryMode chatMode, + String selectedDocumentId, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { if (chatMode == null) { throw new IllegalArgumentException("chatMode 不能为空"); } @@ -1068,19 +1099,23 @@ public class BusinessChatService { throw new IllegalArgumentException("当前文档问答模式下必须选择一个文档"); } final Long resolvedDocumentId = parseRequiredLong(normalizedDocumentId, "selectedDocumentId"); - return documentKnowledgeService.listRetrievableDocuments().stream() + List searchableDocuments = knowledgeBaseSelection == null + || knowledgeBaseSelection.getSelectionMode() == KnowledgeBaseSelectionMode.NONE + ? List.of() + : knowledgeBaseSelection.getAllowedDocuments(); + return searchableDocuments.stream() .filter(item -> Objects.equals(item.getDocumentId(), resolvedDocumentId)) .findFirst() - .orElseThrow(() -> new IllegalArgumentException("所选文档当前不可检索: " + normalizedDocumentId)); + .orElseThrow(() -> new IllegalArgumentException("所选文档不在当前知识库范围内,或当前不可检索: " + normalizedDocumentId)); } private KnowledgeDocumentOptionView toKnowledgeDocumentOptionView(KnowledgeDocumentDescriptor descriptor) { return new KnowledgeDocumentOptionView( descriptor.getDocumentId() == null ? "" : String.valueOf(descriptor.getDocumentId()), descriptor.getDocumentName(), - descriptor.getKnowledgeScopeName(), - descriptor.getBusinessCategory(), - descriptor.getDocumentTags() + descriptor.getKnowledgeBaseId() == null ? "" : String.valueOf(descriptor.getKnowledgeBaseId()), + descriptor.getKnowledgeBaseCode(), + descriptor.getKnowledgeBaseName() ); } @@ -1130,6 +1165,32 @@ public class BusinessChatService { return chatMode; } + private KnowledgeBaseSelectionMode parseKnowledgeBaseSelectionMode(String value) { + try { + return KnowledgeBaseSelectionMode.fromName(value); + } + catch (IllegalArgumentException exception) { + throw new IllegalArgumentException("knowledgeBaseSelectionMode 非法: " + value, exception); + } + } + + private void validateChatModeAndKnowledgeBaseSelection(ChatQueryMode chatMode, + KnowledgeBaseSelectionMode selectionMode) { + KnowledgeBaseSelectionMode mode = selectionMode == null ? KnowledgeBaseSelectionMode.NONE : selectionMode; + if (chatMode == ChatQueryMode.OPEN_CHAT) { + if (mode != KnowledgeBaseSelectionMode.NONE) { + throw new IllegalArgumentException("开放式提问模式必须使用 NONE 知识库选择模式"); + } + return; + } + if (chatMode == ChatQueryMode.AUTO_DOCUMENT && mode == KnowledgeBaseSelectionMode.NONE) { + throw new IllegalArgumentException("自动知识问答模式必须选择知识库或使用全部知识库"); + } + if (chatMode == ChatQueryMode.DOCUMENT && mode == KnowledgeBaseSelectionMode.NONE) { + throw new IllegalArgumentException("当前文档问答模式必须选择知识库或使用全部知识库"); + } + } + private ChatTurnStatus parseOptionalTurnStatus(String value) { if (StrUtil.isBlank(value) || "ALL".equalsIgnoreCase(value.trim())) { return null; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/ConversationArchiveStore.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/ConversationArchiveStore.java index 410fd96..350c26f 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/ConversationArchiveStore.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/ConversationArchiveStore.java @@ -3,6 +3,7 @@ package org.javaup.ai.chatagent.service; import org.javaup.ai.chatagent.model.ConversationExchangeView; import org.javaup.ai.chatagent.model.SearchReference; import org.javaup.ai.chatagent.model.debug.ChatDebugTrace; +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; import org.javaup.enums.ChatQueryMode; import org.javaup.enums.ChatTurnStatus; @@ -22,12 +23,14 @@ public interface ConversationArchiveStore { String question, ChatQueryMode chatMode, Long selectedDocumentId, - String selectedDocumentName); + String selectedDocumentName, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection); void refreshSessionScope(String conversationId, ChatQueryMode chatMode, Long selectedDocumentId, - String selectedDocumentName); + String selectedDocumentName, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection); void completeExchange(String conversationId, long exchangeId, @@ -66,6 +69,9 @@ public interface ConversationArchiveStore { ChatQueryMode chatMode, Long selectedDocumentId, String selectedDocumentName, + String knowledgeBaseSelectionMode, + List selectedKnowledgeBaseIds, + List selectedKnowledgeBaseNames, Instant createdAt, Instant updatedAt, List exchanges diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/MybatisConversationArchiveStore.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/MybatisConversationArchiveStore.java index 74df2b3..f6b22d9 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/MybatisConversationArchiveStore.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/MybatisConversationArchiveStore.java @@ -16,8 +16,10 @@ import org.javaup.ai.chatagent.mapper.SuperAgentChatExchangeMapper; import org.javaup.ai.chatagent.model.ConversationExchangeView; import org.javaup.ai.chatagent.model.SearchReference; import org.javaup.ai.chatagent.model.debug.ChatDebugTrace; +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; import org.javaup.enums.BusinessStatus; import org.javaup.enums.ChatQueryMode; +import org.javaup.enums.KnowledgeBaseSelectionMode; import org.javaup.enums.ChatSessionStatus; import org.javaup.enums.ChatTurnStatus; import org.javaup.util.DateUtils; @@ -70,8 +72,9 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore String question, ChatQueryMode chatMode, Long selectedDocumentId, - String selectedDocumentName) { - upsertDialogue(conversationId, ChatSessionStatus.RUNNING, chatMode, selectedDocumentId, selectedDocumentName); + String selectedDocumentName, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { + upsertDialogue(conversationId, ChatSessionStatus.RUNNING, chatMode, selectedDocumentId, selectedDocumentName, knowledgeBaseSelection); long exchangeId = uidGenerator.getUid(); @@ -90,6 +93,10 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore exchange.setErrorMessage(""); exchange.setFirstResponseTimeMs(null); exchange.setTotalResponseTimeMs(null); + exchange.setKnowledgeBaseSelectionMode(selectionModeName(knowledgeBaseSelection)); + exchange.setSelectedKnowledgeBaseIdsJson(writeJson(selectionIds(knowledgeBaseSelection))); + exchange.setSelectedKnowledgeBaseNamesJson(writeJson(selectionNames(knowledgeBaseSelection))); + exchange.setRetrievalConfigSnapshotJson(writeNullableJson(knowledgeBaseSelection == null ? null : knowledgeBaseSelection.getRagRuntimeOptions())); exchange.setStatus(BusinessStatus.YES.getCode()); exchangeMapper.insert(exchange); @@ -106,8 +113,12 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore "", null, null, - DateUtils.now(), - DateUtils.now() + selectionModeName(knowledgeBaseSelection), + selectionIds(knowledgeBaseSelection), + selectionNames(knowledgeBaseSelection), + writeNullableJson(knowledgeBaseSelection == null ? null : knowledgeBaseSelection.getRagRuntimeOptions()), + DateUtils.now(), + DateUtils.now() ); } @@ -116,8 +127,9 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore public void refreshSessionScope(String conversationId, ChatQueryMode chatMode, Long selectedDocumentId, - String selectedDocumentName) { - upsertDialogue(conversationId, ChatSessionStatus.RUNNING, chatMode, selectedDocumentId, selectedDocumentName); + String selectedDocumentName, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { + upsertDialogue(conversationId, ChatSessionStatus.RUNNING, chatMode, selectedDocumentId, selectedDocumentName, knowledgeBaseSelection); } @Override @@ -191,6 +203,9 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore resolveChatMode(dialogue), dialogue.getSelectedDocumentId(), safeText(dialogue.getSelectedDocumentName()), + safeText(dialogue.getKnowledgeBaseSelectionMode()), + readStringList(dialogue.getSelectedKnowledgeBaseIdsJson()), + readStringList(dialogue.getSelectedKnowledgeBaseNamesJson()), toInstant(dialogue.getCreateTime()), toInstant(dialogue.getEditTime()), exchanges @@ -279,6 +294,9 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore resolveChatMode(dialogue), dialogue.getSelectedDocumentId(), safeText(dialogue.getSelectedDocumentName()), + safeText(dialogue.getKnowledgeBaseSelectionMode()), + readStringList(dialogue.getSelectedKnowledgeBaseIdsJson()), + readStringList(dialogue.getSelectedKnowledgeBaseNamesJson()), toInstant(dialogue.getCreateTime()), toInstant(dialogue.getEditTime()), exchangeViewMap.getOrDefault(dialogue.getConversationId(), List.of()) @@ -320,6 +338,9 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore resolveChatMode(dialogue), dialogue.getSelectedDocumentId(), safeText(dialogue.getSelectedDocumentName()), + safeText(dialogue.getKnowledgeBaseSelectionMode()), + readStringList(dialogue.getSelectedKnowledgeBaseIdsJson()), + readStringList(dialogue.getSelectedKnowledgeBaseNamesJson()), toInstant(dialogue.getCreateTime()), toInstant(dialogue.getEditTime()), latestExchangeMap.containsKey(dialogue.getConversationId()) @@ -360,7 +381,8 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore ChatSessionStatus dialogueStage, ChatQueryMode chatMode, Long selectedDocumentId, - String selectedDocumentName) { + String selectedDocumentName, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelection) { Objects.requireNonNull(chatMode, "chatMode 不能为空"); SuperAgentChatDialogue dialogue = dialogueMapper.selectOne( activeDialogueByConversation(conversationId) @@ -376,6 +398,9 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore newDialogue.setChatMode(chatMode.getCode()); newDialogue.setSelectedDocumentId(selectedDocumentId); newDialogue.setSelectedDocumentName(selectedDocumentName); + newDialogue.setKnowledgeBaseSelectionMode(selectionModeName(knowledgeBaseSelection)); + newDialogue.setSelectedKnowledgeBaseIdsJson(writeJson(selectionIds(knowledgeBaseSelection))); + newDialogue.setSelectedKnowledgeBaseNamesJson(writeJson(selectionNames(knowledgeBaseSelection))); newDialogue.setStatus(BusinessStatus.YES.getCode()); dialogueMapper.insert(newDialogue); @@ -386,14 +411,20 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore boolean chatModeChanged = !Objects.equals(chatMode.getCode(), dialogue.getChatMode()); boolean documentScopeChanged = !Objects.equals(selectedDocumentId, dialogue.getSelectedDocumentId()) || !Objects.equals(safeText(selectedDocumentName), safeText(dialogue.getSelectedDocumentName())); + boolean knowledgeBaseScopeChanged = !Objects.equals(selectionModeName(knowledgeBaseSelection), safeText(dialogue.getKnowledgeBaseSelectionMode())) + || !Objects.equals(selectionIds(knowledgeBaseSelection), readStringList(dialogue.getSelectedKnowledgeBaseIdsJson())) + || !Objects.equals(selectionNames(knowledgeBaseSelection), readStringList(dialogue.getSelectedKnowledgeBaseNamesJson())); - if (stageChanged || chatModeChanged || documentScopeChanged) { + if (stageChanged || chatModeChanged || documentScopeChanged || knowledgeBaseScopeChanged) { SuperAgentChatDialogue updateDialogue = new SuperAgentChatDialogue(); updateDialogue.setId(dialogue.getId()); updateDialogue.setSessionStatus(dialogueStage.getCode()); updateDialogue.setChatMode(chatMode.getCode()); updateDialogue.setSelectedDocumentId(selectedDocumentId); updateDialogue.setSelectedDocumentName(selectedDocumentName); + updateDialogue.setKnowledgeBaseSelectionMode(selectionModeName(knowledgeBaseSelection)); + updateDialogue.setSelectedKnowledgeBaseIdsJson(writeJson(selectionIds(knowledgeBaseSelection))); + updateDialogue.setSelectedKnowledgeBaseNamesJson(writeJson(selectionNames(knowledgeBaseSelection))); dialogueMapper.updateById(updateDialogue); } } @@ -522,11 +553,42 @@ public class MybatisConversationArchiveStore implements ConversationArchiveStore safeText(exchange.getErrorMessage()), exchange.getFirstResponseTimeMs(), exchange.getTotalResponseTimeMs(), + safeText(exchange.getKnowledgeBaseSelectionMode()), + readStringList(exchange.getSelectedKnowledgeBaseIdsJson()), + readStringList(exchange.getSelectedKnowledgeBaseNamesJson()), + safeText(exchange.getRetrievalConfigSnapshotJson()), exchange.getCreateTime(), exchange.getEditTime() ); } + private String selectionModeName(KnowledgeBaseSelectionSnapshot snapshot) { + KnowledgeBaseSelectionMode mode = snapshot == null || snapshot.getSelectionMode() == null + ? KnowledgeBaseSelectionMode.NONE + : snapshot.getSelectionMode(); + return mode.name(); + } + + private List selectionIds(KnowledgeBaseSelectionSnapshot snapshot) { + if (snapshot == null || snapshot.getSelectedKnowledgeBaseIds() == null) { + return List.of(); + } + return snapshot.getSelectedKnowledgeBaseIds().stream() + .filter(Objects::nonNull) + .map(String::valueOf) + .toList(); + } + + private List selectionNames(KnowledgeBaseSelectionSnapshot snapshot) { + if (snapshot == null || snapshot.getSelectedKnowledgeBaseNames() == null) { + return List.of(); + } + return snapshot.getSelectedKnowledgeBaseNames().stream() + .filter(StrUtil::isNotBlank) + .map(String::trim) + .toList(); + } + private List readStringList(String json) { if (StrUtil.isBlank(json)) { return List.of(); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/StreamLaunchPlan.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/StreamLaunchPlan.java index e05832f..9eae6ee 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/StreamLaunchPlan.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/StreamLaunchPlan.java @@ -2,6 +2,7 @@ package org.javaup.ai.chatagent.service; import lombok.AllArgsConstructor; import lombok.Data; +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; import org.javaup.enums.ChatQueryMode; import java.time.LocalDate; @@ -28,6 +29,8 @@ public class StreamLaunchPlan { private final Long selectedTaskId; + private final KnowledgeBaseSelectionSnapshot knowledgeBaseSelectionSnapshot; + private final String leaseKey; private final String leaseOwnerToken; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/TaskInfo.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/TaskInfo.java index 07e5522..78df1d3 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/TaskInfo.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/chatagent/service/TaskInfo.java @@ -6,6 +6,7 @@ import org.javaup.ai.chatagent.model.debug.ChatDebugTrace; import org.javaup.ai.chatagent.rag.model.ConversationExecutionPlan; import org.javaup.ai.chatagent.model.SearchReference; import org.javaup.ai.chatagent.support.StreamEventMetadata; +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; import org.javaup.enums.ChatQueryMode; import reactor.core.Disposable; import reactor.core.publisher.Sinks; @@ -33,6 +34,7 @@ public class TaskInfo { private final Long selectedDocumentId; private final String selectedDocumentName; private final Long selectedTaskId; + private final KnowledgeBaseSelectionSnapshot knowledgeBaseSelectionSnapshot; private final LocalDate currentDate; private final String currentDateText; @@ -70,6 +72,7 @@ public class TaskInfo { Long selectedDocumentId, String selectedDocumentName, Long selectedTaskId, + KnowledgeBaseSelectionSnapshot knowledgeBaseSelectionSnapshot, LocalDate currentDate, String currentDateText, ConversationExecutionPlan executionPlan, @@ -92,6 +95,7 @@ public class TaskInfo { this.selectedDocumentId = selectedDocumentId; this.selectedDocumentName = selectedDocumentName; this.selectedTaskId = selectedTaskId; + this.knowledgeBaseSelectionSnapshot = knowledgeBaseSelectionSnapshot; this.currentDate = currentDate; this.currentDateText = currentDateText; this.executionPlan = executionPlan; @@ -148,6 +152,10 @@ public class TaskInfo { return selectedTaskId; } + public KnowledgeBaseSelectionSnapshot knowledgeBaseSelectionSnapshot() { + return knowledgeBaseSelectionSnapshot; + } + public LocalDate currentDate() { return currentDate; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/config/DocumentElasticsearchIndexInitializer.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/config/DocumentElasticsearchIndexInitializer.java index 852555a..c144f5a 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/config/DocumentElasticsearchIndexInitializer.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/config/DocumentElasticsearchIndexInitializer.java @@ -78,12 +78,6 @@ public class DocumentElasticsearchIndexInitializer { .properties("pageRange", property -> property.keyword(keyword -> keyword)) .properties("bboxJson", property -> property.keyword(keyword -> keyword)) .properties("sourceBlockIds", property -> property.keyword(keyword -> keyword)) - .properties("knowledgeScopeCode", property -> property.keyword(keyword -> keyword)) - .properties("knowledgeScopeName", property -> property.text(text -> text - .analyzer(analyzer) - .searchAnalyzer(searchAnalyzer))) - .properties("businessCategory", property -> property.keyword(keyword -> keyword)) - .properties("documentTags", property -> property.keyword(keyword -> keyword)) .properties("contentWithWeight", property -> property.text(text -> text .analyzer(analyzer) .searchAnalyzer(searchAnalyzer))) diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/config/KnowledgeRouteElasticsearchIndexInitializer.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/config/KnowledgeRouteElasticsearchIndexInitializer.java index 59affe2..90a1c5b 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/config/KnowledgeRouteElasticsearchIndexInitializer.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/config/KnowledgeRouteElasticsearchIndexInitializer.java @@ -68,7 +68,6 @@ public class KnowledgeRouteElasticsearchIndexInitializer { .properties("topicCode", property -> property.keyword(keyword -> keyword)) .properties("topicName", property -> property.text(text -> text.analyzer(analyzer).searchAnalyzer(searchAnalyzer))) .properties("documentName", property -> property.text(text -> text.analyzer(analyzer).searchAnalyzer(searchAnalyzer))) - .properties("businessCategory", property -> property.keyword(keyword -> keyword)) .properties("displayName", property -> property.text(text -> text.analyzer(analyzer).searchAnalyzer(searchAnalyzer))) .properties("descriptionText", property -> property.text(text -> text.analyzer(analyzer).searchAnalyzer(searchAnalyzer))) .properties("aliasesText", property -> property.text(text -> text.analyzer(analyzer).searchAnalyzer(searchAnalyzer))) diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/controller/KnowledgeBaseManageController.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/controller/KnowledgeBaseManageController.java new file mode 100644 index 0000000..6065c61 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/controller/KnowledgeBaseManageController.java @@ -0,0 +1,56 @@ +package org.javaup.ai.manage.controller; + +import io.swagger.v3.oas.annotations.Operation; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.javaup.ai.manage.dto.KnowledgeBaseConfigUpdateDto; +import org.javaup.ai.manage.dto.KnowledgeBaseDeleteDto; +import org.javaup.ai.manage.dto.KnowledgeBaseDetailDto; +import org.javaup.ai.manage.dto.KnowledgeBaseSaveDto; +import org.javaup.ai.manage.service.KnowledgeBaseManageService; +import org.javaup.ai.manage.vo.KnowledgeBaseItemVo; +import org.javaup.common.ApiResponse; +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 java.util.List; + +@AllArgsConstructor +@RestController +@RequestMapping("/manage/knowledge/base") +public class KnowledgeBaseManageController { + + private final KnowledgeBaseManageService knowledgeBaseManageService; + + @Operation(summary = "保存知识库") + @PostMapping("/save") + public ApiResponse save(@Valid @RequestBody KnowledgeBaseSaveDto dto) { + return ApiResponse.ok(knowledgeBaseManageService.save(dto)); + } + + @Operation(summary = "删除知识库") + @PostMapping("/delete") + public ApiResponse delete(@Valid @RequestBody KnowledgeBaseDeleteDto dto) { + return ApiResponse.ok(knowledgeBaseManageService.delete(dto)); + } + + @Operation(summary = "查询知识库列表") + @PostMapping("/list") + public ApiResponse> list() { + return ApiResponse.ok(knowledgeBaseManageService.list()); + } + + @Operation(summary = "查询知识库详情") + @PostMapping("/detail") + public ApiResponse detail(@Valid @RequestBody KnowledgeBaseDetailDto dto) { + return ApiResponse.ok(knowledgeBaseManageService.detail(dto)); + } + + @Operation(summary = "更新知识库检索配置") + @PostMapping("/config/update") + public ApiResponse updateConfig(@Valid @RequestBody KnowledgeBaseConfigUpdateDto dto) { + return ApiResponse.ok(knowledgeBaseManageService.updateConfig(dto)); + } +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/controller/KnowledgeManageController.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/controller/KnowledgeManageController.java index da7503e..faf1058 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/controller/KnowledgeManageController.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/controller/KnowledgeManageController.java @@ -7,6 +7,7 @@ import org.javaup.ai.manage.dto.DocumentProfileDetailQueryDto; import org.javaup.ai.manage.dto.DocumentProfileRegenerateDto; import org.javaup.ai.manage.dto.KnowledgeRouteTraceQueryDto; import org.javaup.ai.manage.dto.KnowledgeScopeDeleteDto; +import org.javaup.ai.manage.dto.KnowledgeScopeQueryDto; import org.javaup.ai.manage.dto.KnowledgeScopeSaveDto; import org.javaup.ai.manage.dto.KnowledgeTopicDeleteDto; import org.javaup.ai.manage.dto.KnowledgeTopicQueryDto; @@ -57,8 +58,8 @@ public class KnowledgeManageController { @Operation(summary = "查询知识范围列表") @PostMapping("/scope/list") - public ApiResponse> listScopes() { - return ApiResponse.ok(knowledgeManageService.listScopes()); + public ApiResponse> listScopes(@RequestBody(required = false) KnowledgeScopeQueryDto dto) { + return ApiResponse.ok(knowledgeManageService.listScopes(dto == null ? new KnowledgeScopeQueryDto() : dto)); } @Operation(summary = "保存知识主题节点") diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentDocument.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentDocument.java index 7d18c0a..d951cc3 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentDocument.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentDocument.java @@ -61,13 +61,11 @@ public class SuperAgentDocument extends BaseTableData { private String parseErrorMsg; - private String knowledgeScopeCode; + private Long knowledgeBaseId; - private String knowledgeScopeName; + private String knowledgeBaseCode; - private String businessCategory; - - private String documentTags; + private String knowledgeBaseName; private Long currentPlanId; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeBase.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeBase.java new file mode 100644 index 0000000..f54f2c3 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeBase.java @@ -0,0 +1,41 @@ +package org.javaup.ai.manage.data; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.javaup.database.data.BaseTableData; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("super_agent_knowledge_base") +@EqualsAndHashCode(callSuper = true) +public class SuperAgentKnowledgeBase extends BaseTableData { + + @TableId(value = "id", type = IdType.INPUT) + private Long id; + + private String baseCode; + + private String baseName; + + private String description; + + private String embeddingModel; + + private String retrievalConfigJson; + + private String graphRagConfigJson; + + private String raptorConfigJson; + + private String metadataFilterJson; + + private Integer isDefault; + + private Integer sortOrder; +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeRouteTrace.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeRouteTrace.java index 3cbd757..d748cf9 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeRouteTrace.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeRouteTrace.java @@ -36,6 +36,14 @@ public class SuperAgentKnowledgeRouteTrace extends BaseTableData { private String mode; + private String knowledgeBaseSelectionMode; + + private String selectedKnowledgeBaseIdsJson; + + private String selectedKnowledgeBaseNamesJson; + + private String allowedDocumentIdsJson; + private String topScopesJson; private String topTopicsJson; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeScopeNode.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeScopeNode.java index 4a5f567..c4cba8b 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeScopeNode.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeScopeNode.java @@ -24,6 +24,8 @@ public class SuperAgentKnowledgeScopeNode extends BaseTableData { @TableId(value = "id", type = IdType.INPUT) private Long id; + private Long knowledgeBaseId; + private String scopeCode; private String scopeName; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeTopicNode.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeTopicNode.java index 770f7e9..3447e5f 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeTopicNode.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentKnowledgeTopicNode.java @@ -24,6 +24,8 @@ public class SuperAgentKnowledgeTopicNode extends BaseTableData { @TableId(value = "id", type = IdType.INPUT) private Long id; + private Long knowledgeBaseId; + private String topicCode; private String topicName; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentTopicDocumentRelation.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentTopicDocumentRelation.java index 3322829..6043991 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentTopicDocumentRelation.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/data/SuperAgentTopicDocumentRelation.java @@ -26,6 +26,8 @@ public class SuperAgentTopicDocumentRelation extends BaseTableData { @TableId(value = "id", type = IdType.INPUT) private Long id; + private Long knowledgeBaseId; + private String topicCode; private Long documentId; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/DocumentUploadDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/DocumentUploadDto.java index a7713c0..8760504 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/DocumentUploadDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/DocumentUploadDto.java @@ -15,11 +15,5 @@ public class DocumentUploadDto { private String operatorId; - private String knowledgeScopeCode; - - private String knowledgeScopeName; - - private String businessCategory; - - private String documentTags; + private String knowledgeBaseId; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseConfigUpdateDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseConfigUpdateDto.java new file mode 100644 index 0000000..f7ae480 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseConfigUpdateDto.java @@ -0,0 +1,19 @@ +package org.javaup.ai.manage.dto; + +import lombok.Data; + +@Data +public class KnowledgeBaseConfigUpdateDto { + + private String id; + + private String retrievalConfigJson; + + private String graphRagConfigJson; + + private String raptorConfigJson; + + private String metadataFilterJson; + + private String operatorId; +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseDeleteDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseDeleteDto.java new file mode 100644 index 0000000..b8dfe30 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseDeleteDto.java @@ -0,0 +1,9 @@ +package org.javaup.ai.manage.dto; + +import lombok.Data; + +@Data +public class KnowledgeBaseDeleteDto { + + private String id; +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseDetailDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseDetailDto.java new file mode 100644 index 0000000..fc65a74 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseDetailDto.java @@ -0,0 +1,9 @@ +package org.javaup.ai.manage.dto; + +import lombok.Data; + +@Data +public class KnowledgeBaseDetailDto { + + private String id; +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseSaveDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseSaveDto.java new file mode 100644 index 0000000..9782c0f --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeBaseSaveDto.java @@ -0,0 +1,31 @@ +package org.javaup.ai.manage.dto; + +import lombok.Data; + +@Data +public class KnowledgeBaseSaveDto { + + private String id; + + private String baseCode; + + private String baseName; + + private String description; + + private String embeddingModel; + + private String retrievalConfigJson; + + private String graphRagConfigJson; + + private String raptorConfigJson; + + private String metadataFilterJson; + + private String isDefault; + + private String sortOrder; + + private String operatorId; +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeDeleteDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeDeleteDto.java index 2e3d60a..de656f7 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeDeleteDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeDeleteDto.java @@ -10,6 +10,8 @@ import lombok.Data; @Data public class KnowledgeScopeDeleteDto { + private String knowledgeBaseId; + private String scopeCode; private String operatorId; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeQueryDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeQueryDto.java new file mode 100644 index 0000000..53a7e4a --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeQueryDto.java @@ -0,0 +1,9 @@ +package org.javaup.ai.manage.dto; + +import lombok.Data; + +@Data +public class KnowledgeScopeQueryDto { + + private String knowledgeBaseId; +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeSaveDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeSaveDto.java index b1aee25..ff1f2f0 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeSaveDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeScopeSaveDto.java @@ -12,6 +12,8 @@ public class KnowledgeScopeSaveDto { private String id; + private String knowledgeBaseId; + private String scopeCode; private String scopeName; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicDeleteDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicDeleteDto.java index 0b22b31..6511b83 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicDeleteDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicDeleteDto.java @@ -10,6 +10,8 @@ import lombok.Data; @Data public class KnowledgeTopicDeleteDto { + private String knowledgeBaseId; + private String topicCode; private String operatorId; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicQueryDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicQueryDto.java index 33a371b..558baea 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicQueryDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicQueryDto.java @@ -10,5 +10,7 @@ import lombok.Data; @Data public class KnowledgeTopicQueryDto { + private String knowledgeBaseId; + private String scopeCode; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicSaveDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicSaveDto.java index 2c20c70..f8e6245 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicSaveDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/KnowledgeTopicSaveDto.java @@ -12,6 +12,8 @@ public class KnowledgeTopicSaveDto { private String id; + private String knowledgeBaseId; + private String topicCode; private String topicName; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationListQueryDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationListQueryDto.java index 2a37424..cb469a4 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationListQueryDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationListQueryDto.java @@ -10,5 +10,7 @@ import lombok.Data; @Data public class TopicDocumentRelationListQueryDto { + private String knowledgeBaseId; + private String topicCode; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationRemoveDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationRemoveDto.java index 8784bc7..dd3c2ec 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationRemoveDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationRemoveDto.java @@ -10,6 +10,8 @@ import lombok.Data; @Data public class TopicDocumentRelationRemoveDto { + private String knowledgeBaseId; + private String topicCode; private String documentId; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationSaveDto.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationSaveDto.java index b9e7b54..d9eb12b 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationSaveDto.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/dto/TopicDocumentRelationSaveDto.java @@ -10,6 +10,8 @@ import lombok.Data; @Data public class TopicDocumentRelationSaveDto { + private String knowledgeBaseId; + private String topicCode; private String documentId; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/mapper/SuperAgentKnowledgeBaseMapper.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/mapper/SuperAgentKnowledgeBaseMapper.java new file mode 100644 index 0000000..3d63555 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/mapper/SuperAgentKnowledgeBaseMapper.java @@ -0,0 +1,9 @@ +package org.javaup.ai.manage.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.javaup.ai.manage.data.SuperAgentKnowledgeBase; + +@Mapper +public interface SuperAgentKnowledgeBaseMapper extends BaseMapper { +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/DocumentRetrieveFilters.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/DocumentRetrieveFilters.java index b5a07ba..fd99e15 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/DocumentRetrieveFilters.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/DocumentRetrieveFilters.java @@ -23,12 +23,6 @@ public class DocumentRetrieveFilters { @Builder.Default private List documentNameHints = new ArrayList<>(); - @Builder.Default - private List businessCategoryHints = new ArrayList<>(); - - @Builder.Default - private List documentTagHints = new ArrayList<>(); - @Builder.Default private List sectionPathHints = new ArrayList<>(); @@ -46,8 +40,6 @@ public class DocumentRetrieveFilters { public boolean isEmpty() { return documentNameHints.isEmpty() - && businessCategoryHints.isEmpty() - && documentTagHints.isEmpty() && sectionPathHints.isEmpty() && canonicalPathHints.isEmpty() && structureNodeIdHints.isEmpty() diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/KnowledgeBaseSelectionSnapshot.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/KnowledgeBaseSelectionSnapshot.java new file mode 100644 index 0000000..972891c --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/KnowledgeBaseSelectionSnapshot.java @@ -0,0 +1,49 @@ +package org.javaup.ai.manage.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; +import org.javaup.ai.manage.data.SuperAgentKnowledgeBase; +import org.javaup.enums.KnowledgeBaseSelectionMode; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KnowledgeBaseSelectionSnapshot { + + @Builder.Default + private KnowledgeBaseSelectionMode selectionMode = KnowledgeBaseSelectionMode.NONE; + + @Builder.Default + private List selectedKnowledgeBaseIds = new ArrayList<>(); + + @Builder.Default + private List selectedKnowledgeBaseNames = new ArrayList<>(); + + @Builder.Default + private List selectedKnowledgeBases = new ArrayList<>(); + + @Builder.Default + private List allowedDocuments = new ArrayList<>(); + + @Builder.Default + private List allowedDocumentIds = new ArrayList<>(); + + @Builder.Default + private List allowedTaskIds = new ArrayList<>(); + + private RagRuntimeOptions ragRuntimeOptions; + + public static KnowledgeBaseSelectionSnapshot none(RagRuntimeOptions options) { + return KnowledgeBaseSelectionSnapshot.builder() + .selectionMode(KnowledgeBaseSelectionMode.NONE) + .ragRuntimeOptions(options) + .build(); + } +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/KnowledgeDocumentDescriptor.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/KnowledgeDocumentDescriptor.java index f4547ec..c5e3291 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/KnowledgeDocumentDescriptor.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/KnowledgeDocumentDescriptor.java @@ -21,11 +21,9 @@ public class KnowledgeDocumentDescriptor { private Long lastIndexTaskId; - private String knowledgeScopeCode; + private Long knowledgeBaseId; - private String knowledgeScopeName; + private String knowledgeBaseCode; - private String businessCategory; - - private String documentTags; + private String knowledgeBaseName; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/es/DocumentKeywordIndexRecord.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/es/DocumentKeywordIndexRecord.java index 1433eb2..edabc02 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/es/DocumentKeywordIndexRecord.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/es/DocumentKeywordIndexRecord.java @@ -50,15 +50,6 @@ public class DocumentKeywordIndexRecord { private String sourceBlockIds; - private String knowledgeScopeCode; - - private String knowledgeScopeName; - - private String businessCategory; - - @Builder.Default - private List documentTags = new ArrayList<>(); - private String contentWithWeight; private String chunkType; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/es/KnowledgeRouteIndexRecord.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/es/KnowledgeRouteIndexRecord.java index d28b5ad..b138b47 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/es/KnowledgeRouteIndexRecord.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/es/KnowledgeRouteIndexRecord.java @@ -28,6 +28,8 @@ public class KnowledgeRouteIndexRecord { private Long documentId; + private Long knowledgeBaseId; + private String scopeCode; private String scopeName; @@ -38,8 +40,6 @@ public class KnowledgeRouteIndexRecord { private String documentName; - private String businessCategory; - private String displayName; private String descriptionText; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/route/DocumentRouteCandidate.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/route/DocumentRouteCandidate.java index e075587..4499050 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/route/DocumentRouteCandidate.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/route/DocumentRouteCandidate.java @@ -22,14 +22,6 @@ public class DocumentRouteCandidate { private String lastIndexTaskId; - private String knowledgeScopeCode; - - private String knowledgeScopeName; - - private String businessCategory; - - private String documentTags; - private BigDecimal score; private String reason; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/route/KnowledgeRouteContext.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/route/KnowledgeRouteContext.java new file mode 100644 index 0000000..b7b2c32 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/model/route/KnowledgeRouteContext.java @@ -0,0 +1,37 @@ +package org.javaup.ai.manage.model.route; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; +import org.javaup.enums.KnowledgeBaseSelectionMode; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KnowledgeRouteContext { + + private String question; + + private String rewriteQuestion; + + @Builder.Default + private KnowledgeBaseSelectionMode knowledgeBaseSelectionMode = KnowledgeBaseSelectionMode.NONE; + + @Builder.Default + private List selectedKnowledgeBaseIds = new ArrayList<>(); + + @Builder.Default + private List selectedKnowledgeBaseNames = new ArrayList<>(); + + @Builder.Default + private List allowedDocuments = new ArrayList<>(); + + @Builder.Default + private List allowedDocumentIds = new ArrayList<>(); +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/DocumentKnowledgeService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/DocumentKnowledgeService.java index 039ba58..b35dc40 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/DocumentKnowledgeService.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/DocumentKnowledgeService.java @@ -4,6 +4,7 @@ import org.javaup.ai.manage.model.DocumentRetrieveRequest; import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; import org.springframework.ai.document.Document; +import java.util.Collection; import java.util.List; /** @@ -16,6 +17,8 @@ public interface DocumentKnowledgeService { List listRetrievableDocuments(); + List listRetrievableDocumentsByKnowledgeBaseIds(Collection knowledgeBaseIds); + List vectorSearch(DocumentRetrieveRequest request); List keywordSearch(DocumentRetrieveRequest request); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeBaseManageService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeBaseManageService.java new file mode 100644 index 0000000..4cb2f82 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeBaseManageService.java @@ -0,0 +1,33 @@ +package org.javaup.ai.manage.service; + +import org.javaup.ai.manage.data.SuperAgentKnowledgeBase; +import org.javaup.ai.manage.dto.KnowledgeBaseConfigUpdateDto; +import org.javaup.ai.manage.dto.KnowledgeBaseDeleteDto; +import org.javaup.ai.manage.dto.KnowledgeBaseDetailDto; +import org.javaup.ai.manage.dto.KnowledgeBaseSaveDto; +import org.javaup.ai.manage.vo.KnowledgeBaseItemVo; +import org.javaup.ai.manage.vo.KnowledgeBaseOptionVo; + +import java.util.Collection; +import java.util.List; + +public interface KnowledgeBaseManageService { + + KnowledgeBaseItemVo save(KnowledgeBaseSaveDto dto); + + boolean delete(KnowledgeBaseDeleteDto dto); + + List list(); + + KnowledgeBaseItemVo detail(KnowledgeBaseDetailDto dto); + + KnowledgeBaseItemVo updateConfig(KnowledgeBaseConfigUpdateDto dto); + + List listOptions(); + + List listEnabledByIds(Collection ids); + + List listAllEnabled(); + + SuperAgentKnowledgeBase requireEnabled(Long id); +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeBaseRetrievalScopeService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeBaseRetrievalScopeService.java new file mode 100644 index 0000000..d84cb47 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeBaseRetrievalScopeService.java @@ -0,0 +1,14 @@ +package org.javaup.ai.manage.service; + +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; +import org.javaup.enums.ChatQueryMode; +import org.javaup.enums.KnowledgeBaseSelectionMode; + +import java.util.Collection; + +public interface KnowledgeBaseRetrievalScopeService { + + KnowledgeBaseSelectionSnapshot resolve(ChatQueryMode chatMode, + KnowledgeBaseSelectionMode selectionMode, + Collection selectedKnowledgeBaseIds); +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeManageService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeManageService.java index 3b907fd..b245096 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeManageService.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeManageService.java @@ -5,6 +5,7 @@ import org.javaup.ai.manage.dto.DocumentProfileDetailQueryDto; import org.javaup.ai.manage.dto.DocumentProfileRegenerateDto; import org.javaup.ai.manage.dto.KnowledgeRouteTraceQueryDto; import org.javaup.ai.manage.dto.KnowledgeScopeDeleteDto; +import org.javaup.ai.manage.dto.KnowledgeScopeQueryDto; import org.javaup.ai.manage.dto.KnowledgeScopeSaveDto; import org.javaup.ai.manage.dto.KnowledgeTopicDeleteDto; import org.javaup.ai.manage.dto.KnowledgeTopicQueryDto; @@ -31,7 +32,7 @@ public interface KnowledgeManageService { boolean deleteScope(KnowledgeScopeDeleteDto dto); - List listScopes(); + List listScopes(KnowledgeScopeQueryDto dto); KnowledgeTopicItemVo saveTopic(KnowledgeTopicSaveDto dto); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeRouteIndexService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeRouteIndexService.java index 0b0853a..0e04f9b 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeRouteIndexService.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeRouteIndexService.java @@ -1,5 +1,6 @@ package org.javaup.ai.manage.service; +import java.util.Collection; import java.util.List; /** @@ -12,7 +13,7 @@ public interface KnowledgeRouteIndexService { void refreshIfNeeded(); - List search(String routingText, String entityType, int size); + List search(String routingText, String entityType, int size, Collection knowledgeBaseIds); void deleteDocumentRoute(Long documentId); @@ -21,6 +22,7 @@ public interface KnowledgeRouteIndexService { String entityCode, String entityType, Long documentId, + Long knowledgeBaseId, String scopeCode, String topicCode, String documentName, diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeRouteService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeRouteService.java index e1595e0..a566174 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeRouteService.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/KnowledgeRouteService.java @@ -1,5 +1,6 @@ package org.javaup.ai.manage.service; +import org.javaup.ai.manage.model.route.KnowledgeRouteContext; import org.javaup.ai.manage.model.route.KnowledgeRouteDecision; /** @@ -9,17 +10,15 @@ import org.javaup.ai.manage.model.route.KnowledgeRouteDecision; **/ public interface KnowledgeRouteService { - KnowledgeRouteDecision route(String question, String rewriteQuestion); + KnowledgeRouteDecision route(KnowledgeRouteContext context); void recordShadowRoute(String conversationId, long exchangeId, Long selectedDocumentId, - String question, - String rewriteQuestion); + KnowledgeRouteContext context); void recordAutoRoute(String conversationId, long exchangeId, - String question, - String rewriteQuestion, + KnowledgeRouteContext context, KnowledgeRouteDecision decision); } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/RaptorBuildService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/RaptorBuildService.java index 8145a9a..a919899 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/RaptorBuildService.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/RaptorBuildService.java @@ -9,7 +9,7 @@ public interface RaptorBuildService { RaptorBuildResult rebuildDocumentTree(Long documentId, Long taskId, List chunks); - RaptorBuildResult rebuildKnowledgeScopeTree(String knowledgeScopeCode); + RaptorBuildResult rebuildKnowledgeScopeTree(Long knowledgeBaseId, String knowledgeScopeCode); void deleteByTask(Long documentId, Long taskId); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentAsyncProcessServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentAsyncProcessServiceImpl.java index e18fcd9..cd9185e 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentAsyncProcessServiceImpl.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentAsyncProcessServiceImpl.java @@ -54,7 +54,6 @@ import org.javaup.ai.manage.support.DocumentStrategyPlanDraft; import org.javaup.ai.manage.support.DocumentStrategyStepDraft; import org.javaup.ai.manage.support.MybatisBatchExecutor; import org.javaup.ai.manage.support.ParentBlockCandidate; -import org.javaup.ai.manage.support.RaptorScopeSupport; import org.javaup.enums.BusinessStatus; import org.javaup.enums.DocumentChunkSourceTypeEnum; import org.javaup.enums.DocumentFileTypeEnum; @@ -102,8 +101,6 @@ public class DocumentAsyncProcessServiceImpl implements DocumentAsyncProcessServ private static final Set RUNNING_INDEX_TASK_IDS = ConcurrentHashMap.newKeySet(); - private static final Set RUNNING_DATASET_RAPTOR_SCOPE_KEYS = ConcurrentHashMap.newKeySet(); - private static final TypeReference> EXT_JSON_TYPE = new TypeReference<>() { }; @@ -864,8 +861,6 @@ public class DocumentAsyncProcessServiceImpl implements DocumentAsyncProcessServ document.setLastIndexTaskId(taskId); documentMapper.updateById(document); - submitDatasetRaptorBuild(document, taskId); - finishTaskSuccess(task, DocumentTaskStageEnum.STORE_COMPLETE.getCode(), startTime); progressCacheService.update(document, task); saveIndexBuildLog(taskId, documentId, @@ -919,143 +914,6 @@ public class DocumentAsyncProcessServiceImpl implements DocumentAsyncProcessServ } } - private void submitDatasetRaptorBuild(SuperAgentDocument document, Long taskId) { - if (document == null || StrUtil.isBlank(document.getKnowledgeScopeCode())) { - return; - } - Long documentId = document.getId(); - String knowledgeScopeCode = document.getKnowledgeScopeCode(); - String knowledgeScopeName = document.getKnowledgeScopeName(); - String normalizedScopeCode = RaptorScopeSupport.normalizeScopeCode(knowledgeScopeCode); - String scopeKey = RaptorScopeSupport.knowledgeScopeKey(normalizedScopeCode); - if (StrUtil.isBlank(normalizedScopeCode) || StrUtil.isBlank(scopeKey)) { - return; - } - if (!RUNNING_DATASET_RAPTOR_SCOPE_KEYS.add(scopeKey)) { - log.info("RAPTOR dataset-level 构建已在后台执行中,跳过重复提交,documentId={}, taskId={}, scopeKey={}", - documentId, taskId, scopeKey); - saveIndexBuildLog(taskId, documentId, - DocumentTaskStageEnum.RAPTOR.getCode(), - DocumentTaskEventTypeEnum.COMPLETE.getCode(), - DocumentLogLevelEnum.INFO.getCode(), - DocumentOperatorTypeEnum.SYSTEM.getCode(), - null, - "knowledge scope 级 RAPTOR 跨文档摘要树已有后台任务执行中,本次跳过重复提交。", - detail("knowledgeScopeCode", knowledgeScopeCode, - "knowledgeScopeName", knowledgeScopeName, - "scopeKey", scopeKey, - "async", true, - "deduplicated", true)); - return; - } - try { - datasetRaptorExecutorService.execute(() -> { - try { - runDatasetRaptorBuild(documentId, taskId, knowledgeScopeCode, knowledgeScopeName, scopeKey); - } - finally { - RUNNING_DATASET_RAPTOR_SCOPE_KEYS.remove(scopeKey); - } - }); - log.info("RAPTOR dataset-level 构建已提交后台执行,documentId={}, taskId={}, scopeKey={}", - documentId, taskId, scopeKey); - saveIndexBuildLog(taskId, documentId, - DocumentTaskStageEnum.RAPTOR.getCode(), - DocumentTaskEventTypeEnum.START.getCode(), - DocumentLogLevelEnum.INFO.getCode(), - DocumentOperatorTypeEnum.SYSTEM.getCode(), - null, - "knowledge scope 级 RAPTOR 跨文档摘要树已提交后台构建。", - detail("knowledgeScopeCode", knowledgeScopeCode, - "knowledgeScopeName", knowledgeScopeName, - "scopeKey", scopeKey, - "async", true)); - } - catch (RejectedExecutionException exception) { - RUNNING_DATASET_RAPTOR_SCOPE_KEYS.remove(scopeKey); - log.warn("RAPTOR dataset-level 后台线程池已满,跳过本次提交,documentId={}, taskId={}, scopeKey={}", - documentId, taskId, scopeKey, exception); - saveIndexBuildLog(taskId, documentId, - DocumentTaskStageEnum.RAPTOR.getCode(), - DocumentTaskEventTypeEnum.FAILED.getCode(), - DocumentLogLevelEnum.WARN.getCode(), - DocumentOperatorTypeEnum.SYSTEM.getCode(), - null, - "knowledge scope 级 RAPTOR 跨文档摘要树后台线程池已满,本次未提交。", - detail("knowledgeScopeCode", knowledgeScopeCode, - "knowledgeScopeName", knowledgeScopeName, - "scopeKey", scopeKey, - "async", true, - "error", exception.getMessage())); - } - } - - private void runDatasetRaptorBuild(Long documentId, - Long taskId, - String knowledgeScopeCode, - String knowledgeScopeName, - String scopeKey) { - long datasetRaptorStartedNanos = System.nanoTime(); - saveIndexBuildLog(taskId, documentId, - DocumentTaskStageEnum.RAPTOR.getCode(), - DocumentTaskEventTypeEnum.START.getCode(), - DocumentLogLevelEnum.INFO.getCode(), - DocumentOperatorTypeEnum.SYSTEM.getCode(), - null, - "开始后台构建 knowledge scope 级 RAPTOR 跨文档摘要树。", - detail("knowledgeScopeCode", knowledgeScopeCode, - "knowledgeScopeName", knowledgeScopeName, - "scopeKey", scopeKey, - "async", true)); - try { - RaptorBuildResult datasetRaptorBuildResult = raptorBuildService.rebuildKnowledgeScopeTree(knowledgeScopeCode); - long datasetRaptorCostMillis = elapsedMillis(datasetRaptorStartedNanos); - log.info("RAPTOR dataset-level 后台构建完成,documentId={}, taskId={}, scopeKey={}, inputMode={}, inputCount={}, nodeCount={}, levelCount={}, sourceChunkCount={}, costMillis={}", - documentId, taskId, scopeKey, datasetRaptorBuildResult.getInputMode(), datasetRaptorBuildResult.getInputCount(), - datasetRaptorBuildResult.getNodeCount(), datasetRaptorBuildResult.getLevelCount(), - datasetRaptorBuildResult.getSourceChunkCount(), datasetRaptorCostMillis); - saveIndexBuildLog(taskId, documentId, - DocumentTaskStageEnum.RAPTOR.getCode(), - DocumentTaskEventTypeEnum.COMPLETE.getCode(), - DocumentLogLevelEnum.INFO.getCode(), - DocumentOperatorTypeEnum.SYSTEM.getCode(), - null, - "knowledge scope 级 RAPTOR 跨文档摘要树后台构建完成,耗时 " + datasetRaptorCostMillis + "ms。", - detail("knowledgeScopeCode", knowledgeScopeCode, - "knowledgeScopeName", knowledgeScopeName, - "scopeKey", scopeKey, - "async", true, - "inputMode", datasetRaptorBuildResult.getInputMode(), - "inputCount", datasetRaptorBuildResult.getInputCount(), - "reusableSummaryInputCount", datasetRaptorBuildResult.getReusableSummaryInputCount(), - "originalChunkInputCount", datasetRaptorBuildResult.getOriginalChunkInputCount(), - "nodeCount", datasetRaptorBuildResult.getNodeCount(), - "levelCount", datasetRaptorBuildResult.getLevelCount(), - "sourceChunkCount", datasetRaptorBuildResult.getSourceChunkCount(), - "sourceQualityReport", datasetRaptorBuildResult.getSourceQualityReport(), - "savedQualityReport", datasetRaptorBuildResult.getSavedQualityReport(), - "costMillis", datasetRaptorCostMillis)); - } - catch (Exception exception) { - long datasetRaptorCostMillis = elapsedMillis(datasetRaptorStartedNanos); - log.error("RAPTOR dataset-level 后台构建失败,documentId={}, taskId={}, scopeKey={}, costMillis={}", - documentId, taskId, scopeKey, datasetRaptorCostMillis, exception); - saveIndexBuildLog(taskId, documentId, - DocumentTaskStageEnum.RAPTOR.getCode(), - DocumentTaskEventTypeEnum.FAILED.getCode(), - DocumentLogLevelEnum.ERROR.getCode(), - DocumentOperatorTypeEnum.SYSTEM.getCode(), - null, - "knowledge scope 级 RAPTOR 跨文档摘要树后台构建失败,单文档索引结果保持成功。", - detail("knowledgeScopeCode", knowledgeScopeCode, - "knowledgeScopeName", knowledgeScopeName, - "scopeKey", scopeKey, - "async", true, - "error", exception.getMessage(), - "costMillis", datasetRaptorCostMillis)); - } - } - private void cleanupIndexBuildTaskArtifacts(Long documentId, Long taskId) { log.warn("检测到索引构建任务重入,开始清理同 task 旧产物,documentId={}, taskId={}", documentId, taskId); long startedNanos = System.nanoTime(); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentKnowledgeServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentKnowledgeServiceImpl.java index 10ebd55..a9d40ef 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentKnowledgeServiceImpl.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentKnowledgeServiceImpl.java @@ -28,6 +28,7 @@ import org.springframework.stereotype.Service; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collection; import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedHashMap; @@ -95,12 +96,32 @@ public class DocumentKnowledgeServiceImpl implements DocumentKnowledgeService { @Override public List listRetrievableDocuments() { - List documents = documentMapper.selectList(new LambdaQueryWrapper() + return toDescriptors(documentMapper.selectList(new LambdaQueryWrapper() .eq(SuperAgentDocument::getStatus, BusinessStatus.YES.getCode()) .eq(SuperAgentDocument::getIndexStatus, DocumentIndexStatusEnum.BUILD_SUCCESS.getCode()) .isNotNull(SuperAgentDocument::getLastIndexTaskId) .orderByDesc(SuperAgentDocument::getEditTime) - .orderByDesc(SuperAgentDocument::getId)); + .orderByDesc(SuperAgentDocument::getId))); + } + + @Override + public List listRetrievableDocumentsByKnowledgeBaseIds(Collection knowledgeBaseIds) { + List ids = knowledgeBaseIds == null + ? List.of() + : knowledgeBaseIds.stream().filter(Objects::nonNull).distinct().toList(); + if (ids.isEmpty()) { + return List.of(); + } + return toDescriptors(documentMapper.selectList(new LambdaQueryWrapper() + .eq(SuperAgentDocument::getStatus, BusinessStatus.YES.getCode()) + .eq(SuperAgentDocument::getIndexStatus, DocumentIndexStatusEnum.BUILD_SUCCESS.getCode()) + .isNotNull(SuperAgentDocument::getLastIndexTaskId) + .in(SuperAgentDocument::getKnowledgeBaseId, ids) + .orderByDesc(SuperAgentDocument::getEditTime) + .orderByDesc(SuperAgentDocument::getId))); + } + + private List toDescriptors(List documents) { if (CollUtil.isEmpty(documents)) { return List.of(); } @@ -110,10 +131,9 @@ public class DocumentKnowledgeServiceImpl implements DocumentKnowledgeService { document.getId(), document.getDocumentName(), document.getLastIndexTaskId(), - document.getKnowledgeScopeCode(), - document.getKnowledgeScopeName(), - document.getBusinessCategory(), - document.getDocumentTags() + document.getKnowledgeBaseId(), + document.getKnowledgeBaseCode(), + document.getKnowledgeBaseName() )) .toList(); } @@ -323,10 +343,9 @@ public class DocumentKnowledgeServiceImpl implements DocumentKnowledgeService { metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_ID, descriptor.getDocumentId()); metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_NAME, safeText(descriptor.getDocumentName())); - metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_SCOPE_CODE, safeText(descriptor.getKnowledgeScopeCode())); - metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_SCOPE_NAME, safeText(descriptor.getKnowledgeScopeName())); - metadata.put(DocumentKnowledgeMetadataKeys.BUSINESS_CATEGORY, safeText(descriptor.getBusinessCategory())); - metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_TAGS, safeText(descriptor.getDocumentTags())); + putIfNotNull(metadata, DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_ID, descriptor.getKnowledgeBaseId()); + metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_CODE, safeText(descriptor.getKnowledgeBaseCode())); + metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_BASE_NAME, safeText(descriptor.getKnowledgeBaseName())); } graphRagTypedChunkMetadataSupport.enrichMetadata(metadata, chunkType, sourceBlockIds); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentManageServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentManageServiceImpl.java index 054ba8a..186e04b 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentManageServiceImpl.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentManageServiceImpl.java @@ -18,6 +18,7 @@ import org.javaup.ai.manage.data.SuperAgentDocumentStrategyPlan; import org.javaup.ai.manage.data.SuperAgentDocumentStrategyStep; import org.javaup.ai.manage.data.SuperAgentDocumentTask; import org.javaup.ai.manage.data.SuperAgentDocumentTaskLog; +import org.javaup.ai.manage.data.SuperAgentKnowledgeBase; import org.javaup.ai.manage.data.SuperAgentTopicDocumentRelation; import org.javaup.ai.manage.dto.DocumentChunkQueryDto; import org.javaup.ai.manage.dto.DocumentChunkDetailQueryDto; @@ -58,6 +59,7 @@ import org.javaup.ai.manage.service.DocumentStrategyService; import org.javaup.ai.manage.service.DocumentTaskLogService; import org.javaup.ai.manage.service.DocumentVectorGateway; import org.javaup.ai.manage.service.GraphRagBuildService; +import org.javaup.ai.manage.service.KnowledgeBaseManageService; import org.javaup.ai.manage.service.KnowledgeRouteIndexService; import org.javaup.ai.manage.service.RaptorBuildService; import org.javaup.ai.manage.service.keyword.DocumentKeywordSearchGateway; @@ -188,6 +190,8 @@ public class DocumentManageServiceImpl implements DocumentManageService { private final RaptorBuildService raptorBuildService; + private final KnowledgeBaseManageService knowledgeBaseManageService; + private final DocumentKafkaProducer kafkaProducer; private final TransactionTemplate transactionTemplate; @@ -220,6 +224,8 @@ public class DocumentManageServiceImpl implements DocumentManageService { byte[] fileBytes = getFileBytes(file); Long documentId = uidGenerator.getUid(); + Long knowledgeBaseId = parseRequiredLong(dto.getKnowledgeBaseId(), "knowledgeBaseId"); + SuperAgentKnowledgeBase knowledgeBase = knowledgeBaseManageService.requireEnabled(knowledgeBaseId); StoredObjectInfo storedObjectInfo = storageService.uploadOriginalFile( documentId, originalFileName, fileBytes, file.getContentType()); @@ -241,10 +247,9 @@ public class DocumentManageServiceImpl implements DocumentManageService { document.setCharCount(0); document.setTokenCount(0); - document.setKnowledgeScopeCode(StrUtil.trimToNull(dto.getKnowledgeScopeCode())); - document.setKnowledgeScopeName(StrUtil.trimToNull(dto.getKnowledgeScopeName())); - document.setBusinessCategory(StrUtil.trimToNull(dto.getBusinessCategory())); - document.setDocumentTags(StrUtil.trimToNull(dto.getDocumentTags())); + document.setKnowledgeBaseId(knowledgeBase.getId()); + document.setKnowledgeBaseCode(knowledgeBase.getBaseCode()); + document.setKnowledgeBaseName(knowledgeBase.getBaseName()); document.setStatus(BusinessStatus.YES.getCode()); Long taskId = uidGenerator.getUid(); @@ -1337,10 +1342,9 @@ public class DocumentManageServiceImpl implements DocumentManageService { document.getIndexStatus(), enumMsg(DocumentIndexStatusEnum.getRc(document.getIndexStatus())), document.getParseErrorMsg(), - document.getKnowledgeScopeCode(), - document.getKnowledgeScopeName(), - document.getBusinessCategory(), - document.getDocumentTags(), + document.getKnowledgeBaseId(), + document.getKnowledgeBaseCode(), + document.getKnowledgeBaseName(), document.getCurrentPlanId(), document.getLastIndexTaskId(), latestTask == null ? null : latestTask.getId(), diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentProfileServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentProfileServiceImpl.java index 50bb67c..3297c36 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentProfileServiceImpl.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/DocumentProfileServiceImpl.java @@ -94,15 +94,11 @@ public class DocumentProfileServiceImpl implements DocumentProfileService { documentProfileMapper.updateById(profile); } - backfillDocumentMetadata(document, draft); - log.info("文档画像生成完成: documentId={}, documentType={}, graphFriendly={}, supportsItemLookup={}, scopeCode='{}', businessCategory='{}', tags='{}'", + log.info("文档画像生成完成: documentId={}, documentType={}, graphFriendly={}, supportsItemLookup={}", documentId, draft.documentType(), draft.graphFriendly(), - draft.supportsItemLookup(), - draft.knowledgeScopeCode(), - draft.businessCategory(), - draft.documentTags()); + draft.supportsItemLookup()); return profile; } @@ -164,10 +160,6 @@ public class DocumentProfileServiceImpl implements DocumentProfileService { List coreTopics = buildCoreTopics(document, sectionTitles); List exampleQuestions = buildExampleQuestions(documentType, coreTopics); String summary = buildSummary(document, sectionTitles, parsedText); - String knowledgeScopeCode = inferKnowledgeScopeCode(document, sectionTitles, parsedText); - String knowledgeScopeName = inferKnowledgeScopeName(knowledgeScopeCode); - String businessCategory = inferBusinessCategory(documentType, parsedText); - String documentTags = buildDocumentTags(document, knowledgeScopeCode, documentType, coreTopics); return new DocumentProfileDraft( summary, documentType, @@ -176,37 +168,10 @@ public class DocumentProfileServiceImpl implements DocumentProfileService { graphFriendly, supportsGraphOutline, supportsItemLookup, - true, - knowledgeScopeCode, - knowledgeScopeName, - businessCategory, - documentTags + true ); } - private void backfillDocumentMetadata(SuperAgentDocument document, DocumentProfileDraft draft) { - boolean changed = false; - if (StrUtil.isBlank(document.getKnowledgeScopeCode()) && StrUtil.isNotBlank(draft.knowledgeScopeCode())) { - document.setKnowledgeScopeCode(draft.knowledgeScopeCode()); - changed = true; - } - if (StrUtil.isBlank(document.getKnowledgeScopeName()) && StrUtil.isNotBlank(draft.knowledgeScopeName())) { - document.setKnowledgeScopeName(draft.knowledgeScopeName()); - changed = true; - } - if (StrUtil.isBlank(document.getBusinessCategory()) && StrUtil.isNotBlank(draft.businessCategory())) { - document.setBusinessCategory(draft.businessCategory()); - changed = true; - } - if (StrUtil.isBlank(document.getDocumentTags()) && StrUtil.isNotBlank(draft.documentTags())) { - document.setDocumentTags(draft.documentTags()); - changed = true; - } - if (changed) { - documentMapper.updateById(document); - } - } - private List extractSectionTitles(List structureNodes) { if (CollUtil.isEmpty(structureNodes)) { return List.of(); @@ -294,84 +259,6 @@ public class DocumentProfileServiceImpl implements DocumentProfileService { return builder.toString().trim(); } - private String inferKnowledgeScopeCode(SuperAgentDocument document, - List sectionTitles, - String parsedText) { - String combined = combinedText(document, parsedText, sectionTitles); - if (containsAny(combined, "上线观察", "值班规则", "观察时长", "运营")) { - return "operation_rule"; - } - if (containsAny(combined, "机器人", "知识召回", "意图识别", "策略设计")) { - return "robot_strategy"; - } - if (containsAny(combined, "安装", "部署", "默认密码", "访问地址")) { - return "deployment"; - } - if (containsAny(combined, "故障", "排查", "异常", "检查顺序")) { - return "troubleshooting"; - } - if (containsAny(combined, "产品简介", "核心特性", "技术规格", "产品概述")) { - return "product"; - } - return "general_document"; - } - - private String inferKnowledgeScopeName(String scopeCode) { - return switch (StrUtil.blankToDefault(scopeCode, "")) { - case "operation_rule" -> "运营规则"; - case "robot_strategy" -> "机器人策略"; - case "deployment" -> "安装部署"; - case "troubleshooting" -> "故障排查"; - case "product" -> "产品资料"; - default -> "通用文档"; - }; - } - - private String inferBusinessCategory(String documentType, String parsedText) { - if ("troubleshooting".equals(documentType)) { - return "故障排查"; - } - if ("rule".equals(documentType)) { - return "规则"; - } - if ("spec".equals(documentType)) { - return "规格说明"; - } - if ("manual".equals(documentType)) { - return containsAny(parsedText.toLowerCase(Locale.ROOT), "步骤", "操作", "部署") - ? "操作手册" - : "手册"; - } - return "介绍"; - } - - private String buildDocumentTags(SuperAgentDocument document, - String knowledgeScopeCode, - String documentType, - List coreTopics) { - LinkedHashSet tags = new LinkedHashSet<>(); - if (StrUtil.isNotBlank(document.getDocumentTags())) { - tags.addAll(List.of(document.getDocumentTags().split(","))); - } - addTag(tags, knowledgeScopeCode); - addTag(tags, documentType); - coreTopics.stream().limit(4).forEach(topic -> addTag(tags, topic)); - return tags.stream() - .map(String::trim) - .filter(StrUtil::isNotBlank) - .distinct() - .limit(8) - .collect(Collectors.joining(",")); - } - - private void addTag(Set tags, String tag) { - String normalized = StrUtil.blankToDefault(tag, "").trim(); - if (normalized.isBlank()) { - return; - } - tags.add(normalized); - } - private boolean containsAny(String text, String... values) { String normalized = StrUtil.blankToDefault(text, "").toLowerCase(Locale.ROOT); for (String value : values) { @@ -420,11 +307,7 @@ public class DocumentProfileServiceImpl implements DocumentProfileService { boolean graphFriendly, boolean supportsGraphOutline, boolean supportsItemLookup, - boolean supportsGraphAssist, - String knowledgeScopeCode, - String knowledgeScopeName, - String businessCategory, - String documentTags + boolean supportsGraphAssist ) { } } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/ElasticsearchKnowledgeRouteIndexService.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/ElasticsearchKnowledgeRouteIndexService.java index 77387b8..380f293 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/ElasticsearchKnowledgeRouteIndexService.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/ElasticsearchKnowledgeRouteIndexService.java @@ -2,6 +2,7 @@ package org.javaup.ai.manage.service.impl; import cn.hutool.core.util.StrUtil; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.FieldValue; import co.elastic.clients.elasticsearch._types.Refresh; import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType; import co.elastic.clients.elasticsearch.core.BulkRequest; @@ -34,11 +35,13 @@ import java.io.IOException; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; @@ -87,18 +90,31 @@ public class ElasticsearchKnowledgeRouteIndexService implements KnowledgeRouteIn } @Override - public List search(String routingText, String entityType, int size) { + public List search(String routingText, String entityType, int size, Collection knowledgeBaseIds) { if (StrUtil.isBlank(routingText) || StrUtil.isBlank(entityType)) { return List.of(); } refreshIfNeeded(); List entityTerms = extractEntityTerms(routingText); + List selectedKnowledgeBaseIds = knowledgeBaseIds == null + ? List.of() + : knowledgeBaseIds.stream() + .filter(Objects::nonNull) + .distinct() + .map(FieldValue::of) + .toList(); try { SearchResponse response = elasticsearchClient.search(search -> search .index(properties.getElasticsearch().getRouteIndexName()) .size(Math.max(1, Math.min(size, 10))) .query(query -> query.bool(bool -> { bool.filter(filter -> filter.term(term -> term.field("entityType").value(entityType))); + if (!selectedKnowledgeBaseIds.isEmpty()) { + bool.filter(filter -> filter.terms(terms -> terms + .field("knowledgeBaseId") + .terms(value -> value.value(selectedKnowledgeBaseIds)) + )); + } bool.should(should -> should.matchPhrase(matchPhrase -> matchPhrase .field("displayName") .query(routingText) @@ -131,6 +147,7 @@ public class ElasticsearchKnowledgeRouteIndexService implements KnowledgeRouteIn source.getEntityCode(), source.getEntityType(), source.getDocumentId(), + source.getKnowledgeBaseId(), source.getScopeCode(), source.getTopicCode(), source.getDocumentName(), @@ -221,23 +238,24 @@ public class ElasticsearchKnowledgeRouteIndexService implements KnowledgeRouteIn .stream() .collect(Collectors.toMap(SuperAgentDocumentProfile::getDocumentId, item -> item, (left, right) -> right)); Map> topicByScope = topics.stream() - .collect(Collectors.groupingBy(SuperAgentKnowledgeTopicNode::getScopeCode)); + .collect(Collectors.groupingBy(topic -> routeKey(topic.getKnowledgeBaseId(), topic.getScopeCode()))); Map> relationByTopic = topicDocumentRelationMapper.selectList( new LambdaQueryWrapper() .eq(SuperAgentTopicDocumentRelation::getStatus, BusinessStatus.YES.getCode())) .stream() - .collect(Collectors.groupingBy(SuperAgentTopicDocumentRelation::getTopicCode)); + .collect(Collectors.groupingBy(relation -> routeKey(relation.getKnowledgeBaseId(), relation.getTopicCode()))); for (SuperAgentKnowledgeScopeNode scope : scopes) { List scopeTags = new ArrayList<>(); - topicByScope.getOrDefault(scope.getScopeCode(), List.of()).forEach(topic -> { + topicByScope.getOrDefault(routeKey(scope.getKnowledgeBaseId(), scope.getScopeCode()), List.of()).forEach(topic -> { addUnique(scopeTags, topic.getTopicName()); parseCommaText(topic.getAliases()).forEach(item -> addUnique(scopeTags, item)); }); records.add(KnowledgeRouteIndexRecord.builder() - .routeId("scope:" + scope.getScopeCode()) + .routeId("scope:" + safeIdPart(scope.getKnowledgeBaseId()) + ":" + scope.getScopeCode()) .entityType("scope") .entityCode(scope.getScopeCode()) + .knowledgeBaseId(scope.getKnowledgeBaseId()) .scopeCode(scope.getScopeCode()) .scopeName(scope.getScopeName()) .displayName(safeText(scope.getScopeName())) @@ -256,9 +274,10 @@ public class ElasticsearchKnowledgeRouteIndexService implements KnowledgeRouteIn parseJsonArray(topic.getExamples()).forEach(item -> addUnique(tags, item)); parseCommaText(topic.getAliases()).forEach(item -> addUnique(tags, item)); records.add(KnowledgeRouteIndexRecord.builder() - .routeId("topic:" + topic.getTopicCode()) + .routeId("topic:" + safeIdPart(topic.getKnowledgeBaseId()) + ":" + topic.getTopicCode()) .entityType("topic") .entityCode(topic.getTopicCode()) + .knowledgeBaseId(topic.getKnowledgeBaseId()) .scopeCode(topic.getScopeCode()) .topicCode(topic.getTopicCode()) .topicName(topic.getTopicName()) @@ -280,27 +299,21 @@ public class ElasticsearchKnowledgeRouteIndexService implements KnowledgeRouteIn .build()); } - Map topicDocumentMap = new LinkedHashMap<>(); - for (SuperAgentKnowledgeTopicNode topic : topics) { - for (SuperAgentTopicDocumentRelation relation : relationByTopic.getOrDefault(topic.getTopicCode(), List.of())) { - topicDocumentMap.put(relation.getDocumentId(), topic); - } - } - for (SuperAgentDocument document : documents) { SuperAgentDocumentProfile profile = profileMap.get(document.getId()); List tags = new ArrayList<>(); - parseCommaText(document.getDocumentTags()).forEach(item -> addUnique(tags, item)); if (profile != null) { parseJsonArray(profile.getCoreTopics()).forEach(item -> addUnique(tags, item)); parseJsonArray(profile.getExampleQuestions()).forEach(item -> addUnique(tags, item)); } relationByTopic.forEach((topicCode, relations) -> relations.stream() - .filter(relation -> document.getId().equals(relation.getDocumentId())) + .filter(relation -> document.getId().equals(relation.getDocumentId()) + && Objects.equals(document.getKnowledgeBaseId(), relation.getKnowledgeBaseId())) .findFirst() .ifPresent(relation -> { SuperAgentKnowledgeTopicNode topic = topics.stream() - .filter(item -> topicCode.equals(item.getTopicCode())) + .filter(item -> Objects.equals(item.getKnowledgeBaseId(), relation.getKnowledgeBaseId()) + && relation.getTopicCode().equals(item.getTopicCode())) .findFirst() .orElse(null); if (topic != null) { @@ -313,10 +326,8 @@ public class ElasticsearchKnowledgeRouteIndexService implements KnowledgeRouteIn .entityType("document") .entityCode(String.valueOf(document.getId())) .documentId(document.getId()) - .scopeCode(safeText(document.getKnowledgeScopeCode())) - .scopeName(safeText(document.getKnowledgeScopeName())) + .knowledgeBaseId(document.getKnowledgeBaseId()) .documentName(safeText(document.getDocumentName())) - .businessCategory(safeText(document.getBusinessCategory())) .displayName(safeText(document.getDocumentName())) .descriptionText(profile == null ? "" : safeText(profile.getDocumentType())) .aliasesText("") @@ -324,22 +335,26 @@ public class ElasticsearchKnowledgeRouteIndexService implements KnowledgeRouteIn .summaryText(profile == null ? "" : safeText(profile.getDocumentSummary())) .routeText(join( document.getDocumentName(), - document.getKnowledgeScopeCode(), - document.getKnowledgeScopeName(), - document.getBusinessCategory(), - document.getDocumentTags(), profile == null ? "" : profile.getDocumentSummary(), profile == null ? "" : profile.getCoreTopics(), profile == null ? "" : profile.getExampleQuestions(), profile == null ? "" : profile.getDocumentType() )) - .entityTerms(extractEntityTerms(join(document.getDocumentName(), document.getDocumentTags(), document.getKnowledgeScopeName()))) + .entityTerms(extractEntityTerms(document.getDocumentName())) .tags(tags) .build()); } return records; } + private String routeKey(Long knowledgeBaseId, String code) { + return safeIdPart(knowledgeBaseId) + ":" + safeText(code); + } + + private String safeIdPart(Long value) { + return value == null ? "none" : String.valueOf(value); + } + private List extractEntityTerms(String text) { if (StrUtil.isBlank(text)) { return List.of(); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/GraphRagCrossDocumentIndexServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/GraphRagCrossDocumentIndexServiceImpl.java index 3ef81d9..8b028bd 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/GraphRagCrossDocumentIndexServiceImpl.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/GraphRagCrossDocumentIndexServiceImpl.java @@ -17,6 +17,8 @@ import org.javaup.ai.manage.data.SuperAgentKgEvidence; import org.javaup.ai.manage.data.SuperAgentKgRelation; import org.javaup.ai.manage.data.SuperAgentKgRelationGroup; import org.javaup.ai.manage.data.SuperAgentKgRelationGroupMember; +import org.javaup.ai.manage.data.SuperAgentKnowledgeTopicNode; +import org.javaup.ai.manage.data.SuperAgentTopicDocumentRelation; import org.javaup.ai.manage.mapper.SuperAgentDocumentMapper; import org.javaup.ai.manage.mapper.SuperAgentKgCanonicalEntityGroupMapper; import org.javaup.ai.manage.mapper.SuperAgentKgCanonicalEntityMemberMapper; @@ -27,10 +29,13 @@ import org.javaup.ai.manage.mapper.SuperAgentKgEvidenceMapper; import org.javaup.ai.manage.mapper.SuperAgentKgRelationGroupMapper; import org.javaup.ai.manage.mapper.SuperAgentKgRelationGroupMemberMapper; import org.javaup.ai.manage.mapper.SuperAgentKgRelationMapper; +import org.javaup.ai.manage.mapper.SuperAgentKnowledgeTopicNodeMapper; +import org.javaup.ai.manage.mapper.SuperAgentTopicDocumentRelationMapper; import org.javaup.ai.manage.model.graph.GraphRagCrossDocumentIndexBuildResult; import org.javaup.ai.manage.service.GraphRagCrossDocumentIndexService; import org.javaup.ai.manage.support.GraphRagCrossDocumentIndex; import org.javaup.ai.manage.support.GraphRagCrossDocumentIndexSupport; +import org.javaup.ai.manage.support.RaptorScopeSupport; import org.javaup.enums.BusinessStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -54,7 +59,6 @@ import java.util.stream.Collectors; @Slf4j public class GraphRagCrossDocumentIndexServiceImpl implements GraphRagCrossDocumentIndexService { - private static final String SCOPE_PREFIX_KNOWLEDGE = "knowledge:"; private static final String DERIVED_INDEX_SOURCE_TYPE = "java.cross_document_index.v1"; private final SuperAgentDocumentMapper documentMapper; @@ -67,6 +71,8 @@ public class GraphRagCrossDocumentIndexServiceImpl implements GraphRagCrossDocum private final SuperAgentKgRelationGroupMemberMapper relationGroupMemberMapper; private final SuperAgentKgCrossDocumentCommunityMapper communityMapper; private final SuperAgentKgCrossDocumentCommunityMemberMapper communityMemberMapper; + private final SuperAgentKnowledgeTopicNodeMapper topicNodeMapper; + private final SuperAgentTopicDocumentRelationMapper topicDocumentRelationMapper; private final GraphRagCrossDocumentIndexSupport indexSupport; private final UidGenerator uidGenerator; private final ObjectMapper objectMapper; @@ -81,6 +87,8 @@ public class GraphRagCrossDocumentIndexServiceImpl implements GraphRagCrossDocum SuperAgentKgRelationGroupMemberMapper relationGroupMemberMapper, SuperAgentKgCrossDocumentCommunityMapper communityMapper, SuperAgentKgCrossDocumentCommunityMemberMapper communityMemberMapper, + SuperAgentKnowledgeTopicNodeMapper topicNodeMapper, + SuperAgentTopicDocumentRelationMapper topicDocumentRelationMapper, GraphRagCrossDocumentIndexSupport indexSupport, UidGenerator uidGenerator, ObjectMapper objectMapper) { @@ -94,6 +102,8 @@ public class GraphRagCrossDocumentIndexServiceImpl implements GraphRagCrossDocum this.relationGroupMemberMapper = relationGroupMemberMapper; this.communityMapper = communityMapper; this.communityMemberMapper = communityMemberMapper; + this.topicNodeMapper = topicNodeMapper; + this.topicDocumentRelationMapper = topicDocumentRelationMapper; this.indexSupport = indexSupport; this.uidGenerator = uidGenerator; this.objectMapper = objectMapper; @@ -146,9 +156,6 @@ public class GraphRagCrossDocumentIndexServiceImpl implements GraphRagCrossDocum Set taskIdSet = new LinkedHashSet<>(taskIds); members = members.stream().filter(member -> taskIdSet.contains(member.getTaskId())).toList(); } - if (members.isEmpty() && !GLOBAL_SCOPE_KEY.equals(scopeKey)) { - return loadIndexByScope(GLOBAL_SCOPE_KEY, documentIds, taskIds); - } if (members.isEmpty()) { return GraphRagCrossDocumentIndex.empty(); } @@ -284,13 +291,35 @@ public class GraphRagCrossDocumentIndexServiceImpl implements GraphRagCrossDocum Map scopeByDocumentId = new LinkedHashMap<>(); for (SuperAgentDocument document : documentMap.values()) { - String scopeCode = StrUtil.blankToDefault(document.getKnowledgeScopeCode(), "").trim(); - if (StrUtil.isNotBlank(scopeCode)) { - scopeByDocumentId.put(document.getId(), SCOPE_PREFIX_KNOWLEDGE + scopeCode); + if (document.getKnowledgeBaseId() != null) { + scopeByDocumentId.put(document.getId(), RaptorScopeSupport.knowledgeBaseScopeKey(document.getKnowledgeBaseId())); } } + appendGroupedScopes(scopes, entities, relations, evidences, scopeByDocumentId); + + Map> relationScopeKeysByDocumentId = scopeByDocumentId(documentMap.keySet()); + LinkedHashSet relationScopeKeys = relationScopeKeysByDocumentId.values().stream() + .flatMap(List::stream) + .collect(Collectors.toCollection(LinkedHashSet::new)); + for (String scopeKey : relationScopeKeys) { + Map scopedDocumentMap = new LinkedHashMap<>(); + relationScopeKeysByDocumentId.forEach((documentId, scopeKeys) -> { + if (scopeKeys.contains(scopeKey)) { + scopedDocumentMap.put(documentId, scopeKey); + } + }); + appendGroupedScopes(scopes, entities, relations, evidences, scopedDocumentMap); + } + return scopes; + } + + private void appendGroupedScopes(LinkedHashMap scopes, + List entities, + List relations, + List evidences, + Map scopeByDocumentId) { if (scopeByDocumentId.isEmpty()) { - return scopes; + return; } Map> entitiesByScope = entities.stream() .filter(entity -> scopeByDocumentId.containsKey(entity.getDocumentId())) @@ -308,22 +337,62 @@ public class GraphRagCrossDocumentIndexServiceImpl implements GraphRagCrossDocum .toList(); scopes.put(entry.getKey(), ScopeDataset.of(entry.getValue(), scopedRelations, scopedEvidences)); } - return scopes; } private String resolveLoadScopeKey(List documentIds) { Map documents = listDocuments(new LinkedHashSet<>(documentIds)); - LinkedHashSet scopeCodes = documents.values().stream() - .map(SuperAgentDocument::getKnowledgeScopeCode) - .filter(StrUtil::isNotBlank) - .map(String::trim) + LinkedHashSet knowledgeBaseIds = documents.values().stream() + .map(SuperAgentDocument::getKnowledgeBaseId) + .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); - if (scopeCodes.size() == 1) { - return SCOPE_PREFIX_KNOWLEDGE + scopeCodes.iterator().next(); + Map> scopeKeysByDocumentId = scopeByDocumentId(new LinkedHashSet<>(documentIds)); + LinkedHashSet sharedScopeKeys = scopeKeysByDocumentId.values().stream() + .flatMap(List::stream) + .collect(Collectors.toCollection(LinkedHashSet::new)); + if (knowledgeBaseIds.size() == 1 && sharedScopeKeys.size() == 1) { + return sharedScopeKeys.iterator().next(); + } + if (knowledgeBaseIds.size() == 1) { + return RaptorScopeSupport.knowledgeBaseScopeKey(knowledgeBaseIds.iterator().next()); } return GLOBAL_SCOPE_KEY; } + private Map> scopeByDocumentId(Collection documentIds) { + if (CollUtil.isEmpty(documentIds)) { + return Map.of(); + } + List relations = topicDocumentRelationMapper.selectList(new LambdaQueryWrapper() + .in(SuperAgentTopicDocumentRelation::getDocumentId, documentIds) + .eq(SuperAgentTopicDocumentRelation::getStatus, BusinessStatus.YES.getCode())); + if (relations.isEmpty()) { + return Map.of(); + } + Map topicByKey = topicNodeMapper.selectList(new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeTopicNode::getStatus, BusinessStatus.YES.getCode())) + .stream() + .collect(Collectors.toMap( + topic -> topicRouteKey(topic.getKnowledgeBaseId(), topic.getTopicCode()), + topic -> topic, + (left, right) -> left, + LinkedHashMap::new)); + Map> grouped = new LinkedHashMap<>(); + for (SuperAgentTopicDocumentRelation relation : relations) { + SuperAgentKnowledgeTopicNode topic = topicByKey.get(topicRouteKey(relation.getKnowledgeBaseId(), relation.getTopicCode())); + if (topic == null || topic.getKnowledgeBaseId() == null || StrUtil.isBlank(topic.getScopeCode())) { + continue; + } + grouped.computeIfAbsent(relation.getDocumentId(), ignored -> new LinkedHashSet<>()) + .add(RaptorScopeSupport.knowledgeScopeKey(topic.getKnowledgeBaseId(), topic.getScopeCode())); + } + return grouped.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> List.copyOf(entry.getValue()), (left, right) -> left, LinkedHashMap::new)); + } + + private String topicRouteKey(Long knowledgeBaseId, String topicCode) { + return (knowledgeBaseId == null ? "none" : String.valueOf(knowledgeBaseId)) + ":" + StrUtil.blankToDefault(topicCode, ""); + } + private List listEntities(List documentIds, List taskIds) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(SuperAgentKgEntity::getStatus, BusinessStatus.YES.getCode()); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeBaseManageServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeBaseManageServiceImpl.java new file mode 100644 index 0000000..23016ab --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeBaseManageServiceImpl.java @@ -0,0 +1,291 @@ +package org.javaup.ai.manage.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.baidu.fsg.uid.UidGenerator; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.AllArgsConstructor; +import org.javaup.ai.manage.data.SuperAgentDocument; +import org.javaup.ai.manage.data.SuperAgentKnowledgeBase; +import org.javaup.ai.manage.dto.KnowledgeBaseConfigUpdateDto; +import org.javaup.ai.manage.dto.KnowledgeBaseDeleteDto; +import org.javaup.ai.manage.dto.KnowledgeBaseDetailDto; +import org.javaup.ai.manage.dto.KnowledgeBaseSaveDto; +import org.javaup.ai.manage.mapper.SuperAgentDocumentMapper; +import org.javaup.ai.manage.mapper.SuperAgentKnowledgeBaseMapper; +import org.javaup.ai.manage.service.KnowledgeBaseManageService; +import org.javaup.ai.manage.vo.KnowledgeBaseItemVo; +import org.javaup.ai.manage.vo.KnowledgeBaseOptionVo; +import org.javaup.enums.BaseCode; +import org.javaup.enums.BusinessStatus; +import org.javaup.enums.DocumentIndexStatusEnum; +import org.javaup.exception.SuperAgentFrameException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class KnowledgeBaseManageServiceImpl implements KnowledgeBaseManageService { + + private final SuperAgentKnowledgeBaseMapper knowledgeBaseMapper; + private final SuperAgentDocumentMapper documentMapper; + private final UidGenerator uidGenerator; + private final ObjectMapper objectMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public KnowledgeBaseItemVo save(KnowledgeBaseSaveDto dto) { + validateSave(dto); + Long id = parseOptionalLong(dto.getId()); + SuperAgentKnowledgeBase entity = id == null ? null : knowledgeBaseMapper.selectById(id); + if (entity == null) { + entity = knowledgeBaseMapper.selectOne(new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeBase::getBaseCode, safeText(dto.getBaseCode())) + .eq(SuperAgentKnowledgeBase::getStatus, BusinessStatus.YES.getCode()) + .last("LIMIT 1")); + } + if (entity == null) { + entity = new SuperAgentKnowledgeBase(); + entity.setId(uidGenerator.getUid()); + entity.setBaseCode(safeText(dto.getBaseCode())); + entity.setStatus(BusinessStatus.YES.getCode()); + } + entity.setBaseName(safeText(dto.getBaseName())); + entity.setDescription(safeText(dto.getDescription())); + entity.setEmbeddingModel(safeText(dto.getEmbeddingModel())); + entity.setRetrievalConfigJson(validateJson(dto.getRetrievalConfigJson(), "retrievalConfigJson")); + entity.setGraphRagConfigJson(validateJson(dto.getGraphRagConfigJson(), "graphRagConfigJson")); + entity.setRaptorConfigJson(validateJson(dto.getRaptorConfigJson(), "raptorConfigJson")); + entity.setMetadataFilterJson(validateJson(dto.getMetadataFilterJson(), "metadataFilterJson")); + entity.setIsDefault(parseInteger(dto.getIsDefault(), 0)); + entity.setSortOrder(parseInteger(dto.getSortOrder(), 0)); + if (Objects.equals(entity.getIsDefault(), 1)) { + clearOtherDefaults(entity.getId()); + } + if (entity.getCreateTime() == null) { + knowledgeBaseMapper.insert(entity); + } + else { + knowledgeBaseMapper.updateById(entity); + } + return toItemVo(entity, countDocumentsByBaseId(List.of(entity.getId())), countRetrievableDocumentsByBaseId(List.of(entity.getId()))); + } + + @Override + public boolean delete(KnowledgeBaseDeleteDto dto) { + Long id = parseRequiredLong(dto == null ? null : dto.getId(), "id"); + return knowledgeBaseMapper.update(null, new LambdaUpdateWrapper() + .eq(SuperAgentKnowledgeBase::getId, id) + .eq(SuperAgentKnowledgeBase::getStatus, BusinessStatus.YES.getCode()) + .set(SuperAgentKnowledgeBase::getStatus, BusinessStatus.NO.getCode())) > 0; + } + + @Override + public List list() { + List bases = knowledgeBaseMapper.selectList(baseListWrapper()); + Map documentCounts = countDocumentsByBaseId(bases.stream().map(SuperAgentKnowledgeBase::getId).toList()); + Map retrievableCounts = countRetrievableDocumentsByBaseId(bases.stream().map(SuperAgentKnowledgeBase::getId).toList()); + return bases.stream() + .map(base -> toItemVo(base, documentCounts, retrievableCounts)) + .toList(); + } + + @Override + public KnowledgeBaseItemVo detail(KnowledgeBaseDetailDto dto) { + SuperAgentKnowledgeBase entity = requireEnabled(parseRequiredLong(dto == null ? null : dto.getId(), "id")); + return toItemVo(entity, countDocumentsByBaseId(List.of(entity.getId())), countRetrievableDocumentsByBaseId(List.of(entity.getId()))); + } + + @Override + public KnowledgeBaseItemVo updateConfig(KnowledgeBaseConfigUpdateDto dto) { + SuperAgentKnowledgeBase entity = requireEnabled(parseRequiredLong(dto == null ? null : dto.getId(), "id")); + entity.setRetrievalConfigJson(validateJson(dto.getRetrievalConfigJson(), "retrievalConfigJson")); + entity.setGraphRagConfigJson(validateJson(dto.getGraphRagConfigJson(), "graphRagConfigJson")); + entity.setRaptorConfigJson(validateJson(dto.getRaptorConfigJson(), "raptorConfigJson")); + entity.setMetadataFilterJson(validateJson(dto.getMetadataFilterJson(), "metadataFilterJson")); + knowledgeBaseMapper.updateById(entity); + return toItemVo(entity, countDocumentsByBaseId(List.of(entity.getId())), countRetrievableDocumentsByBaseId(List.of(entity.getId()))); + } + + @Override + public List listOptions() { + List bases = knowledgeBaseMapper.selectList(baseListWrapper()); + Map retrievableCounts = countRetrievableDocumentsByBaseId(bases.stream().map(SuperAgentKnowledgeBase::getId).toList()); + return bases.stream() + .map(base -> new KnowledgeBaseOptionVo( + String.valueOf(base.getId()), + safeText(base.getBaseCode()), + safeText(base.getBaseName()), + safeText(base.getDescription()), + String.valueOf(nullToZero(base.getIsDefault())), + String.valueOf(retrievableCounts.getOrDefault(base.getId(), 0L)) + )) + .toList(); + } + + @Override + public List listEnabledByIds(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return List.of(); + } + return knowledgeBaseMapper.selectList(new LambdaQueryWrapper() + .in(SuperAgentKnowledgeBase::getId, ids.stream().filter(Objects::nonNull).toList()) + .eq(SuperAgentKnowledgeBase::getStatus, BusinessStatus.YES.getCode()) + .orderByAsc(SuperAgentKnowledgeBase::getSortOrder, SuperAgentKnowledgeBase::getId)); + } + + @Override + public List listAllEnabled() { + return knowledgeBaseMapper.selectList(baseListWrapper()); + } + + @Override + public SuperAgentKnowledgeBase requireEnabled(Long id) { + if (id == null || id <= 0) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "knowledgeBaseId 不能为空。"); + } + SuperAgentKnowledgeBase entity = knowledgeBaseMapper.selectOne(new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeBase::getId, id) + .eq(SuperAgentKnowledgeBase::getStatus, BusinessStatus.YES.getCode()) + .last("LIMIT 1")); + if (entity == null) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "知识库不存在或已停用。"); + } + return entity; + } + + private LambdaQueryWrapper baseListWrapper() { + return new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeBase::getStatus, BusinessStatus.YES.getCode()) + .orderByAsc(SuperAgentKnowledgeBase::getSortOrder, SuperAgentKnowledgeBase::getId); + } + + private void clearOtherDefaults(Long currentId) { + knowledgeBaseMapper.update(null, new LambdaUpdateWrapper() + .ne(SuperAgentKnowledgeBase::getId, currentId) + .eq(SuperAgentKnowledgeBase::getStatus, BusinessStatus.YES.getCode()) + .set(SuperAgentKnowledgeBase::getIsDefault, 0)); + } + + private Map countDocumentsByBaseId(List baseIds) { + if (CollUtil.isEmpty(baseIds)) { + return Map.of(); + } + return documentMapper.selectList(new LambdaQueryWrapper() + .in(SuperAgentDocument::getKnowledgeBaseId, baseIds) + .eq(SuperAgentDocument::getStatus, BusinessStatus.YES.getCode())) + .stream() + .collect(Collectors.groupingBy( + SuperAgentDocument::getKnowledgeBaseId, + LinkedHashMap::new, + Collectors.counting() + )); + } + + private Map countRetrievableDocumentsByBaseId(List baseIds) { + if (CollUtil.isEmpty(baseIds)) { + return Map.of(); + } + return documentMapper.selectList(new LambdaQueryWrapper() + .in(SuperAgentDocument::getKnowledgeBaseId, baseIds) + .eq(SuperAgentDocument::getStatus, BusinessStatus.YES.getCode()) + .eq(SuperAgentDocument::getIndexStatus, DocumentIndexStatusEnum.BUILD_SUCCESS.getCode()) + .isNotNull(SuperAgentDocument::getLastIndexTaskId)) + .stream() + .collect(Collectors.groupingBy( + SuperAgentDocument::getKnowledgeBaseId, + LinkedHashMap::new, + Collectors.counting() + )); + } + + private KnowledgeBaseItemVo toItemVo(SuperAgentKnowledgeBase entity, + Map documentCounts, + Map retrievableCounts) { + return new KnowledgeBaseItemVo( + String.valueOf(entity.getId()), + safeText(entity.getBaseCode()), + safeText(entity.getBaseName()), + safeText(entity.getDescription()), + safeText(entity.getEmbeddingModel()), + safeText(entity.getRetrievalConfigJson()), + safeText(entity.getGraphRagConfigJson()), + safeText(entity.getRaptorConfigJson()), + safeText(entity.getMetadataFilterJson()), + String.valueOf(nullToZero(entity.getIsDefault())), + String.valueOf(nullToZero(entity.getSortOrder())), + String.valueOf(documentCounts.getOrDefault(entity.getId(), 0L)), + String.valueOf(retrievableCounts.getOrDefault(entity.getId(), 0L)) + ); + } + + private void validateSave(KnowledgeBaseSaveDto dto) { + if (dto == null || safeText(dto.getBaseCode()).isBlank() || safeText(dto.getBaseName()).isBlank()) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "baseCode 和 baseName 不能为空。"); + } + } + + private String validateJson(String rawJson, String fieldName) { + String text = safeText(rawJson); + if (text.isBlank()) { + return null; + } + try { + objectMapper.readTree(text); + return text; + } + catch (JsonProcessingException | RuntimeException exception) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), fieldName + " 不是合法 JSON。"); + } + } + + private Long parseRequiredLong(String rawValue, String fieldName) { + Long value = parseOptionalLong(rawValue); + if (value == null || value <= 0) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), fieldName + "不能为空。"); + } + return value; + } + + private Long parseOptionalLong(String rawValue) { + if (StrUtil.isBlank(rawValue)) { + return null; + } + try { + return Long.valueOf(rawValue.trim()); + } + catch (NumberFormatException exception) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "id 格式非法。"); + } + } + + private Integer parseInteger(String rawValue, Integer fallback) { + if (StrUtil.isBlank(rawValue)) { + return fallback; + } + try { + return Integer.valueOf(rawValue.trim()); + } + catch (NumberFormatException exception) { + return fallback; + } + } + + private int nullToZero(Integer value) { + return value == null ? 0 : value; + } + + private String safeText(String text) { + return text == null ? "" : text.trim(); + } +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeBaseRetrievalScopeServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeBaseRetrievalScopeServiceImpl.java new file mode 100644 index 0000000..b6863ef --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeBaseRetrievalScopeServiceImpl.java @@ -0,0 +1,136 @@ +package org.javaup.ai.manage.service.impl; + +import cn.hutool.core.collection.CollUtil; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; +import org.javaup.ai.chatagent.rag.service.KnowledgeBaseRuntimeConfigResolver; +import org.javaup.ai.manage.data.SuperAgentKnowledgeBase; +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; +import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; +import org.javaup.ai.manage.service.DocumentKnowledgeService; +import org.javaup.ai.manage.service.KnowledgeBaseManageService; +import org.javaup.ai.manage.service.KnowledgeBaseRetrievalScopeService; +import org.javaup.enums.BaseCode; +import org.javaup.enums.ChatQueryMode; +import org.javaup.enums.KnowledgeBaseSelectionMode; +import org.javaup.exception.SuperAgentFrameException; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service +public class KnowledgeBaseRetrievalScopeServiceImpl implements KnowledgeBaseRetrievalScopeService { + + private final KnowledgeBaseManageService knowledgeBaseManageService; + private final DocumentKnowledgeService documentKnowledgeService; + private final KnowledgeBaseRuntimeConfigResolver runtimeConfigResolver; + + public KnowledgeBaseRetrievalScopeServiceImpl(KnowledgeBaseManageService knowledgeBaseManageService, + DocumentKnowledgeService documentKnowledgeService, + KnowledgeBaseRuntimeConfigResolver runtimeConfigResolver) { + this.knowledgeBaseManageService = knowledgeBaseManageService; + this.documentKnowledgeService = documentKnowledgeService; + this.runtimeConfigResolver = runtimeConfigResolver; + } + + @Override + public KnowledgeBaseSelectionSnapshot resolve(ChatQueryMode chatMode, + KnowledgeBaseSelectionMode selectionMode, + Collection selectedKnowledgeBaseIds) { + KnowledgeBaseSelectionMode resolvedMode = selectionMode == null ? KnowledgeBaseSelectionMode.NONE : selectionMode; + if (chatMode == ChatQueryMode.OPEN_CHAT || resolvedMode == KnowledgeBaseSelectionMode.NONE) { + return KnowledgeBaseSelectionSnapshot.none(runtimeConfigResolver.resolve(List.of())); + } + List selectedBases = switch (resolvedMode) { + case ALL -> selectAllWithRetrievableDocuments(); + case SELECTED -> selectExplicit(selectedKnowledgeBaseIds); + case NONE -> List.of(); + }; + if (selectedBases.isEmpty()) { + return KnowledgeBaseSelectionSnapshot.builder() + .selectionMode(resolvedMode) + .ragRuntimeOptions(runtimeConfigResolver.resolve(List.of())) + .build(); + } + List selectedBaseIds = selectedBases.stream().map(SuperAgentKnowledgeBase::getId).toList(); + List allowedDocuments = documentKnowledgeService.listRetrievableDocumentsByKnowledgeBaseIds(selectedBaseIds); + RagRuntimeOptions options = runtimeConfigResolver.resolve(selectedBases); + return KnowledgeBaseSelectionSnapshot.builder() + .selectionMode(resolvedMode) + .selectedKnowledgeBases(selectedBases) + .selectedKnowledgeBaseIds(selectedBaseIds) + .selectedKnowledgeBaseNames(selectedBases.stream().map(SuperAgentKnowledgeBase::getBaseName).toList()) + .allowedDocuments(allowedDocuments) + .allowedDocumentIds(allowedDocuments.stream() + .map(KnowledgeDocumentDescriptor::getDocumentId) + .filter(Objects::nonNull) + .distinct() + .toList()) + .allowedTaskIds(allowedDocuments.stream() + .map(KnowledgeDocumentDescriptor::getLastIndexTaskId) + .filter(Objects::nonNull) + .distinct() + .toList()) + .ragRuntimeOptions(options) + .build(); + } + + private List selectAllWithRetrievableDocuments() { + List enabledBases = knowledgeBaseManageService.listAllEnabled(); + if (enabledBases.isEmpty()) { + return List.of(); + } + List baseIds = enabledBases.stream().map(SuperAgentKnowledgeBase::getId).toList(); + LinkedHashSet nonEmptyBaseIds = documentKnowledgeService.listRetrievableDocumentsByKnowledgeBaseIds(baseIds) + .stream() + .map(KnowledgeDocumentDescriptor::getKnowledgeBaseId) + .filter(Objects::nonNull) + .collect(Collectors.toCollection(LinkedHashSet::new)); + return enabledBases.stream() + .filter(base -> nonEmptyBaseIds.contains(base.getId())) + .toList(); + } + + private List selectExplicit(Collection selectedKnowledgeBaseIds) { + List ids = selectedKnowledgeBaseIds == null + ? List.of() + : selectedKnowledgeBaseIds.stream() + .map(this::parseKnowledgeBaseId) + .filter(Objects::nonNull) + .distinct() + .toList(); + if (CollUtil.isEmpty(ids)) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "请选择至少一个知识库。"); + } + List bases = knowledgeBaseManageService.listEnabledByIds(ids); + Map byId = bases.stream() + .collect(Collectors.toMap(SuperAgentKnowledgeBase::getId, Function.identity())); + for (Long id : ids) { + if (!byId.containsKey(id)) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "知识库不存在或已停用: " + id); + } + } + return ids.stream().map(byId::get).toList(); + } + + private Long parseKnowledgeBaseId(String rawId) { + if (rawId == null || rawId.isBlank()) { + return null; + } + try { + Long id = Long.valueOf(rawId.trim()); + if (id <= 0) { + throw new NumberFormatException("must be positive"); + } + return id; + } + catch (NumberFormatException exception) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "knowledgeBaseId 格式非法。"); + } + } +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeManageServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeManageServiceImpl.java index f7fa372..493cd50 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeManageServiceImpl.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeManageServiceImpl.java @@ -15,6 +15,7 @@ import org.javaup.ai.manage.dto.DocumentProfileDetailQueryDto; import org.javaup.ai.manage.dto.DocumentProfileRegenerateDto; import org.javaup.ai.manage.dto.KnowledgeRouteTraceQueryDto; import org.javaup.ai.manage.dto.KnowledgeScopeDeleteDto; +import org.javaup.ai.manage.dto.KnowledgeScopeQueryDto; import org.javaup.ai.manage.dto.KnowledgeScopeSaveDto; import org.javaup.ai.manage.dto.KnowledgeTopicDeleteDto; import org.javaup.ai.manage.dto.KnowledgeTopicQueryDto; @@ -28,6 +29,7 @@ import org.javaup.ai.manage.mapper.SuperAgentKnowledgeTopicNodeMapper; import org.javaup.ai.manage.mapper.SuperAgentKnowledgeRouteTraceMapper; import org.javaup.ai.manage.mapper.SuperAgentTopicDocumentRelationMapper; import org.javaup.ai.manage.service.DocumentProfileService; +import org.javaup.ai.manage.service.KnowledgeBaseManageService; import org.javaup.ai.manage.service.KnowledgeManageService; import org.javaup.ai.manage.vo.DocumentProfileVo; import org.javaup.ai.manage.vo.KnowledgeRouteTraceItemVo; @@ -59,12 +61,16 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { private final SuperAgentKnowledgeRouteTraceMapper knowledgeRouteTraceMapper; private final SuperAgentDocumentMapper documentMapper; private final DocumentProfileService documentProfileService; + private final KnowledgeBaseManageService knowledgeBaseManageService; private final UidGenerator uidGenerator; @Override public KnowledgeScopeItemVo saveScope(KnowledgeScopeSaveDto dto) { validateScope(dto); + Long knowledgeBaseId = parseRequiredLong(dto.getKnowledgeBaseId(), "knowledgeBaseId"); + knowledgeBaseManageService.requireEnabled(knowledgeBaseId); SuperAgentKnowledgeScopeNode entity = scopeNodeMapper.selectOne(new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeScopeNode::getKnowledgeBaseId, knowledgeBaseId) .eq(SuperAgentKnowledgeScopeNode::getScopeCode, dto.getScopeCode().trim()) .eq(SuperAgentKnowledgeScopeNode::getStatus, BusinessStatus.YES.getCode()) .last("LIMIT 1")); @@ -72,6 +78,7 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { entity = new SuperAgentKnowledgeScopeNode(); entity.setId(uidGenerator.getUid()); entity.setStatus(BusinessStatus.YES.getCode()); + entity.setKnowledgeBaseId(knowledgeBaseId); entity.setScopeCode(dto.getScopeCode().trim()); } entity.setScopeName(safeText(dto.getScopeName())); @@ -91,21 +98,28 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { @Override public boolean deleteScope(KnowledgeScopeDeleteDto dto) { + Long knowledgeBaseId = parseRequiredLong(dto.getKnowledgeBaseId(), "knowledgeBaseId"); String scopeCode = safeText(dto.getScopeCode()); if (scopeCode.isBlank()) { throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "scopeCode 不能为空。"); } return scopeNodeMapper.update(null, new LambdaUpdateWrapper() + .eq(SuperAgentKnowledgeScopeNode::getKnowledgeBaseId, knowledgeBaseId) .eq(SuperAgentKnowledgeScopeNode::getScopeCode, scopeCode) .eq(SuperAgentKnowledgeScopeNode::getStatus, BusinessStatus.YES.getCode()) .set(SuperAgentKnowledgeScopeNode::getStatus, BusinessStatus.NO.getCode())) > 0; } @Override - public List listScopes() { - return scopeNodeMapper.selectList(new LambdaQueryWrapper() - .eq(SuperAgentKnowledgeScopeNode::getStatus, BusinessStatus.YES.getCode()) - .orderByAsc(SuperAgentKnowledgeScopeNode::getSortOrder, SuperAgentKnowledgeScopeNode::getId)) + public List listScopes(KnowledgeScopeQueryDto dto) { + Long knowledgeBaseId = parseOptionalPositiveLong(dto == null ? null : dto.getKnowledgeBaseId()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeScopeNode::getStatus, BusinessStatus.YES.getCode()) + .orderByAsc(SuperAgentKnowledgeScopeNode::getSortOrder, SuperAgentKnowledgeScopeNode::getId); + if (knowledgeBaseId != null) { + wrapper.eq(SuperAgentKnowledgeScopeNode::getKnowledgeBaseId, knowledgeBaseId); + } + return scopeNodeMapper.selectList(wrapper) .stream() .map(this::toScopeVo) .toList(); @@ -114,7 +128,10 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { @Override public KnowledgeTopicItemVo saveTopic(KnowledgeTopicSaveDto dto) { validateTopic(dto); + Long knowledgeBaseId = parseRequiredLong(dto.getKnowledgeBaseId(), "knowledgeBaseId"); + knowledgeBaseManageService.requireEnabled(knowledgeBaseId); SuperAgentKnowledgeTopicNode entity = topicNodeMapper.selectOne(new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeTopicNode::getKnowledgeBaseId, knowledgeBaseId) .eq(SuperAgentKnowledgeTopicNode::getTopicCode, dto.getTopicCode().trim()) .eq(SuperAgentKnowledgeTopicNode::getStatus, BusinessStatus.YES.getCode()) .last("LIMIT 1")); @@ -122,6 +139,7 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { entity = new SuperAgentKnowledgeTopicNode(); entity.setId(uidGenerator.getUid()); entity.setStatus(BusinessStatus.YES.getCode()); + entity.setKnowledgeBaseId(knowledgeBaseId); entity.setTopicCode(dto.getTopicCode().trim()); } entity.setTopicName(safeText(dto.getTopicName())); @@ -143,11 +161,13 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { @Override public boolean deleteTopic(KnowledgeTopicDeleteDto dto) { + Long knowledgeBaseId = parseRequiredLong(dto.getKnowledgeBaseId(), "knowledgeBaseId"); String topicCode = safeText(dto.getTopicCode()); if (topicCode.isBlank()) { throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "topicCode 不能为空。"); } return topicNodeMapper.update(null, new LambdaUpdateWrapper() + .eq(SuperAgentKnowledgeTopicNode::getKnowledgeBaseId, knowledgeBaseId) .eq(SuperAgentKnowledgeTopicNode::getTopicCode, topicCode) .eq(SuperAgentKnowledgeTopicNode::getStatus, BusinessStatus.YES.getCode()) .set(SuperAgentKnowledgeTopicNode::getStatus, BusinessStatus.NO.getCode())) > 0; @@ -156,9 +176,13 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { @Override public List listTopics(KnowledgeTopicQueryDto dto) { String scopeCode = dto == null ? "" : safeText(dto.getScopeCode()); + Long knowledgeBaseId = parseOptionalPositiveLong(dto == null ? null : dto.getKnowledgeBaseId()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(SuperAgentKnowledgeTopicNode::getStatus, BusinessStatus.YES.getCode()) .orderByAsc(SuperAgentKnowledgeTopicNode::getSortOrder, SuperAgentKnowledgeTopicNode::getId); + if (knowledgeBaseId != null) { + wrapper.eq(SuperAgentKnowledgeTopicNode::getKnowledgeBaseId, knowledgeBaseId); + } if (scopeCode != null && !scopeCode.isBlank()) { wrapper.eq(SuperAgentKnowledgeTopicNode::getScopeCode, scopeCode); } @@ -192,9 +216,13 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { @Override public List listTopicDocuments(TopicDocumentRelationListQueryDto dto) { String topicCode = dto == null ? "" : safeText(dto.getTopicCode()); + Long knowledgeBaseId = parseOptionalPositiveLong(dto == null ? null : dto.getKnowledgeBaseId()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(SuperAgentTopicDocumentRelation::getStatus, BusinessStatus.YES.getCode()) .orderByDesc(SuperAgentTopicDocumentRelation::getRelationScore, SuperAgentTopicDocumentRelation::getId); + if (knowledgeBaseId != null) { + wrapper.eq(SuperAgentTopicDocumentRelation::getKnowledgeBaseId, knowledgeBaseId); + } if (topicCode != null && !topicCode.isBlank()) { wrapper.eq(SuperAgentTopicDocumentRelation::getTopicCode, topicCode); } @@ -205,12 +233,19 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { @Override public TopicDocumentRelationItemVo saveTopicDocumentRelation(TopicDocumentRelationSaveDto dto) { + Long knowledgeBaseId = parseRequiredLong(dto.getKnowledgeBaseId(), "knowledgeBaseId"); + knowledgeBaseManageService.requireEnabled(knowledgeBaseId); String topicCode = safeText(dto.getTopicCode()); Long documentId = parseRequiredLong(dto.getDocumentId(), "documentId"); if (topicCode.isBlank()) { throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "topicCode 不能为空。"); } + SuperAgentDocument document = documentMapper.selectById(documentId); + if (document == null || !Long.valueOf(knowledgeBaseId).equals(document.getKnowledgeBaseId())) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "文档不属于当前知识库。"); + } SuperAgentTopicDocumentRelation relation = topicDocumentRelationMapper.selectOne(new LambdaQueryWrapper() + .eq(SuperAgentTopicDocumentRelation::getKnowledgeBaseId, knowledgeBaseId) .eq(SuperAgentTopicDocumentRelation::getTopicCode, topicCode) .eq(SuperAgentTopicDocumentRelation::getDocumentId, documentId) .eq(SuperAgentTopicDocumentRelation::getStatus, BusinessStatus.YES.getCode()) @@ -218,6 +253,7 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { if (relation == null) { relation = new SuperAgentTopicDocumentRelation(); relation.setId(uidGenerator.getUid()); + relation.setKnowledgeBaseId(knowledgeBaseId); relation.setTopicCode(topicCode); relation.setDocumentId(documentId); relation.setStatus(BusinessStatus.YES.getCode()); @@ -236,12 +272,14 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { @Override public boolean removeTopicDocumentRelation(TopicDocumentRelationRemoveDto dto) { + Long knowledgeBaseId = parseRequiredLong(dto.getKnowledgeBaseId(), "knowledgeBaseId"); String topicCode = safeText(dto.getTopicCode()); Long documentId = parseRequiredLong(dto.getDocumentId(), "documentId"); if (topicCode.isBlank()) { throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "topicCode 不能为空。"); } return topicDocumentRelationMapper.update(null, new LambdaUpdateWrapper() + .eq(SuperAgentTopicDocumentRelation::getKnowledgeBaseId, knowledgeBaseId) .eq(SuperAgentTopicDocumentRelation::getTopicCode, topicCode) .eq(SuperAgentTopicDocumentRelation::getDocumentId, documentId) .eq(SuperAgentTopicDocumentRelation::getStatus, BusinessStatus.YES.getCode()) @@ -318,6 +356,7 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { private KnowledgeScopeItemVo toScopeVo(SuperAgentKnowledgeScopeNode node) { return new KnowledgeScopeItemVo( String.valueOf(node.getId()), + node.getKnowledgeBaseId() == null ? "" : String.valueOf(node.getKnowledgeBaseId()), safeText(node.getScopeCode()), safeText(node.getScopeName()), safeText(node.getParentScopeCode()), @@ -331,6 +370,7 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { private KnowledgeTopicItemVo toTopicVo(SuperAgentKnowledgeTopicNode node) { return new KnowledgeTopicItemVo( String.valueOf(node.getId()), + node.getKnowledgeBaseId() == null ? "" : String.valueOf(node.getKnowledgeBaseId()), safeText(node.getTopicCode()), safeText(node.getTopicName()), safeText(node.getScopeCode()), @@ -363,13 +403,10 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { private TopicDocumentRelationItemVo toRelationVo(SuperAgentTopicDocumentRelation relation) { SuperAgentDocument document = documentMapper.selectById(relation.getDocumentId()); return new TopicDocumentRelationItemVo( + relation.getKnowledgeBaseId() == null ? "" : String.valueOf(relation.getKnowledgeBaseId()), safeText(relation.getTopicCode()), String.valueOf(relation.getDocumentId()), document == null ? "" : safeText(document.getDocumentName()), - document == null ? "" : safeText(document.getKnowledgeScopeCode()), - document == null ? "" : safeText(document.getKnowledgeScopeName()), - document == null ? "" : safeText(document.getBusinessCategory()), - document == null ? "" : safeText(document.getDocumentTags()), relation.getRelationScore() == null ? "0.0000" : relation.getRelationScore().toPlainString(), safeText(relation.getRelationSource()), safeText(relation.getReason()) @@ -392,6 +429,19 @@ public class KnowledgeManageServiceImpl implements KnowledgeManageService { } } + private Long parseOptionalPositiveLong(String rawValue) { + if (StrUtil.isBlank(rawValue)) { + return null; + } + try { + Long value = Long.valueOf(rawValue.trim()); + return value > 0 ? value : null; + } + catch (NumberFormatException exception) { + throw new SuperAgentFrameException(BaseCode.PARAMETER_ERROR.getCode(), "id 格式非法。"); + } + } + private Integer parseInteger(String rawValue, Integer fallback) { if (StrUtil.isBlank(rawValue)) { return fallback; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeRouteServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeRouteServiceImpl.java index 074248c..28e413e 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeRouteServiceImpl.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/KnowledgeRouteServiceImpl.java @@ -17,7 +17,9 @@ import org.javaup.ai.manage.mapper.SuperAgentKnowledgeRouteTraceMapper; import org.javaup.ai.manage.mapper.SuperAgentKnowledgeScopeNodeMapper; import org.javaup.ai.manage.mapper.SuperAgentKnowledgeTopicNodeMapper; import org.javaup.ai.manage.mapper.SuperAgentTopicDocumentRelationMapper; +import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; import org.javaup.ai.manage.model.route.DocumentRouteCandidate; +import org.javaup.ai.manage.model.route.KnowledgeRouteContext; import org.javaup.ai.manage.model.route.KnowledgeRouteDecision; import org.javaup.ai.manage.model.route.ScopeRouteCandidate; import org.javaup.ai.manage.model.route.TopicRouteCandidate; @@ -25,6 +27,7 @@ import org.javaup.ai.manage.service.KnowledgeRouteIndexService; import org.javaup.ai.manage.service.KnowledgeRouteService; import org.javaup.enums.BusinessStatus; import org.javaup.enums.DocumentIndexStatusEnum; +import org.javaup.enums.KnowledgeBaseSelectionMode; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Service; @@ -69,8 +72,8 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { private final UidGenerator uidGenerator; @Override - public KnowledgeRouteDecision route(String question, String rewriteQuestion) { - RouteQueryContext queryContext = buildQueryContext(question, rewriteQuestion); + public KnowledgeRouteDecision route(KnowledgeRouteContext context) { + RouteQueryContext queryContext = buildQueryContext(context); KnowledgeRouteDecision decision = new KnowledgeRouteDecision(); if (queryContext.queryTerms().isEmpty()) { decision.setRouteStatus("FAILED"); @@ -98,8 +101,8 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { ? "没有找到可用候选文档" : resolveDecisionReason(documentCandidates, confidence)); log.info("知识范围路由完成: question='{}', rewriteQuestion='{}', scopeCount={}, topicCount={}, documentCount={}, confidence={}, topDocument='{}'", - StrUtil.blankToDefault(question, ""), - StrUtil.blankToDefault(rewriteQuestion, ""), + StrUtil.blankToDefault(queryContext.originalQuestion(), ""), + StrUtil.blankToDefault(queryContext.rewriteQuestion(), ""), scopeCandidates.size(), topicCandidates.size(), documentCandidates.size(), @@ -112,11 +115,10 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { public void recordShadowRoute(String conversationId, long exchangeId, Long selectedDocumentId, - String question, - String rewriteQuestion) { + KnowledgeRouteContext context) { try { - KnowledgeRouteDecision decision = route(question, rewriteQuestion); - saveTrace(conversationId, exchangeId, selectedDocumentId, question, rewriteQuestion, "shadow", decision); + KnowledgeRouteDecision decision = route(context); + saveTrace(conversationId, exchangeId, selectedDocumentId, context, "shadow", decision); } catch (Exception exception) { log.warn("记录知识路由影子结果失败: conversationId={}, exchangeId={}", conversationId, exchangeId, exception); @@ -126,14 +128,13 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { @Override public void recordAutoRoute(String conversationId, long exchangeId, - String question, - String rewriteQuestion, + KnowledgeRouteContext context, KnowledgeRouteDecision decision) { try { Long selectedDocumentId = decision == null || decision.topDocument() == null || StrUtil.isBlank(decision.topDocument().getDocumentId()) ? null : Long.valueOf(decision.topDocument().getDocumentId()); - saveTrace(conversationId, exchangeId, selectedDocumentId, question, rewriteQuestion, "auto", decision); + saveTrace(conversationId, exchangeId, selectedDocumentId, context, "auto", decision); } catch (Exception exception) { log.warn("记录知识路由 AUTO 结果失败: conversationId={}, exchangeId={}", conversationId, exchangeId, exception); @@ -143,17 +144,20 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { private void saveTrace(String conversationId, long exchangeId, Long selectedDocumentId, - String question, - String rewriteQuestion, + KnowledgeRouteContext context, String mode, KnowledgeRouteDecision decision) { SuperAgentKnowledgeRouteTrace trace = new SuperAgentKnowledgeRouteTrace(); trace.setId(uidGenerator.getUid()); trace.setConversationId(conversationId); trace.setExchangeId(exchangeId); - trace.setQuestion(question); - trace.setRewriteQuestion(rewriteQuestion); + trace.setQuestion(context == null ? "" : context.getQuestion()); + trace.setRewriteQuestion(context == null ? "" : context.getRewriteQuestion()); trace.setMode(mode); + trace.setKnowledgeBaseSelectionMode(context == null || context.getKnowledgeBaseSelectionMode() == null ? KnowledgeBaseSelectionMode.NONE.name() : context.getKnowledgeBaseSelectionMode().name()); + trace.setSelectedKnowledgeBaseIdsJson(writeStringJson(context == null ? List.of() : context.getSelectedKnowledgeBaseIds().stream().map(String::valueOf).toList())); + trace.setSelectedKnowledgeBaseNamesJson(writeStringJson(context == null ? List.of() : context.getSelectedKnowledgeBaseNames())); + trace.setAllowedDocumentIdsJson(writeStringJson(context == null ? List.of() : context.getAllowedDocumentIds().stream().map(String::valueOf).toList())); trace.setTopScopesJson(writeScopeJson(decision == null ? List.of() : decision.getScopes())); trace.setTopTopicsJson(writeTopicJson(decision == null ? List.of() : decision.getTopics())); trace.setTopDocumentsJson(writeDocumentJson(decision == null ? List.of() : decision.getDocuments())); @@ -188,49 +192,31 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { } private List rankScopes(RouteQueryContext queryContext) { - List nodes = scopeNodeMapper.selectList(new LambdaQueryWrapper() - .eq(SuperAgentKnowledgeScopeNode::getStatus, BusinessStatus.YES.getCode())); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeScopeNode::getStatus, BusinessStatus.YES.getCode()); + if (queryContext.selectedKnowledgeBaseIds() != null && !queryContext.selectedKnowledgeBaseIds().isEmpty()) { + wrapper.in(SuperAgentKnowledgeScopeNode::getKnowledgeBaseId, queryContext.selectedKnowledgeBaseIds()); + } + List nodes = scopeNodeMapper.selectList(wrapper); if (nodes.isEmpty()) { - return deriveScopesFromDocuments(queryContext); + return List.of(); } List routeTexts = nodes.stream() .map(node -> join(node.getScopeName(), node.getDescription(), node.getAliases(), node.getExamples())) .toList(); List semanticScores = computeSemanticScores(queryContext, routeTexts); - Map lexicalScores = searchLexicalScores(queryContext.routingText(), "scope", 5).stream() + Map lexicalScores = searchLexicalScores(queryContext, "scope", 5).stream() .collect(Collectors.toMap(KnowledgeRouteIndexService.RouteLexicalHit::entityCode, KnowledgeRouteIndexService.RouteLexicalHit::score, (left, right) -> left)); return buildScopeCandidates(queryContext, nodes, routeTexts, semanticScores, lexicalScores); } - private List deriveScopesFromDocuments(RouteQueryContext queryContext) { - List documents = listRetrievableDocuments(); - Map accumulatorMap = new LinkedHashMap<>(); - for (SuperAgentDocument document : documents) { - if (StrUtil.isBlank(document.getKnowledgeScopeCode()) && StrUtil.isBlank(document.getKnowledgeScopeName())) { - continue; - } - String code = firstNonBlank(document.getKnowledgeScopeCode(), "general_document"); - String name = firstNonBlank(document.getKnowledgeScopeName(), "通用文档"); - String routeText = join(code, name, document.getBusinessCategory(), document.getDocumentTags()); - double score = keywordEntityAssist(queryContext.queryTerms(), routeText); - double semanticScore = semanticScore(queryContext, routeText); - ScopeAccumulator accumulator = accumulatorMap.computeIfAbsent(code, key -> new ScopeAccumulator(code, name)); - if (score + semanticMainScore(semanticScore) > accumulator.maxScore) { - accumulator.maxScore = score + semanticMainScore(semanticScore); - accumulator.reason = buildReason(queryContext.queryTerms(), routeText, semanticScore); - } - } - return accumulatorMap.values().stream() - .filter(item -> item.maxScore > 0D || queryContext.semanticEnabled()) - .map(item -> new ScopeRouteCandidate(item.scopeCode, item.scopeName, scoreToBigDecimal(item.maxScore), item.reason)) - .sorted((left, right) -> right.getScore().compareTo(left.getScore())) - .limit(5) - .toList(); - } - private List rankTopics(RouteQueryContext queryContext, List scopeCandidates) { - List nodes = topicNodeMapper.selectList(new LambdaQueryWrapper() - .eq(SuperAgentKnowledgeTopicNode::getStatus, BusinessStatus.YES.getCode())); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeTopicNode::getStatus, BusinessStatus.YES.getCode()); + if (queryContext.selectedKnowledgeBaseIds() != null && !queryContext.selectedKnowledgeBaseIds().isEmpty()) { + wrapper.in(SuperAgentKnowledgeTopicNode::getKnowledgeBaseId, queryContext.selectedKnowledgeBaseIds()); + } + List nodes = topicNodeMapper.selectList(wrapper); Set preferredScopes = scopeCandidates.stream().map(ScopeRouteCandidate::getScopeCode).collect(Collectors.toSet()); if (nodes.isEmpty()) { return deriveTopicsFromProfiles(queryContext, preferredScopes); @@ -246,7 +232,7 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { )) .toList(); List semanticScores = computeSemanticScores(queryContext, routeTexts); - Map lexicalScores = searchLexicalScores(queryContext.routingText(), "topic", 8).stream() + Map lexicalScores = searchLexicalScores(queryContext, "topic", 8).stream() .collect(Collectors.toMap(KnowledgeRouteIndexService.RouteLexicalHit::entityCode, KnowledgeRouteIndexService.RouteLexicalHit::score, (left, right) -> left)); List candidates = new ArrayList<>(nodes.size()); for (int index = 0; index < nodes.size(); index++) { @@ -275,23 +261,22 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { } private List deriveTopicsFromProfiles(RouteQueryContext queryContext, Set preferredScopes) { - List profiles = documentProfileMapper.selectList(new LambdaQueryWrapper() - .eq(SuperAgentDocumentProfile::getStatus, BusinessStatus.YES.getCode()) - .eq(SuperAgentDocumentProfile::getProfileStatus, 2)); Map accumulatorMap = new LinkedHashMap<>(); - Map documentMap = listRetrievableDocuments().stream() + Map documentMap = listRetrievableDocuments(queryContext).stream() .collect(Collectors.toMap(SuperAgentDocument::getId, item -> item)); + if (documentMap.isEmpty()) { + return List.of(); + } + List profiles = documentProfileMapper.selectList(new LambdaQueryWrapper() + .eq(SuperAgentDocumentProfile::getStatus, BusinessStatus.YES.getCode()) + .eq(SuperAgentDocumentProfile::getProfileStatus, 2) + .in(SuperAgentDocumentProfile::getDocumentId, documentMap.keySet())); for (SuperAgentDocumentProfile profile : profiles) { - SuperAgentDocument document = documentMap.get(profile.getDocumentId()); - String scopeCode = document == null ? "" : StrUtil.blankToDefault(document.getKnowledgeScopeCode(), ""); for (String topic : parseJsonArray(profile.getCoreTopics())) { String routeText = join(topic, profile.getDocumentSummary(), profile.getExampleQuestions()); double score = keywordEntityAssist(queryContext.queryTerms(), routeText); double semanticScore = semanticScore(queryContext, routeText); - if (!preferredScopes.isEmpty() && preferredScopes.contains(scopeCode)) { - score += 6D; - } - TopicAccumulator accumulator = accumulatorMap.computeIfAbsent(topic, key -> new TopicAccumulator(topic, scopeCode)); + TopicAccumulator accumulator = accumulatorMap.computeIfAbsent(topic, key -> new TopicAccumulator(topic, "")); double finalScore = score + semanticMainScore(semanticScore); if (finalScore > accumulator.maxScore) { accumulator.maxScore = finalScore; @@ -310,7 +295,7 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { private List rankDocuments(RouteQueryContext queryContext, List scopeCandidates, List topicCandidates) { - List documents = listRetrievableDocuments(); + List documents = listRetrievableDocuments(queryContext); if (documents.isEmpty()) { return List.of(); } @@ -319,9 +304,15 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { .eq(SuperAgentDocumentProfile::getProfileStatus, 2)) .stream() .collect(Collectors.toMap(SuperAgentDocumentProfile::getDocumentId, item -> item, (left, right) -> right)); - Map> topicRelationMap = topicDocumentRelationMapper.selectList( - new LambdaQueryWrapper() - .eq(SuperAgentTopicDocumentRelation::getStatus, BusinessStatus.YES.getCode())) + LambdaQueryWrapper relationWrapper = new LambdaQueryWrapper() + .eq(SuperAgentTopicDocumentRelation::getStatus, BusinessStatus.YES.getCode()); + if (queryContext.selectedKnowledgeBaseIds() != null && !queryContext.selectedKnowledgeBaseIds().isEmpty()) { + relationWrapper.in(SuperAgentTopicDocumentRelation::getKnowledgeBaseId, queryContext.selectedKnowledgeBaseIds()); + } + if (queryContext.allowedDocumentIds() != null && !queryContext.allowedDocumentIds().isEmpty()) { + relationWrapper.in(SuperAgentTopicDocumentRelation::getDocumentId, queryContext.allowedDocumentIds()); + } + Map> topicRelationMap = topicDocumentRelationMapper.selectList(relationWrapper) .stream() .collect(Collectors.groupingBy(SuperAgentTopicDocumentRelation::getTopicCode, Collectors.toMap(SuperAgentTopicDocumentRelation::getDocumentId, item -> item, (left, right) -> right))); @@ -331,7 +322,7 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { .map(document -> buildDocumentRouteMaterial(document, profileMap.get(document.getId()))) .toList(); List semanticScores = computeSemanticScores(queryContext, materials.stream().map(DocumentRouteMaterial::routeText).toList()); - Map lexicalScores = searchLexicalScores(queryContext.routingText(), "document", 5).stream() + Map lexicalScores = searchLexicalScores(queryContext, "document", 5).stream() .filter(hit -> hit.documentId() != null) .collect(Collectors.toMap(KnowledgeRouteIndexService.RouteLexicalHit::documentId, KnowledgeRouteIndexService.RouteLexicalHit::score, (left, right) -> left)); return documents.stream() @@ -362,20 +353,11 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { List semanticScores, Map lexicalScores) { int materialIndex = findMaterialIndex(materials, document.getId()); - String routeText = materialIndex >= 0 ? materials.get(materialIndex).routeText() : join( - document.getDocumentName(), - document.getKnowledgeScopeName(), - document.getKnowledgeScopeCode(), - document.getBusinessCategory(), - document.getDocumentTags() - ); + String routeText = materialIndex >= 0 ? materials.get(materialIndex).routeText() : document.getDocumentName(); double semanticScore = materialIndex >= 0 && materialIndex < semanticScores.size() ? semanticScores.get(materialIndex) : 0D; double score = semanticMainScore(semanticScore) + lexicalAssist(lexicalScores.get(document.getId())) + keywordEntityAssist(queryContext.queryTerms(), routeText); - if (StrUtil.isNotBlank(topScopeCode) && topScopeCode.equals(document.getKnowledgeScopeCode())) { - score += 15D; - } if (StrUtil.isNotBlank(topTopicCode)) { Map relationMap = topicRelationMap.get(topTopicCode); if (relationMap != null) { @@ -390,10 +372,6 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { String.valueOf(document.getId()), document.getDocumentName(), document.getLastIndexTaskId() == null ? "" : String.valueOf(document.getLastIndexTaskId()), - StrUtil.blankToDefault(document.getKnowledgeScopeCode(), ""), - StrUtil.blankToDefault(document.getKnowledgeScopeName(), ""), - StrUtil.blankToDefault(document.getBusinessCategory(), ""), - StrUtil.blankToDefault(document.getDocumentTags(), ""), BigDecimal.ZERO, "未命中路由关键词" ); @@ -402,25 +380,35 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { String.valueOf(document.getId()), document.getDocumentName(), document.getLastIndexTaskId() == null ? "" : String.valueOf(document.getLastIndexTaskId()), - StrUtil.blankToDefault(document.getKnowledgeScopeCode(), ""), - StrUtil.blankToDefault(document.getKnowledgeScopeName(), ""), - StrUtil.blankToDefault(document.getBusinessCategory(), ""), - StrUtil.blankToDefault(document.getDocumentTags(), ""), scoreToBigDecimal(score), buildReason(queryContext.queryTerms(), routeText, semanticScore) ); } - private RouteQueryContext buildQueryContext(String question, String rewriteQuestion) { + private RouteQueryContext buildQueryContext(KnowledgeRouteContext context) { + String question = context == null ? "" : context.getQuestion(); + String rewriteQuestion = context == null ? "" : context.getRewriteQuestion(); String routingText = buildRoutingText(question, rewriteQuestion); List queryTerms = tokenize(routingText); float[] queryEmbedding = embedSingle(routingText); + List selectedKnowledgeBaseIds = context == null || context.getSelectedKnowledgeBaseIds() == null + ? List.of() + : context.getSelectedKnowledgeBaseIds().stream().filter(Objects::nonNull).distinct().toList(); + List allowedDocumentIds = context == null || context.getAllowedDocumentIds() == null + ? List.of() + : context.getAllowedDocumentIds().stream().filter(Objects::nonNull).distinct().toList(); + List allowedDocuments = context == null || context.getAllowedDocuments() == null + ? List.of() + : context.getAllowedDocuments(); return new RouteQueryContext( StrUtil.blankToDefault(question, ""), StrUtil.blankToDefault(rewriteQuestion, ""), routingText, queryTerms, - queryEmbedding + queryEmbedding, + selectedKnowledgeBaseIds, + allowedDocumentIds, + allowedDocuments ); } @@ -468,10 +456,6 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { document.getId(), join( document.getDocumentName(), - document.getKnowledgeScopeName(), - document.getKnowledgeScopeCode(), - document.getBusinessCategory(), - document.getDocumentTags(), profile == null ? "" : profile.getDocumentSummary(), profile == null ? "" : profile.getCoreTopics(), profile == null ? "" : profile.getExampleQuestions(), @@ -500,11 +484,22 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { } private List listRetrievableDocuments() { - return documentMapper.selectList(new LambdaQueryWrapper() + return listRetrievableDocuments(null); + } + + private List listRetrievableDocuments(RouteQueryContext queryContext) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() .eq(SuperAgentDocument::getStatus, BusinessStatus.YES.getCode()) .eq(SuperAgentDocument::getIndexStatus, DocumentIndexStatusEnum.BUILD_SUCCESS.getCode()) .isNotNull(SuperAgentDocument::getLastIndexTaskId) - .orderByAsc(SuperAgentDocument::getId)); + .orderByAsc(SuperAgentDocument::getId); + if (queryContext != null && queryContext.allowedDocumentIds() != null && !queryContext.allowedDocumentIds().isEmpty()) { + wrapper.in(SuperAgentDocument::getId, queryContext.allowedDocumentIds()); + } + else if (queryContext != null && queryContext.selectedKnowledgeBaseIds() != null && !queryContext.selectedKnowledgeBaseIds().isEmpty()) { + wrapper.in(SuperAgentDocument::getKnowledgeBaseId, queryContext.selectedKnowledgeBaseIds()); + } + return documentMapper.selectList(wrapper); } private double lexicalScore(List queryTerms, String content) { @@ -537,12 +532,31 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { return score; } - private List searchLexicalScores(String routingText, String entityType, int size) { + private List searchLexicalScores(RouteQueryContext queryContext, String entityType, int size) { KnowledgeRouteIndexService routeIndexService = knowledgeRouteIndexServiceProvider.getIfAvailable(); if (routeIndexService == null) { return List.of(); } - return routeIndexService.search(routingText, entityType, size); + List hits = routeIndexService.search( + queryContext.routingText(), + entityType, + size, + queryContext.selectedKnowledgeBaseIds() + ); + if (hits == null || hits.isEmpty()) { + return hits == null ? List.of() : hits; + } + if (queryContext.selectedKnowledgeBaseIds() != null && !queryContext.selectedKnowledgeBaseIds().isEmpty()) { + hits = hits.stream() + .filter(hit -> hit.knowledgeBaseId() == null || queryContext.selectedKnowledgeBaseIds().contains(hit.knowledgeBaseId())) + .toList(); + } + if (queryContext.allowedDocumentIds() == null || queryContext.allowedDocumentIds().isEmpty()) { + return hits == null ? List.of() : hits; + } + return hits.stream() + .filter(hit -> hit.documentId() == null || queryContext.allowedDocumentIds().contains(hit.documentId())) + .toList(); } private List tokenize(String text) { @@ -811,6 +825,16 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { .collect(Collectors.joining(",", "[", "]")); } + private String writeStringJson(List values) { + if (values == null || values.isEmpty()) { + return "[]"; + } + return values.stream() + .filter(Objects::nonNull) + .map(value -> "\"" + escapeJson(value) + "\"") + .collect(Collectors.joining(",", "[", "]")); + } + private String escapeJson(String text) { return StrUtil.blankToDefault(text, "").replace("\"", "\\\""); } @@ -843,7 +867,10 @@ public class KnowledgeRouteServiceImpl implements KnowledgeRouteService { String rewriteQuestion, String routingText, List queryTerms, - float[] queryEmbedding) { + float[] queryEmbedding, + List selectedKnowledgeBaseIds, + List allowedDocumentIds, + List allowedDocuments) { private boolean semanticEnabled() { return queryEmbedding != null && queryEmbedding.length > 0; } diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/RaptorBuildServiceImpl.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/RaptorBuildServiceImpl.java index fe7d2ab..06e330e 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/RaptorBuildServiceImpl.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/impl/RaptorBuildServiceImpl.java @@ -11,10 +11,14 @@ import lombok.extern.slf4j.Slf4j; import org.javaup.ai.manage.config.DocumentManageProperties; import org.javaup.ai.manage.data.SuperAgentDocument; import org.javaup.ai.manage.data.SuperAgentDocumentChunk; +import org.javaup.ai.manage.data.SuperAgentKnowledgeTopicNode; import org.javaup.ai.manage.data.SuperAgentRaptorNode; +import org.javaup.ai.manage.data.SuperAgentTopicDocumentRelation; import org.javaup.ai.manage.mapper.SuperAgentDocumentChunkMapper; import org.javaup.ai.manage.mapper.SuperAgentDocumentMapper; +import org.javaup.ai.manage.mapper.SuperAgentKnowledgeTopicNodeMapper; import org.javaup.ai.manage.mapper.SuperAgentRaptorNodeMapper; +import org.javaup.ai.manage.mapper.SuperAgentTopicDocumentRelationMapper; import org.javaup.ai.manage.model.raptor.RaptorBuildResult; import org.javaup.ai.manage.model.raptor.RaptorQualityReport; import org.javaup.ai.manage.service.RaptorBuildService; @@ -105,6 +109,10 @@ public class RaptorBuildServiceImpl implements RaptorBuildService { private final SuperAgentDocumentChunkMapper chunkMapper; + private final SuperAgentKnowledgeTopicNodeMapper topicNodeMapper; + + private final SuperAgentTopicDocumentRelationMapper topicDocumentRelationMapper; + private final RagToolsClient ragToolsClient; private final ObjectMapper objectMapper; @@ -221,14 +229,44 @@ public class RaptorBuildServiceImpl implements RaptorBuildService { @Override @Transactional(rollbackFor = Exception.class) - public RaptorBuildResult rebuildKnowledgeScopeTree(String knowledgeScopeCode) { + public RaptorBuildResult rebuildKnowledgeScopeTree(Long knowledgeBaseId, String knowledgeScopeCode) { String normalizedScopeCode = RaptorScopeSupport.normalizeScopeCode(knowledgeScopeCode); - if (StrUtil.isBlank(normalizedScopeCode)) { + if (knowledgeBaseId == null || StrUtil.isBlank(normalizedScopeCode)) { + return RaptorBuildResult.builder().build(); + } + String scopeKey = RaptorScopeSupport.knowledgeScopeKey(knowledgeBaseId, normalizedScopeCode); + List topicCodes = topicNodeMapper.selectList(new LambdaQueryWrapper() + .eq(SuperAgentKnowledgeTopicNode::getKnowledgeBaseId, knowledgeBaseId) + .eq(SuperAgentKnowledgeTopicNode::getScopeCode, knowledgeScopeCode) + .eq(SuperAgentKnowledgeTopicNode::getStatus, BusinessStatus.YES.getCode())) + .stream() + .map(SuperAgentKnowledgeTopicNode::getTopicCode) + .filter(StrUtil::isNotBlank) + .distinct() + .toList(); + if (topicCodes.isEmpty()) { + deleteByScope(RaptorScopeSupport.SCOPE_TYPE_DATASET, scopeKey); + log.info("跳过 RAPTOR dataset-level 构建,知识范围没有可用主题: scopeKey={}", scopeKey); + return RaptorBuildResult.builder().build(); + } + List documentIds = topicDocumentRelationMapper.selectList(new LambdaQueryWrapper() + .eq(SuperAgentTopicDocumentRelation::getKnowledgeBaseId, knowledgeBaseId) + .in(SuperAgentTopicDocumentRelation::getTopicCode, topicCodes) + .eq(SuperAgentTopicDocumentRelation::getStatus, BusinessStatus.YES.getCode())) + .stream() + .map(SuperAgentTopicDocumentRelation::getDocumentId) + .filter(Objects::nonNull) + .distinct() + .toList(); + if (documentIds.isEmpty()) { + deleteByScope(RaptorScopeSupport.SCOPE_TYPE_DATASET, scopeKey); + log.info("跳过 RAPTOR dataset-level 构建,知识范围没有主题文档关联: scopeKey={}, topicCount={}", + scopeKey, topicCodes.size()); return RaptorBuildResult.builder().build(); } - String scopeKey = RaptorScopeSupport.knowledgeScopeKey(normalizedScopeCode); List documents = documentMapper.selectList(new LambdaQueryWrapper() - .eq(SuperAgentDocument::getKnowledgeScopeCode, knowledgeScopeCode) + .in(SuperAgentDocument::getId, documentIds) + .eq(SuperAgentDocument::getKnowledgeBaseId, knowledgeBaseId) .eq(SuperAgentDocument::getIndexStatus, DocumentIndexStatusEnum.BUILD_SUCCESS.getCode()) .eq(SuperAgentDocument::getStatus, BusinessStatus.YES.getCode()) .isNotNull(SuperAgentDocument::getLastIndexTaskId) diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/keyword/ElasticsearchDocumentKeywordSearchGateway.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/keyword/ElasticsearchDocumentKeywordSearchGateway.java index 808aa92..2dfd011 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/keyword/ElasticsearchDocumentKeywordSearchGateway.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/service/keyword/ElasticsearchDocumentKeywordSearchGateway.java @@ -214,24 +214,9 @@ public class ElasticsearchDocumentKeywordSearchGateway implements DocumentKeywor bool.should(should -> should.multiMatch(multiMatch -> multiMatch .query(retrievalQuery) .fields("title^10", "sectionPath^8", "contentWithWeight^6", "questions^5", - "keywords^5", "documentName^4", "knowledgeScopeName^3", "chunkText^2") + "keywords^5", "documentName^4", "chunkText^2") .type(TextQueryType.BestFields) )); - if (filters != null && CollUtil.isNotEmpty(filters.getBusinessCategoryHints())) { - - bool.should(should -> should.multiMatch(multiMatch -> multiMatch - .query(String.join(" ", filters.getBusinessCategoryHints())) - .fields("businessCategory^5", "knowledgeScopeName^2") - .type(TextQueryType.BestFields) - )); - } - if (filters != null && CollUtil.isNotEmpty(filters.getDocumentTagHints())) { - bool.should(should -> should.multiMatch(multiMatch -> multiMatch - .query(String.join(" ", filters.getDocumentTagHints())) - .fields("documentTags^4", "keywords^3", "documentName^2", "contentWithWeight^2", "chunkText") - .type(TextQueryType.BestFields) - )); - } if (filters != null && CollUtil.isNotEmpty(filters.getDocumentNameHints())) { bool.should(should -> should.multiMatch(multiMatch -> multiMatch .query(String.join(" ", filters.getDocumentNameHints())) @@ -249,7 +234,7 @@ public class ElasticsearchDocumentKeywordSearchGateway implements DocumentKeywor if (CollUtil.isNotEmpty(queryContextHints)) { bool.should(should -> should.multiMatch(multiMatch -> multiMatch .query(String.join(" ", queryContextHints)) - .fields("documentName^2", "knowledgeScopeName^2", "title^3", "sectionPath^2", + .fields("documentName^2", "title^3", "sectionPath^2", "keywords^3", "questions^3", "contentWithWeight^2", "chunkText") .type(TextQueryType.BestFields) )); @@ -355,10 +340,6 @@ public class ElasticsearchDocumentKeywordSearchGateway implements DocumentKeywor .pageRange(safeText(chunk.getPageRange())) .bboxJson(safeText(chunk.getBboxJson())) .sourceBlockIds(safeText(chunk.getSourceBlockIds())) - .knowledgeScopeCode(document == null ? "" : safeText(document.getKnowledgeScopeCode())) - .knowledgeScopeName(document == null ? "" : safeText(document.getKnowledgeScopeName())) - .businessCategory(document == null ? "" : safeText(document.getBusinessCategory())) - .documentTags(splitTags(document == null ? "" : document.getDocumentTags())) .contentWithWeight(safeText(chunk.getContentWithWeight())) .chunkType(safeText(chunk.getChunkType())) .title(safeText(chunk.getTitle())) @@ -389,10 +370,6 @@ public class ElasticsearchDocumentKeywordSearchGateway implements DocumentKeywor metadata.put(DocumentKnowledgeMetadataKeys.BBOX_JSON, safeText(source.getBboxJson())); metadata.put(DocumentKnowledgeMetadataKeys.SOURCE_BLOCK_IDS, safeText(source.getSourceBlockIds())); metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_NAME, safeText(source.getDocumentName())); - metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_SCOPE_CODE, safeText(source.getKnowledgeScopeCode())); - metadata.put(DocumentKnowledgeMetadataKeys.KNOWLEDGE_SCOPE_NAME, safeText(source.getKnowledgeScopeName())); - metadata.put(DocumentKnowledgeMetadataKeys.BUSINESS_CATEGORY, safeText(source.getBusinessCategory())); - metadata.put(DocumentKnowledgeMetadataKeys.DOCUMENT_TAGS, String.join(",", source.getDocumentTags())); metadata.put(DocumentKnowledgeMetadataKeys.CONTENT_WITH_WEIGHT, safeText(source.getContentWithWeight())); metadata.put(DocumentKnowledgeMetadataKeys.CHUNK_TYPE, safeText(source.getChunkType())); metadata.put(DocumentKnowledgeMetadataKeys.TITLE, safeText(source.getTitle())); @@ -426,17 +403,6 @@ public class ElasticsearchDocumentKeywordSearchGateway implements DocumentKeywor return topK <= 0 ? 10 : Math.min(topK, 50); } - private List splitTags(String documentTags) { - if (StrUtil.isBlank(documentTags)) { - return List.of(); - } - return Arrays.stream(documentTags.split(",")) - .map(String::trim) - .filter(StrUtil::isNotBlank) - .distinct() - .toList(); - } - private List readStringArray(String text) { if (StrUtil.isBlank(text)) { return List.of(); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/support/DocumentKnowledgeMetadataKeys.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/support/DocumentKnowledgeMetadataKeys.java index 9104e29..e4ee904 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/support/DocumentKnowledgeMetadataKeys.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/support/DocumentKnowledgeMetadataKeys.java @@ -15,6 +15,9 @@ public final class DocumentKnowledgeMetadataKeys { public static final String SCORE = "score"; public static final String DOCUMENT_ID = "documentId"; public static final String DOCUMENT_NAME = "documentName"; + public static final String KNOWLEDGE_BASE_ID = "knowledgeBaseId"; + public static final String KNOWLEDGE_BASE_CODE = "knowledgeBaseCode"; + public static final String KNOWLEDGE_BASE_NAME = "knowledgeBaseName"; public static final String TASK_ID = "taskId"; public static final String PARENT_BLOCK_ID = "parentBlockId"; public static final String PARENT_BLOCK_NO = "parentBlockNo"; @@ -33,10 +36,6 @@ public final class DocumentKnowledgeMetadataKeys { public static final String PAGE_RANGE = "pageRange"; public static final String BBOX_JSON = "bboxJson"; public static final String SOURCE_BLOCK_IDS = "sourceBlockIds"; - public static final String KNOWLEDGE_SCOPE_CODE = "knowledgeScopeCode"; - public static final String KNOWLEDGE_SCOPE_NAME = "knowledgeScopeName"; - public static final String BUSINESS_CATEGORY = "businessCategory"; - public static final String DOCUMENT_TAGS = "documentTags"; public static final String TITLE = "title"; public static final String URL = "url"; public static final String TOOL_NAME = "toolName"; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/support/RaptorScopeSupport.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/support/RaptorScopeSupport.java index 06210c3..e57ca0a 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/support/RaptorScopeSupport.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/support/RaptorScopeSupport.java @@ -1,7 +1,6 @@ package org.javaup.ai.manage.support; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; import org.javaup.ai.manage.data.SuperAgentDocument; import java.util.LinkedHashSet; @@ -23,8 +22,12 @@ public final class RaptorScopeSupport { return "document:" + documentId; } - public static String knowledgeScopeKey(String knowledgeScopeCode) { - return "knowledge:" + normalizeScopeCode(knowledgeScopeCode); + public static String knowledgeBaseScopeKey(Long knowledgeBaseId) { + return "kb:" + knowledgeBaseId; + } + + public static String knowledgeScopeKey(Long knowledgeBaseId, String knowledgeScopeCode) { + return knowledgeBaseScopeKey(knowledgeBaseId) + ":scope:" + normalizeScopeCode(knowledgeScopeCode); } public static List searchScopeKeys(List documents) { @@ -33,21 +36,20 @@ public final class RaptorScopeSupport { } LinkedHashSet scopeKeys = new LinkedHashSet<>(); for (SuperAgentDocument document : documents) { - if (document == null || StrUtil.isBlank(document.getKnowledgeScopeCode())) { + if (document == null || document.getKnowledgeBaseId() == null) { continue; } - scopeKeys.add(knowledgeScopeKey(document.getKnowledgeScopeCode())); + scopeKeys.add(knowledgeBaseScopeKey(document.getKnowledgeBaseId())); } - scopeKeys.add(GLOBAL_SCOPE_KEY); return List.copyOf(scopeKeys); } public static boolean isDatasetScope(String scopeType) { - return SCOPE_TYPE_DATASET.equalsIgnoreCase(StrUtil.blankToDefault(scopeType, "")); + return SCOPE_TYPE_DATASET.equalsIgnoreCase(scopeType == null ? "" : scopeType); } public static String normalizeScopeCode(String knowledgeScopeCode) { - return StrUtil.blankToDefault(knowledgeScopeCode, "") + return (knowledgeScopeCode == null ? "" : knowledgeScopeCode) .trim() .toLowerCase(Locale.ROOT) .replaceAll("[^a-z0-9._-]+", "_"); diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/DocumentListItemVo.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/DocumentListItemVo.java index 230b609..ddf7fdd 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/DocumentListItemVo.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/DocumentListItemVo.java @@ -47,13 +47,11 @@ public class DocumentListItemVo { private String parseErrorMsg; - private String knowledgeScopeCode; + private Long knowledgeBaseId; - private String knowledgeScopeName; + private String knowledgeBaseCode; - private String businessCategory; - - private String documentTags; + private String knowledgeBaseName; private Long currentPlanId; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeBaseItemVo.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeBaseItemVo.java new file mode 100644 index 0000000..b34710b --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeBaseItemVo.java @@ -0,0 +1,37 @@ +package org.javaup.ai.manage.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class KnowledgeBaseItemVo { + + private String id; + + private String baseCode; + + private String baseName; + + private String description; + + private String embeddingModel; + + private String retrievalConfigJson; + + private String graphRagConfigJson; + + private String raptorConfigJson; + + private String metadataFilterJson; + + private String isDefault; + + private String sortOrder; + + private String documentCount; + + private String retrievableDocumentCount; +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeBaseOptionVo.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeBaseOptionVo.java new file mode 100644 index 0000000..0d96525 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeBaseOptionVo.java @@ -0,0 +1,23 @@ +package org.javaup.ai.manage.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class KnowledgeBaseOptionVo { + + private String id; + + private String baseCode; + + private String baseName; + + private String description; + + private String isDefault; + + private String retrievableDocumentCount; +} diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeScopeItemVo.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeScopeItemVo.java index 93fcae6..8e39af4 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeScopeItemVo.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeScopeItemVo.java @@ -16,6 +16,8 @@ public class KnowledgeScopeItemVo { private String id; + private String knowledgeBaseId; + private String scopeCode; private String scopeName; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeTopicItemVo.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeTopicItemVo.java index 7c0a3a6..98307c1 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeTopicItemVo.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/KnowledgeTopicItemVo.java @@ -16,6 +16,8 @@ public class KnowledgeTopicItemVo { private String id; + private String knowledgeBaseId; + private String topicCode; private String topicName; diff --git a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/TopicDocumentRelationItemVo.java b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/TopicDocumentRelationItemVo.java index 67d0ec2..3aa43f7 100644 --- a/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/TopicDocumentRelationItemVo.java +++ b/super-agent-business/super-agent-business-chat/src/main/java/org/javaup/ai/manage/vo/TopicDocumentRelationItemVo.java @@ -14,20 +14,14 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class TopicDocumentRelationItemVo { + private String knowledgeBaseId; + private String topicCode; private String documentId; private String documentName; - private String knowledgeScopeCode; - - private String knowledgeScopeName; - - private String businessCategory; - - private String documentTags; - private String relationScore; private String relationSource; diff --git a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/DocumentScopeRetrievalChannelTest.java b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/DocumentScopeRetrievalChannelTest.java index 5241c6f..0126f33 100644 --- a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/DocumentScopeRetrievalChannelTest.java +++ b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/DocumentScopeRetrievalChannelTest.java @@ -112,6 +112,11 @@ class DocumentScopeRetrievalChannelTest { return List.of(); } + @Override + public List listRetrievableDocumentsByKnowledgeBaseIds(java.util.Collection knowledgeBaseIds) { + return List.of(); + } + @Override public List vectorSearch(DocumentRetrieveRequest request) { this.vectorRequest = request; diff --git a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/GraphRagRetrievalChannelTest.java b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/GraphRagRetrievalChannelTest.java index 5ee70c7..61de3f5 100644 --- a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/GraphRagRetrievalChannelTest.java +++ b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/GraphRagRetrievalChannelTest.java @@ -118,7 +118,7 @@ class GraphRagRetrievalChannelTest { } @Test - void graphSearchUsesRetrievalQueryWithHistoryHintsButKeepsOriginalQuestionInEvidenceText() { + void graphSearchKeepsHistoryHintsOutOfFreshTopicQueryAndEvidenceText() { GraphRagSearchResult evidence = GraphRagSearchResult.builder() .documentId(100L) .taskId(900L) @@ -151,7 +151,9 @@ class GraphRagRetrievalChannelTest { RetrievalChannelResult result = channel.retrieve("这个相关部门是谁?", plan); - assertThat(graphRagSearchService.question).contains("这个相关部门是谁?", "审计系统", "AuditTrail", "权限审批"); + assertThat(graphRagSearchService.question) + .isEqualTo("这个相关部门是谁?") + .doesNotContain("审计系统", "AuditTrail", "权限审批"); assertThat(graphRagSearchService.documentIds).containsExactly(100L); assertThat(graphRagSearchService.taskIds).containsExactly(900L); assertThat(result.getDocuments()).hasSize(1); @@ -286,13 +288,17 @@ class GraphRagRetrievalChannelTest { 100L, "星联智服全渠道客服平台上线与运营管理手册.md", 900L, - "", - "", - "", - "" + 1L, + "test-kb", + "测试知识库" )); } + @Override + public List listRetrievableDocumentsByKnowledgeBaseIds(java.util.Collection knowledgeBaseIds) { + return listRetrievableDocuments(); + } + @Override public List vectorSearch(DocumentRetrieveRequest request) { return List.of(); diff --git a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/TableRetrievalChannelTest.java b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/TableRetrievalChannelTest.java index ff53965..d8992a9 100644 --- a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/TableRetrievalChannelTest.java +++ b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/retrieve/channel/TableRetrievalChannelTest.java @@ -149,13 +149,17 @@ class TableRetrievalChannelTest { 10L, "费用报销制度.xlsx", 20L, - "", - "", - "", - "" + 1L, + "test-kb", + "测试知识库" )); } + @Override + public List listRetrievableDocumentsByKnowledgeBaseIds(java.util.Collection knowledgeBaseIds) { + return listRetrievableDocuments(); + } + @Override public List vectorSearch(DocumentRetrieveRequest request) { return List.of(); diff --git a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/ChatPreparationOrchestratorTest.java b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/ChatPreparationOrchestratorTest.java index 314a00f..52dc330 100644 --- a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/ChatPreparationOrchestratorTest.java +++ b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/ChatPreparationOrchestratorTest.java @@ -11,11 +11,14 @@ import org.javaup.ai.chatagent.service.ConversationMemoryService; import org.javaup.ai.chatagent.service.TaskInfo; import org.javaup.ai.chatagent.support.StreamEventMetadata; import org.javaup.ai.manage.model.KnowledgeDocumentDescriptor; +import org.javaup.ai.manage.model.KnowledgeBaseSelectionSnapshot; import org.javaup.ai.manage.model.route.DocumentRouteCandidate; +import org.javaup.ai.manage.model.route.KnowledgeRouteContext; import org.javaup.ai.manage.model.route.KnowledgeRouteDecision; import org.javaup.ai.manage.service.DocumentKnowledgeService; import org.javaup.ai.manage.service.KnowledgeRouteService; import org.javaup.enums.ChatQueryMode; +import org.javaup.enums.KnowledgeBaseSelectionMode; import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; @@ -100,10 +103,6 @@ class ChatPreparationOrchestratorTest { documentId, name, taskId, - "scope-a", - "测试知识范围", - "", - "", BigDecimal.valueOf(score), "test" ); @@ -119,6 +118,17 @@ class ChatPreparationOrchestratorTest { null, "", null, + KnowledgeBaseSelectionSnapshot.builder() + .selectionMode(KnowledgeBaseSelectionMode.SELECTED) + .selectedKnowledgeBaseIds(List.of(1L)) + .selectedKnowledgeBaseNames(List.of("测试知识库")) + .allowedDocuments(List.of( + new KnowledgeDocumentDescriptor(1001L, "O6跨文档图谱-审计系统别名说明B.md", 2001L, 1L, "test-kb", "测试知识库"), + new KnowledgeDocumentDescriptor(1002L, "O6跨文档图谱-审计证据规范A.md", 2002L, 1L, "test-kb", "测试知识库") + )) + .allowedDocumentIds(List.of(1001L, 1002L)) + .allowedTaskIds(List.of(2001L, 2002L)) + .build(), LocalDate.of(2026, 6, 26), "2026年6月26日", null, @@ -192,7 +202,7 @@ class ChatPreparationOrchestratorTest { private static class StaticDocumentQuestionRouter extends DocumentQuestionRouter { StaticDocumentQuestionRouter() { - super(null, null, null, null, null); + super(null, null, null); } @Override @@ -219,7 +229,7 @@ class ChatPreparationOrchestratorTest { } @Override - public KnowledgeRouteDecision route(String question, String rewriteQuestion) { + public KnowledgeRouteDecision route(KnowledgeRouteContext context) { return decision; } @@ -227,15 +237,13 @@ class ChatPreparationOrchestratorTest { public void recordShadowRoute(String conversationId, long exchangeId, Long selectedDocumentId, - String question, - String rewriteQuestion) { + KnowledgeRouteContext context) { } @Override public void recordAutoRoute(String conversationId, long exchangeId, - String question, - String rewriteQuestion, + KnowledgeRouteContext context, KnowledgeRouteDecision decision) { } } @@ -247,6 +255,11 @@ class ChatPreparationOrchestratorTest { return List.of(); } + @Override + public List listRetrievableDocumentsByKnowledgeBaseIds(java.util.Collection knowledgeBaseIds) { + return List.of(); + } + @Override public List vectorSearch(org.javaup.ai.manage.model.DocumentRetrieveRequest request) { return List.of(); diff --git a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/KnowledgeBaseRuntimeConfigResolverTest.java b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/KnowledgeBaseRuntimeConfigResolverTest.java new file mode 100644 index 0000000..ee422a3 --- /dev/null +++ b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/KnowledgeBaseRuntimeConfigResolverTest.java @@ -0,0 +1,95 @@ +package org.javaup.ai.chatagent.rag.service; + +import org.javaup.ai.chatagent.rag.config.ChatRagProperties; +import org.javaup.ai.chatagent.rag.model.RagRuntimeOptions; +import org.javaup.ai.manage.data.SuperAgentKnowledgeBase; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class KnowledgeBaseRuntimeConfigResolverTest { + + @Test + void singleKnowledgeBaseOverridesNonEmptyRetrievalConfig() { + ChatRagProperties defaults = defaults(); + KnowledgeBaseRuntimeConfigResolver resolver = new KnowledgeBaseRuntimeConfigResolver(defaults); + SuperAgentKnowledgeBase kb = knowledgeBase(1L, "研发库", """ + { + "vectorTopK": 12, + "keywordTopK": 6, + "minVectorSimilarity": 0.62, + "keywordChannelEnabled": false, + "hybrid": { + "vectorWeight": 1.7, + "keywordWeight": 0.4 + } + } + """); + + RagRuntimeOptions options = resolver.resolve(List.of(kb)); + + assertThat(options.getVectorTopK()).isEqualTo(12); + assertThat(options.getKeywordTopK()).isEqualTo(6); + assertThat(options.getMinVectorSimilarity()).isEqualTo(0.62D); + assertThat(options.isKeywordChannelEnabled()).isFalse(); + assertThat(options.getHybrid().getVectorWeight()).isEqualTo(1.7D); + assertThat(options.getHybrid().getKeywordWeight()).isEqualTo(0.4D); + assertThat(options.getKbConfigConflictFields()).isEmpty(); + } + + @Test + void multipleKnowledgeBasesOnlyOverrideConsistentFieldsAndReportConflicts() { + ChatRagProperties defaults = defaults(); + KnowledgeBaseRuntimeConfigResolver resolver = new KnowledgeBaseRuntimeConfigResolver(defaults); + SuperAgentKnowledgeBase kbA = knowledgeBase(1L, "研发库", """ + { + "vectorTopK": 12, + "keywordTopK": 6, + "graphRagTopK": 9, + "hybrid": { + "vectorWeight": 1.5 + } + } + """); + SuperAgentKnowledgeBase kbB = knowledgeBase(2L, "客服库", """ + { + "vectorTopK": 12, + "keywordTopK": 8, + "graphRagTopK": 9, + "hybrid": { + "vectorWeight": 1.5 + } + } + """); + + RagRuntimeOptions options = resolver.resolve(List.of(kbA, kbB)); + + assertThat(options.getVectorTopK()).isEqualTo(12); + assertThat(options.getGraphRagTopK()).isEqualTo(9); + assertThat(options.getKeywordTopK()).isEqualTo(defaults.getKeywordTopK()); + assertThat(options.getHybrid().getVectorWeight()).isEqualTo(1.5D); + assertThat(options.getKbConfigConflictFields()).containsExactly("keywordTopK"); + } + + private static ChatRagProperties defaults() { + ChatRagProperties properties = new ChatRagProperties(); + properties.setVectorTopK(8); + properties.setKeywordTopK(8); + properties.setGraphRagTopK(5); + properties.setMinVectorSimilarity(0.45D); + properties.setKeywordChannelEnabled(true); + properties.getHybrid().setVectorWeight(1.0D); + properties.getHybrid().setKeywordWeight(1.0D); + return properties; + } + + private static SuperAgentKnowledgeBase knowledgeBase(Long id, String name, String retrievalConfigJson) { + SuperAgentKnowledgeBase knowledgeBase = new SuperAgentKnowledgeBase(); + knowledgeBase.setId(id); + knowledgeBase.setBaseName(name); + knowledgeBase.setRetrievalConfigJson(retrievalConfigJson); + return knowledgeBase; + } +} diff --git a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/RagRetrievalEngineTest.java b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/RagRetrievalEngineTest.java index f8ec8dd..f66b7d8 100644 --- a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/RagRetrievalEngineTest.java +++ b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/chatagent/rag/service/RagRetrievalEngineTest.java @@ -130,7 +130,7 @@ class RagRetrievalEngineTest { assertThat(documents.get(0).getMetadata()) .containsEntry(DocumentKnowledgeMetadataKeys.RETRIEVAL_INTENT, RetrievalIntent.TABLE.name()); assertThat(((Number) documents.get(0).getMetadata().get(DocumentKnowledgeMetadataKeys.CHANNEL_WEIGHT)).doubleValue()) - .isCloseTo(1.74D, org.assertj.core.data.Offset.offset(0.0001D)); + .isCloseTo(1.296D, org.assertj.core.data.Offset.offset(0.0001D)); } finally { executorService.shutdownNow(); @@ -1060,6 +1060,11 @@ class RagRetrievalEngineTest { return List.of(); } + @Override + public List listRetrievableDocumentsByKnowledgeBaseIds(java.util.Collection knowledgeBaseIds) { + return List.of(); + } + @Override public List vectorSearch(DocumentRetrieveRequest request) { return List.of(); diff --git a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/manage/service/impl/GraphRagCrossDocumentIndexServiceImplTest.java b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/manage/service/impl/GraphRagCrossDocumentIndexServiceImplTest.java index a8833c2..84bb196 100644 --- a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/manage/service/impl/GraphRagCrossDocumentIndexServiceImplTest.java +++ b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/manage/service/impl/GraphRagCrossDocumentIndexServiceImplTest.java @@ -3,6 +3,7 @@ package org.javaup.ai.manage.service.impl; import com.baidu.fsg.uid.UidGenerator; import com.fasterxml.jackson.databind.ObjectMapper; import org.javaup.ai.manage.data.SuperAgentDocument; +import org.javaup.ai.manage.data.SuperAgentKnowledgeTopicNode; import org.javaup.ai.manage.data.SuperAgentKgCanonicalEntityGroup; import org.javaup.ai.manage.data.SuperAgentKgCanonicalEntityMember; import org.javaup.ai.manage.data.SuperAgentKgCrossDocumentCommunity; @@ -12,7 +13,9 @@ import org.javaup.ai.manage.data.SuperAgentKgEvidence; import org.javaup.ai.manage.data.SuperAgentKgRelation; import org.javaup.ai.manage.data.SuperAgentKgRelationGroup; import org.javaup.ai.manage.data.SuperAgentKgRelationGroupMember; +import org.javaup.ai.manage.data.SuperAgentTopicDocumentRelation; import org.javaup.ai.manage.mapper.SuperAgentDocumentMapper; +import org.javaup.ai.manage.mapper.SuperAgentKnowledgeTopicNodeMapper; import org.javaup.ai.manage.mapper.SuperAgentKgCanonicalEntityGroupMapper; import org.javaup.ai.manage.mapper.SuperAgentKgCanonicalEntityMemberMapper; import org.javaup.ai.manage.mapper.SuperAgentKgCrossDocumentCommunityMapper; @@ -22,6 +25,7 @@ import org.javaup.ai.manage.mapper.SuperAgentKgEvidenceMapper; import org.javaup.ai.manage.mapper.SuperAgentKgRelationGroupMapper; import org.javaup.ai.manage.mapper.SuperAgentKgRelationGroupMemberMapper; import org.javaup.ai.manage.mapper.SuperAgentKgRelationMapper; +import org.javaup.ai.manage.mapper.SuperAgentTopicDocumentRelationMapper; import org.javaup.ai.manage.model.graph.GraphRagCrossDocumentIndexBuildResult; import org.javaup.ai.manage.support.GraphRagCrossDocumentIndex; import org.javaup.ai.manage.support.GraphRagCrossDocumentIndexSupport; @@ -42,9 +46,9 @@ class GraphRagCrossDocumentIndexServiceImplTest { @Test void rebuildAllPersistsGlobalAndKnowledgeScopeDerivedIndexThenLoadsKnowledgeScope() { InMemoryMapper documentStore = new InMemoryMapper<>(List.of( - document(10L, "security"), - document(11L, "security"), - document(12L, "release") + document(10L), + document(11L), + document(12L) )); InMemoryMapper entityStore = new InMemoryMapper<>(List.of( entity(1001L, 10L, 20L, "AuditTrail", null, "SYSTEM", "{\"aliases\":[\"审计系统\"],\"rankBoost\":0.7}"), @@ -69,6 +73,15 @@ class GraphRagCrossDocumentIndexServiceImplTest { InMemoryMapper relationGroupMemberStore = new InMemoryMapper<>(List.of()); InMemoryMapper communityStore = new InMemoryMapper<>(List.of()); InMemoryMapper communityMemberStore = new InMemoryMapper<>(List.of()); + InMemoryMapper topicStore = new InMemoryMapper<>(List.of( + topic(1L, "security-topic", "security"), + topic(2L, "release-topic", "release") + )); + InMemoryMapper topicDocumentRelationStore = new InMemoryMapper<>(List.of( + topicRelation(1L, "security-topic", 10L), + topicRelation(2L, "security-topic", 11L), + topicRelation(3L, "release-topic", 12L) + )); ObjectMapper objectMapper = new ObjectMapper(); GraphRagCrossDocumentIndexServiceImpl service = new GraphRagCrossDocumentIndexServiceImpl( documentStore.proxy(SuperAgentDocumentMapper.class), @@ -81,6 +94,8 @@ class GraphRagCrossDocumentIndexServiceImplTest { relationGroupMemberStore.proxy(SuperAgentKgRelationGroupMemberMapper.class), communityStore.proxy(SuperAgentKgCrossDocumentCommunityMapper.class), communityMemberStore.proxy(SuperAgentKgCrossDocumentCommunityMemberMapper.class), + topicStore.proxy(SuperAgentKnowledgeTopicNodeMapper.class), + topicDocumentRelationStore.proxy(SuperAgentTopicDocumentRelationMapper.class), new GraphRagCrossDocumentIndexSupport(objectMapper), uidGenerator(), objectMapper @@ -89,17 +104,17 @@ class GraphRagCrossDocumentIndexServiceImplTest { List results = service.rebuildAll(); assertThat(results).extracting(GraphRagCrossDocumentIndexBuildResult::getScopeKey) - .containsExactly("global", "knowledge:security", "knowledge:release"); + .containsExactly("global", "kb:1", "kb:1:scope:security", "kb:1:scope:release"); assertThat(canonicalGroupStore.items()).extracting(SuperAgentKgCanonicalEntityGroup::getScopeKey) - .contains("global", "knowledge:security", "knowledge:release"); + .contains("global", "kb:1", "kb:1:scope:security", "kb:1:scope:release"); assertThat(canonicalMemberStore.items()).extracting(SuperAgentKgCanonicalEntityMember::getScopeKey) - .contains("global", "knowledge:security", "knowledge:release"); + .contains("global", "kb:1", "kb:1:scope:security", "kb:1:scope:release"); assertThat(relationGroupStore.items()).extracting(SuperAgentKgRelationGroup::getScopeKey) - .contains("global", "knowledge:security"); + .contains("global", "kb:1", "kb:1:scope:security"); assertThat(communityStore.items()).extracting(SuperAgentKgCrossDocumentCommunity::getScopeKey) - .contains("global", "knowledge:security"); + .contains("global", "kb:1", "kb:1:scope:security"); assertThat(communityMemberStore.items()).extracting(SuperAgentKgCrossDocumentCommunityMember::getScopeKey) - .contains("global", "knowledge:security"); + .contains("global", "kb:1", "kb:1:scope:security"); assertThat(communityStore.items()).allSatisfy(community -> { Map metadata = objectMapper.readValue(community.getMetadataJson(), Map.class); assertThat(metadata).containsEntry("sourceType", "java.cross_document_community.v1"); @@ -150,7 +165,7 @@ class GraphRagCrossDocumentIndexServiceImplTest { assertThat(communityMemberStore.deleteCount()).isEqualTo(1); GraphRagCrossDocumentIndexServiceImpl loadService = new GraphRagCrossDocumentIndexServiceImpl( - new InMemoryMapper<>(List.of(document(10L, "security"), document(11L, "security"))).proxy(SuperAgentDocumentMapper.class), + new InMemoryMapper<>(List.of(document(10L), document(11L))).proxy(SuperAgentDocumentMapper.class), new InMemoryMapper<>(entityStore.items()).proxy(SuperAgentKgEntityMapper.class), new InMemoryMapper<>(List.of( relation(2001L, 10L, 20L, 1001L, 1002L, "RECORDS"), @@ -158,23 +173,28 @@ class GraphRagCrossDocumentIndexServiceImplTest { )).proxy(SuperAgentKgRelationMapper.class), new InMemoryMapper<>(evidenceStore.items()).proxy(SuperAgentKgEvidenceMapper.class), new InMemoryMapper<>(canonicalGroupStore.items().stream() - .filter(group -> "knowledge:security".equals(group.getScopeKey())) + .filter(group -> "kb:1:scope:security".equals(group.getScopeKey())) .toList()).proxy(SuperAgentKgCanonicalEntityGroupMapper.class), new InMemoryMapper<>(canonicalMemberStore.items().stream() - .filter(member -> "knowledge:security".equals(member.getScopeKey())) + .filter(member -> "kb:1:scope:security".equals(member.getScopeKey())) .toList()).proxy(SuperAgentKgCanonicalEntityMemberMapper.class), new InMemoryMapper<>(relationGroupStore.items().stream() - .filter(group -> "knowledge:security".equals(group.getScopeKey())) + .filter(group -> "kb:1:scope:security".equals(group.getScopeKey())) .toList()).proxy(SuperAgentKgRelationGroupMapper.class), new InMemoryMapper<>(relationGroupMemberStore.items().stream() - .filter(member -> "knowledge:security".equals(member.getScopeKey())) + .filter(member -> "kb:1:scope:security".equals(member.getScopeKey())) .toList()).proxy(SuperAgentKgRelationGroupMemberMapper.class), new InMemoryMapper<>(communityStore.items().stream() - .filter(community -> "knowledge:security".equals(community.getScopeKey())) + .filter(community -> "kb:1:scope:security".equals(community.getScopeKey())) .toList()).proxy(SuperAgentKgCrossDocumentCommunityMapper.class), new InMemoryMapper<>(communityMemberStore.items().stream() - .filter(member -> "knowledge:security".equals(member.getScopeKey())) + .filter(member -> "kb:1:scope:security".equals(member.getScopeKey())) .toList()).proxy(SuperAgentKgCrossDocumentCommunityMemberMapper.class), + new InMemoryMapper<>(List.of(topic(1L, "security-topic", "security"))).proxy(SuperAgentKnowledgeTopicNodeMapper.class), + new InMemoryMapper<>(List.of( + topicRelation(1L, "security-topic", 10L), + topicRelation(2L, "security-topic", 11L) + )).proxy(SuperAgentTopicDocumentRelationMapper.class), new GraphRagCrossDocumentIndexSupport(objectMapper), uidGenerator(), objectMapper @@ -220,8 +240,8 @@ class GraphRagCrossDocumentIndexServiceImplTest { @Test void rebuildAllDoesNotMergeUnrelatedEntitiesByGeneratedCanonicalKeyOnly() { InMemoryMapper documentStore = new InMemoryMapper<>(List.of( - document(10L, "security"), - document(11L, "security") + document(10L), + document(11L) )); InMemoryMapper entityStore = new InMemoryMapper<>(List.of( entity(1001L, 10L, 20L, "AuditTrail", null, "CONCEPT", "{\"canonicalKey\":\"ENT_SHARED\",\"rankBoost\":0.5}"), @@ -243,6 +263,8 @@ class GraphRagCrossDocumentIndexServiceImplTest { new InMemoryMapper(List.of()).proxy(SuperAgentKgRelationGroupMemberMapper.class), new InMemoryMapper(List.of()).proxy(SuperAgentKgCrossDocumentCommunityMapper.class), new InMemoryMapper(List.of()).proxy(SuperAgentKgCrossDocumentCommunityMemberMapper.class), + new InMemoryMapper(List.of()).proxy(SuperAgentKnowledgeTopicNodeMapper.class), + new InMemoryMapper(List.of()).proxy(SuperAgentTopicDocumentRelationMapper.class), new GraphRagCrossDocumentIndexSupport(objectMapper), uidGenerator(), objectMapper @@ -263,7 +285,7 @@ class GraphRagCrossDocumentIndexServiceImplTest { @Test void rebuildAllPersistsEntityQualityNoiseReasonsForSentenceLikeNames() throws Exception { InMemoryMapper documentStore = new InMemoryMapper<>(List.of( - document(10L, "security") + document(10L) )); InMemoryMapper entityStore = new InMemoryMapper<>(List.of( entity( @@ -298,6 +320,8 @@ class GraphRagCrossDocumentIndexServiceImplTest { new InMemoryMapper(List.of()).proxy(SuperAgentKgRelationGroupMemberMapper.class), new InMemoryMapper(List.of()).proxy(SuperAgentKgCrossDocumentCommunityMapper.class), new InMemoryMapper(List.of()).proxy(SuperAgentKgCrossDocumentCommunityMemberMapper.class), + new InMemoryMapper(List.of()).proxy(SuperAgentKnowledgeTopicNodeMapper.class), + new InMemoryMapper(List.of()).proxy(SuperAgentTopicDocumentRelationMapper.class), new GraphRagCrossDocumentIndexSupport(objectMapper), uidGenerator(), objectMapper @@ -321,14 +345,38 @@ class GraphRagCrossDocumentIndexServiceImplTest { assertThat(sentenceLikeGroup.getRankScore()).isLessThan(auditGroup.getRankScore()); } - private static SuperAgentDocument document(Long id, String knowledgeScopeCode) { + private static SuperAgentDocument document(Long id) { SuperAgentDocument document = new SuperAgentDocument(); document.setId(id); - document.setKnowledgeScopeCode(knowledgeScopeCode); + document.setKnowledgeBaseId(1L); + document.setKnowledgeBaseCode("kb-test"); + document.setKnowledgeBaseName("测试知识库"); document.setStatus(BusinessStatus.YES.getCode()); return document; } + private static SuperAgentKnowledgeTopicNode topic(Long id, String topicCode, String scopeCode) { + SuperAgentKnowledgeTopicNode topic = new SuperAgentKnowledgeTopicNode(); + topic.setId(id); + topic.setKnowledgeBaseId(1L); + topic.setTopicCode(topicCode); + topic.setTopicName(topicCode); + topic.setScopeCode(scopeCode); + topic.setStatus(BusinessStatus.YES.getCode()); + return topic; + } + + private static SuperAgentTopicDocumentRelation topicRelation(Long id, String topicCode, Long documentId) { + SuperAgentTopicDocumentRelation relation = new SuperAgentTopicDocumentRelation(); + relation.setId(id); + relation.setKnowledgeBaseId(1L); + relation.setTopicCode(topicCode); + relation.setDocumentId(documentId); + relation.setRelationScore(BigDecimal.ONE); + relation.setStatus(BusinessStatus.YES.getCode()); + return relation; + } + private static SuperAgentKgEntity entity(Long id, Long documentId, Long taskId, diff --git a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/manage/support/RaptorScopeSupportTest.java b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/manage/support/RaptorScopeSupportTest.java index e9e3f16..ecf3eef 100644 --- a/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/manage/support/RaptorScopeSupportTest.java +++ b/super-agent-business/super-agent-business-chat/src/test/java/org/javaup/ai/manage/support/RaptorScopeSupportTest.java @@ -10,28 +10,34 @@ import static org.assertj.core.api.Assertions.assertThat; class RaptorScopeSupportTest { @Test - void buildsKnowledgeAndGlobalScopeKeysFromJavaSelectedDocuments() { - SuperAgentDocument release = document(1L, "release"); - SuperAgentDocument qa = document(2L, "qa"); - SuperAgentDocument blank = document(3L, " "); + void buildsKnowledgeBaseAwareScopeKeysFromSelectedDocuments() { + SuperAgentDocument release = document(1L); + release.setKnowledgeBaseId(10L); + SuperAgentDocument qa = document(2L); + qa.setKnowledgeBaseId(10L); + SuperAgentDocument blank = document(3L); + blank.setKnowledgeBaseId(20L); List scopeKeys = RaptorScopeSupport.searchScopeKeys(List.of(release, qa, blank)); - assertThat(scopeKeys).containsExactly("knowledge:release", "knowledge:qa", "global"); + assertThat(scopeKeys).containsExactly( + "kb:10", + "kb:20" + ); } @Test void keepsDocumentScopeSeparateFromDatasetScope() { assertThat(RaptorScopeSupport.documentScopeKey(99L)).isEqualTo("document:99"); - assertThat(RaptorScopeSupport.knowledgeScopeKey(" Release Ops ")).isEqualTo("knowledge:release_ops"); + assertThat(RaptorScopeSupport.knowledgeBaseScopeKey(10L)).isEqualTo("kb:10"); + assertThat(RaptorScopeSupport.knowledgeScopeKey(10L, " Release Ops ")).isEqualTo("kb:10:scope:release_ops"); assertThat(RaptorScopeSupport.isDatasetScope(RaptorScopeSupport.SCOPE_TYPE_DATASET)).isTrue(); assertThat(RaptorScopeSupport.isDatasetScope(RaptorScopeSupport.SCOPE_TYPE_DOCUMENT)).isFalse(); } - private static SuperAgentDocument document(Long id, String scopeCode) { + private static SuperAgentDocument document(Long id) { SuperAgentDocument document = new SuperAgentDocument(); document.setId(id); - document.setKnowledgeScopeCode(scopeCode); return document; } } diff --git a/super-agent-common/super-agent-common-frame/src/main/java/org/javaup/enums/KnowledgeBaseSelectionMode.java b/super-agent-common/super-agent-common-frame/src/main/java/org/javaup/enums/KnowledgeBaseSelectionMode.java new file mode 100644 index 0000000..d89db2e --- /dev/null +++ b/super-agent-common/super-agent-common-frame/src/main/java/org/javaup/enums/KnowledgeBaseSelectionMode.java @@ -0,0 +1,31 @@ +package org.javaup.enums; + +import lombok.Getter; + +@Getter +public enum KnowledgeBaseSelectionMode { + + NONE("不使用知识库检索"), + + ALL("使用全部启用知识库"), + + SELECTED("使用显式选择知识库"); + + private final String label; + + KnowledgeBaseSelectionMode(String label) { + this.label = label; + } + + public static KnowledgeBaseSelectionMode fromName(String value) { + if (value == null || value.isBlank()) { + return NONE; + } + for (KnowledgeBaseSelectionMode mode : values()) { + if (mode.name().equalsIgnoreCase(value.trim())) { + return mode; + } + } + throw new IllegalArgumentException("未知的知识库选择模式: " + value); + } +} diff --git a/vue/src/api/api.js b/vue/src/api/api.js index cfa398e..7ba57d6 100644 --- a/vue/src/api/api.js +++ b/vue/src/api/api.js @@ -315,6 +315,13 @@ export const chatApi = { }) }, + listKnowledgeBaseOptions() { + return requestApiEnvelope('/api/chat/knowledge-base/options', { + method: 'POST', + body: {} + }) + }, + listSessions(query = {}) { return chatApi.listSessionsPage({ keyword: query.keyword || '', @@ -482,23 +489,55 @@ export const adminAuthApi = { } export const manageApi = { - uploadDocument({ file, documentName, operatorId, knowledgeScopeCode, knowledgeScopeName, businessCategory, documentTags }) { + uploadDocument({ file, documentName, operatorId, knowledgeBaseId }) { const formData = new FormData() formData.append('file', file) const meta = stringifyManageValue({ documentName: documentName || '', operatorId: operatorId ?? '', - knowledgeScopeCode: knowledgeScopeCode || '', - knowledgeScopeName: knowledgeScopeName || '', - businessCategory: businessCategory || '', - documentTags: documentTags || '' + knowledgeBaseId: knowledgeBaseId || '' }) formData.append('meta', new Blob([JSON.stringify(meta)], { type: 'application/json' })) return requestMultipartApiEnvelope('/manage/document/upload', formData) }, + saveKnowledgeBase(payload) { + return requestApiEnvelope('/manage/knowledge/base/save', { + method: 'POST', + body: stringifyManageValue(payload) + }) + }, + + deleteKnowledgeBase(payload) { + return requestApiEnvelope('/manage/knowledge/base/delete', { + method: 'POST', + body: stringifyManageValue(payload) + }) + }, + + listKnowledgeBases() { + return requestApiEnvelope('/manage/knowledge/base/list', { + method: 'POST', + body: {} + }) + }, + + queryKnowledgeBaseDetail(payload) { + return requestApiEnvelope('/manage/knowledge/base/detail', { + method: 'POST', + body: stringifyManageValue(payload) + }) + }, + + updateKnowledgeBaseConfig(payload) { + return requestApiEnvelope('/manage/knowledge/base/config/update', { + method: 'POST', + body: stringifyManageValue(payload) + }) + }, + queryDocumentPage(payload) { return requestApiEnvelope('/manage/document/page/query', { method: 'POST', @@ -625,10 +664,10 @@ export const manageApi = { }) }, - listKnowledgeScopes() { + listKnowledgeScopes(payload = {}) { return requestApiEnvelope('/manage/knowledge/scope/list', { method: 'POST', - body: {} + body: stringifyManageValue(payload) }) }, diff --git a/vue/src/router/index.js b/vue/src/router/index.js index 5fdfbcf..2ac668c 100644 --- a/vue/src/router/index.js +++ b/vue/src/router/index.js @@ -62,6 +62,14 @@ const router = createRouter({ title: '文档详情' } }, + { + path: 'knowledge-bases', + name: 'AdminKnowledgeBases', + component: () => import('../views/admin/AdminKnowledgeBaseView.vue'), + meta: { + title: '知识库管理' + } + }, { path: 'knowledge-route', name: 'AdminKnowledgeRoute', diff --git a/vue/src/views/BusinessChatView.vue b/vue/src/views/BusinessChatView.vue index 742a97f..1d5cb86 100644 --- a/vue/src/views/BusinessChatView.vue +++ b/vue/src/views/BusinessChatView.vue @@ -92,6 +92,55 @@ 正在生成回答... +
+ 知识库 +
+ + +
+ +
+
+ + 暂无可用知识库,可在管理端创建 + +
+
回答模式
@@ -102,28 +151,15 @@
-
- - - +
+ 提问文档 + + 当前文档:{{ selectedDocumentName }} + 当前文档模式需要先选择知识库 + 请先选择一个文档再发送问题
+
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+ + + + + + + + + diff --git a/vue/src/views/admin/AdminKnowledgeRouteTraceView.vue b/vue/src/views/admin/AdminKnowledgeRouteTraceView.vue index b1f9df8..a98fd94 100644 --- a/vue/src/views/admin/AdminKnowledgeRouteTraceView.vue +++ b/vue/src/views/admin/AdminKnowledgeRouteTraceView.vue @@ -382,8 +382,8 @@ function recommendationTitle(item) { } function recommendationText(item) { - if (item.lowConfidenceWidened || item.statusKey === 'LOW_CONFIDENCE') return '优先补 documentTags、knowledgeScopeName、topic 别名,以及 topic-document relation 的人工确认。' - if (item.statusKey === 'FAILED') return '当前路由没有形成稳定候选,先检查上传元数据、文档画像和主题树是否为空。' + if (item.lowConfidenceWidened || item.statusKey === 'LOW_CONFIDENCE') return '优先补 topic 别名、示例问题,以及 topic-document relation 的人工确认。' + if (item.statusKey === 'FAILED') return '当前路由没有形成稳定候选,先检查文档画像、主题树和主题文档关联是否为空。' if (item.mode === 'shadow' && item.missedTop3) return '人工选文档和自动路由差异较大,建议对比问题表达与文档画像的关键词覆盖情况。' return '当前样本已经接近可教学展示状态,下一步重点看不同问题类型下是否还能持续稳定。' } diff --git a/vue/src/views/admin/AdminKnowledgeRouteView.vue b/vue/src/views/admin/AdminKnowledgeRouteView.vue index 7d92467..6860261 100644 --- a/vue/src/views/admin/AdminKnowledgeRouteView.vue +++ b/vue/src/views/admin/AdminKnowledgeRouteView.vue @@ -6,6 +6,10 @@

按 范围 → 主题 → 画像 → 关联 的顺序逐步配置,构建自动知识问答的候选预选体系。

+
@@ -83,7 +87,7 @@ {{ item.description || '暂无描述' }}
主题 {{ topics.filter(t => t.scopeCode === item.scopeCode).length }} - 文档 {{ documents.filter(d => d.knowledgeScopeCode === item.scopeCode).length }} + 文档 {{ linkedDocumentCountByScope(item.scopeCode) }}
没有匹配的知识范围。
@@ -126,7 +130,7 @@

文档画像

查看文档的类型、摘要、核心主题和图能力开关,判断自动路由是否有足够信息。

-
+
@@ -201,7 +205,7 @@ @click="openDrawer('relation', item, 'view')">
{{ item.documentName }} - {{ item.topicCode }} · 分数 {{ item.relationScore }} · {{ item.knowledgeScopeName || item.knowledgeScopeCode || '未分范围' }} + {{ item.topicCode }} · 分数 {{ item.relationScore }} · {{ topicScopeText(item.topicCode) }} {{ item.reason || documentMetaLine(item) }}
@@ -237,6 +241,13 @@