(improvement)(Headless) support multiturn text-to-sql (#983)

This commit is contained in:
mainmain
2024-05-13 14:13:02 +08:00
committed by GitHub
parent 947a01e8ba
commit 0e28d6cbcc
15 changed files with 407 additions and 11 deletions

View File

@@ -1,18 +1,47 @@
package com.tencent.supersonic.chat.server.parser;
import com.tencent.supersonic.chat.server.persistence.repository.ChatQueryRepository;
import com.tencent.supersonic.chat.server.pojo.ChatParseContext;
import com.tencent.supersonic.chat.server.util.QueryReqConverter;
import com.tencent.supersonic.common.util.ContextUtils;
import com.tencent.supersonic.common.util.JsonUtil;
import com.tencent.supersonic.headless.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.headless.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.headless.api.pojo.request.QueryReq;
import com.tencent.supersonic.headless.api.pojo.response.ParseResp;
import com.tencent.supersonic.headless.core.chat.mapper.SchemaMapper;
import com.tencent.supersonic.headless.core.chat.parser.llm.LLMRequestService;
import com.tencent.supersonic.headless.core.chat.parser.llm.RewriteQueryGeneration;
import com.tencent.supersonic.headless.core.chat.query.llm.s2sql.LLMReq;
import com.tencent.supersonic.headless.core.chat.query.llm.s2sql.LLMSqlQuery;
import com.tencent.supersonic.headless.core.pojo.QueryContext;
import com.tencent.supersonic.headless.server.service.ChatQueryService;
import java.util.List;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import com.tencent.supersonic.headless.server.service.impl.ChatQueryServiceImpl;
import com.tencent.supersonic.headless.server.utils.ComponentFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import static com.tencent.supersonic.common.pojo.Constants.CONTEXT;
@Slf4j
public class NL2SQLParser implements ChatParser {
private int contextualNum = 5;
private List<SchemaMapper> schemaMappers = ComponentFactory.getSchemaMappers();
@Override
public void parse(ChatParseContext chatParseContext, ParseResp parseResp) {
if (!chatParseContext.enableNL2SQL()) {
@@ -21,7 +50,15 @@ public class NL2SQLParser implements ChatParser {
if (checkSkip(parseResp)) {
return;
}
considerMultiturn(chatParseContext, parseResp);
QueryReq queryReq = QueryReqConverter.buildText2SqlQueryReq(chatParseContext);
Environment environment = ContextUtils.getBean(Environment.class);
String multiTurn = environment.getProperty("multi.turn");
if (StringUtils.isNotBlank(multiTurn) && Boolean.parseBoolean(multiTurn)) {
queryReq.setMapInfo(new SchemaMapInfo());
}
ChatQueryService chatQueryService = ContextUtils.getBean(ChatQueryService.class);
ParseResp text2SqlParseResp = chatQueryService.performParsing(queryReq);
if (!ParseResp.ParseState.FAILED.equals(text2SqlParseResp.getState())) {
@@ -30,6 +67,100 @@ public class NL2SQLParser implements ChatParser {
parseResp.getParseTimeCost().setSqlTime(text2SqlParseResp.getParseTimeCost().getSqlTime());
}
private void considerMultiturn(ChatParseContext chatParseContext, ParseResp parseResp) {
Environment environment = ContextUtils.getBean(Environment.class);
RewriteQueryGeneration rewriteQueryGeneration = ContextUtils.getBean(RewriteQueryGeneration.class);
String multiTurn = environment.getProperty("multi.turn");
String multiNum = environment.getProperty("multi.num");
if (StringUtils.isBlank(multiTurn) || !Boolean.parseBoolean(multiTurn)) {
return;
}
log.info("multi turn text-to-sql!");
List<ParseResp> contextualList = getContextualList(parseResp, multiNum);
List<String> contextualQuestions = getContextualQuestionsWithLink(contextualList);
StringBuffer currentPromptSb = new StringBuffer();
if (contextualQuestions.size() == 0) {
currentPromptSb.append("contextualQuestions:" + "\n");
} else {
currentPromptSb.append("contextualQuestions:" + "\n" + String.join("\n", contextualQuestions) + "\n");
}
String currentQuestion = getQueryLinks(chatParseContext);
currentPromptSb.append("currentQuestion:" + currentQuestion + "\n");
currentPromptSb.append("rewritingCurrentQuestion:\n");
String rewriteQuery = rewriteQueryGeneration.generation(currentPromptSb.toString());
log.info("rewriteQuery:{}", rewriteQuery);
chatParseContext.setQueryText(rewriteQuery);
}
private List<String> getContextualQuestionsWithLink(List<ParseResp> contextualList) {
List<String> contextualQuestions = new ArrayList<>();
contextualList.stream().forEach(o -> {
Map<String, Object> map = JsonUtil.toMap(JsonUtil.toString(
o.getSelectedParses().get(0).getProperties().get(CONTEXT)), String.class, Object.class);
LLMReq llmReq = JsonUtil.toObject(JsonUtil.toString(map.get("llmReq")), LLMReq.class);
List<LLMReq.ElementValue> linking = llmReq.getLinking();
List<String> priorLinkingList = new ArrayList<>();
for (LLMReq.ElementValue priorLinking : linking) {
String fieldName = priorLinking.getFieldName();
String fieldValue = priorLinking.getFieldValue();
priorLinkingList.add("" + fieldValue + "‘是一个‘" + fieldName + "");
}
String linkingListStr = String.join("", priorLinkingList);
String questionAugmented = String.format("%s (补充信息:%s) ", o.getQueryText(), linkingListStr);
contextualQuestions.add(questionAugmented);
});
return contextualQuestions;
}
private List<ParseResp> getContextualList(ParseResp parseResp, String multiNum) {
ChatQueryRepository chatQueryRepository = ContextUtils.getBean(ChatQueryRepository.class);
List<ParseResp> contextualParseInfoList = chatQueryRepository.getContextualParseInfo(
parseResp.getChatId()).stream().filter(o -> o.getSelectedParses().get(0)
.getQueryMode().equals(LLMSqlQuery.QUERY_MODE)
).collect(Collectors.toList());
if (StringUtils.isNotBlank(multiNum) && StringUtils.isNumeric(multiNum)) {
int num = Integer.parseInt(multiNum);
contextualNum = Math.min(contextualNum, num);
}
List<ParseResp> contextualList = contextualParseInfoList.subList(0,
Math.min(contextualNum, contextualParseInfoList.size()));
Collections.reverse(contextualList);
return contextualList;
}
private String getQueryLinks(ChatParseContext chatParseContext) {
QueryReq queryReq = QueryReqConverter.buildText2SqlQueryReq(chatParseContext);
ChatQueryServiceImpl chatQueryService = ContextUtils.getBean(ChatQueryServiceImpl.class);
// build queryContext and chatContext
QueryContext queryCtx = chatQueryService.buildQueryContext(queryReq);
// 1. mapper
if (Objects.isNull(chatParseContext.getMapInfo())
|| MapUtils.isEmpty(chatParseContext.getMapInfo().getDataSetElementMatches())) {
schemaMappers.forEach(mapper -> {
mapper.map(queryCtx);
});
}
LLMRequestService requestService = ContextUtils.getBean(LLMRequestService.class);
Long dataSetId = requestService.getDataSetId(queryCtx);
log.info("dataSetId:{}", dataSetId);
if (dataSetId == null) {
return null;
}
List<LLMReq.ElementValue> linkingValues = requestService.getValueList(queryCtx, dataSetId);
List<String> priorLinkingList = new ArrayList<>();
for (LLMReq.ElementValue priorLinking : linkingValues) {
String fieldName = priorLinking.getFieldName();
String fieldValue = priorLinking.getFieldValue();
priorLinkingList.add("" + fieldValue + "‘是一个‘" + fieldName + "");
}
String linkingListStr = String.join("", priorLinkingList);
String questionAugmented = String.format("%s (补充信息:%s) ", chatParseContext.getQueryText(), linkingListStr);
log.info("questionAugmented:{}", questionAugmented);
return questionAugmented;
}
private boolean checkSkip(ParseResp parseResp) {
List<SemanticParseInfo> selectedParses = parseResp.getSelectedParses();
for (SemanticParseInfo semanticParseInfo : selectedParses) {

View File

@@ -18,4 +18,6 @@ public interface ChatParseMapper {
List<ChatParseDO> getParseInfoList(List<Long> questionIds);
List<ChatParseDO> getContextualParseInfo(Integer chatId);
}

View File

@@ -36,4 +36,6 @@ public interface ChatQueryRepository {
Boolean deleteChatQuery(Long questionId);
List<ParseResp> getContextualParseInfo(Integer chatId);
}

View File

@@ -26,6 +26,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
@@ -44,8 +45,8 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository {
private final ShowCaseCustomMapper showCaseCustomMapper;
public ChatQueryRepositoryImpl(ChatQueryDOMapper chatQueryDOMapper,
ChatParseMapper chatParseMapper,
ShowCaseCustomMapper showCaseCustomMapper) {
ChatParseMapper chatParseMapper,
ShowCaseCustomMapper showCaseCustomMapper) {
this.chatQueryDOMapper = chatQueryDOMapper;
this.chatParseMapper = chatParseMapper;
this.showCaseCustomMapper = showCaseCustomMapper;
@@ -131,7 +132,7 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository {
@Override
public List<ChatParseDO> batchSaveParseInfo(ChatParseReq chatParseReq,
ParseResp parseResult, List<SemanticParseInfo> candidateParses) {
ParseResp parseResult, List<SemanticParseInfo> candidateParses) {
List<ChatParseDO> chatParseDOList = new ArrayList<>();
getChatParseDO(chatParseReq, parseResult.getQueryId(), candidateParses, chatParseDOList);
if (!CollectionUtils.isEmpty(candidateParses)) {
@@ -141,7 +142,7 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository {
}
public void getChatParseDO(ChatParseReq chatParseReq, Long queryId,
List<SemanticParseInfo> parses, List<ChatParseDO> chatParseDOList) {
List<SemanticParseInfo> parses, List<ChatParseDO> chatParseDOList) {
for (int i = 0; i < parses.size(); i++) {
ChatParseDO chatParseDO = new ChatParseDO();
chatParseDO.setChatId(Long.valueOf(chatParseReq.getChatId()));
@@ -193,4 +194,17 @@ public class ChatQueryRepositoryImpl implements ChatQueryRepository {
return chatQueryDOMapper.deleteByPrimaryKey(questionId);
}
@Override
public List<ParseResp> getContextualParseInfo(Integer chatId) {
List<ChatParseDO> chatParseDOList = chatParseMapper.getContextualParseInfo(chatId);
List<ParseResp> semanticParseInfoList = chatParseDOList.stream().map(parseInfo -> {
ParseResp parseResp = new ParseResp(chatId, parseInfo.getQueryText());
List<SemanticParseInfo> selectedParses = new ArrayList<>();
selectedParses.add(JSONObject.parseObject(parseInfo.getParseInfo(), SemanticParseInfo.class));
parseResp.setSelectedParses(selectedParses);
return parseResp;
}).collect(Collectors.toList());
return semanticParseInfoList;
}
}

View File

@@ -8,6 +8,7 @@ import com.tencent.supersonic.chat.api.pojo.response.QueryResp;
import com.tencent.supersonic.chat.api.pojo.response.ShowCaseResp;
import com.tencent.supersonic.chat.server.persistence.dataobject.ChatDO;
import com.tencent.supersonic.chat.server.service.ChatManageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@@ -24,11 +25,8 @@ import java.util.List;
@RequestMapping({"/api/chat/manage", "/openapi/chat/manage"})
public class ChatController {
private final ChatManageService chatService;
public ChatController(ChatManageService chatService) {
this.chatService = chatService;
}
@Autowired
private ChatManageService chatService;
@PostMapping("/save")
public Boolean save(@RequestParam(value = "chatName") String chatName,

View File

@@ -64,6 +64,7 @@ public class ChatServiceImpl implements ChatService {
@Override
public ParseResp performParsing(ChatParseReq chatParseReq) {
String queryText = chatParseReq.getQueryText();
ParseResp parseResp = new ParseResp(chatParseReq.getChatId(), chatParseReq.getQueryText());
chatManageService.createChatQuery(chatParseReq, parseResp);
ChatParseContext chatParseContext = buildParseContext(chatParseReq);
@@ -73,6 +74,8 @@ public class ChatServiceImpl implements ChatService {
for (ParseResultProcessor processor : parseResultProcessors) {
processor.process(chatParseContext, parseResp);
}
parseResp.setQueryText(queryText);
chatParseReq.setQueryText(queryText);
chatManageService.batchAddParse(chatParseReq, parseResp);
return parseResp;
}

View File

@@ -46,4 +46,10 @@
</foreach>
</select>
<select id="getContextualParseInfo" resultMap="ChatParse">
select *
from s2_chat_parse
where chat_id = #{chatId} order by question_id desc limit 10
</select>
</mapper>

View File

@@ -143,7 +143,7 @@ public class LLMRequestService {
return extraInfoSb.toString();
}
protected List<ElementValue> getValueList(QueryContext queryCtx, Long dataSetId) {
public List<ElementValue> getValueList(QueryContext queryCtx, Long dataSetId) {
Map<Long, String> itemIdToName = getItemIdToName(queryCtx, dataSetId);
List<SchemaElementMatch> matchedElements = queryCtx.getMapInfo().getMatchedElements(dataSetId);
if (CollectionUtils.isEmpty(matchedElements)) {

View File

@@ -0,0 +1,32 @@
package com.tencent.supersonic.headless.core.chat.parser.llm;
import com.fasterxml.jackson.core.type.TypeReference;
import com.tencent.supersonic.common.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import java.io.InputStream;
import java.util.List;
import java.util.ArrayList;
@Slf4j
@Component
public class RewriteExamplarLoader {
private static final String EXAMPLE_JSON_FILE = "rewrite_examplar.json";
private TypeReference<List<RewriteExample>> valueTypeRef = new TypeReference<List<RewriteExample>>() {
};
public List<RewriteExample> getRewriteExamples() {
try {
ClassPathResource resource = new ClassPathResource(EXAMPLE_JSON_FILE);
InputStream inputStream = resource.getInputStream();
return JsonUtil.INSTANCE.getObjectMapper().readValue(inputStream, valueTypeRef);
} catch (Exception e) {
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,14 @@
package com.tencent.supersonic.headless.core.chat.parser.llm;
import lombok.Data;
@Data
public class RewriteExample {
private String contextualQuestions;
private String currentQuestion;
private String rewritingCurrentQuestion;
}

View File

@@ -0,0 +1,54 @@
package com.tencent.supersonic.headless.core.chat.parser.llm;
import com.tencent.supersonic.common.util.JsonUtil;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.model.output.Response;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@Slf4j
public class RewriteQueryGeneration {
private static final Logger keyPipelineLog = LoggerFactory.getLogger("keyPipeline");
@Autowired
private ChatLanguageModel chatLanguageModel;
@Autowired
private RewriteExamplarLoader rewriteExamplarLoader;
@Autowired
private SqlPromptGenerator sqlPromptGenerator;
public String generation(String currentPromptStr) {
//1.retriever sqlExamples
List<Map<String, String>> rewriteExamples = rewriteExamplarLoader.getRewriteExamples().stream().map(o -> {
return JsonUtil.toMap(JsonUtil.toString(o), String.class, String.class);
}).collect(Collectors.toList());
//2.generator linking and sql prompt by sqlExamples,and generate response.
String promptStr = sqlPromptGenerator.generateRewritePrompt(rewriteExamples) + currentPromptStr;
Prompt prompt = PromptTemplate.from(JsonUtil.toString(promptStr)).apply(new HashMap<>());
keyPipelineLog.info("request prompt:{}", prompt.toSystemMessage());
Response<AiMessage> response = chatLanguageModel.generate(prompt.toSystemMessage());
String result = response.content().text();
keyPipelineLog.info("model response:{}", result);
//3.format response.
String rewriteQuery = response.content().text();
return rewriteQuery;
}
}

View File

@@ -129,4 +129,19 @@ public class SqlPromptGenerator {
return sqlPromptPool;
}
public String generateRewritePrompt(List<Map<String, String>> rewriteExamples) {
String instruction = "#this is a multi-turn text-to-sql scenes,you need consider the contextual "
+ "questions and semantics, rewriting current question for expressing complete semantics of "
+ "the current question based on the contextual questions.";
List<String> exampleKeys = Arrays.asList("contextualQuestions", "currentQuestion", "rewritingCurrentQuestion");
StringBuilder rewriteSb = new StringBuilder();
rewriteExamples.stream().forEach(o -> {
exampleKeys.stream().forEach(example -> {
rewriteSb.append(example + ":" + o.get(example) + "\n");
});
rewriteSb.append("\n");
});
return instruction + InputFormat.SEPERATOR + rewriteSb.toString();
}
}

View File

@@ -134,7 +134,7 @@ public class ChatQueryServiceImpl implements ChatQueryService {
return parseResult;
}
private QueryContext buildQueryContext(QueryReq queryReq) {
public QueryContext buildQueryContext(QueryReq queryReq) {
SemanticSchema semanticSchema = semanticService.getSemanticSchema();
Map<Long, List<Long>> modelIdToDataSetIds = dataSetService.getModelIdToDataSetIds();

View File

@@ -102,3 +102,6 @@ inMemoryEmbeddingStore:
query:
optimizer:
enable: true
multi:
turn: false
num: 5

View File

@@ -0,0 +1,122 @@
[
{
"contextualQuestions": "[“近7天纯音乐的歌曲播放量 (补充信息: '纯音乐'‘是一个’语种‘。)”]",
"currentQuestion": "对比翻唱版呢 (补充信息: '翻唱版'‘是一个’歌曲版本‘。)",
"rewritingCurrentQuestion": "对比近7天翻唱版和纯音乐的歌曲播放量"
},
{
"contextualQuestions": "[]",
"currentQuestion": "robinlee在内容库的访问次数 (补充信息: 'robinlee'‘是一个’用户名‘。)",
"rewritingCurrentQuestion": "robinlee在内容库的访问次数"
},
{
"contextualQuestions": "[\"robinlee在内容库的访问次数 (补充信息: 'robinlee'‘是一个’用户名‘。)\"]",
"currentQuestion": "对比jackjchen呢 (补充信息: 'jackjchen'‘是一个’用户名‘。)",
"rewritingCurrentQuestion": "robinlee对比jackjchen在内容库的访问次数"
},
{
"contextualQuestions": "[\"robinlee在内容库的访问次数 (补充信息: 'robinlee'‘是一个’用户名‘。)\",\"对比jackjchen呢 (补充信息: 'jackjchen'‘是一个’用户名‘。)\"]。",
"currentQuestion": "内容库近12个月访问人数按部门",
"rewritingCurrentQuestion": "内容库近12个月访问人数按部门"
},
{
"contextualQuestions": "[\"robinlee在内容库的访问次数 (补充信息: 'robinlee'‘是一个’用户名‘。)\",\"对比jackjchen呢 (补充信息: 'jackjchen'‘是一个’用户名‘。)\",\"内容库近12个月访问人数按部门\"]",
"currentQuestion": "访问次数呢?",
"rewritingCurrentQuestion": "内容库近12个月访问次数按部门"
},
{
"contextualQuestions": "[]",
"currentQuestion": "近3天海田飞系MPPM结算播放份额 (补充信息:'海田飞系'‘是一个’严选版权归属系‘)",
"rewritingCurrentQuestion": "近3天海田飞系MPPM结算播放份额"
},
{
"contextualQuestions": "[\"近3天海田飞系MPPM结算播放份额(补充信息:'海田飞系'‘是一个’严选版权归属系‘) \"]",
"currentQuestion": "近60天呢",
"rewritingCurrentQuestion": "近60天海田飞系MPPM结算播放份额"
},
{
"contextualQuestions": "[\"近3天海田飞系MPPM结算播放份额(补充信息:'海田飞系'‘是一个’严选版权归属系‘) \",\"近60天呢\"]",
"currentQuestion": "飞天系呢(补充信息:'飞天系'‘是一个’严选版权归属系‘)",
"rewritingCurrentQuestion": "近60天飞天系MPPM结算播放份额"
},
{
"contextualQuestions": "[“近90天袁亚伟播放量是多少 (补充信息:'袁亚伟'是一个歌手名)”]",
"currentQuestion": "平均值是多少",
"rewritingCurrentQuestion": "近90天袁亚伟播放量的平均值是多少"
},
{
"contextualQuestions": "[“近90天袁亚伟播放量是多少 (补充信息:'袁亚伟'是一个歌手名)”,\"平均值是多少\",\"总和是多少\"]",
"currentQuestion": "总和是多少",
"rewritingCurrentQuestion": "近90天袁亚伟播放量的总和是多少"
},
{
"contextualQuestions": "[\"播放量大于1万的歌曲有多少\"]",
"currentQuestion": "下载量大于10万的呢",
"rewritingCurrentQuestion": "下载量大于10万的歌曲有多少"
},
{
"contextualQuestions": "[\"周杰伦2023年6月之后发布的歌曲有哪些(补充信息:'周杰伦'是一个歌手名)\"]",
"currentQuestion": "这些歌曲有哪些播放量大于500W的",
"rewritingCurrentQuestion": "周杰伦2023年6月之后发布的歌曲有哪些播放量大于500W的"
},
{
"contextualQuestions": "[“陈奕迅唱的所有的播放量大于20万的歌曲有哪些(补充信息:'陈奕迅'是一个歌手名)”]",
"currentQuestion": "大于100万的呢",
"rewritingCurrentQuestion": "陈奕迅唱的所有的播放量大于100万的歌曲有哪些"
},
{
"contextualQuestions": "[“陈奕迅唱的所有的播放量大于20万的歌曲有哪些(补充信息:'陈奕迅'是一个歌手名)”,\"大于100万的呢\"]",
"currentQuestion": "周杰伦去年发布的歌曲有哪些(补充信息:'周杰伦'是一个歌手名)",
"rewritingCurrentQuestion": "周杰伦去年发布的歌曲有哪些"
},
{
"contextualQuestions": "[“陈奕迅唱的所有的播放量大于20万的歌曲有哪些(补充信息:'陈奕迅'是一个歌手名)”,\"大于100万的呢\",\"周杰伦去年发布的歌曲有哪些(补充信息:'周杰伦'是一个歌手名)\"]",
"currentQuestion": "他今年发布的呢",
"rewritingCurrentQuestion": "周杰伦今年发布的歌曲有哪些"
},
{
"contextualQuestions": "[“陈奕迅唱的所有的播放量大于20万的歌曲有哪些(补充信息:'陈奕迅'是一个歌手名)”,\"大于100万的呢\",\"周杰伦去年发布的歌曲有哪些(补充信息:'周杰伦'是一个歌手名)\",\"他今年发布的呢\"]",
"currentQuestion": "我想要近半年签约的播放量前十的歌手有哪些",
"rewritingCurrentQuestion": "我想要近半年签约的播放量前十的歌手有哪些"
},
{
"contextualQuestions": "[]",
"currentQuestion": "最近一年发行的歌曲中有哪些在近7天播放超过一千万的",
"rewritingCurrentQuestion": "最近一年发行的歌曲中有哪些在近7天播放超过一千万的"
},
{
"contextualQuestions": "[“最近一年发行的歌曲中有哪些在近7天播放超过一千万的”]",
"currentQuestion": "今年以来呢?",
"rewritingCurrentQuestion": "今年以来发行的歌曲中有哪些在近7天播放超过一千万的"
},
{
"contextualQuestions": "[“最近一年发行的歌曲中有哪些在近7天播放超过一千万的”,\"今年以来呢?\"]",
"currentQuestion": "2023年以来呢",
"rewritingCurrentQuestion": "2023年以来发行的歌曲中有哪些在近7天播放超过一千万的"
},
{
"contextualQuestions": "[\"内容库近20天访问次数\"]",
"currentQuestion": "按部门看一下",
"rewritingCurrentQuestion": "内容库近20天按部门的访问次数"
},
{
"contextualQuestions": "[\"内容库近20天访问次数\",\"按部门看一下\"]",
"currentQuestion": "按模块看一下",
"rewritingCurrentQuestion": "内容库近20天按模块的访问次数"
},
{
"contextualQuestions": "[\"内容库近20天访问次数\",\"按部门看一下\",\"按模块看一下\"]",
"currentQuestion": "看一下技术部的 (补充信息:'技术部'‘是一个’部门‘)",
"rewritingCurrentQuestion": "技术部在内容库近20天的访问次数"
},
{
"contextualQuestions": "[\"内容库近20天访问次数\",\"按部门看一下\",\"按模块看一下\",\"看一下技术部的 (补充信息:'技术部'‘是一个’部门‘)\"]",
"currentQuestion": "看一下产品部的 (补充信息:'产品部'‘是一个’部门‘)",
"rewritingCurrentQuestion": "产品部在内容库近20天的访问次数"
},
{
"contextualQuestions": "[\"内容库近20天访问次数\",\"按部门看一下\",\"按模块看一下\",\"看一下技术部的 (补充信息:'技术部'‘是一个’部门‘)\",\"看一下产品部的 (补充信息:'产品部'‘是一个’部门‘)\"]",
"currentQuestion": "对比一下技术部、产品部(补充信息:'技术部'、‘产品部’分别是一个’部门‘)",
"rewritingCurrentQuestion": "对比一下技术部、产品部在内容库近20天的访问次数"
}
]