package com.smtaiserver.smtaiserver.javaai.qwen.agent; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import org.apache.commons.text.similarity.JaccardSimilarity; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.Node; import com.smtaiserver.smtaiserver.core.SMTAIServerApp; import com.smtaiserver.smtaiserver.core.SMTAIServerRequest; import com.smtaiserver.smtaiserver.database.SMTDatabase.DBRecord; import com.smtaiserver.smtaiserver.javaai.SMTJavaAIError; import com.smtaiserver.smtaiserver.javaai.ast.ASTCubeRecs; import com.smtaiserver.smtaiserver.javaai.ast.ASTCubeRecs.ASTCubeRecsType; import com.smtaiserver.smtaiserver.javaai.metrics.base.SMTDimensionDef; import com.smtaiserver.smtaiserver.javaai.metrics.base.SMTMetricsDef; import com.smtaiserver.smtaiserver.javaai.ast.ASTCubeRecsValue; import com.smtaiserver.smtaiserver.javaai.ast.ASTDBMap; import com.smtaiserver.smtaiserver.javaai.ast.ASTMergeResult; import com.smtaiserver.smtaiserver.javaai.ast.ASTResult; import com.smtaiserver.smtaiserver.javaai.llm.core.SMTLLMConnect; import com.smtservlet.util.Json; import com.smtservlet.util.SMTJsonWriter; import com.smtservlet.util.SMTStatic; public class SMTQwenAgentMetrics extends SMTQwenAgent { ////////////////////////////////////////////////////////////////// private boolean _questionToStep = false; private String _promptVPROP; private List _metricsAlisList = new ArrayList<>(); private static Map _mapGroupType2Match = new HashMap<>(); private static Map _mapDiffType2Match = new HashMap<>(); static { _mapGroupType2Match.put("AVG", new String[]{"平均值", "平均", "AVG","sum","实时"}); _mapGroupType2Match.put("SUM", new String[]{"累计值", "累计", "SUM","sum","求和"}); _mapGroupType2Match.put("MAX", new String[]{"最大值", "最大", "MAX","max"}); _mapGroupType2Match.put("MIN", new String[]{"最小值", "MIN","min","最小"}); _mapGroupType2Match.put("COUNT", new String[]{"个数", "COUNT","count"}); _mapDiffType2Match.put("ADD", new String[]{"求和", "ADD","add","相加", "累计"}); _mapDiffType2Match.put("SUB", new String[]{"求差", "SUB","sub","相减", "差值"}); _mapDiffType2Match.put("YOY", new String[]{"同比", "YOY","yoy"}); _mapDiffType2Match.put("MOM", new String[]{"环比", "MOM","mom"}); } @Override public void initInstance(DBRecord rec) throws Exception { super.initInstance(rec); try { Document doc = SMTStatic.convStrToXmlDoc("" + rec.getString("clz_arguments") + ""); Element xmlPrompt = (Element)doc.selectSingleNode("ROOT/PROMPT"); _questionToStep = "true".equals(SMTStatic.getXmlAttr(xmlPrompt, "question_to_step", "false")); _promptVPROP = SMTStatic.trimStrLines(xmlPrompt.getText()) .replace("{{{DIM_NAME_LIST}}}", getDimNameList()) .replace("{{{METRICS_NAME_LIST}}}", getMetricsAliasList()) ; Node nodeMetricsAlias = doc.selectSingleNode("ROOT/METRICS_ALIAS"); if(nodeMetricsAlias != null) { String sMetricsAlias = SMTStatic.getXmlAttr((Element)nodeMetricsAlias, "names"); if(!SMTStatic.isNullOrEmpty(sMetricsAlias)) { for(String s : sMetricsAlias.split(",")) { _metricsAlisList.add(s); } } } } catch(Exception ex) { throw new Exception("init mertic agent error : " + this._agentId, ex); } } @Override public SMTJavaAIError mergeASTList(List listASTSrc, List r_listASTTag) { StringBuilder sbQuestion = new StringBuilder(); for(Json jsonASTSrc : listASTSrc) { String question = jsonASTSrc.getJson("args").getJson("question").asString(); sbQuestion.append("问题:" + question + "\n"); } Json jsonTag = Json.object( "call", this.getAgentId(), "args", Json.object( "question", sbQuestion.toString() ) ); r_listASTTag.add(jsonTag); return null; } @Override protected String getToolDesc(Element xmlTitle) throws Exception { String toolDesc = super.getToolDesc(xmlTitle); Map mapId2Metrics = SMTAIServerApp.getApp().getMetricsMap(this.getAgentId()); if(mapId2Metrics.size() > 0) { toolDesc += "\n此操作包含以下指标:\n"; for(SMTMetricsDef metricsDef : mapId2Metrics.values()) { toolDesc += " " + metricsDef.getTitle() + "\n"; } } return toolDesc; } private String getMetricsAliasList() throws Exception { Set setAlias = new HashSet<>(); StringBuilder sbResult = new StringBuilder(); Map mapId2Metrics = SMTAIServerApp.getApp().getMetricsMap(this.getAgentId()); for(SMTMetricsDef metricsDef : mapId2Metrics.values()) { metricsDef.addMetricsAliasToSet(setAlias); } for(String alias : setAlias) { sbResult.append(alias + "\n"); } return sbResult.toString(); } private String getDimNameList() throws Exception { Set setExistDim = new HashSet<>(); StringBuilder sbResult = new StringBuilder(); Map mapId2Metrics = SMTAIServerApp.getApp().getMetricsMap(this.getAgentId()); for(SMTMetricsDef metricsDef : mapId2Metrics.values()) { Map mapId2DimDef = metricsDef.getDimensionMap(); if(mapId2DimDef == null) continue; for(SMTDimensionDef dimDef : mapId2DimDef.values()) { if(setExistDim.contains(dimDef.getId())) continue; setExistDim.add(dimDef.getId()); sbResult.append(" " + dimDef.getId() + ":" + dimDef.getPrompt() + "\n"); } } return sbResult.toString(); } public SMTJavaAIError callSupervisorJson(String agentId, String jsonPath, Json jsonSupervisorArg, Json jsonAST, SMTJsonWriter jsonWr, SMTAIServerRequest tranReq) throws Exception { tranReq.traceLLMDebug("callAgents:[\n" + SMTStatic.formatJson(jsonAST) + "\n]"); jsonWr.addKeyValue("answer_type", "summary"); jsonWr.beginArray("summary"); SMTJavaAIError aiError = queryRecordByAST(jsonPath, jsonAST.asJsonList(), tranReq); jsonWr.endArray(); return aiError; } @Override public SMTJavaAIError callAgents(String jsonPath, Json jsonArgs, SMTLLMConnect llm, String question, SMTAIServerRequest tranReq) throws Exception { long tick = System.currentTimeMillis(); // 如果发生要把问题变成步骤,调用大模型 if(_questionToStep) { tranReq.sendChunkedBlock("begin", "将问题拆解成步骤"); String promptToStep = (String)SMTAIServerApp.getApp().getGlobalConfig("prompt.question_to_step"); question = llm.callWithMessage(new String[] {promptToStep}, question, tranReq).replace("\r", ""); tranReq.traceLLMDebug("questionToStep:[\n" + question + "\n]"); tranReq.traceLLMPrompt(promptToStep); tranReq.sendChunkedBlock("end", "拆解后的步骤是:" + question.replace("\r", "").replace("\n", " ")); } String answer = llm.callWithMessage(new String[] {_promptVPROP}, question, tranReq).replace("\r", ""); Json jsonAST = SMTStatic.convLLMAnswerToJson(answer, true); tranReq.appendCallFuncJson(jsonPath, jsonAST); tranReq.traceLLMDebug("callAgents:[" + ((double)(System.currentTimeMillis() - tick) / 1000) + "秒] [\n" + SMTStatic.formatJson(jsonAST) + "\n]"); if(jsonAST.isObject()) { String error = jsonAST.safeGetStr("error", "未知错误:" + jsonAST.toString()); return this.queryUnknowQuestion(error, llm, question, tranReq); } else if(!jsonAST.isArray()) { String error = "未知错误:" + jsonAST.asString(); return this.queryUnknowQuestion(error, llm, question, tranReq); } return this.callAgentsByAST(jsonPath, jsonArgs, jsonAST, tranReq); } public SMTJavaAIError callAgentsByAST(String jsonPath, Json jsonArgs, Json jsonAST, SMTAIServerRequest tranReq) throws Exception { SMTJsonWriter jsonWrResult = tranReq.getResultJsonWr(); jsonWrResult.addKeyValue("answer_type", "summary"); jsonWrResult.beginArray("summary"); SMTJavaAIError aiError = queryRecordByAST(jsonPath, jsonAST.asJsonList(), tranReq); jsonWrResult.endArray(); return aiError; } private SMTJavaAIError queryRecordByAST(String jsonPath, List jsonASTList, SMTAIServerRequest tranReq) throws Exception { SMTJsonWriter jsonWrResult = tranReq.getResultJsonWr(); ASTDBMap dbMap = new ASTDBMap(); try { ASTResult astResult = new ASTResult(); // 解析所有语法树 boolean existAST = false; int count = jsonASTList.size(); for(int jsonIdx = 0; jsonIdx < count; jsonIdx ++) { Json jsonAST = jsonASTList.get(jsonIdx); // 如果当前is_output = false if(!jsonAST.getJson("args").safeGetBoolean("is_output", true)) { // 如果已经存在执行器,则忽略 if(existAST) continue; // 如果当前执行器不是最后一个,则跳过 if(jsonIdx < (count -1)) continue; } existAST = true; // 如果发现语法树报错,则直接返回错误 { String error; error = jsonAST.safeGetStr("error", null); if(!SMTStatic.isNullOrEmpty(error)) return new SMTJavaAIError(error); } // 解析语法树 ASTResult curAstResult = new ASTResult(); SMTJavaAIError error = queryRecordAST2SQLTree("#" + SMTStatic.toString(jsonIdx) + "/", dbMap, jsonAST, null, tranReq, curAstResult); if(error != null) return error; for(ASTCubeRecs cubeRecs : curAstResult._listRecordset) { astResult._listRecordset.add(cubeRecs); } } ASTMergeResult mergeResult = new ASTMergeResult(); mergeResult.outputResultToJson(jsonPath, astResult, jsonWrResult); // // // 将生成的cube进行组合 // List listMergeGroup = mergeASTResult(astResult); // // // 输出组合后的cube // for(ASTMergeGroup mergeGroup : listMergeGroup) // { // mergeGroup.outputGroupToJson(jsonPath, jsonWr); // } } finally { dbMap.close(); } return null; } private SMTJavaAIError queryRecordAST2SQLTree(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { SMTJavaAIError aiError = null; String call = jsonAST.getJson("call").asString(); Json jsonArgs = jsonAST.getJson("args"); if("query_metrics".equals(call)) { aiError = call_query_metrics(jsonPath + "args/", dbMap, jsonArgs, extArg, tranReq, r_result); } else if("query_only_metrics".equals(call)) { aiError = call_query_only_metrics(jsonPath + "args/", dbMap, jsonArgs, extArg, tranReq, r_result); } else if("agg_metrics".equals(call)) { aiError = call_agg_metrics(jsonPath + "args/", dbMap, jsonArgs, extArg, tranReq, r_result); } else if("top_metrics".equals(call)) { aiError = call_top_metrics(jsonPath + "args/", dbMap, jsonArgs, extArg, tranReq, r_result); } else if("diff_metrics".equals(call)) { aiError = call_diff_metrics(jsonPath + "args/", dbMap, jsonArgs, extArg, tranReq, r_result); } else if("query_base_info".equals(call)) { aiError = call_query_base_info(jsonPath + "args/", dbMap, jsonArgs, extArg, tranReq, r_result); } else { return new SMTJavaAIError("未知调用:" + call); } return aiError; } private SMTJavaAIError call_query_base_info(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { SMTJavaAIError error = null; String metricsId = jsonAST.safeGetStr("metrics", null); SMTMetricsDef metricsDef = null; // 如果未配置指标,则取出第一个指标,且本配置只需要一个指标 if(SMTStatic.isNullOrEmpty(metricsId)) { Map mapId2Metrics = SMTAIServerApp.getApp().getMetricsMap(this.getAgentId()); if(mapId2Metrics.size() != 1) throw new Exception("call_query_base_info only one metrics : " + this.getAgentId()); for(SMTMetricsDef curMetricsDef : mapId2Metrics.values()) { metricsDef = curMetricsDef; break; } } else { metricsDef = queryMetricsType(metricsId); if(metricsDef == null) return new SMTJavaAIError("未发现指标:" + metricsId); } if((error = metricsDef.queryMetrics(jsonPath, dbMap, jsonAST, extArg, tranReq, r_result)) != null) return error; return null; } private SMTJavaAIError call_diff_metrics(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { SMTJavaAIError error = null; // 查询子结果集1 ASTResult childResult1 = new ASTResult(); error = queryRecordAST2SQLTree(jsonPath + "recordset1/", dbMap, jsonAST.getJson("recordset1"), extArg, tranReq, childResult1); if(error != null) return error; // 查询子结果集2 ASTResult childResult2 = new ASTResult(); error = queryRecordAST2SQLTree(jsonPath + "recordset2/", dbMap, jsonAST.getJson("recordset2"), extArg, tranReq, childResult2); if(error != null) return error; // 判断是否可以对比 if(childResult1._listRecordset.size() != 1 || childResult2._listRecordset.size() != 1) return new SMTJavaAIError("查询出多个结果集无法对比"); ASTCubeRecs astCubeRecs1 = childResult1._listRecordset.get(0); ASTCubeRecs astCubeRecs2 = childResult2._listRecordset.get(0); if(astCubeRecs1._dimNames.size() > 0 || astCubeRecs2._dimNames.size() > 0) return new SMTJavaAIError("查询多维度结果集无法对比"); // 获取对比方式 String userOperate = jsonAST.safeGetStr("operate", ""); if(SMTStatic.isNullOrEmpty(userOperate)) return new SMTJavaAIError("未指定对比方式"); String[] operate = matchGroupType(userOperate, _mapDiffType2Match); if(operate == null) return new SMTJavaAIError("对比方式" + userOperate + "不支持"); // 复制第一个结果集 List listCubeRecs = new ArrayList<>(); for(ASTCubeRecs astResult : childResult1._listRecordset) { listCubeRecs.add(astResult.cloneCubeRecs()); } // 附加第二个结果集 for(ASTCubeRecs astResult : childResult2._listRecordset) { listCubeRecs.add(astResult); } // 设置标题 astCubeRecs1._title += operate[1]; // 对比数据 if((error = astCubeRecs1._mapDimId2Recs.get(-1).diffValueRecords(operate[0], astCubeRecs2._mapDimId2Recs.get(-1))) != null) return error; astCubeRecs1._mapDimId2Recs.get(-1)._title += "和" + astCubeRecs2._mapDimId2Recs.get(-1)._title; r_result._listRecordset.add(astCubeRecs1); // 将原始曲线加入 for(ASTCubeRecs cubeRecs : listCubeRecs) { cubeRecs._isRawRS = true; r_result._listRecordset.add(cubeRecs); } return null; } private SMTJavaAIError call_top_metrics(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { int limit = jsonAST.safeGetInt("limit", 5); // 查询子结果集 SMTJavaAIError error = queryRecordAST2SQLTree(jsonPath + "recordset/", dbMap, jsonAST.getJson("recordset"), extArg, tranReq, r_result); if(error != null) return error; for(ASTCubeRecs astRecs : r_result._listRecordset) { for(ASTCubeRecsValue astRecsValue : astRecs._mapDimId2Recs.values()) { astRecsValue.limitRecords(limit); } } return null; } private String[] matchGroupType(String operate, Map mapGroupType2Match) { JaccardSimilarity jaccardSimilarity = new JaccardSimilarity(); String maxTitle = null; String maxKey = null; double maxMatch = 0; for(Entry entry : mapGroupType2Match.entrySet()) { String curKey = entry.getKey(); for(String line : entry.getValue()) { double curMatch = jaccardSimilarity.apply(line, operate); if(curMatch == 1) { return new String[] {curKey, entry.getValue()[0]}; } if(curMatch >= 0.8 && curMatch > maxMatch) { maxKey = curKey; maxTitle = entry.getValue()[0]; maxMatch = curMatch; } } } if(maxKey == null) return null; return new String[] {maxKey, maxTitle}; } private SMTJavaAIError call_agg_metrics(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { // 获取聚合操作 String[] operate = null; String userOperate = jsonAST.safeGetStr("operate", ""); if(SMTStatic.isNullOrEmpty(userOperate)) { operate = new String[]{"AVG", "平均"}; } else { operate = matchGroupType(userOperate, _mapGroupType2Match); if(operate == null) { operate = new String[]{"AVG", "平均"}; } } // 获取时间步长 String userStep = jsonAST.safeGetStr("step_time", ""); // 如果存在时间步长,且子操作是:query_metrics,则将其传递到子对象参数 boolean groupInQuery = false; Json jsonRS = jsonAST.getJson("recordset"); String childCall = jsonRS.safeGetStr("call", ""); //if(!SMTStatic.isNullOrEmpty(userStep)) { if("query_metrics".equals(childCall)) { Json jsonRSArgs = jsonRS.getJson("args"); jsonRSArgs.set("step_op_key", operate[0]); jsonRSArgs.set("step_op_title", operate[1]); if(!SMTStatic.isNullOrEmpty(userStep)) { jsonRSArgs.set("step_time_path", jsonPath + "step_time"); jsonRSArgs.set("step_time", userStep); } String stepDim = jsonAST.safeGetStr("dim_name", null); if(!SMTStatic.isNullOrEmpty(stepDim)) jsonRSArgs.set("step_dim_name", stepDim); groupInQuery = true; } } // 如果存在分组,且子操作是query_base_info,则将其传递到子对象参数 if(operate != null && ("query_base_info".equals(childCall) || "query_only_metrics".equals(childCall))) { Json jsonRSArgs = jsonRS.getJson("args"); jsonRSArgs.set("step_op_key", operate[0]); jsonRSArgs.set("step_op_title", operate[1]); jsonRSArgs.set("step_time_path", jsonPath + "step_time"); String stepDim = jsonAST.safeGetStr("dim_name", null); if(!SMTStatic.isNullOrEmpty(stepDim)) jsonRSArgs.set("step_dim_name", stepDim); groupInQuery = true; } // 查询子结果集 SMTJavaAIError error = queryRecordAST2SQLTree(jsonPath + "recordset/", dbMap, jsonRS, null, tranReq, r_result); if(error != null) return error; // 聚合操作 if(!groupInQuery) { for(ASTCubeRecs astRecs : r_result._listRecordset) { ASTCubeRecsType[] cubeRecsType = new ASTCubeRecsType[1]; for(ASTCubeRecsValue astRecsValue : astRecs._mapDimId2Recs.values()) { String aggDimName = jsonAST.safeGetStr("dim_name", null); if(!SMTStatic.isNullOrEmpty(aggDimName)) { SMTDimensionDef dimDef = SMTAIServerApp.getApp().getDimensionDef(aggDimName); astRecsValue._groupField = dimDef.getId(); astRecsValue._groupType = dimDef.getType(); } cubeRecsType[0] = astRecs._recsType; if((error = astRecsValue.groupValueRecords(operate[0], userStep, cubeRecsType, new String[][] {astRecs._colTitles})) != null) return error; astRecs._colTitles = new String[] {operate[1]}; if(cubeRecsType[0] == ASTCubeRecsType.SUMMARY) astRecsValue._title += operate[1]; } astRecs._recsType = cubeRecsType[0]; if(cubeRecsType[0] != ASTCubeRecsType.SUMMARY) { if(!SMTStatic.isNullOrEmpty(userStep)) { int[] timeStep = SMTAIServerApp.convStrToTimeStep(userOperate); astRecs._title += "每" + SMTAIServerApp.convTimeStepToStr(timeStep[0], timeStep[1]) + "的" + operate[1]; } else { astRecs._title += operate[1]; } } } } return null; } private SMTJavaAIError call_query_metrics(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { // 获取指标对象 Object oMetrics = queryMetricsType(dbMap, jsonAST, tranReq); if(oMetrics instanceof SMTJavaAIError) return (SMTJavaAIError)oMetrics; SMTMetricsDef matchMetrics = (SMTMetricsDef)oMetrics; SMTJavaAIError error = matchMetrics.queryMetrics(jsonPath, dbMap, jsonAST, extArg, tranReq, r_result); return error; } private SMTJavaAIError call_query_only_metrics(String jsonPath, ASTDBMap dbMap, Json jsonAST, Map extArg, SMTAIServerRequest tranReq, ASTResult r_result) throws Exception { // 获取指标对象 SMTMetricsDef matchMetrics = null; Map mapId2Metrics = SMTAIServerApp.getApp().getMetricsMap(this.getAgentId()); for(SMTMetricsDef curMatchMetrics : mapId2Metrics.values()) { matchMetrics = curMatchMetrics; break; } SMTJavaAIError error = matchMetrics.queryMetrics(jsonPath, dbMap, jsonAST, extArg, tranReq, r_result); return error; } private Object queryMetricsType(ASTDBMap dbMap, Json jsonAST, SMTAIServerRequest tranReq) throws Exception { // 从参数中获取用户输入的指标类型 String metricsName = jsonAST.safeGetStr("metrics", ""); if(SMTStatic.isNullOrEmpty(metricsName)) { for(String metricsAilas : _metricsAlisList) { metricsName = jsonAST.safeGetStr(metricsAilas, ""); if(!SMTStatic.isNullOrEmpty(metricsName)) { SMTDimensionDef dimDef = SMTAIServerApp.getApp().getDimensionDef(metricsAilas); if(dimDef == null) continue; JaccardSimilarity jaccardSimilarity = new JaccardSimilarity(); metricsName = dimDef.matchDimension(metricsName, jaccardSimilarity); if(metricsName == null) continue; break; } } if(SMTStatic.isNullOrEmpty(metricsName)) return new SMTJavaAIError("未解析出指标信息"); } SMTMetricsDef metricsDef = queryMetricsType(metricsName); if(metricsDef != null) return metricsDef; if(metricsName.startsWith("监测") && jsonAST.has("device")) { metricsName = jsonAST.getJson("device").asString() + jsonAST.safeGetStr("metrics", ""); metricsDef = queryMetricsType(metricsName); } if(metricsDef != null) return metricsDef; return new SMTJavaAIError("未找到匹配的指标:" + metricsName); } private SMTMetricsDef queryMetricsType(String metricsName) throws Exception { double minSimValue = SMTStatic.toDouble(SMTAIServerApp.getApp().getGlobalConfig("history.math.ratio")); // 匹配指标定义 double maxSim = 0; SMTMetricsDef matchMetrics = null; TreeMap mapSim2Metrics = new TreeMap<>(); Map mapId2Metrics = SMTAIServerApp.getApp().getMetricsMap(this.getAgentId()); JaccardSimilarity jaccardSimilarity = new JaccardSimilarity(); for(SMTMetricsDef metricsDef : mapId2Metrics.values()) { double curSim = metricsDef.matchMetrics(metricsName, jaccardSimilarity); if(curSim == 1) { maxSim = 1; matchMetrics = metricsDef; break; } else if(curSim >= minSimValue) { if(maxSim <= curSim) { maxSim = curSim; matchMetrics = metricsDef; } } mapSim2Metrics.put(curSim, metricsDef); } if(maxSim < minSimValue) return null; return matchMetrics; } @Override public void queryUnknowQuestionList(String keyword, SMTAIServerRequest tranReq) throws Exception { Map mapId2Metrics = SMTAIServerApp.getApp().getMetricsMap(this.getAgentId()); JaccardSimilarity jaccardSimilarity = new JaccardSimilarity(); for(SMTMetricsDef metricsDef : mapId2Metrics.values()) { metricsDef.queryUnknowQuestionList(jaccardSimilarity, keyword, tranReq); } } }