diff --git a/assembly/bin/supersonic-build.bat b/assembly/bin/supersonic-build.bat index b554cbd34..94d44822d 100644 --- a/assembly/bin/supersonic-build.bat +++ b/assembly/bin/supersonic-build.bat @@ -43,10 +43,26 @@ if "%service%"=="webapp" ( call mvn -f %projectDir% clean package -DskipTests -Dspotless.skip=true IF ERRORLEVEL 1 ( ECHO Failed to build backend Java modules. + ECHO Please check Maven and Java versions are compatible. + ECHO Current Java: %JAVA_HOME% + ECHO Current Maven: %MAVEN_HOME% EXIT /B 1 ) + + REM extract and copy files to deployment directory + cd %projectDir%\launchers\%model_name%\target + if exist "launchers-%model_name%-%MVN_VERSION%-bin.tar.gz" ( + echo "Extracting launchers-%model_name%-%MVN_VERSION%-bin.tar.gz..." + tar -xf "launchers-%model_name%-%MVN_VERSION%-bin.tar.gz" + if exist "launchers-%model_name%-%MVN_VERSION%" ( + echo "Copying files to deployment directory..." + xcopy /E /Y "launchers-%model_name%-%MVN_VERSION%\*" "%buildDir%\supersonic-%model_name%-%MVN_VERSION%\" + ) + ) + copy /y %projectDir%\launchers\%model_name%\target\*.tar.gz %buildDir%\ echo "finished building supersonic-%model_name% service" + cd %baseDir% goto :EOF @@ -72,22 +88,55 @@ if "%service%"=="webapp" ( cd %buildDir% if exist %release_dir% rmdir /s /q %release_dir% if exist %release_dir%.zip del %release_dir%.zip - mkdir %release_dir% - rem package webapp - tar xvf supersonic-webapp.tar.gz - move /y supersonic-webapp webapp - echo {"env": ""} > webapp\supersonic.config.json - move /y webapp %release_dir% - rem package java service - tar xvf %service_name%-bin.tar.gz - for /d %%D in ("%service_name%\*") do ( - move "%%D" "%release_dir%" + + rem check if release directory already exists from buildJavaService + if exist %release_dir% ( + echo "Release directory already prepared by buildJavaService" + ) else ( + mkdir %release_dir% + + rem package java service + tar xvf %service_name%-bin.tar.gz 2>nul + if errorlevel 1 ( + echo "Warning: tar command failed, trying PowerShell extraction..." + powershell -Command "Expand-Archive -Path '%service_name%-bin.tar.gz' -DestinationPath '.' -Force" + ) + for /d %%D in ("%service_name%\*") do ( + move "%%D" "%release_dir%" + ) + rmdir /s /q %service_name% 2>nul ) + + rem package webapp + if exist supersonic-webapp.tar.gz ( + tar xvf supersonic-webapp.tar.gz 2>nul + if errorlevel 1 ( + echo "Warning: tar command failed, trying PowerShell extraction..." + powershell -Command "Expand-Archive -Path 'supersonic-webapp.tar.gz' -DestinationPath '.' -Force" + ) + move /y supersonic-webapp webapp + echo {"env": ""} > webapp\supersonic.config.json + move /y webapp %release_dir% + del supersonic-webapp.tar.gz 2>nul + ) + + rem verify deployment structure + if exist "%release_dir%\lib\launchers-%model_name%-%MVN_VERSION%.jar" ( + echo "Deployment structure verified successfully" + ) else ( + echo "Warning: Main jar file not found in deployment structure" + echo "Expected: %release_dir%\lib\launchers-%model_name%-%MVN_VERSION%.jar" + ) + rem generate zip file - powershell Compress-Archive -Path %release_dir% -DestinationPath %release_dir%.zip - del %service_name%-bin.tar.gz - del supersonic-webapp.tar.gz - rmdir /s /q %service_name% + powershell -Command "Compress-Archive -Path '%release_dir%' -DestinationPath '%release_dir%.zip' -Force" + if errorlevel 1 ( + echo "Warning: PowerShell compression failed, release directory still available: %release_dir%" + ) else ( + echo "Successfully created release package: %release_dir%.zip" + ) + + del %service_name%-bin.tar.gz 2>nul echo "finished packaging supersonic release" goto :EOF diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/adaptor/DefaultUserAdaptor.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/adaptor/DefaultUserAdaptor.java index 8e24db424..abff472e5 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/adaptor/DefaultUserAdaptor.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/adaptor/DefaultUserAdaptor.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import java.sql.Timestamp; +import java.util.Date; import java.util.List; import java.util.Optional; import java.util.Set; @@ -222,8 +223,9 @@ public class DefaultUserAdaptor implements UserAdaptor { new UserWithPassword(userDO.getId(), userDO.getName(), userDO.getDisplayName(), userDO.getEmail(), userDO.getPassword(), userDO.getIsAdmin()); - String token = - tokenService.generateToken(UserWithPassword.convert(userWithPassword), expireTime); + // 使用令牌名称作为生成key ,这样可以区分正常请求和api 请求,api 的令牌失效时间很长,需考虑令牌泄露的情况 + String token = tokenService.generateToken(UserWithPassword.convert(userWithPassword), + "SysDbToken:" + name, (new Date().getTime() + expireTime)); UserTokenDO userTokenDO = saveUserToken(name, userName, token, expireTime); return convertUserToken(userTokenDO); } diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/UserRepository.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/UserRepository.java index a15ceaab0..d217c5082 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/UserRepository.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/UserRepository.java @@ -21,6 +21,8 @@ public interface UserRepository { UserTokenDO getUserToken(Long tokenId); + UserTokenDO getUserTokenByName(String tokenName); + void deleteUserTokenByName(String userName); void deleteUserToken(Long tokenId); diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/impl/UserRepositoryImpl.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/impl/UserRepositoryImpl.java index 511494cdd..106623b96 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/impl/UserRepositoryImpl.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/persistence/repository/impl/UserRepositoryImpl.java @@ -65,6 +65,13 @@ public class UserRepositoryImpl implements UserRepository { return userTokenDOMapper.selectById(tokenId); } + @Override + public UserTokenDO getUserTokenByName(String tokenName) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().eq(UserTokenDO::getName, tokenName); + return userTokenDOMapper.selectOne(queryWrapper); + } + @Override public void deleteUserTokenByName(String userName) { QueryWrapper queryWrapper = new QueryWrapper<>(); diff --git a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/TokenService.java b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/TokenService.java index a8b249602..85b1804ff 100644 --- a/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/TokenService.java +++ b/auth/authentication/src/main/java/com/tencent/supersonic/auth/authentication/utils/TokenService.java @@ -6,7 +6,10 @@ import javax.crypto.spec.SecretKeySpec; import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig; import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword; +import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO; +import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository; import com.tencent.supersonic.common.pojo.exception.AccessException; +import com.tencent.supersonic.common.util.ContextUtils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -71,6 +74,7 @@ public class TokenService { return generateToken(UserWithPassword.convert(appUser), request); } + public Optional getClaims(HttpServletRequest request) { String token = request.getHeader(authenticationConfig.getTokenHttpHeaderKey()); String appKey = getAppKey(request); @@ -90,6 +94,14 @@ public class TokenService { public Optional getClaims(String token, String appKey) { try { + if (StringUtils.isNotBlank(appKey) && appKey.startsWith("SysDbToken:")) {// 如果是配置的长期令牌,需校验数据库是否存在该配置 + UserRepository userRepository = ContextUtils.getBean(UserRepository.class); + UserTokenDO dbToken = + userRepository.getUserTokenByName(appKey.substring("SysDbToken:".length())); + if (dbToken == null || !dbToken.getToken().equals(token.replace("Bearer ", ""))) { + throw new AccessException("Token does not exist :" + appKey); + } + } String tokenSecret = getTokenSecret(appKey); Claims claims = Jwts.parser().setSigningKey(tokenSecret.getBytes(StandardCharsets.UTF_8)) @@ -122,6 +134,16 @@ public class TokenService { Map appKeyToSecretMap = authenticationConfig.getAppKeyToSecretMap(); String secret = appKeyToSecretMap.get(appKey); if (StringUtils.isBlank(secret)) { + if (StringUtils.isNotBlank(appKey) && appKey.startsWith("SysDbToken:")) { // 是配置的长期令牌 + String realAppKey = appKey.substring("SysDbToken:".length()); + String tmp = + "WIaO9YRRVt+7QtpPvyWsARFngnEcbaKBk783uGFwMrbJBaochsqCH62L4Kijcb0sZCYoSsiKGV/zPml5MnZ3uQ=="; + if (tmp.length() <= realAppKey.length()) { + return realAppKey; + } else { + return realAppKey + tmp.substring(realAppKey.length()); + } + } throw new AccessException("get secret from appKey failed :" + appKey); } return secret; diff --git a/chat/server/src/main/java/com/tencent/supersonic/chat/server/executor/SqlExecutor.java b/chat/server/src/main/java/com/tencent/supersonic/chat/server/executor/SqlExecutor.java index 0871dbae8..02a5df72d 100644 --- a/chat/server/src/main/java/com/tencent/supersonic/chat/server/executor/SqlExecutor.java +++ b/chat/server/src/main/java/com/tencent/supersonic/chat/server/executor/SqlExecutor.java @@ -75,8 +75,12 @@ public class SqlExecutor implements ChatQueryExecutor { return null; } - QuerySqlReq sqlReq = - QuerySqlReq.builder().sql(parseInfo.getSqlInfo().getCorrectedS2SQL()).build(); + // 使用querySQL,它已经包含了所有修正(包括物理SQL修正) + String finalSql = StringUtils.isNotBlank(parseInfo.getSqlInfo().getQuerySQL()) + ? parseInfo.getSqlInfo().getQuerySQL() + : parseInfo.getSqlInfo().getCorrectedS2SQL(); + + QuerySqlReq sqlReq = QuerySqlReq.builder().sql(finalSql).build(); sqlReq.setSqlInfo(parseInfo.getSqlInfo()); sqlReq.setDataSetId(parseInfo.getDataSetId()); @@ -90,7 +94,7 @@ public class SqlExecutor implements ChatQueryExecutor { queryResult.setQueryTimeCost(System.currentTimeMillis() - startTime); if (queryResp != null) { queryResult.setQueryAuthorization(queryResp.getQueryAuthorization()); - queryResult.setQuerySql(queryResp.getSql()); + queryResult.setQuerySql(finalSql); queryResult.setQueryResults(queryResp.getResultList()); queryResult.setQueryColumns(queryResp.getColumns()); queryResult.setQueryState(QueryState.SUCCESS); diff --git a/chat/server/src/main/java/com/tencent/supersonic/chat/server/processor/execute/DataInterpretProcessor.java b/chat/server/src/main/java/com/tencent/supersonic/chat/server/processor/execute/DataInterpretProcessor.java index fc0f12e99..047d3ddfe 100644 --- a/chat/server/src/main/java/com/tencent/supersonic/chat/server/processor/execute/DataInterpretProcessor.java +++ b/chat/server/src/main/java/com/tencent/supersonic/chat/server/processor/execute/DataInterpretProcessor.java @@ -47,7 +47,8 @@ public class DataInterpretProcessor implements ExecuteResultProcessor { Agent agent = executeContext.getAgent(); ChatApp chatApp = agent.getChatAppConfig().get(APP_KEY); return Objects.nonNull(chatApp) && chatApp.isEnable() - && StringUtils.isNotBlank(executeContext.getResponse().getTextResult()); // 如果都没结果,则无法处理,直接跳过 + && StringUtils.isNotBlank(executeContext.getResponse().getTextResult()) // 如果都没结果,则无法处理 + && StringUtils.isBlank(executeContext.getResponse().getTextSummary()); // 如果已经有汇总的结果了,无法再次处理 } @Override @@ -57,7 +58,16 @@ public class DataInterpretProcessor implements ExecuteResultProcessor { ChatApp chatApp = agent.getChatAppConfig().get(APP_KEY); Map variable = new HashMap<>(); - variable.put("question", executeContext.getRequest().getQueryText()); + String question = executeContext.getResponse().getTextResult();// 结果解析应该用改写的问题,因为改写的内容信息量更大 + if (executeContext.getParseInfo().getProperties() != null + && executeContext.getParseInfo().getProperties().containsKey("CONTEXT")) { + Map context = (Map) executeContext.getParseInfo() + .getProperties().get("CONTEXT"); + if (context.get("queryText") != null && "".equals(context.get("queryText"))) { + question = context.get("queryText").toString(); + } + } + variable.put("question", question); variable.put("data", queryResult.getTextResult()); Prompt prompt = PromptTemplate.from(chatApp.getPrompt()).apply(variable); diff --git a/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/AgentServiceImpl.java b/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/AgentServiceImpl.java index 2d76875bb..f09a4e30d 100644 --- a/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/AgentServiceImpl.java +++ b/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/AgentServiceImpl.java @@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -39,6 +40,7 @@ public class AgentServiceImpl extends ServiceImpl implem private MemoryService memoryService; @Autowired + @Lazy private ChatQueryService chatQueryService; @Autowired diff --git a/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/ChatQueryServiceImpl.java b/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/ChatQueryServiceImpl.java index 484e22305..1aca41e2d 100644 --- a/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/ChatQueryServiceImpl.java +++ b/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/ChatQueryServiceImpl.java @@ -49,6 +49,7 @@ import net.sf.jsqlparser.schema.Column; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -66,6 +67,7 @@ public class ChatQueryServiceImpl implements ChatQueryService { @Autowired private SemanticLayerService semanticLayerService; @Autowired + @Lazy private AgentService agentService; private final List chatQueryParsers = ComponentFactory.getChatParsers(); diff --git a/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/MemoryServiceImpl.java b/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/MemoryServiceImpl.java index 6ceeb1127..5f0ee7cf5 100644 --- a/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/MemoryServiceImpl.java +++ b/chat/server/src/main/java/com/tencent/supersonic/chat/server/service/impl/MemoryServiceImpl.java @@ -26,7 +26,6 @@ import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Objects; @@ -110,6 +109,14 @@ public class MemoryServiceImpl implements MemoryService, CommandLineRunner { @Override public void batchDelete(List ids) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.lambda().in(ChatMemoryDO::getId, ids); + List chatMemoryDOS = chatMemoryRepository.getMemories(queryWrapper); + chatMemoryDOS.forEach(chatMemoryDO -> { + if (MemoryStatus.ENABLED.toString().equals(chatMemoryDO.getStatus().trim())) { + disableMemory(chatMemoryDO); + } + }); chatMemoryRepository.batchDelete(ids); } diff --git a/common/src/main/java/com/hankcs/hanlp/LoadRemoveService.java b/common/src/main/java/com/hankcs/hanlp/LoadRemoveService.java index 3472a6277..09ee8e0f2 100644 --- a/common/src/main/java/com/hankcs/hanlp/LoadRemoveService.java +++ b/common/src/main/java/com/hankcs/hanlp/LoadRemoveService.java @@ -21,7 +21,8 @@ public class LoadRemoveService { List resultList = new ArrayList<>(value); if (!CollectionUtils.isEmpty(modelIdOrDataSetIds)) { resultList.removeIf(nature -> { - if (Objects.isNull(nature)) { + if (Objects.isNull(nature) || !nature.startsWith("_")) { // 系统的字典是以 _ 开头的, + // 过滤因引用外部字典导致的异常 return false; } Long id = getId(nature); diff --git a/common/src/main/java/com/tencent/supersonic/common/pojo/Text2SQLExemplar.java b/common/src/main/java/com/tencent/supersonic/common/pojo/Text2SQLExemplar.java index d878c13c2..d50f54121 100644 --- a/common/src/main/java/com/tencent/supersonic/common/pojo/Text2SQLExemplar.java +++ b/common/src/main/java/com/tencent/supersonic/common/pojo/Text2SQLExemplar.java @@ -22,4 +22,6 @@ public class Text2SQLExemplar implements Serializable { private String dbSchema; private String sql; + + protected double similarity; // 传递相似度,可以作为样本筛选的依据 } diff --git a/common/src/main/java/com/tencent/supersonic/common/service/impl/ExemplarServiceImpl.java b/common/src/main/java/com/tencent/supersonic/common/service/impl/ExemplarServiceImpl.java index 8852375e0..31457dde1 100644 --- a/common/src/main/java/com/tencent/supersonic/common/service/impl/ExemplarServiceImpl.java +++ b/common/src/main/java/com/tencent/supersonic/common/service/impl/ExemplarServiceImpl.java @@ -72,7 +72,10 @@ public class ExemplarServiceImpl implements ExemplarService, CommandLineRunner { embeddingService.retrieveQuery(collection, retrieveQuery, num); results.forEach(ret -> { ret.getRetrieval().forEach(r -> { - exemplars.add(JsonUtil.mapToObject(r.getMetadata(), Text2SQLExemplar.class)); + Text2SQLExemplar tmp = // 传递相似度,可以作为样本筛选的依据 + JsonUtil.mapToObject(r.getMetadata(), Text2SQLExemplar.class); + tmp.setSimilarity(r.getSimilarity()); + exemplars.add(tmp); }); }); diff --git a/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/SemanticParseInfo.java b/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/SemanticParseInfo.java index d7c9df4d8..9be8c5cec 100644 --- a/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/SemanticParseInfo.java +++ b/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/SemanticParseInfo.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import static com.tencent.supersonic.common.pojo.Constants.DEFAULT_DETAIL_LIMIT; import static com.tencent.supersonic.common.pojo.Constants.DEFAULT_METRIC_LIMIT; @@ -65,12 +66,23 @@ public class SemanticParseInfo implements Serializable { DataSetMatchResult mr2 = getDataSetMatchResult(o2.getElementMatches()); double difference = mr1.getMaxDatesetSimilarity() - mr2.getMaxDatesetSimilarity(); - if (difference == 0) { + if (Math.abs(difference) < 0.0005) { // 看完全匹配的个数,实践证明,可以用户输入规范后,该逻辑具有优势 + if (!o1.getDataSetId().equals(o2.getDataSetId())) { + List elementMatches1 = o1.getElementMatches().stream() + .filter(e -> e.getSimilarity() == 1).collect(Collectors.toList()); + List elementMatches2 = o2.getElementMatches().stream() + .filter(e -> e.getSimilarity() == 1).collect(Collectors.toList()); + if (elementMatches1.size() > elementMatches2.size()) { + return -1; + } else if (elementMatches1.size() < elementMatches2.size()) { + return 1; + } + } difference = mr1.getMaxMetricSimilarity() - mr2.getMaxMetricSimilarity(); - if (difference == 0) { + if (Math.abs(difference) < 0.0005) { difference = mr1.getTotalSimilarity() - mr2.getTotalSimilarity(); } - if (difference == 0) { + if (Math.abs(difference) < 0.0005) { difference = mr1.getMaxMetricUseCnt() - mr2.getMaxMetricUseCnt(); } } diff --git a/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/SqlInfo.java b/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/SqlInfo.java index 3eab2bccf..ede33c71a 100644 --- a/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/SqlInfo.java +++ b/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/SqlInfo.java @@ -16,4 +16,7 @@ public class SqlInfo implements Serializable { // SQL to be executed finally private String querySQL; + + // Physical SQL corrected by LLM for performance optimization + private String correctedQuerySQL; } diff --git a/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/enums/ChatWorkflowState.java b/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/enums/ChatWorkflowState.java index 953f1f020..79ba4d77d 100644 --- a/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/enums/ChatWorkflowState.java +++ b/headless/api/src/main/java/com/tencent/supersonic/headless/api/pojo/enums/ChatWorkflowState.java @@ -8,5 +8,6 @@ public enum ChatWorkflowState { VALIDATING, SQL_CORRECTING, PROCESSING, + PHYSICAL_SQL_CORRECTING, FINISHED } diff --git a/headless/chat/src/main/java/com/tencent/supersonic/headless/chat/corrector/LLMPhysicalSqlCorrector.java b/headless/chat/src/main/java/com/tencent/supersonic/headless/chat/corrector/LLMPhysicalSqlCorrector.java new file mode 100644 index 000000000..2d04e8ba7 --- /dev/null +++ b/headless/chat/src/main/java/com/tencent/supersonic/headless/chat/corrector/LLMPhysicalSqlCorrector.java @@ -0,0 +1,98 @@ +package com.tencent.supersonic.headless.chat.corrector; + +import com.tencent.supersonic.common.pojo.ChatApp; +import com.tencent.supersonic.common.pojo.enums.AppModule; +import com.tencent.supersonic.common.util.ChatAppManager; +import com.tencent.supersonic.headless.api.pojo.SemanticParseInfo; +import com.tencent.supersonic.headless.chat.ChatQueryContext; +import dev.langchain4j.model.chat.ChatLanguageModel; +import dev.langchain4j.model.input.Prompt; +import dev.langchain4j.model.input.PromptTemplate; +import dev.langchain4j.model.output.structured.Description; +import dev.langchain4j.provider.ModelProvider; +import dev.langchain4j.service.AiServices; +import lombok.Data; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 物理SQL修正器 - 使用LLM优化物理SQL性能 + */ +@Slf4j +public class LLMPhysicalSqlCorrector extends BaseSemanticCorrector { + + private static final Logger keyPipelineLog = LoggerFactory.getLogger("keyPipeline"); + + public static final String APP_KEY = "PHYSICAL_SQL_CORRECTOR"; + private static final String INSTRUCTION = "" + + "#Role: You are a senior database performance optimization expert experienced in SQL tuning." + + "\n\n#Task: You will be provided with a user question and the corresponding physical SQL query," + + " please analyze and optimize this SQL to improve query performance." + "\n\n#Rules:" + + "\n1. DO NOT add or introduce any new fields, columns, or aliases that are not in the original SQL." + + "\n2. Push WHERE conditions into JOIN ON clauses when possible to reduce intermediate result sets." + + "\n3. Optimize JOIN order by placing smaller tables or tables with selective conditions first." + + "\n4. For date range conditions, ensure they are applied as early as possible in the query execution." + + "\n5. Remove or comment out database-specific index hints (like USE INDEX) that may cause syntax errors." + + "\n6. ONLY modify the structure and order of existing elements, do not change field names or add new ones." + + "\n7. Ensure the optimized SQL is syntactically correct and logically equivalent to the original." + + "\n\n#Question: {{question}}" + "\n\n#OriginalSQL: {{sql}}"; + + public LLMPhysicalSqlCorrector() { + ChatAppManager.register(APP_KEY, ChatApp.builder().prompt(INSTRUCTION).name("物理SQL修正") + .appModule(AppModule.CHAT).description("通过大模型对物理SQL做性能优化").enable(false).build()); + } + + @Data + @ToString + static class PhysicalSql { + @Description("either positive or negative") + private String opinion; + + @Description("optimized sql if negative") + private String sql; + } + + interface PhysicalSqlExtractor { + PhysicalSql generatePhysicalSql(String text); + } + + @Override + public void doCorrect(ChatQueryContext chatQueryContext, SemanticParseInfo semanticParseInfo) { + ChatApp chatApp = chatQueryContext.getRequest().getChatAppConfig().get(APP_KEY); + if (!chatQueryContext.getRequest().getText2SQLType().enableLLM() || Objects.isNull(chatApp) + || !chatApp.isEnable()) { + return; + } + + ChatLanguageModel chatLanguageModel = + ModelProvider.getChatModel(chatApp.getChatModelConfig()); + PhysicalSqlExtractor extractor = + AiServices.create(PhysicalSqlExtractor.class, chatLanguageModel); + Prompt prompt = generatePrompt(chatQueryContext.getRequest().getQueryText(), + semanticParseInfo, chatApp.getPrompt()); + PhysicalSql physicalSql = + extractor.generatePhysicalSql(prompt.toUserMessage().singleText()); + keyPipelineLog.info("LLMPhysicalSqlCorrector modelReq:\n{} \nmodelResp:\n{}", prompt.text(), + physicalSql); + if ("NEGATIVE".equalsIgnoreCase(physicalSql.getOpinion()) + && StringUtils.isNotBlank(physicalSql.getSql())) { + semanticParseInfo.getSqlInfo().setCorrectedQuerySQL(physicalSql.getSql()); + } + } + + private Prompt generatePrompt(String queryText, SemanticParseInfo semanticParseInfo, + String promptTemplate) { + Map variable = new HashMap<>(); + variable.put("question", queryText); + variable.put("sql", semanticParseInfo.getSqlInfo().getQuerySQL()); + + return PromptTemplate.from(promptTemplate).apply(variable); + } +} diff --git a/headless/chat/src/main/java/com/tencent/supersonic/headless/chat/parser/llm/PromptHelper.java b/headless/chat/src/main/java/com/tencent/supersonic/headless/chat/parser/llm/PromptHelper.java index a319b8491..8cf15a01d 100644 --- a/headless/chat/src/main/java/com/tencent/supersonic/headless/chat/parser/llm/PromptHelper.java +++ b/headless/chat/src/main/java/com/tencent/supersonic/headless/chat/parser/llm/PromptHelper.java @@ -14,10 +14,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.stream.Collectors; import static com.tencent.supersonic.headless.chat.parser.ParserConfig.*; @@ -51,13 +49,33 @@ public class PromptHelper { // use random collection of exemplars for each self-consistency inference for (int i = 0; i < selfConsistencyNumber; i++) { List shuffledList = new ArrayList<>(exemplars); - // only shuffle the exemplars from config - List subList = - shuffledList.subList(llmReq.getDynamicExemplars().size(), shuffledList.size()); - Collections.shuffle(subList); - results.add(shuffledList.subList(0, Math.min(shuffledList.size(), fewShotNumber))); + List same = shuffledList.stream() // 相似度极高的话,先找出来 + .filter(e -> e.getSimilarity() > 0.989).collect(Collectors.toList()); + List noSame = shuffledList.stream() + .filter(e -> e.getSimilarity() <= 0.989).collect(Collectors.toList()); + if ((noSame.size() - same.size()) > fewShotNumber) {// 去除部分最低分 + noSame.sort(Comparator.comparingDouble(Text2SQLExemplar::getSimilarity)); + noSame = noSame.subList((noSame.size() - fewShotNumber) / 2, noSame.size()); + } + Text2SQLExemplar mostSimilar = noSame.get(noSame.size() - 1); + Collections.shuffle(noSame); + List ts; + if (same.size() > 0) {// 一样的话,必须作为提示语 + ts = new ArrayList<>(); + int needSize = Math.min(noSame.size() + same.size(), fewShotNumber); + if (needSize > same.size()) { + ts.addAll(noSame.subList(0, needSize - same.size())); + } + ts.addAll(same); + } else { // 至少要一个最像的 + ts = noSame.subList(0, Math.min(noSame.size(), fewShotNumber)); + if (!ts.contains(mostSimilar)) { + ts.remove(ts.size() - 1); + ts.add(mostSimilar); + } + } + results.add(ts); } - return results; } diff --git a/headless/core/src/main/java/com/tencent/supersonic/headless/core/pojo/QueryStatement.java b/headless/core/src/main/java/com/tencent/supersonic/headless/core/pojo/QueryStatement.java index 83ac4fbf9..9381cac43 100644 --- a/headless/core/src/main/java/com/tencent/supersonic/headless/core/pojo/QueryStatement.java +++ b/headless/core/src/main/java/com/tencent/supersonic/headless/core/pojo/QueryStatement.java @@ -1,5 +1,6 @@ package com.tencent.supersonic.headless.core.pojo; +import com.tencent.supersonic.common.pojo.User; import com.tencent.supersonic.headless.api.pojo.response.QueryState; import com.tencent.supersonic.headless.api.pojo.response.SemanticSchemaResp; import lombok.Data; @@ -24,6 +25,7 @@ public class QueryStatement { private SemanticSchemaResp semanticSchema; private Integer limit = 1000; private Boolean isTranslated = false; + private User user; public boolean isOk() { return StringUtils.isBlank(errMsg) && StringUtils.isNotBlank(sql); diff --git a/headless/server/src/main/java/com/tencent/supersonic/headless/server/aspect/DimValueAspect.java b/headless/server/src/main/java/com/tencent/supersonic/headless/server/aspect/DimValueAspect.java index 75a91e969..8af101fe2 100644 --- a/headless/server/src/main/java/com/tencent/supersonic/headless/server/aspect/DimValueAspect.java +++ b/headless/server/src/main/java/com/tencent/supersonic/headless/server/aspect/DimValueAspect.java @@ -102,7 +102,7 @@ public class DimValueAspect { continue; } for (DimensionResp dimension : dimensions) { - if (!expression.getFieldName().equals(dimension.getName()) + if (!expression.getFieldName().equals(dimension.getBizName()) || CollectionUtils.isEmpty(dimension.getDimValueMaps())) { continue; } @@ -124,6 +124,7 @@ public class DimValueAspect { sql = SqlReplaceHelper.replaceValue(sql, filedNameToValueMap); log.debug("correctorSql after replacing:{}", sql); querySqlReq.setSql(sql); + querySqlReq.getSqlInfo().setQuerySQL(sql); Map> techNameToBizName = getTechNameToBizName(dimensions); SemanticQueryResp queryResultWithColumns = (SemanticQueryResp) joinPoint.proceed(); diff --git a/headless/server/src/main/java/com/tencent/supersonic/headless/server/facade/service/impl/S2SemanticLayerService.java b/headless/server/src/main/java/com/tencent/supersonic/headless/server/facade/service/impl/S2SemanticLayerService.java index c9739bf99..3d3969cb8 100644 --- a/headless/server/src/main/java/com/tencent/supersonic/headless/server/facade/service/impl/S2SemanticLayerService.java +++ b/headless/server/src/main/java/com/tencent/supersonic/headless/server/facade/service/impl/S2SemanticLayerService.java @@ -296,6 +296,9 @@ public class S2SemanticLayerService implements SemanticLayerService { queryStatement.setSql(semanticQueryReq.getSqlInfo().getQuerySQL()); queryStatement.setIsTranslated(true); } + if (queryStatement != null) { + queryStatement.setUser(user); + } return queryStatement; } diff --git a/headless/server/src/main/java/com/tencent/supersonic/headless/server/persistence/repository/impl/DimensionRepositoryImpl.java b/headless/server/src/main/java/com/tencent/supersonic/headless/server/persistence/repository/impl/DimensionRepositoryImpl.java index 2122b68a2..7ba0d3ebf 100644 --- a/headless/server/src/main/java/com/tencent/supersonic/headless/server/persistence/repository/impl/DimensionRepositoryImpl.java +++ b/headless/server/src/main/java/com/tencent/supersonic/headless/server/persistence/repository/impl/DimensionRepositoryImpl.java @@ -83,13 +83,10 @@ public class DimensionRepositoryImpl implements DimensionRepository { } if (StringUtils.isNotBlank(dimensionFilter.getKey())) { String key = dimensionFilter.getKey(); - queryWrapper.lambda() - .and(wrapper -> wrapper - .like(DimensionDO::getName, key).or() - .like(DimensionDO::getBizName, key).or().like(DimensionDO::getDescription, key) - .or().like(DimensionDO::getAlias, key).or() - .like(DimensionDO::getCreatedBy, key) - ); + queryWrapper.and(qw->qw.lambda().like(DimensionDO::getName, key).or() + .like(DimensionDO::getBizName, key).or().like(DimensionDO::getDescription, key) + .or().like(DimensionDO::getAlias, key).or() + .like(DimensionDO::getCreatedBy, key)); } return dimensionDOMapper.selectList(queryWrapper); diff --git a/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/ChatWorkflowEngine.java b/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/ChatWorkflowEngine.java index 6fcbb983a..05b1d582a 100644 --- a/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/ChatWorkflowEngine.java +++ b/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/ChatWorkflowEngine.java @@ -8,6 +8,7 @@ import com.tencent.supersonic.headless.api.pojo.request.SemanticQueryReq; import com.tencent.supersonic.headless.api.pojo.response.ParseResp; import com.tencent.supersonic.headless.api.pojo.response.SemanticTranslateResp; import com.tencent.supersonic.headless.chat.ChatQueryContext; +import com.tencent.supersonic.headless.chat.corrector.LLMPhysicalSqlCorrector; import com.tencent.supersonic.headless.chat.corrector.SemanticCorrector; import com.tencent.supersonic.headless.chat.mapper.SchemaMapper; import com.tencent.supersonic.headless.chat.parser.SemanticParser; @@ -76,6 +77,10 @@ public class ChatWorkflowEngine { long start = System.currentTimeMillis(); performTranslating(queryCtx, parseResult); parseResult.getParseTimeCost().setSqlTime(System.currentTimeMillis() - start); + queryCtx.setChatWorkflowState(ChatWorkflowState.PHYSICAL_SQL_CORRECTING); + break; + case PHYSICAL_SQL_CORRECTING: + performPhysicalSqlCorrecting(queryCtx); queryCtx.setChatWorkflowState(ChatWorkflowState.FINISHED); break; default: @@ -162,4 +167,26 @@ public class ChatWorkflowEngine { parseResult.setErrorMsg(String.join("\n", errorMsg)); } } + + private void performPhysicalSqlCorrecting(ChatQueryContext queryCtx) { + List candidateQueries = queryCtx.getCandidateQueries(); + if (CollectionUtils.isNotEmpty(candidateQueries)) { + for (SemanticQuery semanticQuery : candidateQueries) { + for (SemanticCorrector corrector : semanticCorrectors) { + if (corrector instanceof LLMPhysicalSqlCorrector) { + corrector.correct(queryCtx, semanticQuery.getParseInfo()); + // 如果物理SQL被修正了,更新querySQL为修正后的版本 + SemanticParseInfo parseInfo = semanticQuery.getParseInfo(); + if (StringUtils.isNotBlank(parseInfo.getSqlInfo().getCorrectedQuerySQL())) { + parseInfo.getSqlInfo() + .setQuerySQL(parseInfo.getSqlInfo().getCorrectedQuerySQL()); + log.info("Physical SQL corrected and updated querySQL: {}", + parseInfo.getSqlInfo().getQuerySQL()); + } + break; + } + } + } + } + } } diff --git a/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/DataSetSchemaBuilder.java b/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/DataSetSchemaBuilder.java index 3befbb47d..1e57e5f05 100644 --- a/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/DataSetSchemaBuilder.java +++ b/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/DataSetSchemaBuilder.java @@ -19,9 +19,10 @@ public class DataSetSchemaBuilder { public static DataSetSchema build(DataSetSchemaResp resp) { DataSetSchema dataSetSchema = new DataSetSchema(); dataSetSchema.setQueryConfig(resp.getQueryConfig()); - SchemaElement dataSet = SchemaElement.builder().dataSetId(resp.getId()) - .dataSetName(resp.getName()).id(resp.getId()).name(resp.getName()) - .bizName(resp.getBizName()).type(SchemaElementType.DATASET).build(); + SchemaElement dataSet = + SchemaElement.builder().dataSetId(resp.getId()).dataSetName(resp.getName()) + .id(resp.getId()).name(resp.getName()).description(resp.getDescription()) + .bizName(resp.getBizName()).type(SchemaElementType.DATASET).build(); dataSetSchema.setDataSet(dataSet); dataSetSchema.setDatabaseType(resp.getDatabaseType()); dataSetSchema.setDatabaseVersion(resp.getDatabaseVersion()); diff --git a/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/DictUtils.java b/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/DictUtils.java index ce6319931..5e95063e0 100644 --- a/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/DictUtils.java +++ b/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/DictUtils.java @@ -138,7 +138,8 @@ public class DictUtils { semanticQueryReq.setNeedAuth(false); String bizName = dictItemResp.getBizName(); try { - SemanticQueryResp semanticQueryResp = queryService.queryByReq(semanticQueryReq, null); + SemanticQueryResp semanticQueryResp = + queryService.queryByReq(semanticQueryReq, User.getDefaultUser()); if (Objects.isNull(semanticQueryResp) || CollectionUtils.isEmpty(semanticQueryResp.getResultList())) { return lines; @@ -274,6 +275,9 @@ public class DictUtils { private QuerySqlReq constructQuerySqlReq(DictItemResp dictItemResp) { ModelResp model = modelService.getModel(dictItemResp.getModelId()); + String tableStr = StringUtils.isNotBlank(model.getModelDetail().getTableQuery()) + ? model.getModelDetail().getTableQuery() + : "(" + model.getModelDetail().getSqlQuery() + ")"; String sqlPattern = "select %s,count(1) from %s %s group by %s order by count(1) desc limit %d"; String dimBizName = dictItemResp.getBizName(); @@ -287,8 +291,7 @@ public class DictUtils { limit = Integer.MAX_VALUE; } - String sql = - String.format(sqlPattern, dimBizName, model.getBizName(), where, dimBizName, limit); + String sql = String.format(sqlPattern, dimBizName, tableStr, where, dimBizName, limit); Set modelIds = new HashSet<>(); modelIds.add(dictItemResp.getModelId()); QuerySqlReq querySqlReq = new QuerySqlReq(); diff --git a/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/QueryUtils.java b/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/QueryUtils.java index 75f3ca44f..83300a5f6 100644 --- a/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/QueryUtils.java +++ b/headless/server/src/main/java/com/tencent/supersonic/headless/server/utils/QueryUtils.java @@ -109,7 +109,8 @@ public class QueryUtils { column.setModelId(metric.getModelId()); } // if column nameEn contains metric alias, use metric dataFormatType - if (column.getDataFormatType() == null && metric.getAlias() != null) { + if (column.getDataFormatType() == null + && StringUtils.isNotEmpty(metric.getAlias())) { for (String alias : metric.getAlias().split(",")) { if (nameEn.contains(alias)) { column.setDataFormatType(metric.getDataFormatType()); diff --git a/launchers/headless/src/main/resources/META-INF/spring.factories b/launchers/headless/src/main/resources/META-INF/spring.factories index 0adcac744..cd5643357 100644 --- a/launchers/headless/src/main/resources/META-INF/spring.factories +++ b/launchers/headless/src/main/resources/META-INF/spring.factories @@ -14,7 +14,8 @@ com.tencent.supersonic.headless.chat.parser.SemanticParser=\ com.tencent.supersonic.headless.chat.corrector.SemanticCorrector=\ com.tencent.supersonic.headless.chat.corrector.RuleSqlCorrector,\ - com.tencent.supersonic.headless.chat.corrector.LLMSqlCorrector + com.tencent.supersonic.headless.chat.corrector.LLMSqlCorrector,\ + com.tencent.supersonic.headless.chat.corrector.LLMPhysicalSqlCorrector com.tencent.supersonic.headless.chat.knowledge.file.FileHandler=\ com.tencent.supersonic.headless.chat.knowledge.file.FileHandlerImpl diff --git a/launchers/standalone/src/main/resources/META-INF/spring.factories b/launchers/standalone/src/main/resources/META-INF/spring.factories index 8b17394eb..c12dfcea5 100644 --- a/launchers/standalone/src/main/resources/META-INF/spring.factories +++ b/launchers/standalone/src/main/resources/META-INF/spring.factories @@ -15,7 +15,8 @@ com.tencent.supersonic.headless.chat.parser.SemanticParser=\ com.tencent.supersonic.headless.chat.corrector.SemanticCorrector=\ com.tencent.supersonic.headless.chat.corrector.RuleSqlCorrector,\ - com.tencent.supersonic.headless.chat.corrector.LLMSqlCorrector + com.tencent.supersonic.headless.chat.corrector.LLMSqlCorrector,\ + com.tencent.supersonic.headless.chat.corrector.LLMPhysicalSqlCorrector com.tencent.supersonic.headless.chat.knowledge.file.FileHandler=\ com.tencent.supersonic.headless.chat.knowledge.file.FileHandlerImpl diff --git a/launchers/standalone/src/main/resources/config.update/sql-update-mysql.sql b/launchers/standalone/src/main/resources/config.update/sql-update-mysql.sql index b7b6c1b9b..cf92e1b0c 100644 --- a/launchers/standalone/src/main/resources/config.update/sql-update-mysql.sql +++ b/launchers/standalone/src/main/resources/config.update/sql-update-mysql.sql @@ -420,4 +420,4 @@ ALTER TABLE s2_chat_model add column is_open tinyint DEFAULT NULL COMMENT '是 ALTER TABLE s2_database add column is_open tinyint DEFAULT NULL COMMENT '是否公开'; --20250321 -ALTER TABLE s2_user add column last_loin datetime DEFAULT NULL; \ No newline at end of file +ALTER TABLE s2_user add column last_login datetime DEFAULT NULL; diff --git a/pom.xml b/pom.xml index 8db6a8336..1a81d9238 100644 --- a/pom.xml +++ b/pom.xml @@ -10,9 +10,9 @@ ${revision} + common auth chat - common launchers headless @@ -31,6 +31,7 @@ 21 21 21 + 21 UTF-8 4.9 6.1.0 @@ -254,6 +255,13 @@ ${java.source.version} ${java.target.version} ${file.encoding} + + + org.projectlombok + lombok + ${lombok.version} + + diff --git a/webapp/package.json b/webapp/package.json new file mode 100644 index 000000000..cc1b48756 --- /dev/null +++ b/webapp/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "supersonic-chat-sdk": "link:packages/chat-sdk" + } +} diff --git a/webapp/packages/chat-sdk/src/common/type.ts b/webapp/packages/chat-sdk/src/common/type.ts index da70d4c51..e706311e4 100644 --- a/webapp/packages/chat-sdk/src/common/type.ts +++ b/webapp/packages/chat-sdk/src/common/type.ts @@ -79,6 +79,7 @@ export type SqlInfoType = { parsedS2SQL: string; correctedS2SQL: string; querySQL: string; + correctedQuerySQL?: string; }; export type ChatContextType = { diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/SqlItem.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/SqlItem.tsx index 96439d770..d33275683 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/SqlItem.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/SqlItem.tsx @@ -58,32 +58,28 @@ const SqlItem: React.FC = ({ const getSchemaMapText = () => { return ` Schema映射 -${schema?.fieldNameList?.length > 0 ? `名称:${schema.fieldNameList.join('、')}` : ''}${ - schema?.values?.length > 0 +${schema?.fieldNameList?.length > 0 ? `名称:${schema.fieldNameList.join('、')}` : ''}${schema?.values?.length > 0 ? ` 取值:${schema.values - .map((item: any) => { - return `${item.fieldName}: ${item.fieldValue}`; - }) - .join('、')}` + .map((item: any) => { + return `${item.fieldName}: ${item.fieldValue}`; + }) + .join('、')}` : '' - }${ - priorExts + }${priorExts ? ` 附加:${priorExts}` : '' - }${ - terms?.length > 0 + }${terms?.length > 0 ? ` 术语:${terms - .map((item: any) => { - return `${item.name}${item.alias?.length > 0 ? `(${item.alias.join(',')})` : ''}: ${ - item.description + .map((item: any) => { + return `${item.name}${item.alias?.length > 0 ? `(${item.alias.join(',')})` : ''}: ${item.description }`; - }) - .join('、')}` + }) + .join('、')}` : '' - } + } `; }; @@ -91,16 +87,16 @@ ${schema?.fieldNameList?.length > 0 ? `名称:${schema.fieldNameList.join('、 const getFewShotText = () => { return ` Few-shot示例${fewShots - .map((item: any, index: number) => { - return ` + .map((item: any, index: number) => { + return ` 示例${index + 1}: 问题:${item.question} SQL: ${format(item.sql)} `; - }) - .join('')} + }) + .join('')} `; }; @@ -120,6 +116,14 @@ ${format(sqlInfo.correctedS2SQL)} `; }; + const getCorrectedQuerySQLText = () => { + return ` +物理SQL修正 + +${format(sqlInfo.correctedQuerySQL || '')} +`; + }; + const getQuerySQLText = () => { return ` 最终执行SQL @@ -155,6 +159,9 @@ ${executeErrorMsg} if (sqlInfo.correctedS2SQL) { text += getCorrectedS2SQLText(); } + if (sqlInfo.correctedQuerySQL) { + text += getCorrectedQuerySQLText(); + } if (sqlInfo.querySQL) { text += getQuerySQLText(); } @@ -183,9 +190,8 @@ ${executeErrorMsg}
{llmReq && (
{ setSqlType(sqlType === 'schemaMap' ? '' : 'schemaMap'); }} @@ -195,9 +201,8 @@ ${executeErrorMsg} )} {fewShots.length > 0 && (
{ setSqlType(sqlType === 'fewShots' ? '' : 'fewShots'); }} @@ -207,9 +212,8 @@ ${executeErrorMsg} )} {sqlInfo.parsedS2SQL && (
{ setSqlType(sqlType === 'parsedS2SQL' ? '' : 'parsedS2SQL'); }} @@ -219,9 +223,8 @@ ${executeErrorMsg} )} {sqlInfo.correctedS2SQL && (
{ setSqlType(sqlType === 'correctedS2SQL' ? '' : 'correctedS2SQL'); }} @@ -229,16 +232,26 @@ ${executeErrorMsg} 修正S2SQL
)} + {sqlInfo.correctedQuerySQL && ( +
{ + setSqlType(sqlType === 'correctedQuerySQL' ? '' : 'correctedQuerySQL'); + }} + > + 物理SQL修正 +
+ )} {sqlInfo.querySQL && (
{ setSqlType(sqlType === 'querySQL' ? '' : 'querySQL'); }} > - 最终执行SQL + {sqlInfo.correctedQuerySQL ? '最终执行SQL' : '最终执行SQL'}
)}
{sqlType === 'schemaMap' && (
@@ -290,9 +302,8 @@ ${executeErrorMsg}
{terms .map((item: any) => { - return `${item.name}${ - item.alias?.length > 0 ? `(${item.alias.join(',')})` : '' - }: ${item.description}`; + return `${item.name}${item.alias?.length > 0 ? `(${item.alias.join(',')})` : '' + }: ${item.description}`; }) .join('、')}
diff --git a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx index 9be689b7b..b8af45bff 100644 --- a/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatItem/index.tsx @@ -416,11 +416,10 @@ const ChatItem: React.FC = ({ const onExportData = () => { const { queryColumns, queryResults } = data || {}; - if (!!queryResults) { + if (!!queryResults && !!queryColumns) { const exportData = queryResults.map(item => { - return Object.keys(item).reduce((result, key) => { - const columnName = queryColumns?.find(column => column.nameEn === key)?.name || key; - result[columnName] = item[key]; + return queryColumns.reduce((result, column) => { + result[column.name || column.nameEn] = item[column.nameEn]; return result; }, {}); }); diff --git a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx index 3596c116b..11b621ec0 100644 --- a/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx +++ b/webapp/packages/chat-sdk/src/components/ChatMsg/Table/index.tsx @@ -24,7 +24,6 @@ const Table: React.FC = ({ data, size, loading, question, onApplyAuth }) dataIndex: bizName, key: bizName, title: name || bizName, - defaultSortOrder: 'descend', sorter: showType === 'NUMBER' ? (a, b) => { @@ -73,10 +72,11 @@ const Table: React.FC = ({ data, size, loading, question, onApplyAuth }) return index % 2 !== 0 ? `${prefixCls}-even-row` : ''; }; - const dateColumn = queryColumns.find(column => column.type === 'DATE'); + const dateColumn = queryColumns.find(column => column.type === 'DATE' || column.showType === 'DATE'); const dataSource = dateColumn ? queryResults.sort((a, b) => moment(a[dateColumn.bizName]).diff(moment(b[dateColumn.bizName]))) : queryResults; + return (
{question && (