diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/dto/PromptConfigDTO.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/dto/PromptConfigDTO.java index 98cd6ab9ed..92a0fa2d62 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/dto/PromptConfigDTO.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/dto/PromptConfigDTO.java @@ -21,16 +21,17 @@ * * @author Makoto */ + public record PromptConfigDTO(String id, // Configuration ID (required for update) String name, // Configuration name String promptType, // Prompt type - String systemPrompt, // User-defined system prompt content + String optimizationPrompt, // User-defined system prompt content Boolean enabled, // Whether to enable this configuration String description, // Configuration description String creator // Creator ) { - public PromptConfigDTO(String promptType, String systemPrompt) { - this(null, null, promptType, systemPrompt, true, null, null); + public PromptConfigDTO(String promptType, String optimizationPrompt) { + this(null, null, promptType, optimizationPrompt, true, null, null); } @Override @@ -61,8 +62,8 @@ public String promptType() { } @Override - public String systemPrompt() { - return systemPrompt; + public String optimizationPrompt() { + return optimizationPrompt; } @Override diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/ReportGeneratorNode.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/ReportGeneratorNode.java index ae87ba7ce5..36028d4c98 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/ReportGeneratorNode.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/node/ReportGeneratorNode.java @@ -16,6 +16,7 @@ package com.alibaba.cloud.ai.node; +import com.alibaba.cloud.ai.entity.UserPromptConfig; import com.alibaba.cloud.ai.enums.StreamResponseType; import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.action.NodeAction; @@ -136,14 +137,15 @@ private Flux generateReport(String userInput, Plan plan, HashMap optimizationConfigs = promptConfigService.getOptimizationConfigs("report-generator"); - // Use PromptHelper to build report generation prompt with custom prompt support - String reportPrompt = PromptHelper.buildReportGeneratorPromptWithCustom(userRequirementsAndPlan, - analysisStepsAndData, summaryAndRecommendations, customPrompt); + // Use PromptHelper to build report generation prompt with optimization support + String reportPrompt = PromptHelper.buildReportGeneratorPromptWithOptimization(userRequirementsAndPlan, + analysisStepsAndData, summaryAndRecommendations, optimizationConfigs); - logger.info("Using {} prompt for report generation", customPrompt != null ? "custom" : "default"); + logger.info("Using {} prompt for report generation", + !optimizationConfigs.isEmpty() ? "optimized (" + optimizationConfigs.size() + " configs)" : "default"); return chatClient.prompt().user(reportPrompt).stream().chatResponse(); } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/prompt/PromptHelper.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/prompt/PromptHelper.java index aab1426877..6c2e7f13cb 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/prompt/PromptHelper.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/prompt/PromptHelper.java @@ -22,8 +22,11 @@ import com.alibaba.cloud.ai.dto.schema.ColumnDTO; import com.alibaba.cloud.ai.dto.schema.SchemaDTO; import com.alibaba.cloud.ai.dto.schema.TableDTO; +import com.alibaba.cloud.ai.entity.UserPromptConfig; + import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.springframework.ai.chat.prompt.PromptTemplate; import org.apache.commons.collections.CollectionUtils; import java.util.*; @@ -235,21 +238,20 @@ public static String buildSemanticConsistenPrompt(String nlReq, String sql) { * @param customPrompt user-defined prompt content, use default prompt if null * @return built prompt */ - public static String buildReportGeneratorPromptWithCustom(String userRequirementsAndPlan, - String analysisStepsAndData, String summaryAndRecommendations, String customPrompt) { + public static String buildReportGeneratorPromptWithOptimization(String userRequirementsAndPlan, + String analysisStepsAndData, String summaryAndRecommendations, List optimizationConfigs) { + Map params = new HashMap<>(); params.put("user_requirements_and_plan", userRequirementsAndPlan); params.put("analysis_steps_and_data", analysisStepsAndData); params.put("summary_and_recommendations", summaryAndRecommendations); - if (customPrompt != null && !customPrompt.trim().isEmpty()) { - // Use custom prompt - return new org.springframework.ai.chat.prompt.PromptTemplate(customPrompt).render(params); - } - else { - // Use default prompt - return PromptConstant.getReportGeneratorPromptTemplate().render(params); - } + // Build optional optimization section content from user configs + String optimizationSection = buildOptimizationSection(optimizationConfigs, params); + params.put("optimization_section", optimizationSection); + + // Render using the default report generator template + return PromptConstant.getReportGeneratorPromptTemplate().render(params); } public static String buildSqlErrorFixerPrompt(String question, DbConfig dbConfig, SchemaDTO schemaDTO, @@ -285,4 +287,49 @@ public static String buildSemanticModelPrompt(List semanticMod return PromptConstant.getSemanticModelPromptTemplate().render(params); } + /** + * 构建优化提示词部分内容 + * @param optimizationConfigs 优化配置列表 + * @param params 模板参数 + * @return 优化部分的内容 + */ + private static String buildOptimizationSection(List optimizationConfigs, + Map params) { + + if (optimizationConfigs == null || optimizationConfigs.isEmpty()) { + return ""; + } + + StringBuilder result = new StringBuilder(); + result.append("## 优化要求\n"); + + for (UserPromptConfig config : optimizationConfigs) { + String optimizationContent = renderOptimizationPrompt(config.getSystemPrompt(), params); + if (!optimizationContent.trim().isEmpty()) { + result.append("- ").append(optimizationContent).append("\n"); + } + } + + return result.toString().trim(); + } + + /** + * 渲染优化提示词模板 + * @param optimizationPrompt 优化提示词模板 + * @param params 参数 + * @return 渲染后的内容 + */ + private static String renderOptimizationPrompt(String optimizationPrompt, Map params) { + if (optimizationPrompt == null || optimizationPrompt.trim().isEmpty()) { + return ""; + } + try { + return new PromptTemplate(optimizationPrompt).render(params); + } + catch (Exception e) { + // 如果模板渲染失败,直接返回原始内容 + return optimizationPrompt; + } + } + } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/UserPromptConfigService.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/UserPromptConfigService.java index 7695e59093..e0384f454b 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/UserPromptConfigService.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/java/com/alibaba/cloud/ai/service/UserPromptConfigService.java @@ -26,6 +26,7 @@ import org.springframework.stereotype.Service; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; /** * User Prompt Configuration Management Service Provides CRUD functionality for prompt @@ -41,13 +42,23 @@ public class UserPromptConfigService { @Autowired private UserPromptConfigMapper userPromptConfigMapper; + /** + * 内存存储,用于缓存配置(可选的性能优化) + */ + private final Map configStorage = new ConcurrentHashMap<>(); + + /** + * 根据提示词类型存储启用的配置ID列表(支持多个配置同时启用) + */ + private final Map> promptTypeToConfigIds = new ConcurrentHashMap<>(); + /** * Create or update prompt configuration * @param configDTO configuration data transfer object * @return saved configuration object */ public UserPromptConfig saveOrUpdateConfig(PromptConfigDTO configDTO) { - logger.info("保存或更新提示词配置:{}", configDTO); + logger.info("保存或更新提示词优化配置:{}", configDTO); UserPromptConfig config; if (configDTO.id() != null) { @@ -55,7 +66,7 @@ public UserPromptConfig saveOrUpdateConfig(PromptConfigDTO configDTO) { config = userPromptConfigMapper.selectById(configDTO.id()); if (config != null) { config.setName(configDTO.name()); - config.setSystemPrompt(configDTO.systemPrompt()); + config.setSystemPrompt(configDTO.optimizationPrompt()); config.setEnabled(configDTO.enabled()); config.setDescription(configDTO.description()); userPromptConfigMapper.updateById(config); @@ -66,7 +77,7 @@ public UserPromptConfig saveOrUpdateConfig(PromptConfigDTO configDTO) { config.setId(configDTO.id()); config.setName(configDTO.name()); config.setPromptType(configDTO.promptType()); - config.setSystemPrompt(configDTO.systemPrompt()); + config.setSystemPrompt(configDTO.optimizationPrompt()); config.setEnabled(configDTO.enabled()); config.setDescription(configDTO.description()); config.setCreator(configDTO.creator()); @@ -78,13 +89,19 @@ public UserPromptConfig saveOrUpdateConfig(PromptConfigDTO configDTO) { config = new UserPromptConfig(); config.setName(configDTO.name()); config.setPromptType(configDTO.promptType()); - config.setSystemPrompt(configDTO.systemPrompt()); + config.setSystemPrompt(configDTO.optimizationPrompt()); config.setEnabled(configDTO.enabled()); config.setDescription(configDTO.description()); config.setCreator(configDTO.creator()); userPromptConfigMapper.insert(config); } + // 更新缓存 + configStorage.put(config.getId(), config); + + // 更新类型映射(支持多个配置) + updatePromptTypeMapping(config); + // If the configuration is enabled, disable other configurations of the same type if (Boolean.TRUE.equals(config.getEnabled())) { userPromptConfigMapper.disableAllByPromptType(config.getPromptType()); @@ -104,13 +121,40 @@ public UserPromptConfig getConfigById(String id) { return userPromptConfigMapper.selectById(id); } + /** + * 根据提示词类型获取所有启用的配置 + * @param promptType 提示词类型 + * @return 配置列表 + */ + public List getActiveConfigsByType(String promptType) { + List configIds = promptTypeToConfigIds.get(promptType); + if (configIds == null || configIds.isEmpty()) { + return new ArrayList<>(); + } + + return configIds.stream() + .map(configStorage::get) + .filter(Objects::nonNull) + .filter(config -> Boolean.TRUE.equals(config.getEnabled())) + .sorted(Comparator.comparing(UserPromptConfig::getUpdateTime).reversed()) + .toList(); + } + /** * Get enabled configuration by prompt type * @param promptType prompt type * @return configuration object, returns null if not exists */ public UserPromptConfig getActiveConfigByType(String promptType) { - return userPromptConfigMapper.selectActiveByPromptType(promptType); + // 优先从数据库获取 + UserPromptConfig dbConfig = userPromptConfigMapper.selectActiveByPromptType(promptType); + if (dbConfig != null) { + return dbConfig; + } + + // 备用:从内存缓存获取 + List configs = getActiveConfigsByType(promptType); + return configs.isEmpty() ? null : configs.get(0); } /** @@ -139,8 +183,12 @@ public List getConfigsByType(String promptType) { public boolean deleteConfig(String id) { UserPromptConfig config = userPromptConfigMapper.selectById(id); if (config != null) { + // 从数据库删除 int deleted = userPromptConfigMapper.deleteById(id); if (deleted > 0) { + // 从内存缓存和类型映射中移除该配置 + configStorage.remove(id); + removeFromPromptTypeMapping(config); logger.info("已删除配置:{}", id); return true; } @@ -162,6 +210,10 @@ public boolean enableConfig(String id) { // Enable the current configuration int updated = userPromptConfigMapper.enableById(id); if (updated > 0) { + // 更新内存缓存 + config.setEnabled(true); + configStorage.put(id, config); + updatePromptTypeMapping(config); logger.info("已启用配置:{}", id); return true; } @@ -177,6 +229,12 @@ public boolean enableConfig(String id) { public boolean disableConfig(String id) { int updated = userPromptConfigMapper.disableById(id); if (updated > 0) { + // 更新内存缓存 + UserPromptConfig config = configStorage.get(id); + if (config != null) { + config.setEnabled(false); + removeFromPromptTypeMapping(config); + } logger.info("已禁用配置:{}", id); return true; } @@ -187,8 +245,18 @@ public boolean disableConfig(String id) { * Disable all configurations of specified type * @param promptType prompt type */ - public void disableConfigsByType(String promptType) { - userPromptConfigMapper.disableAllByPromptType(promptType); + private void updatePromptTypeMapping(UserPromptConfig config) { + if (Boolean.TRUE.equals(config.getEnabled())) { + promptTypeToConfigIds.computeIfAbsent(config.getPromptType(), k -> new ArrayList<>()); + List configIds = promptTypeToConfigIds.get(config.getPromptType()); + if (!configIds.contains(config.getId())) { + configIds.add(config.getId()); + logger.info("已将配置 {} 添加到提示词类型 [{}] 的映射中", config.getId(), config.getPromptType()); + } + } + else { + removeFromPromptTypeMapping(config); + } } /** @@ -196,11 +264,15 @@ public void disableConfigsByType(String promptType) { * @param promptType prompt type * @return custom prompt content */ - public String getCustomPromptContent(String promptType) { - // TODO 需要优化,提示词不能完全替代现有的,仅用作补充使用 - return null; - // UserPromptConfig config = getActiveConfigByType(promptType); - // return config != null ? config.getSystemPrompt() : null; + private void removeFromPromptTypeMapping(UserPromptConfig config) { + List configIds = promptTypeToConfigIds.get(config.getPromptType()); + if (configIds != null) { + configIds.remove(config.getId()); + if (configIds.isEmpty()) { + promptTypeToConfigIds.remove(config.getPromptType()); + } + logger.info("已从提示词类型 [{}] 的映射中移除配置 {}", config.getPromptType(), config.getId()); + } } /** @@ -208,8 +280,8 @@ public String getCustomPromptContent(String promptType) { * @param promptType prompt type * @return whether there is custom configuration */ - public boolean hasCustomConfig(String promptType) { - return getActiveConfigByType(promptType) != null; + public List getOptimizationConfigs(String promptType) { + return getActiveConfigsByType(promptType); } } diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/resources/prompts/report-generator.txt b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/resources/prompts/report-generator.txt index 2bb425f96b..03a279de26 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/resources/prompts/report-generator.txt +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-chat/src/main/resources/prompts/report-generator.txt @@ -49,4 +49,6 @@ ## 总结建议要求 {summary_and_recommendations} +{optimization_section} + 请根据以上信息生成一份专业、全面的数据分析报告。 diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/PromptConfigController.java b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/PromptConfigController.java index d48022fa22..6cc09566b5 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/PromptConfigController.java +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/controller/PromptConfigController.java @@ -54,23 +54,23 @@ public PromptConfigController(UserPromptConfigService promptConfigService) { @PostMapping("/save") public ResponseEntity> saveConfig(@RequestBody PromptConfigDTO configDTO) { try { - logger.info("保存提示词配置请求:{}", configDTO); + logger.info("保存提示词优化配置请求:{}", configDTO); UserPromptConfig savedConfig = promptConfigService.saveOrUpdateConfig(configDTO); Map response = new HashMap<>(); response.put("success", true); - response.put("message", "配置保存成功"); + response.put("message", "优化配置保存成功"); response.put("data", savedConfig); return ResponseEntity.ok(response); } catch (Exception e) { - logger.error("保存提示词配置失败", e); + logger.error("保存提示词优化配置失败", e); Map response = new HashMap<>(); response.put("success", false); - response.put("message", "配置保存失败:" + e.getMessage()); + response.put("message", "优化配置保存失败:" + e.getMessage()); return ResponseEntity.badRequest().body(response); } @@ -192,6 +192,35 @@ public ResponseEntity> getActiveConfig(@PathVariable String } } + /** + * 获取某个类型的所有启用的优化配置 + * @param promptType 提示词类型 + * @return 启用的优化配置列表 + */ + @GetMapping("/active-all/{promptType}") + public ResponseEntity> getActiveConfigs(@PathVariable String promptType) { + try { + List configs = promptConfigService.getActiveConfigsByType(promptType); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", configs); + response.put("total", configs.size()); + response.put("hasOptimizationConfigs", !configs.isEmpty()); + + return ResponseEntity.ok(response); + } + catch (Exception e) { + logger.error("获取启用配置列表失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "获取启用配置列表失败:" + e.getMessage()); + + return ResponseEntity.badRequest().body(response); + } + } + /** * Delete configuration * @param id configuration ID diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-web-ui/src/components/PromptOptimizationConfig.vue b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-web-ui/src/components/PromptOptimizationConfig.vue new file mode 100644 index 0000000000..fa58e59d0b --- /dev/null +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-web-ui/src/components/PromptOptimizationConfig.vue @@ -0,0 +1,654 @@ + + + + + + + diff --git a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-web-ui/src/views/AgentDetail.vue b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-web-ui/src/views/AgentDetail.vue index ff374efb61..5899704a34 100644 --- a/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-web-ui/src/views/AgentDetail.vue +++ b/spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-web-ui/src/views/AgentDetail.vue @@ -464,20 +464,10 @@
-
-

自定义Prompt配置(待实现)

-

TODO:这里配置的Prompt仅用作效果优化,需支持多个提示词配置,系统已内置提示词
如:
1. 查询的年销售额精确到小数点后两位。
2. 报告格式第一章节请先总结年销售额

-
-
-
- - -
-
- -
-
+
@@ -1406,11 +1396,13 @@ import { ref, reactive, onMounted, onUnmounted, computed } from 'vue' import { useRouter, useRoute } from 'vue-router' import { agentApi, businessKnowledgeApi, semanticModelApi, datasourceApi, presetQuestionApi } from '../utils/api.js' import AgentDebugPanel from '../components/AgentDebugPanel.vue' +import PromptOptimizationConfig from '../components/PromptOptimizationConfig.vue' export default { name: 'AgentDetail', components: { - AgentDebugPanel + AgentDebugPanel, + PromptOptimizationConfig }, setup() { const router = useRouter() @@ -3785,11 +3777,6 @@ html { border: 1px solid #d9d9d9; } -/* Prompt配置样式 */ -.prompt-config-section { - max-width: 800px; -} - /* 审计日志样式 */ /* 初始化信息源样式 */ .schema-init-section {