23 Commits
v0.5 ... v0.6

Author SHA1 Message Date
lexluo09
041daad1e4 [improvement][project] supersonic 0.6.0 version update (#16)
Co-authored-by: lexluo <lexluo@tencent.com>
2023-07-16 21:32:33 +08:00
Jun Zhang
a0869dc7bd [docs]update README: new architecture diagram and demo gif (#15) 2023-07-14 10:41:19 +08:00
lexluo09
c40efebc7a Merge pull request #14 from lexluo09/master
[improvement](chat) unformatted hanlp code make seach/query work
2023-07-08 15:53:54 +08:00
lexluo
75e15f4c50 [improvement](chat) unformatted hanlp code make seach/query work 2023-07-08 15:50:39 +08:00
lexluo09
d551fcd6d2 Merge pull request #13 from lexluo09/master
[improvement][project] global refactor , code format , support llm.
2023-07-08 15:04:15 +08:00
lexluo
404163f391 [improvement][project] global refactor , code format , support llm , support fuzzy detect ,support query filter and so on. 2023-07-08 15:00:03 +08:00
williamhliu
5ffd617431 metric trend add default date option selected (#12)
* [feature](webapp) modify multy-turn conversation and optimize css styles

* [feature](chat-sdk) add default date option selected
2023-07-08 11:51:26 +08:00
williamhliu
ef4fca9671 [feature](webapp) modify multy-turn conversation and optimize css styles (#11)
williamhliu <williamhliu@tencent.com>
2023-07-03 19:28:55 +08:00
Jun Zhang
291187229c Merge pull request #10 from williamhliu/master
modify chat ui interface and experience
2023-06-30 17:58:39 +08:00
williamhliu
ad83e752b9 [feature](webapp) upgrade chat version 2023-06-30 17:49:25 +08:00
williamhliu
805a59dddd [feature](webapp) upgrade chat version 2023-06-30 17:42:03 +08:00
williamhliu
8639c23dc4 Merge branch 'tencentmusic:master' into master 2023-06-30 15:25:22 +08:00
williamhliu
7c57a1cb33 [feature](chat-sdk) optimize 2023-06-30 15:24:14 +08:00
Jun Zhang
bab8b67217 Merge pull request #9 from daikon12/supersonic-local
[improvement][chat] rename dictionaryInfo to knowledgeInfo
2023-06-22 09:24:56 +08:00
kanedai
b9890d1fd3 [improvement][chat] rename dictionaryInfo to knowledgeInfo 2023-06-21 17:06:00 +08:00
Jun Zhang
358d5e5288 Merge pull request #8 from sevenliu1896/master
semantic-fe  update and fix merge conflicts
2023-06-21 16:55:13 +08:00
tristanliu
8adecd0b58 Merge branch 'feature/tristanliu'
# Conflicts:
#	webapp/packages/supersonic-fe/src/pages/Chat/index.tsx
2023-06-21 16:35:26 +08:00
tristanliu
df74c62ac9 [improvement](semantic-fe) Update the field of /chat/conf interface, change 'dictionaryInfos' to 'knowledgeInfos' 2023-06-21 16:27:03 +08:00
tristanliu
9c9d7382fe [improvement](semantic-fe) Added parameter compatibility for network requests in different modes 2023-06-21 11:10:07 +08:00
Jun Zhang
e4266d37ab Merge pull request #6 from williamhliu/master
[improvement](webapp) change rollup terser package
2023-06-20 16:38:37 +08:00
williamhliu
3f6af3b1fb [improvement](webapp) change rollup terser package 2023-06-20 15:03:48 +08:00
lexluo09
c97456c718 Merge pull request #5 from lexluo09/master
[improvement](chat) remove unnecessary class and adjust package and a…
2023-06-15 19:59:02 +08:00
lexluo
95a698b875 [improvement](chat) remove unnecessary class and adjust package and add flattened-pom.xml ignore 2023-06-15 19:56:51 +08:00
559 changed files with 33543 additions and 8141 deletions

2
.gitignore vendored
View File

@@ -13,3 +13,5 @@ assembly/runtime/*
**/dist/
*.umi/
/assembly/deploy
/runtime
**/.flattened-pom.xml

View File

@@ -8,3 +8,14 @@ davinci 0.3.0 change log
7) optimize css style
8) optimize filter
9) delete view module
supersonic 0.6.0 change log
1) add llm parser and llm api server
2) support fuzzy mapping
3) support query filter and domain filter in query and search
4) support standalone mode
5) add dsl query in semantic
6) code architecture adjustment in semantic and chat
7) add unit testing and integration testing
8) support dimension and metric alias

View File

@@ -2,7 +2,10 @@ English | [中文](README_CN.md)
# SuperSonic (超音数)
**SuperSonic is an out-of-the-box yet highly extensible framework for building a data chatbot**. SuperSonic provides a chat interface that empowers users to query data using natural language and visualize the results with suitable charts. To enable such experience, the only thing necessary is to build a logical semantic model (definition of metrics, dimensions, relationships, etc) on top of the physical data stores, and no data modification or copying is required. Meanwhile SuperSonic is designed to be plug-and-play, allowing new functionalities to be added through plugins and core components to be integrated into other systems.
**SuperSonic is an out-of-the-box yet highly extensible framework for building a data chatbot**. SuperSonic provides a chat interface that empowers users to query data using natural language and visualize the results with suitable charts. To enable such experience, the only thing necessary is to define logical semantic models (metrics, dimensions, relationships, etc) on top of physical data models, and no data modification or copying is required. Meanwhile SuperSonic is designed to be plugable, allowing new functionalities to be added through plugins and core components to be integrated into other systems.
<img src="./docs/images/supersonic_demo.gif" align="center"/>
## Motivation
@@ -19,32 +22,49 @@ With these ideas in mind, we developed SuperSonic as a reference implementation
- Built-in graphical interface for business users to enter data queries
- Built-in graphical interface for analytics engineers to manage semantic models
- Support input auto-completion as well as query recommendation
- Support multi-turn conversation and switch context automatically
- Support multi-turn conversation and history context management
- Support three-level permission control: domain-level, column-level and row-level
## Extensible Components
SuperSonic contains four core components, each of which can be extended or integrated:
SuperSonic is composed of two layers: supersonic-chat and supersonic-semantic. The chat layer is responsible for converting **natural language query** into semantic query (also known as DSL query), whereas the semantic layer is responsible for converting DSL query into **SQL query**. The high-level architecture and main process flow is shown in below diagram:
<img src="./docs/images/supersonic_components.png" height="50%" width="50%" align="center"/>
- **Chat interface:** accepts user queries and answer results with approriate visualization charts. It supports input auto-completion as well as multi-turn conversation.
<img src="./docs/images/supersonic_components.png" height="80%" width="80%" align="center"/>
- **Schema mapper:** identifies references to schema elements in natural language queries. It matches queries against the knowledage base which is constructed using the schema of semantic models.
- **Semantic parser chain:** resolves query mode and choose the most suitable semantic model. It is composed of a group of rule-based and model-based parsers, each of which deals with specific scenarios.
### Chat Layer
- **Semantic model layer:** manages semantic models and generate SQL statement given specific semantic model and related semantic items. It encapsulates technical concepts, calculation formulas and entity relationships of the underlying data.
The chat layer contains four core components:
- **Chat Interface:** accepts user queries and answer results with appropriate visualization charts. It supports input auto-completion as well as multi-turn conversation.
- **Schema Mapper Chain:** identifies references to semantic schema elements in user queries. It matches queries against the knowledage base which is constructed using the schema of semantic models.
- **Semantic Parser Chain:** resolves query mode based on mapped semantic models. It is composed of a group of rule-based and model-based parsers, each of which deals with specific scenarios.
- **Semantic Query:** performs execution according to the results of semantic parsing. The default semantic query would submit DSL to the semantic component, but new types of semantic query can be extended.
### Semantic Layer
The semantic layer contains four core components:
- **Modeling Interface:** empowers analytics engineers to visually define and maintain semantic models. The configurations related to access permission and chat conversation can also be set on the UI.
- **DSL Parser:** converts DSL expression to intermediate structures. To make it easily integratable with analytics applications, SQL (without joins and calculation formulas) is used as the DSL.
- **Query Planner:** builds and optimizes query plans according to various rules.
- **SQL Generator:** generates final SQL expression (with joins and calculation formulas) based on the query plan.
## Quick Demo
SuperSonic comes with a sample semantic data model as well as sample chat that can be used as a starting point. Please follow the steps:
SuperSonic comes with sample semantic models as well as chat conversations that can be used as a starting point. Please follow the steps:
- Download the latest prebuilt binary from the release page
- Run script "bin/start-all.sh" to start services
- Visit http://localhost:9080 in browser to explore chat interface
- Visit http://localhost:9081 in browser to explore modeling interface
- Download the latest prebuilt binary from the [release page](https://github.com/tencentmusic/supersonic/releases)
- Run script "bin/start-standalone.sh" to start a standalone server
- Visit http://localhost:9080 in browser to start exploration
## How to Build
Download the source code and run script "assembly/bin/build-all.sh" to build both front-end webapp and back-end services
Pull the source code and run script "assembly/bin/build-standalone.sh" to build packages in the standalone mode.

View File

@@ -2,6 +2,8 @@
**超音数是一个开箱即用且易于扩展的数据问答对话框架**。通过超音数的问答对话界面,用户能够使用自然语言查询数据,系统会选择合适的可视化图表呈现结果。超音数不需要修改或复制数据,只需要在物理数据库之上构建逻辑语义模型(定义指标、维度、相互间关系等),即可开启数据问答体验。与此同时,超音数被设计为可插拔式框架,允许以插件形式来扩展新功能,或者将核心组件与其他系统集成。
<img src="./docs/images/supersonic_demo.gif" align="center"/>
## 项目动机
大型语言模型LLMs如ChatGPT的出现正在重塑信息检索的方式。在数据分析领域学术界和工业界主要关注利用深度学习模型将自然语言查询转换为SQL查询。虽然一些工作显示出有前景的结果但它们还并不适用于实际场景。
@@ -22,9 +24,13 @@
## 易于扩展的组件
超音数包含四个核心组件,每个都易于扩展或被集成
超音数主要分为两层supersonic-chat and supersonic-semantic。问答层负责将自然语言查询转换为语义查询也称为DSL查询而语义层负责将DSL查询转换为SQL查询。超音数的整体架构和主流程如下图所示
<img src="./docs/images/supersonic_components.png" height="50%" width="50%" align="center"/>
<img src="./docs/images/supersonic_components.png" height="80%" width="80%" align="center"/>
### 问答层
问答层包含以下4个核心组件
- **问答对话界面(chat interface)**:接受用户查询并选择合适的可视化图表呈现结果,支持输入联想和多轮对话。
@@ -32,17 +38,28 @@
- **语义解析器链(semantic parser chain)**:识别查询模式并选择最匹配的语义模型,其由一组基于规则或模型的解析器组成,每个解析器可用于应对不同的特定场景。
- **语义模型层(semantic model layer)**建模阶段负责构建与管理语义模型查询阶段依据给定的语义模型来生成SQL语句。
- **语义查询(semantic query)**: 根据语义解析的结果执行查询默认的语义查询会将DSL提交给语义组件但可以扩展新类型的查询。
### 语义层
语义层包含以下4个核心组件
- **语义建模界面(modeling interface)**:使分析工程师能够通过可视化方式定义和维护语义模型,与访问权限和聊天对话相关的配置也可以在用户界面上设置。
- **DSL解析器(DSL parser)**将DSL表达式转换为中间结构。为了使其易于与分析应用程序集成使用SQL不含join和计算公式来作为DSL。
- **查询计划器(query planner)**:根据各种规则来构建和优化查询计划。
- **SQL生成器(SQL genenrator)**基于查询计划来生成最终的SQL语句含join和计算公式
## 快速体验
超音数自带样例的语义模型和问答对话,只需以下三步即可快速体验:
- 从release page下载预先构建好的发行包
- 运行 "bin/start-all.sh"启动前后端服务
- 在浏览器访问http://localhost:9080 开启数据问答探索
- 在浏览器访问http://localhost:9081 开启语义建模探索
-[release page](https://github.com/tencentmusic/supersonic/releases)下载预先构建好的发行包
- 运行 "bin/start-standalone.sh"启动服务
- 在浏览器访问http://localhost:9080 开启探索
## 如何构建
下载源码包,运行脚本"assembly/bin/build-all.sh",会将前后端一起编译打包
下载源码包,运行脚本"assembly/bin/build-standalone.sh",将所有服务一起编译打包

12
assembly/bin/build-chat.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
sbinDir=$(cd "$(dirname "$0")"; pwd)
baseDir=$(readlink -f $sbinDir/../)
runtimeDir=$baseDir/runtime
buildDir=$baseDir/build
cd $baseDir
#1. move package to build
cp $baseDir/../launchers/chat/target/*.tar.gz ${buildDir}/supersonic-chat.tar.gz

View File

@@ -7,14 +7,13 @@ buildDir=$baseDir/build
cd $baseDir
#1. build semantic chat service
#1. build semantic service
rm -fr ${buildDir}/*.tar.gz
rm -fr dist
mvn -f $baseDir/../ clean package -DskipTests
#2. move package to build
cp $baseDir/../launchers/chat/target/*.tar.gz ${buildDir}/supersonic-chat.tar.gz
cp $baseDir/../launchers/semantic/target/*.tar.gz ${buildDir}/supersonic-semantic.tar.gz
#3. build webapp

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
sbinDir=$(cd "$(dirname "$0")"; pwd)
baseDir=$(readlink -f $sbinDir/../)
runtimeDir=$baseDir/runtime
buildDir=$baseDir/build
cd $baseDir
#1. build semantic chat service
rm -fr ${buildDir}/*.tar.gz
rm -fr dist
mvn -f $baseDir/../ clean package -DskipTests
#2. move package to build
cp $baseDir/../launchers/standalone/target/*.tar.gz ${buildDir}/supersonic.tar.gz
#3. build webapp
chmod +x $baseDir/../webapp/start-fe-prod.sh
cd ../webapp
sh ./start-fe-prod.sh
cp -fr ./supersonic-webapp.tar.gz ${buildDir}/

35
assembly/bin/start-chat.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
sbinDir=$(cd "$(dirname "$0")"; pwd)
baseDir=$(readlink -f $sbinDir/../)
runtimeDir=$baseDir/../runtime
buildDir=$baseDir/build
cd $baseDir
#2. package lib
tar -zxvf ${buildDir}/supersonic-chat.tar.gz -C ${runtimeDir}
mv ${runtimeDir}/launchers-chat-* ${runtimeDir}/supersonic-chat
tar -zxvf ${buildDir}/supersonic-webapp.tar.gz -C ${buildDir}
mkdir -p ${runtimeDir}/supersonic-chat/webapp
cp -fr ${buildDir}/supersonic-webapp/* ${runtimeDir}/supersonic-chat/webapp
rm -fr ${buildDir}/supersonic-webapp
json=$(cat ${runtimeDir}/supersonic-chat/webapp/supersonic.config.json)
json=$(echo $json | jq '.env="chat"')
echo $json > ${runtimeDir}/supersonic-chat/webapp/supersonic.config.json
#3. start service
#3.1 start chat service
echo ${runtimeDir}
sh ${runtimeDir}/supersonic-chat/bin/service.sh restart
#3.2 start llm service
sh ${runtimeDir}/supersonic-chat/llm/bin/service.sh restart

View File

@@ -2,7 +2,7 @@
sbinDir=$(cd "$(dirname "$0")"; pwd)
baseDir=$(readlink -f $sbinDir/../)
runtimeDir=$baseDir/runtime
runtimeDir=$baseDir/../runtime
buildDir=$baseDir/build
cd $baseDir
@@ -14,23 +14,23 @@ rm -fr ${runtimeDir}/*
#2. package lib
tar -zxvf ${buildDir}/supersonic-semantic.tar.gz -C ${runtimeDir}
tar -zxvf ${buildDir}/supersonic-chat.tar.gz -C ${runtimeDir}
mv ${runtimeDir}/launchers-chat-* ${runtimeDir}/supersonic-chat
mv ${runtimeDir}/launchers-semantic-* ${runtimeDir}/supersonic-semantic
tar -zxvf ${buildDir}/supersonic-webapp.tar.gz -C ${buildDir}
mkdir -p ${runtimeDir}/supersonic-semantic/webapp
mkdir -p ${runtimeDir}/supersonic-chat/webapp
cp -fr ${buildDir}/supersonic-webapp/* ${runtimeDir}/supersonic-semantic/webapp
cp -fr ${buildDir}/supersonic-webapp/* ${runtimeDir}/supersonic-chat/webapp
rm -fr ${buildDir}/supersonic-webapp
json=$(cat ${runtimeDir}/supersonic-semantic/webapp/supersonic.config.json)
json=$(echo $json | jq '.env="semantic"')
echo $json > ${runtimeDir}/supersonic-semantic/webapp/supersonic.config.json
#3. start service
sh ${runtimeDir}/supersonic-semantic/bin/service.sh restart
sleep 5
sh ${runtimeDir}/supersonic-chat/bin/service.sh restart

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
sbinDir=$(cd "$(dirname "$0")"; pwd)
baseDir=$(readlink -f $sbinDir/../)
runtimeDir=$baseDir/../runtime
buildDir=$baseDir/build
cd $baseDir
#1. clear file
mkdir -p ${runtimeDir}
rm -fr ${runtimeDir}/*
#2. package lib
tar -zxvf ${buildDir}/supersonic.tar.gz -C ${runtimeDir}
mv ${runtimeDir}/launchers-standalone-* ${runtimeDir}/supersonic-standalone
tar -zxvf ${buildDir}/supersonic-webapp.tar.gz -C ${buildDir}
mkdir -p ${runtimeDir}/supersonic-standalone/webapp
cp -fr ${buildDir}/supersonic-webapp/* ${runtimeDir}/supersonic-standalone/webapp
rm -fr ${buildDir}/supersonic-webapp
#3. start service
#start standalone service
sh ${runtimeDir}/supersonic-standalone/bin/service.sh restart
#start llm service
sh ${runtimeDir}/supersonic-standalone/llm/bin/service.sh restart

View File

@@ -28,6 +28,12 @@
<include>*.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/../../chat/core/src/main/python</directory>
<outputDirectory>llm</outputDirectory>
<fileMode>0777</fileMode>
<directoryMode>0755</directoryMode>
</fileSet>
</fileSets>
<dependencySets>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.tencent.supersonic</groupId>

View File

@@ -1,4 +1,4 @@
package com.tencent.supersonic.auth.authorization.domain.pojo;
package com.tencent.supersonic.auth.api.authorization.pojo;
import java.util.List;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.tencent.supersonic.auth.authorization.domain.pojo;
package com.tencent.supersonic.auth.api.authorization.pojo;
import java.beans.Transient;
import java.util.ArrayList;

View File

@@ -1,6 +1,7 @@
package com.tencent.supersonic.auth.api.authorization.request;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
@@ -11,6 +12,10 @@ import lombok.ToString;
public class QueryAuthResReq {
private String user;
private List<String> departmentIds = new ArrayList<>();
private List<AuthRes> resources;
private String domainId;
}

View File

@@ -1,10 +1,18 @@
package com.tencent.supersonic.auth.api.authorization.service;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup;
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
public interface AuthService {
AuthorizedResourceResp queryAuthorizedResources(HttpServletRequest request, QueryAuthResReq req);
List<AuthGroup> queryAuthGroups(String domainId, Integer groupId);
void updateAuthGroup(AuthGroup group);
void removeAuthGroup(AuthGroup group);
AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, HttpServletRequest request);
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>auth</artifactId>

View File

@@ -37,13 +37,6 @@ public class UserDOExample {
oredCriteria = new ArrayList<Criteria>();
}
/**
* @mbg.generated
*/
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
/**
* @mbg.generated
*/
@@ -54,8 +47,8 @@ public class UserDOExample {
/**
* @mbg.generated
*/
public void setDistinct(boolean distinct) {
this.distinct = distinct;
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
/**
@@ -65,6 +58,13 @@ public class UserDOExample {
return distinct;
}
/**
* @mbg.generated
*/
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
/**
* @mbg.generated
*/
@@ -116,13 +116,6 @@ public class UserDOExample {
distinct = false;
}
/**
* @mbg.generated
*/
public void setLimitStart(Integer limitStart) {
this.limitStart = limitStart;
}
/**
* @mbg.generated
*/
@@ -133,8 +126,8 @@ public class UserDOExample {
/**
* @mbg.generated
*/
public void setLimitEnd(Integer limitEnd) {
this.limitEnd = limitEnd;
public void setLimitStart(Integer limitStart) {
this.limitStart = limitStart;
}
/**
@@ -144,6 +137,13 @@ public class UserDOExample {
return limitEnd;
}
/**
* @mbg.generated
*/
public void setLimitEnd(Integer limitEnd) {
this.limitEnd = limitEnd;
}
/**
* s2_user null
*/
@@ -561,38 +561,6 @@ public class UserDOExample {
private String typeHandler;
public String getCondition() {
return condition;
}
public Object getValue() {
return value;
}
public Object getSecondValue() {
return secondValue;
}
public boolean isNoValue() {
return noValue;
}
public boolean isSingleValue() {
return singleValue;
}
public boolean isBetweenValue() {
return betweenValue;
}
public boolean isListValue() {
return listValue;
}
public String getTypeHandler() {
return typeHandler;
}
protected Criterion(String condition) {
super();
this.condition = condition;
@@ -628,5 +596,37 @@ public class UserDOExample {
protected Criterion(String condition, Object value, Object secondValue) {
this(condition, value, secondValue, null);
}
public String getCondition() {
return condition;
}
public Object getValue() {
return value;
}
public Object getSecondValue() {
return secondValue;
}
public boolean isNoValue() {
return noValue;
}
public boolean isSingleValue() {
return singleValue;
}
public boolean isBetweenValue() {
return betweenValue;
}
public boolean isListValue() {
return listValue;
}
public String getTypeHandler() {
return typeHandler;
}
}
}

View File

@@ -2,6 +2,7 @@ package com.tencent.supersonic.auth.authentication.domain.interceptor;
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
import com.tencent.supersonic.auth.authentication.application.UserServiceImpl;
import com.tencent.supersonic.auth.authentication.domain.utils.UserTokenUtils;
@@ -14,14 +15,11 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
@Component
@Slf4j
public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws AccessException {
@@ -30,11 +28,12 @@ public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor
userTokenUtils = ContextUtils.getBean(UserTokenUtils.class);
s2ThreadContext = ContextUtils.getBean(S2ThreadContext.class);
if (!authenticationConfig.isEnabled()) {
setFakerUser(request);
return true;
}
if (isInternalRequest(request)) {
String token = userTokenUtils.generateAdminToken();
reflectSetparam(request, authenticationConfig.getTokenHttpHeaderKey(), token);
setFakerUser(request);
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
@@ -50,15 +49,25 @@ public class DefaultAuthenticationInterceptor extends AuthenticationInterceptor
UserWithPassword user = userTokenUtils.getUserWithPassword(request);
if (StringUtils.isNotBlank(user.getName())) {
ThreadContext threadContext = ThreadContext.builder()
.token(request.getHeader(authenticationConfig.getTokenHttpHeaderKey()))
.userName(user.getName())
.build();
s2ThreadContext.set(threadContext);
setContext(user.getName(), request);
return true;
}
throw new AccessException("authentication failed, please login");
}
private void setFakerUser(HttpServletRequest request) {
String token = userTokenUtils.generateAdminToken();
reflectSetparam(request, authenticationConfig.getTokenHttpHeaderKey(), token);
setContext(User.getFakeUser().getName(), request);
}
private void setContext(String userName, HttpServletRequest request) {
ThreadContext threadContext = ThreadContext.builder()
.token(request.getHeader(authenticationConfig.getTokenHttpHeaderKey()))
.userName(userName)
.build();
s2ThreadContext.set(threadContext);
}
}

View File

@@ -22,7 +22,7 @@ public class InterceptorFactory implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
for (AuthenticationInterceptor authenticationInterceptor : authenticationInterceptors) {
registry.addInterceptor(authenticationInterceptor).addPathPatterns("/**")
.excludePathPatterns("/", "/webapp/**","/error");
.excludePathPatterns("/", "/webapp/**", "/error");
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>auth</artifactId>

View File

@@ -1,118 +0,0 @@
package com.tencent.supersonic.auth.authorization.application;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthResGrp;
import com.tencent.supersonic.auth.api.authorization.pojo.DimensionFilter;
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
import com.tencent.supersonic.auth.authorization.domain.pojo.AuthGroup;
import com.tencent.supersonic.auth.authorization.domain.pojo.AuthRule;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@Component
@Slf4j
public class AuthApplicationService {
@Autowired
private JdbcTemplate jdbcTemplate;
private List<AuthGroup> load() {
List<String> rows = jdbcTemplate.queryForList("select config from s2_auth_groups", String.class);
Gson g = new Gson();
return rows.stream().map(row -> g.fromJson(row, AuthGroup.class)).collect(Collectors.toList());
}
public List<AuthGroup> queryAuthGroups(String domainId, Integer groupId) {
return load().stream()
.filter(group -> (Objects.isNull(groupId) || groupId.equals(group.getGroupId()))
&& domainId.equals(group.getDomainId()))
.collect(Collectors.toList());
}
public void updateAuthGroup(AuthGroup group) {
Gson g = new Gson();
if (group.getGroupId() == null) {
int nextGroupId = 1;
String sql = "select max(group_id) as group_id from s2_auth_groups";
Integer obj = jdbcTemplate.queryForObject(sql, Integer.class);
if (obj != null) {
nextGroupId = obj + 1;
}
group.setGroupId(nextGroupId);
jdbcTemplate.update("insert into s2_auth_groups (group_id, config) values (?, ?);", nextGroupId,
g.toJson(group));
} else {
jdbcTemplate.update("update s2_auth_groups set config = ? where group_id = ?;", g.toJson(group),
group.getGroupId());
}
}
public AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, HttpServletRequest request) {
List<AuthGroup> groups = load().stream().
filter(group -> group.getAuthorizedUsers().contains(req.getUser()) && req.getDomainId()
.equals(group.getDomainId())).
collect(Collectors.toList());
AuthorizedResourceResp resource = new AuthorizedResourceResp();
Map<String, List<AuthGroup>> authGroupsByDomainId = groups.stream()
.collect(Collectors.groupingBy(AuthGroup::getDomainId));
Map<String, List<AuthRes>> reqAuthRes = req.getResources().stream()
.collect(Collectors.groupingBy(AuthRes::getDomainId));
for (String domainId : reqAuthRes.keySet()) {
List<AuthRes> reqResourcesList = reqAuthRes.get(domainId);
AuthResGrp rg = new AuthResGrp();
if (authGroupsByDomainId.containsKey(domainId)) {
List<AuthGroup> authGroups = authGroupsByDomainId.get(domainId);
for (AuthRes reqRes : reqResourcesList) {
for (AuthGroup authRuleGroup : authGroups) {
List<AuthRule> authRules = authRuleGroup.getAuthRules();
List<String> allAuthItems = new ArrayList<>();
authRules.stream().forEach(authRule -> allAuthItems.addAll(authRule.resourceNames()));
if (allAuthItems.contains(reqRes.getName())) {
rg.getGroup().add(reqRes);
}
}
}
}
if (Objects.nonNull(rg) && !CollectionUtils.isEmpty(rg.getGroup())) {
resource.getResources().add(rg);
}
}
if (StringUtils.isNotEmpty(req.getDomainId())) {
List<AuthGroup> authGroups = authGroupsByDomainId.get(req.getDomainId());
if (!CollectionUtils.isEmpty(authGroups)) {
for (AuthGroup group : authGroups) {
if (group.getDimensionFilters() != null
&& group.getDimensionFilters().stream().anyMatch(expr -> !Strings.isNullOrEmpty(expr))) {
DimensionFilter df = new DimensionFilter();
df.setDescription(group.getDimensionFilterDescription());
df.setExpressions(group.getDimensionFilters());
resource.getFilters().add(df);
}
}
}
}
return resource;
}
public void removeAuthGroup(AuthGroup group) {
jdbcTemplate.update("delete from s2_auth_groups where group_id = ?", group.getGroupId());
}
}

View File

@@ -1,24 +1,148 @@
package com.tencent.supersonic.auth.authorization.application;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRes;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthResGrp;
import com.tencent.supersonic.auth.api.authorization.pojo.DimensionFilter;
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
import com.tencent.supersonic.auth.api.authorization.service.AuthService;
import javax.servlet.http.HttpServletRequest;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthRule;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
@Slf4j
public class AuthServiceImpl implements AuthService {
private final AuthApplicationService authApplicationService;
private JdbcTemplate jdbcTemplate;
public AuthServiceImpl(AuthApplicationService authApplicationService) {
this.authApplicationService = authApplicationService;
public AuthServiceImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private List<AuthGroup> load() {
List<String> rows = jdbcTemplate.queryForList("select config from s2_auth_groups", String.class);
Gson g = new Gson();
return rows.stream().map(row -> g.fromJson(row, AuthGroup.class)).collect(Collectors.toList());
}
@Override
public AuthorizedResourceResp queryAuthorizedResources(HttpServletRequest request, QueryAuthResReq req) {
return authApplicationService.queryAuthorizedResources(req, request);
public List<AuthGroup> queryAuthGroups(String domainId, Integer groupId) {
return load().stream()
.filter(group -> (Objects.isNull(groupId) || groupId.equals(group.getGroupId()))
&& domainId.equals(group.getDomainId()))
.collect(Collectors.toList());
}
@Override
public void updateAuthGroup(AuthGroup group) {
Gson g = new Gson();
if (group.getGroupId() == null) {
int nextGroupId = 1;
String sql = "select max(group_id) as group_id from s2_auth_groups";
Integer obj = jdbcTemplate.queryForObject(sql, Integer.class);
if (obj != null) {
nextGroupId = obj + 1;
}
group.setGroupId(nextGroupId);
jdbcTemplate.update("insert into s2_auth_groups (group_id, config) values (?, ?);", nextGroupId,
g.toJson(group));
} else {
jdbcTemplate.update("update s2_auth_groups set config = ? where group_id = ?;", g.toJson(group),
group.getGroupId());
}
}
@Override
public void removeAuthGroup(AuthGroup group) {
jdbcTemplate.update("delete from s2_auth_groups where group_id = ?", group.getGroupId());
}
@Override
public AuthorizedResourceResp queryAuthorizedResources(QueryAuthResReq req, HttpServletRequest request) {
List<AuthGroup> groups = getAuthGroups(req);
AuthorizedResourceResp resource = new AuthorizedResourceResp();
Map<String, List<AuthGroup>> authGroupsByDomainId = groups.stream()
.collect(Collectors.groupingBy(AuthGroup::getDomainId));
Map<String, List<AuthRes>> reqAuthRes = req.getResources().stream()
.collect(Collectors.groupingBy(AuthRes::getDomainId));
for (String domainId : reqAuthRes.keySet()) {
List<AuthRes> reqResourcesList = reqAuthRes.get(domainId);
AuthResGrp rg = new AuthResGrp();
if (authGroupsByDomainId.containsKey(domainId)) {
List<AuthGroup> authGroups = authGroupsByDomainId.get(domainId);
for (AuthRes reqRes : reqResourcesList) {
for (AuthGroup authRuleGroup : authGroups) {
List<AuthRule> authRules = authRuleGroup.getAuthRules();
List<String> allAuthItems = new ArrayList<>();
authRules.forEach(authRule -> allAuthItems.addAll(authRule.resourceNames()));
if (allAuthItems.contains(reqRes.getName())) {
rg.getGroup().add(reqRes);
}
}
}
}
if (!CollectionUtils.isEmpty(rg.getGroup())) {
resource.getResources().add(rg);
}
}
if (StringUtils.isNotEmpty(req.getDomainId())) {
List<AuthGroup> authGroups = authGroupsByDomainId.get(req.getDomainId());
if (!CollectionUtils.isEmpty(authGroups)) {
for (AuthGroup group : authGroups) {
if (group.getDimensionFilters() != null
&& group.getDimensionFilters().stream().anyMatch(expr -> !Strings.isNullOrEmpty(expr))) {
DimensionFilter df = new DimensionFilter();
df.setDescription(group.getDimensionFilterDescription());
df.setExpressions(group.getDimensionFilters());
resource.getFilters().add(df);
}
}
}
}
return resource;
}
private List<AuthGroup> getAuthGroups(QueryAuthResReq req) {
List<AuthGroup> groups = load().stream().
filter(group -> {
if (!Objects.equals(group.getDomainId(), req.getDomainId())) {
return false;
}
if (!CollectionUtils.isEmpty(group.getAuthorizedUsers()) && group.getAuthorizedUsers()
.contains(req.getUser())) {
return true;
}
for (String deparmentId : req.getDepartmentIds()) {
if (!CollectionUtils.isEmpty(group.getAuthorizedDepartmentIds())
&& group.getAuthorizedDepartmentIds().contains(deparmentId)) {
return true;
}
}
return false;
}).collect(Collectors.toList());
log.info("user:{} department:{} authGroups:{}", req.getUser(), req.getDepartmentIds(), groups);
return groups;
}
}

View File

@@ -2,8 +2,8 @@ package com.tencent.supersonic.auth.authorization.rest;
import com.tencent.supersonic.auth.api.authorization.request.QueryAuthResReq;
import com.tencent.supersonic.auth.api.authorization.response.AuthorizedResourceResp;
import com.tencent.supersonic.auth.authorization.application.AuthApplicationService;
import com.tencent.supersonic.auth.authorization.domain.pojo.AuthGroup;
import com.tencent.supersonic.auth.api.authorization.service.AuthService;
import com.tencent.supersonic.auth.api.authorization.pojo.AuthGroup;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
@@ -19,16 +19,16 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
public class AuthController {
private final AuthApplicationService service;
private final AuthService authService;
public AuthController(AuthApplicationService service) {
this.service = service;
public AuthController(AuthService authService) {
this.authService = authService;
}
@GetMapping("/queryGroup")
public List<AuthGroup> queryAuthGroup(@RequestParam("domainId") String domainId,
@RequestParam(value = "groupId", required = false) Integer groupId) {
return service.queryAuthGroups(domainId, groupId);
return authService.queryAuthGroups(domainId, groupId);
}
/**
@@ -37,12 +37,12 @@ public class AuthController {
@PostMapping("/createGroup")
public void newAuthGroup(@RequestBody AuthGroup group) {
group.setGroupId(null);
service.updateAuthGroup(group);
authService.updateAuthGroup(group);
}
@PostMapping("/removeGroup")
public void removeAuthGroup(@RequestBody AuthGroup group) {
service.removeAuthGroup(group);
authService.removeAuthGroup(group);
}
/**
@@ -55,7 +55,7 @@ public class AuthController {
if (group.getGroupId() == null || group.getGroupId() == 0) {
throw new RuntimeException("groupId is empty");
}
service.updateAuthGroup(group);
authService.updateAuthGroup(group);
}
/**
@@ -68,6 +68,6 @@ public class AuthController {
@PostMapping("/queryAuthorizedRes")
public AuthorizedResourceResp queryAuthorizedResources(@RequestBody QueryAuthResReq req,
HttpServletRequest request) {
return service.queryAuthorizedResources(req, request);
return authService.queryAuthorizedResources(req, request);
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>supersonic</artifactId>

View File

@@ -1,4 +1,4 @@
package com.tencent.supersonic.chat.api.service;
package com.tencent.supersonic.chat.api.component;
import com.tencent.supersonic.chat.api.request.QueryContextReq;

View File

@@ -0,0 +1,51 @@
package com.tencent.supersonic.chat.api.component;
import com.github.pagehelper.PageInfo;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.semantic.api.core.request.PageDimensionReq;
import com.tencent.supersonic.semantic.api.core.request.PageMetricReq;
import com.tencent.supersonic.semantic.api.core.response.*;
import com.tencent.supersonic.semantic.api.query.request.QuerySqlReq;
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
import java.util.List;
/**
* This interface defines the contract for a semantic layer that provides a simplified and
* consistent view of data from multiple sources.
* The semantic layer abstracts away the complexity of the underlying data sources and provides
* a unified view of the data that is easier to understand and use.
* <p>
* The interface defines methods for getting metadata as well as querying data in the semantic layer.
* Implementations of this interface should provide concrete implementations that interact with the
* underlying data sources and return results in a consistent format. Or it can be implemented
* as proxy to a remote semantic service.
* </p>
*/
public interface SemanticLayer {
QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructReq, User user);
QueryResultWithSchemaResp queryBySql(QuerySqlReq querySqlReq, User user);
DomainSchemaResp getDomainSchemaInfo(Long domain, Boolean cacheEnable);
List<DomainSchemaResp> getDomainSchemaInfo(List<Long> ids);
List<DomainResp> getDomainListForViewer();
List<DomainResp> getDomainListForAdmin();
PageInfo<DimensionResp> queryDimensionPage(PageDimensionReq pageDimensionCmd);
PageInfo<MetricResp> queryMetricPage(PageMetricReq pageMetricCmd);
// PageInfo<MetricResp> queryMetricPage(PageMetricReq pageMetricCmd);
//
// PageInfo<DimensionResp> queryDimensionPage(PageDimensionReq pageDimensionCmd);
//
// List<DomainResp> getDomainListForAdmin();
//
// List<DomainResp> getDomainListForViewer();
}

View File

@@ -1,4 +1,4 @@
package com.tencent.supersonic.chat.api.service;
package com.tencent.supersonic.chat.api.component;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
@@ -13,5 +13,5 @@ import com.tencent.supersonic.chat.api.request.QueryContextReq;
*/
public interface SemanticParser {
boolean parse(QueryContextReq queryContext, ChatContext chatCtx);
void parse(QueryContextReq queryContext, ChatContext chatContext);
}

View File

@@ -0,0 +1,18 @@
package com.tencent.supersonic.chat.api.component;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.response.QueryResultResp;
/**
* This class defines the contract for a semantic query that executes specific type of
* query based on the results of semantic parsing.
*/
public interface SemanticQuery {
String getQueryMode();
QueryResultResp execute(User user);
SemanticParseInfo getParseInfo();
}

View File

@@ -0,0 +1,16 @@
package com.tencent.supersonic.chat.api.pojo;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class QueryFilter {
private List<Filter> filters = new ArrayList<>();
private Map<String, Object> params = new HashMap<>();
}

View File

@@ -3,8 +3,10 @@ package com.tencent.supersonic.chat.api.pojo;
import lombok.Data;
@Data
public class SchemaElementCount {
public class QueryMatchInfo {
SchemaElementType elementType;
String detectWord;
private Integer count = 0;
private double maxSimilarity;
}

View File

@@ -1,10 +1,16 @@
package com.tencent.supersonic.chat.api.pojo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SchemaElementMatch {
SchemaElementType elementType;
@@ -18,7 +24,4 @@ public class SchemaElementMatch {
String word;
Long frequency;
public SchemaElementMatch() {
}
}

View File

@@ -21,12 +21,12 @@ public class SchemaMapInfo {
return domainElementMatches;
}
public void setMatchedElements(Integer domain, List<SchemaElementMatch> elementMatches) {
domainElementMatches.put(domain, elementMatches);
}
public void setDomainElementMatches(
Map<Integer, List<SchemaElementMatch>> domainElementMatches) {
this.domainElementMatches = domainElementMatches;
}
public void setMatchedElements(Integer domain, List<SchemaElementMatch> elementMatches) {
domainElementMatches.put(domain, elementMatches);
}
}

View File

@@ -5,6 +5,7 @@ import com.tencent.supersonic.common.enums.AggregateTypeEnum;
import com.tencent.supersonic.common.pojo.DateConf;
import com.tencent.supersonic.common.pojo.Order;
import com.tencent.supersonic.common.pojo.SchemaItem;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
@@ -27,4 +28,7 @@ public class SemanticParseInfo {
private DateConf dateInfo;
private Long limit;
private Boolean nativeQuery = false;
private Double bonus = 0d;
private List<SchemaElementMatch> elementMatches = new ArrayList<>();
private Object info;
}

View File

@@ -1,11 +1,14 @@
package com.tencent.supersonic.chat.api.request;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import com.tencent.supersonic.chat.api.pojo.QueryFilter;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class QueryContextReq {
@@ -13,7 +16,8 @@ public class QueryContextReq {
private Integer chatId;
private Integer domainId = 0;
private User user;
private SemanticParseInfo parseInfo = new SemanticParseInfo();
private QueryFilter queryFilter;
private List<SemanticQuery> candidateQueries = new ArrayList<>();
private SchemaMapInfo mapInfo = new SchemaMapInfo();
private boolean saveAnswer = true;
}

View File

@@ -11,13 +11,13 @@ import lombok.Data;
@Data
public class QueryResultResp {
public EntityInfo entityInfo;
private Long queryId;
private String queryMode;
private String querySql;
private int queryState;
private List<QueryColumn> queryColumns;
private QueryAuthorization queryAuthorization;
public EntityInfo entityInfo;
private SemanticParseInfo chatContext;
private Object response;
private List<Map<String, Object>> queryResults;

View File

@@ -1,30 +0,0 @@
package com.tencent.supersonic.chat.api.service;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
import com.tencent.supersonic.semantic.api.query.request.QueryStructReq;
import java.util.List;
/**
* This interface defines the contract for a semantic layer that provides a simplified and
* consistent view of data from multiple sources.
* The semantic layer abstracts away the complexity of the underlying data sources and provides
* a unified view of the data that is easier to understand and use.
* <p>
* The interface defines methods for getting metadata as well as querying data in the semantic layer.
* Implementations of this interface should provide concrete implementations that interact with the
* underlying data sources and return results in a consistent format. Or it can be implemented
* as proxy to a remote semantic service.
* </p>
*/
public interface SemanticLayer {
QueryResultWithSchemaResp queryByStruct(QueryStructReq queryStructReq, User user);
DomainSchemaResp getDomainSchemaInfo(Long domain);
List<DomainSchemaResp> getDomainSchemaInfo(List<Long> ids);
}

View File

@@ -1,29 +0,0 @@
package com.tencent.supersonic.chat.api.service;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.response.QueryResultResp;
import java.io.Serializable;
import java.util.List;
/**
* This interface defines the contract for a semantic query that executes specific type of
* query based on the results of semantic parsing.
*/
public interface SemanticQuery extends Serializable {
String getQueryMode();
QueryResultResp execute(QueryContextReq queryCtx, ChatContext chatCtx) throws Exception;
SchemaElementCount match(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx);
void updateContext(QueryResultResp queryResponse, ChatContext chatCtx, QueryContextReq queryCtx);
SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx);
}

View File

@@ -113,6 +113,12 @@
<artifactId>semantic-api</artifactId>
<version>${project.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.tencent.supersonic</groupId>-->
<!-- <artifactId>semantic-query</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>com.tencent.supersonic</groupId>
<artifactId>semantic-query</artifactId>
@@ -125,6 +131,12 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.tencent.supersonic</groupId>
<artifactId>semantic-query</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -18,20 +18,19 @@ import com.tencent.supersonic.chat.domain.service.ChatService;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Service("ChatService")
@Primary
@Slf4j
public class ChatServiceImpl implements ChatService {
private ChatContextRepository chatContextRepository;
private ChatRepository chatRepository;
private ChatQueryRepository chatQueryRepository;
private final Logger logger = LoggerFactory.getLogger(ChatService.class);
public ChatServiceImpl(ChatContextRepository chatContextRepository, ChatRepository chatRepository,
ChatQueryRepository chatQueryRepository) {
@@ -64,27 +63,32 @@ public class ChatServiceImpl implements ChatService {
@Override
public void updateContext(ChatContext chatCtx) {
logger.debug("save ChatContext {}", chatCtx);
log.debug("save ChatContext {}", chatCtx);
chatContextRepository.updateContext(chatCtx);
}
@Override
public void updateContext(ChatContext chatCtx, QueryContextReq queryCtx, SemanticParseInfo semanticParseInfo) {
chatCtx.setParseInfo(semanticParseInfo);
chatCtx.setQueryText(queryCtx.getQueryText());
updateContext(chatCtx);
}
@Override
public void switchContext(ChatContext chatCtx) {
logger.debug("switchContext ChatContext {}", chatCtx);
log.debug("switchContext ChatContext {}", chatCtx);
chatCtx.setParseInfo(new SemanticParseInfo());
}
@Override
public Boolean addChat(User user, String chatName) {
SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String datetime = tempDate.format(new java.util.Date());
ChatDO intelligentConversionDO = new ChatDO();
intelligentConversionDO.setChatName(chatName);
intelligentConversionDO.setCreator(user.getName());
intelligentConversionDO.setCreateTime(datetime);
intelligentConversionDO.setCreateTime(getCurrentTime());
intelligentConversionDO.setIsDelete(0);
intelligentConversionDO.setLastTime(datetime);
intelligentConversionDO.setLastTime(getCurrentTime());
intelligentConversionDO.setLastQuestion("Hello, welcome to using supersonic");
intelligentConversionDO.setIsTop(0);
return chatRepository.createChat(intelligentConversionDO);
@@ -97,9 +101,7 @@ public class ChatServiceImpl implements ChatService {
@Override
public boolean updateChatName(Long chatId, String chatName, String userName) {
SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String lastTime = tempDate.format(new java.util.Date());
return chatRepository.updateChatName(chatId, chatName, lastTime, userName);
return chatRepository.updateChatName(chatId, chatName, getCurrentTime(), userName);
}
@Override
@@ -129,6 +131,8 @@ public class ChatServiceImpl implements ChatService {
@Override
public void addQuery(QueryResultResp queryResponse, QueryContextReq queryContext, ChatContext chatCtx) {
chatQueryRepository.createChatQuery(queryResponse, queryContext, chatCtx);
chatRepository.updateLastQuestion(chatCtx.getChatId().longValue(), queryContext.getQueryText(),
getCurrentTime());
}
@Override
@@ -141,4 +145,9 @@ public class ChatServiceImpl implements ChatService {
return chatQueryRepository.updateChatQuery(chatQueryDO);
}
private String getCurrentTime() {
SimpleDateFormat tempDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return tempDate.format(new java.util.Date());
}
}

View File

@@ -2,27 +2,26 @@ package com.tencent.supersonic.chat.application;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.chat.api.service.SemanticLayer;
import com.tencent.supersonic.chat.api.component.SemanticLayer;
import com.tencent.supersonic.chat.domain.pojo.config.*;
import com.tencent.supersonic.chat.domain.utils.ComponentFactory;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.DomainResp;
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfig;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigBase;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigEditReq;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigFilter;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigInfo;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
import com.tencent.supersonic.chat.domain.pojo.config.DefaultMetric;
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
import com.tencent.supersonic.chat.domain.pojo.config.ItemVisibilityInfo;
import com.tencent.supersonic.chat.domain.repository.ChatConfigRepository;
import com.tencent.supersonic.chat.domain.service.ConfigService;
import com.tencent.supersonic.chat.domain.utils.ChatConfigUtils;
import com.tencent.supersonic.common.util.json.JsonUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
@@ -34,32 +33,31 @@ import org.springframework.util.CollectionUtils;
@Service
public class ConfigServiceImpl implements ConfigService {
private final ChatConfigRepository chaConfigRepository;
private final SemanticLayer semanticLayer;
private final ChatConfigRepository chatConfigRepository;
private final ChatConfigUtils chatConfigUtils;
private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
public ConfigServiceImpl(ChatConfigRepository chaConfigRepository,
@Lazy SemanticLayer semanticLayer,
ChatConfigUtils chatConfigUtils) {
this.chaConfigRepository = chaConfigRepository;
this.semanticLayer = semanticLayer;
public ConfigServiceImpl(ChatConfigRepository chatConfigRepository,
ChatConfigUtils chatConfigUtils) {
this.chatConfigRepository = chatConfigRepository;
this.chatConfigUtils = chatConfigUtils;
}
@Override
public Long addConfig(ChatConfigBase configBaseCmd, User user) {
public Long addConfig(ChatConfigBaseReq configBaseCmd, User user) {
log.info("[create domain extend] object:{}", JsonUtil.toString(configBaseCmd, true));
duplicateCheck(configBaseCmd.getDomainId());
permissionCheckLogic(configBaseCmd.getDomainId(), user.getName());
ChatConfig chaConfig = chatConfigUtils.newChatConfig(configBaseCmd, user);
chaConfigRepository.createConfig(chaConfig);
return chaConfig.getDomainId();
Long id = chatConfigRepository.createConfig(chaConfig);
return id;
}
private void duplicateCheck(Long domainId) {
ChatConfigFilter filter = new ChatConfigFilter();
filter.setDomainId(domainId);
List<ChatConfigInfo> chaConfigDescList = chaConfigRepository.getChatConfig(filter);
List<ChatConfigResp> chaConfigDescList = chatConfigRepository.getChatConfig(filter);
if (!CollectionUtils.isEmpty(chaConfigDescList)) {
throw new RuntimeException("chat config existed, no need to add repeatedly");
}
@@ -67,16 +65,16 @@ public class ConfigServiceImpl implements ConfigService {
@Override
public Long editConfig(ChatConfigEditReq configEditCmd, User user) {
public Long editConfig(ChatConfigEditReqReq configEditCmd, User user) {
log.info("[edit domain extend] object:{}", JsonUtil.toString(configEditCmd, true));
if (Objects.isNull(configEditCmd) || Objects.isNull(configEditCmd.getId()) && Objects.isNull(
configEditCmd.getDomainId())) {
throw new RuntimeException("editConfig, id and domainId are not allowed to be empty at the same time");
}
permissionCheckLogic(configEditCmd.getDomainId(), user.getName());
ChatConfig chaConfig = chatConfigUtils.editChaConfig(configEditCmd, user);
chaConfigRepository.updateConfig(chaConfig);
return configEditCmd.getDomainId();
ChatConfig chaConfig = chatConfigUtils.editChatConfig(configEditCmd, user);
chatConfigRepository.updateConfig(chaConfig);
return configEditCmd.getId();
}
@@ -89,94 +87,34 @@ public class ConfigServiceImpl implements ConfigService {
}
@Override
public List<ChatConfigInfo> search(ChatConfigFilter filter, User user) {
public List<ChatConfigResp> search(ChatConfigFilter filter, User user) {
log.info("[search domain extend] object:{}", JsonUtil.toString(filter, true));
List<ChatConfigInfo> chaConfigDescList = chaConfigRepository.getChatConfig(filter);
List<ChatConfigResp> chaConfigDescList = chatConfigRepository.getChatConfig(filter);
return chaConfigDescList;
}
public ChatConfigInfo fetchConfigByDomainId(Long domainId) {
return chaConfigRepository.getConfigByDomainId(domainId);
}
public EntityRichInfo fetchEntityDescByDomainId(Long domainId) {
ChatConfigInfo chaConfigDesc = chaConfigRepository.getConfigByDomainId(domainId);
return fetchEntityDescByConfig(chaConfigDesc);
}
public EntityRichInfo fetchEntityDescByConfig(ChatConfigInfo chatConfigDesc) {
Long domainId = chatConfigDesc.getDomainId();
EntityRichInfo entityDesc = new EntityRichInfo();
if (Objects.isNull(chatConfigDesc) || Objects.isNull(chatConfigDesc.getEntity())) {
log.info("domainId:{}, entityDesc info is null", domainId);
return entityDesc;
}
DomainSchemaResp domain = semanticLayer.getDomainSchemaInfo(domainId);
entityDesc.setDomainId(domain.getId());
entityDesc.setDomainBizName(domain.getBizName());
entityDesc.setDomainName(domain.getName());
entityDesc.setNames(chatConfigDesc.getEntity().getNames());
entityDesc.setEntityIds(chatConfigUtils.generateDimDesc(chatConfigDesc.getEntity().getEntityIds(), domain));
entityDesc.setEntityInternalDetailDesc(
chatConfigUtils.generateEntityDetailData(chatConfigDesc.getEntity().getDetailData(), domain));
return entityDesc;
@Override
public ChatConfigResp fetchConfigByDomainId(Long domainId) {
return chatConfigRepository.getConfigByDomainId(domainId);
}
public List<DefaultMetric> fetchDefaultMetricDescByDomainId(Long domainId) {
ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId);
return fetchDefaultMetricDescByConfig(chatConfigDesc);
}
public List<DefaultMetric> fetchDefaultMetricDescByConfig(ChatConfigInfo chatConfigDesc) {
Long domainId = chatConfigDesc.getDomainId();
DomainSchemaResp domain = semanticLayer.getDomainSchemaInfo(domainId);
List<DefaultMetric> defaultMetricDescList = new ArrayList<>();
if (Objects.isNull(chatConfigDesc) || CollectionUtils.isEmpty(chatConfigDesc.getDefaultMetrics())) {
log.info("domainId:{}, defaultMetricDescList info is null", domainId);
return defaultMetricDescList;
}
List<Long> metricIds = chatConfigDesc.getDefaultMetrics().stream()
.map(defaultMetricInfo -> defaultMetricInfo.getMetricId()).collect(Collectors.toList());
Map<Long, MetricSchemaResp> metricIdAndDescPair = chatConfigUtils.generateMetricIdAndDescPair(metricIds,
domain);
chatConfigDesc.getDefaultMetrics().stream().forEach(defaultMetricInfo -> {
DefaultMetric defaultMetricDesc = new DefaultMetric();
BeanUtils.copyProperties(defaultMetricInfo, defaultMetricDesc);
if (metricIdAndDescPair.containsKey(defaultMetricInfo.getMetricId())) {
MetricSchemaResp metricDesc = metricIdAndDescPair.get(defaultMetricInfo.getMetricId());
defaultMetricDesc.setBizName(metricDesc.getBizName());
defaultMetricDesc.setName(metricDesc.getName());
}
defaultMetricDescList.add(defaultMetricDesc);
});
return defaultMetricDescList;
}
public ItemVisibilityInfo fetchVisibilityDescByDomainId(Long domainId) {
ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId);
return fetchVisibilityDescByConfig(chatConfigDesc);
}
private ItemVisibilityInfo fetchVisibilityDescByConfig(ChatConfigInfo chatConfigDesc) {
private ItemVisibilityInfo fetchVisibilityDescByConfig(ItemVisibility visibility,
DomainSchemaResp domainSchemaDesc) {
ItemVisibilityInfo itemVisibilityDesc = new ItemVisibilityInfo();
Long domainId = chatConfigDesc.getDomainId();
DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId);
List<Long> dimIdAllList = chatConfigUtils.generateAllDimIdList(domainSchemaDesc);
List<Long> metricIdAllList = chatConfigUtils.generateAllMetricIdList(domainSchemaDesc);
List<Long> blackDimIdList = new ArrayList<>();
List<Long> blackMetricIdList = new ArrayList<>();
if (Objects.nonNull(chatConfigDesc.getVisibility())) {
if (!CollectionUtils.isEmpty(chatConfigDesc.getVisibility().getBlackDimIdList())) {
blackDimIdList.addAll(chatConfigDesc.getVisibility().getBlackDimIdList());
if (Objects.nonNull(visibility)) {
if (!CollectionUtils.isEmpty(visibility.getBlackDimIdList())) {
blackDimIdList.addAll(visibility.getBlackDimIdList());
}
if (!CollectionUtils.isEmpty(chatConfigDesc.getVisibility().getBlackMetricIdList())) {
blackMetricIdList.addAll(chatConfigDesc.getVisibility().getBlackMetricIdList());
if (!CollectionUtils.isEmpty(visibility.getBlackMetricIdList())) {
blackMetricIdList.addAll(visibility.getBlackMetricIdList());
}
}
List<Long> whiteMetricIdList = metricIdAllList.stream().filter(id -> !blackMetricIdList.contains(id))
@@ -193,19 +131,136 @@ public class ConfigServiceImpl implements ConfigService {
}
@Override
public ChatConfigRichInfo getConfigRichInfo(Long domainId) {
ChatConfigRichInfo chaConfigRichDesc = new ChatConfigRichInfo();
ChatConfigInfo chatConfigDesc = chaConfigRepository.getConfigByDomainId(domainId);
BeanUtils.copyProperties(chatConfigDesc, chaConfigRichDesc);
public ChatConfigRichResp getConfigRichInfo(Long domainId) {
ChatConfigRichResp chatConfigRichResp = new ChatConfigRichResp();
ChatConfigResp chatConfigResp = chatConfigRepository.getConfigByDomainId(domainId);
if (Objects.isNull(chatConfigResp)) {
log.info("there is no chatConfigDesc for domainId:{}", domainId);
return chatConfigRichResp;
}
BeanUtils.copyProperties(chatConfigResp, chatConfigRichResp);
DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domainId);
chaConfigRichDesc.setBizName(domainSchemaDesc.getBizName());
chaConfigRichDesc.setName(domainSchemaDesc.getName());
SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
DomainSchemaResp domainSchemaInfo = semanticLayer.getDomainSchemaInfo(domainId, false);
chatConfigRichResp.setBizName(domainSchemaInfo.getBizName());
chatConfigRichResp.setDomainName(domainSchemaInfo.getName());
chaConfigRichDesc.setDefaultMetrics(fetchDefaultMetricDescByConfig(chatConfigDesc));
chaConfigRichDesc.setVisibility(fetchVisibilityDescByConfig(chatConfigDesc));
chaConfigRichDesc.setEntity(fetchEntityDescByConfig(chatConfigDesc));
chatConfigRichResp.setChatAggRichConfig(fillChatAggRichConfig(domainSchemaInfo, chatConfigResp));
chatConfigRichResp.setChatDetailRichConfig(fillChatDetailRichConfig(domainSchemaInfo, chatConfigRichResp, chatConfigResp));
return chaConfigRichDesc;
return chatConfigRichResp;
}
private ChatDetailRichConfig fillChatDetailRichConfig(DomainSchemaResp domainSchemaInfo, ChatConfigRichResp chatConfigRichResp, ChatConfigResp chatConfigResp) {
if (Objects.isNull(chatConfigResp) || Objects.isNull(chatConfigResp.getChatDetailConfig())) {
return null;
}
ChatDetailRichConfig detailRichConfig = new ChatDetailRichConfig();
ChatDetailConfig chatDetailConfig = chatConfigResp.getChatDetailConfig();
detailRichConfig.setVisibility(fetchVisibilityDescByConfig(chatDetailConfig.getVisibility(), domainSchemaInfo));
detailRichConfig.setKnowledgeInfos(fillKnowledgeBizName(chatDetailConfig.getKnowledgeInfos(), domainSchemaInfo));
detailRichConfig.setGlobalKnowledgeConfig(chatDetailConfig.getGlobalKnowledgeConfig());
detailRichConfig.setChatDefaultConfig(fetchDefaultConfig(chatDetailConfig.getChatDefaultConfig(), domainSchemaInfo));
detailRichConfig.setEntity(generateRichEntity(chatDetailConfig.getEntity(), domainSchemaInfo));
return detailRichConfig;
}
private EntityRichInfo generateRichEntity(Entity entity, DomainSchemaResp domainSchemaInfo) {
EntityRichInfo entityRichInfo = new EntityRichInfo();
if (Objects.isNull(entity) || Objects.isNull(entity.getEntityId())) {
return entityRichInfo;
}
BeanUtils.copyProperties(entity, entityRichInfo);
Map<Long, DimSchemaResp> dimIdAndRespPair = domainSchemaInfo.getDimensions().stream()
.collect(Collectors.toMap(DimSchemaResp::getId, Function.identity()));
entityRichInfo.setDimItem(dimIdAndRespPair.get(entity.getEntityId()));
return entityRichInfo;
}
private ChatAggRichConfig fillChatAggRichConfig(DomainSchemaResp domainSchemaInfo, ChatConfigResp chatConfigResp) {
if (Objects.isNull(chatConfigResp) || Objects.isNull(chatConfigResp.getChatAggConfig())) {
return null;
}
ChatAggConfig chatAggConfig = chatConfigResp.getChatAggConfig();
ChatAggRichConfig chatAggRichConfig = new ChatAggRichConfig();
chatAggRichConfig.setVisibility(fetchVisibilityDescByConfig(chatAggConfig.getVisibility(), domainSchemaInfo));
chatAggRichConfig.setKnowledgeInfos(fillKnowledgeBizName(chatAggConfig.getKnowledgeInfos(), domainSchemaInfo));
chatAggRichConfig.setGlobalKnowledgeConfig(chatAggConfig.getGlobalKnowledgeConfig());
chatAggRichConfig.setChatDefaultConfig(fetchDefaultConfig(chatAggConfig.getChatDefaultConfig(), domainSchemaInfo));
return chatAggRichConfig;
}
private ChatDefaultRichConfig fetchDefaultConfig(ChatDefaultConfig chatDefaultConfig, DomainSchemaResp domainSchemaInfo) {
ChatDefaultRichConfig defaultRichConfig = new ChatDefaultRichConfig();
if (Objects.isNull(chatDefaultConfig)) {
return defaultRichConfig;
}
BeanUtils.copyProperties(chatDefaultConfig, defaultRichConfig);
Map<Long, DimSchemaResp> dimIdAndRespPair = domainSchemaInfo.getDimensions().stream()
.collect(Collectors.toMap(DimSchemaResp::getId, Function.identity()));
Map<Long, MetricSchemaResp> metricIdAndRespPair = domainSchemaInfo.getMetrics().stream()
.collect(Collectors.toMap(MetricSchemaResp::getId, Function.identity()));
List<SchemaItem> dimensions = new ArrayList<>();
List<SchemaItem> metrics = new ArrayList<>();
if (!CollectionUtils.isEmpty(chatDefaultConfig.getDimensionIds())) {
chatDefaultConfig.getDimensionIds().stream().forEach(dimId -> {
DimSchemaResp dimSchemaResp = dimIdAndRespPair.get(dimId);
SchemaItem dimSchema = new SchemaItem();
BeanUtils.copyProperties(dimSchemaResp, dimSchema);
dimensions.add(dimSchema);
});
}
if (!CollectionUtils.isEmpty(chatDefaultConfig.getMetricIds())) {
chatDefaultConfig.getMetricIds().stream().forEach(metricId -> {
MetricSchemaResp metricSchemaResp = metricIdAndRespPair.get(metricId);
SchemaItem metricSchema = new SchemaItem();
BeanUtils.copyProperties(metricSchemaResp, metricSchema);
metrics.add(metricSchema);
});
}
defaultRichConfig.setDimensions(dimensions);
defaultRichConfig.setMetrics(metrics);
return defaultRichConfig;
}
private List<KnowledgeInfo> fillKnowledgeBizName(List<KnowledgeInfo> knowledgeInfos,
DomainSchemaResp domainSchemaInfo) {
if (CollectionUtils.isEmpty(knowledgeInfos)) {
return new ArrayList<>();
}
Map<Long, DimSchemaResp> dimIdAndRespPair = domainSchemaInfo.getDimensions().stream()
.collect(Collectors.toMap(DimSchemaResp::getId, Function.identity()));
knowledgeInfos.stream().forEach(knowledgeInfo -> {
if (Objects.nonNull(knowledgeInfo)) {
DimSchemaResp dimSchemaResp = dimIdAndRespPair.get(knowledgeInfo.getItemId());
if (Objects.nonNull(dimSchemaResp)) {
knowledgeInfo.setBizName(dimSchemaResp.getBizName());
}
}
});
return knowledgeInfos;
}
@Override
public List<ChatConfigRichResp> getAllChatRichConfig() {
List<ChatConfigRichResp> chatConfigRichInfoList = new ArrayList<>();
List<DomainResp> domainRespList = semanticLayer.getDomainListForAdmin();
domainRespList.stream().forEach(domainResp -> {
ChatConfigRichResp chatConfigRichInfo = getConfigRichInfo(domainResp.getId());
if (Objects.nonNull(chatConfigRichInfo)) {
chatConfigRichInfoList.add(chatConfigRichInfo);
}
});
return chatConfigRichInfoList;
}
}

View File

@@ -2,49 +2,43 @@ package com.tencent.supersonic.chat.application;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.component.SemanticLayer;
import com.tencent.supersonic.chat.api.pojo.DataInfo;
import com.tencent.supersonic.chat.api.pojo.DomainInfo;
import com.tencent.supersonic.chat.api.pojo.EntityInfo;
import com.tencent.supersonic.chat.api.pojo.Filter;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticLayer;
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
import com.tencent.supersonic.chat.api.component.SemanticLayer;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichResp;
import com.tencent.supersonic.chat.domain.pojo.config.ChatDefaultRichConfig;
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils;
import com.tencent.supersonic.chat.domain.utils.ComponentFactory;
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
import com.tencent.supersonic.common.pojo.DateConf;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
@Slf4j
public class DomainEntityService {
private final Logger logger = LoggerFactory.getLogger(DomainEntityService.class);
@Autowired
private SemanticLayer semanticLayer;
private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
@Autowired
private DefaultSemanticInternalUtils defaultSemanticUtils;
public EntityInfo getEntityInfo(QueryContextReq queryCtx, ChatContext chatCtx, User user) {
SemanticParseInfo parseInfo = queryCtx.getParseInfo();
private ConfigServiceImpl configService;
public EntityInfo getEntityInfo(SemanticParseInfo parseInfo, User user) {
if (parseInfo != null && parseInfo.getDomainId() > 0) {
EntityInfo entityInfo = getEntityInfo(parseInfo.getDomainId());
if (parseInfo.getDimensionFilters().size() <= 0) {
@@ -56,7 +50,8 @@ public class DomainEntityService {
String domainInfoPrimaryName = entityInfo.getDomainInfo().getPrimaryEntityBizName();
String domainInfoId = "";
for (Filter chatFilter : parseInfo.getDimensionFilters()) {
if (chatFilter.getBizName().equals(domainInfoPrimaryName)) {
if (chatFilter != null && chatFilter.getBizName() != null && chatFilter.getBizName()
.equals(domainInfoPrimaryName)) {
if (chatFilter.getOperator().equals(FilterOperatorEnum.EQUALS)) {
domainInfoId = chatFilter.getValue().toString();
}
@@ -72,7 +67,7 @@ public class DomainEntityService {
return entityInfo;
} catch (Exception e) {
logger.error("setMaintDomain error {}", e);
log.error("setMaintDomain error {}", e);
}
}
}
@@ -81,42 +76,55 @@ public class DomainEntityService {
}
public EntityInfo getEntityInfo(Long domain) {
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domain);
return getEntityInfo(chaConfigRichDesc.getEntity());
ChatConfigRichResp chaConfigRichDesc = configService.getConfigRichInfo(domain);
if (Objects.isNull(chaConfigRichDesc) || Objects.isNull(chaConfigRichDesc.getChatDetailRichConfig())) {
return new EntityInfo();
}
return getEntityInfo(chaConfigRichDesc);
}
private EntityInfo getEntityInfo(EntityRichInfo entityDesc) {
EntityInfo entityInfo = new EntityInfo();
private EntityInfo getEntityInfo(ChatConfigRichResp chaConfigRichDesc) {
if (entityDesc != null) {
EntityInfo entityInfo = new EntityInfo();
EntityRichInfo entityDesc = chaConfigRichDesc.getChatDetailRichConfig().getEntity();
if (entityDesc != null && Objects.nonNull(chaConfigRichDesc.getDomainId())) {
DomainInfo domainInfo = new DomainInfo();
domainInfo.setItemId(Integer.valueOf(entityDesc.getDomainId().intValue()));
domainInfo.setName(entityDesc.getDomainName());
domainInfo.setItemId(Integer.valueOf(chaConfigRichDesc.getDomainId().intValue()));
domainInfo.setName(chaConfigRichDesc.getDomainName());
domainInfo.setWords(entityDesc.getNames());
domainInfo.setBizName(entityDesc.getDomainBizName());
if (entityDesc.getEntityIds().size() > 0) {
domainInfo.setPrimaryEntityBizName(entityDesc.getEntityIds().get(0).getBizName());
domainInfo.setBizName(chaConfigRichDesc.getBizName());
if (Objects.nonNull(entityDesc.getDimItem())) {
domainInfo.setPrimaryEntityBizName(entityDesc.getDimItem().getBizName());
}
entityInfo.setDomainInfo(domainInfo);
List<DataInfo> dimensions = new ArrayList<>();
List<DataInfo> metrics = new ArrayList<>();
if (entityDesc.getEntityInternalDetailDesc() != null) {
for (DimSchemaResp dimensionDesc : entityDesc.getEntityInternalDetailDesc().getDimensionList()) {
DataInfo mainEntityDimension = new DataInfo();
mainEntityDimension.setItemId(dimensionDesc.getId().intValue());
mainEntityDimension.setName(dimensionDesc.getName());
mainEntityDimension.setBizName(dimensionDesc.getBizName());
dimensions.add(mainEntityDimension);
if (Objects.nonNull(chaConfigRichDesc) && Objects.nonNull(chaConfigRichDesc.getChatDetailRichConfig())
&& Objects.nonNull(chaConfigRichDesc.getChatDetailRichConfig().getChatDefaultConfig())) {
ChatDefaultRichConfig chatDefaultConfig = chaConfigRichDesc.getChatDetailRichConfig().getChatDefaultConfig();
if(!CollectionUtils.isEmpty(chatDefaultConfig.getDimensions())){
for (SchemaItem dimensionDesc : chatDefaultConfig.getDimensions()) {
DataInfo mainEntityDimension = new DataInfo();
mainEntityDimension.setItemId(dimensionDesc.getId().intValue());
mainEntityDimension.setName(dimensionDesc.getName());
mainEntityDimension.setBizName(dimensionDesc.getBizName());
dimensions.add(mainEntityDimension);
}
entityInfo.setDimensions(dimensions);
}
entityInfo.setDimensions(dimensions);
for (MetricSchemaResp metricDesc : entityDesc.getEntityInternalDetailDesc().getMetricList()) {
DataInfo dataInfo = new DataInfo();
dataInfo.setName(metricDesc.getName());
dataInfo.setBizName(metricDesc.getBizName());
dataInfo.setItemId(metricDesc.getId().intValue());
metrics.add(dataInfo);
if(!CollectionUtils.isEmpty(chatDefaultConfig.getMetrics())){
for (SchemaItem metricDesc : chatDefaultConfig.getMetrics()) {
DataInfo dataInfo = new DataInfo();
dataInfo.setName(metricDesc.getName());
dataInfo.setBizName(metricDesc.getBizName());
dataInfo.setItemId(metricDesc.getId().intValue());
metrics.add(dataInfo);
}
entityInfo.setMetrics(metrics);
}
entityInfo.setMetrics(metrics);
}
}
return entityInfo;
@@ -148,7 +156,7 @@ public class DomainEntityService {
queryResultWithColumns = semanticLayer.queryByStruct(SchemaInfoConverter.convertTo(semanticParseInfo),
user);
} catch (Exception e) {
logger.warn("setMainDomain queryByStruct error, e:", e);
log.warn("setMainDomain queryByStruct error, e:", e);
}
if (queryResultWithColumns != null) {

View File

@@ -2,52 +2,45 @@ package com.tencent.supersonic.chat.application;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.chat.api.component.*;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.response.QueryResultResp;
import com.tencent.supersonic.chat.api.service.SchemaMapper;
import com.tencent.supersonic.chat.api.service.SemanticLayer;
import com.tencent.supersonic.chat.api.service.SemanticParser;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
import com.tencent.supersonic.chat.application.query.SemanticQueryFactory;
import com.tencent.supersonic.chat.application.query.QuerySelector;
import com.tencent.supersonic.chat.domain.pojo.chat.QueryData;
import com.tencent.supersonic.chat.domain.service.ChatService;
import com.tencent.supersonic.chat.domain.pojo.search.QueryState;
import com.tencent.supersonic.chat.domain.service.QueryService;
import com.tencent.supersonic.chat.domain.service.ChatService;
import com.tencent.supersonic.chat.domain.utils.ComponentFactory;
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
import com.tencent.supersonic.common.util.json.JsonUtil;
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Service
@Component("chatQueryService")
@Primary
@Slf4j
public class QueryServiceImpl implements QueryService {
private final Logger logger = LoggerFactory.getLogger(QueryServiceImpl.class);
private List<SchemaMapper> schemaMappers;
private List<SemanticParser> semanticParsers;
@Autowired
private ChatService chatService;
@Autowired
private SemanticLayer semanticLayer;
public QueryServiceImpl() {
schemaMappers = SpringFactoriesLoader.loadFactories(SchemaMapper.class,
Thread.currentThread().getContextClassLoader());
semanticParsers = SpringFactoriesLoader.loadFactories(SemanticParser.class,
Thread.currentThread().getContextClassLoader());
}
private List<SchemaMapper> schemaMappers = ComponentFactory.getSchemaMappers();
private List<SemanticParser> semanticParsers = ComponentFactory.getSemanticParsers();
private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
private QuerySelector querySelector = ComponentFactory.getQuerySelector();
@Override
public QueryResultResp executeQuery(QueryContextReq queryCtx) throws Exception {
schemaMappers.stream().forEach(s -> s.map(queryCtx));
@@ -55,24 +48,28 @@ public class QueryServiceImpl implements QueryService {
ChatContext chatCtx = chatService.getOrCreateContext(queryCtx.getChatId());
for (SemanticParser semanticParser : semanticParsers) {
logger.info("semanticParser processing:{}", JsonUtil.prettyToString(semanticParser));
boolean isFinish = semanticParser.parse(queryCtx, chatCtx);
if (isFinish) {
logger.info("semanticParser is finish ,semanticParser:{}", semanticParser.getClass().getName());
break;
log.info("semanticParser processing:[{}]", semanticParser.getClass().getName());
semanticParser.parse(queryCtx, chatCtx);
}
if (queryCtx.getCandidateQueries().size() > 0) {
log.info("pick before [{}]", queryCtx.getCandidateQueries().stream().collect(
Collectors.toList()));
SemanticQuery semanticQuery = querySelector.select(queryCtx.getCandidateQueries());
log.info("pick after [{}]", semanticQuery);
QueryResultResp queryResponse = semanticQuery.execute(queryCtx.getUser());
if (queryResponse != null) {
// update chat context after a successful semantic query
if (queryCtx.isSaveAnswer() && queryResponse.getQueryState() == QueryState.NORMAL.getState()) {
chatService.updateContext(chatCtx, queryCtx, semanticQuery.getParseInfo());
}
queryResponse.setChatContext(chatCtx.getParseInfo());
chatService.addQuery(queryResponse, queryCtx, chatCtx);
return queryResponse;
}
}
// submit semantic query based on the result of semantic parsing
SemanticQuery query = SemanticQueryFactory.get(queryCtx.getParseInfo().getQueryMode());
QueryResultResp queryResponse = query.execute(queryCtx, chatCtx);
// update chat context after a successful semantic query
query.updateContext(queryResponse, chatCtx, queryCtx);
chatService.addQuery(queryResponse, queryCtx, chatCtx);
return queryResponse;
return null;
}
@Override
@@ -82,7 +79,7 @@ public class QueryServiceImpl implements QueryService {
}
@Override
public QueryResultResp queryData(QueryData queryData, User user) throws Exception {
public QueryResultResp executeDirectQuery(QueryData queryData, User user) throws Exception {
SemanticParseInfo semanticParseInfo = new SemanticParseInfo();
QueryResultResp queryResponse = new QueryResultResp();
BeanUtils.copyProperties(queryData, semanticParseInfo);

View File

@@ -2,14 +2,15 @@ package com.tencent.supersonic.chat.application;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticLayer;
import com.tencent.supersonic.chat.api.component.SemanticLayer;
import com.tencent.supersonic.chat.domain.utils.ComponentFactory;
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
import com.tencent.supersonic.chat.domain.pojo.chat.RecommendResponse;
import com.tencent.supersonic.chat.domain.service.RecommendService;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/***
@@ -18,8 +19,7 @@ import org.springframework.stereotype.Service;
@Service
public class RecommendServiceImpl implements RecommendService {
@Autowired
private SemanticLayer semanticLayer;
private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
@Override
public RecommendResponse recommend(QueryContextReq queryCtx) {
@@ -29,7 +29,7 @@ public class RecommendServiceImpl implements RecommendService {
}
DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(
Long.valueOf(domainId));
Long.valueOf(domainId), true);
List<RecommendResponse.Item> dimensions = domainSchemaDesc.getDimensions().stream().map(dimSchemaDesc -> {
RecommendResponse.Item item = new RecommendResponse.Item();

View File

@@ -2,6 +2,8 @@ package com.tencent.supersonic.chat.application;
import com.google.common.collect.Lists;
import com.hankcs.hanlp.seg.common.Term;
import com.tencent.supersonic.chat.api.pojo.Filter;
import com.tencent.supersonic.chat.api.pojo.QueryFilter;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.application.knowledge.NatureHelper;
@@ -11,7 +13,7 @@ import com.tencent.supersonic.chat.domain.pojo.search.DomainInfoStat;
import com.tencent.supersonic.chat.domain.pojo.search.DomainWithSemanticType;
import com.tencent.supersonic.chat.domain.pojo.search.MatchText;
import com.tencent.supersonic.chat.domain.pojo.search.SearchResult;
import com.tencent.supersonic.chat.domain.pojo.semantic.DomainInfos;
import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos;
import com.tencent.supersonic.chat.domain.service.ChatService;
import com.tencent.supersonic.chat.domain.service.SearchService;
import com.tencent.supersonic.chat.domain.utils.NatureConverter;
@@ -32,9 +34,9 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -43,9 +45,11 @@ import org.springframework.stereotype.Service;
* search service impl
*/
@Service
@Slf4j
public class SearchServiceImpl implements SearchService {
private static final Logger LOGGER = LoggerFactory.getLogger(SearchServiceImpl.class);
private static final int RESULT_SIZE = 10;
@Autowired
private WordNatureService wordNatureService;
@Autowired
@@ -53,9 +57,6 @@ public class SearchServiceImpl implements SearchService {
@Autowired
private SearchMatchStrategy searchMatchStrategy;
private static final int RESULT_SIZE = 10;
@Override
public List<SearchResult> search(QueryContextReq queryCtx) {
String queryText = queryCtx.getQueryText();
@@ -65,9 +66,8 @@ public class SearchServiceImpl implements SearchService {
List<ItemDO> metricsDb = domainInfosDb.getMetrics();
final Map<Integer, String> domainToName = domainInfosDb.getDomainToName();
// 2.detect by segment
List<Term> originals = HanlpHelper.getSegment().seg(queryText.toLowerCase()).stream()
.collect(Collectors.toList());
Map<MatchText, List<MapResult>> regTextMap = searchMatchStrategy.matchWithMatchText(queryText, originals,
List<Term> originals = HanlpHelper.getTerms(queryText);
Map<MatchText, List<MapResult>> regTextMap = searchMatchStrategy.match(queryText, originals,
queryCtx.getDomainId());
regTextMap.entrySet().stream().forEach(m -> HanlpHelper.transLetterOriginal(m.getValue()));
// 3.get the most matching data
@@ -77,14 +77,14 @@ public class SearchServiceImpl implements SearchService {
.reduce((entry1, entry2) ->
entry1.getKey().getDetectSegment().length() >= entry2.getKey().getDetectSegment().length()
? entry1 : entry2);
LOGGER.debug("mostSimilarSearchResult:{}", mostSimilarSearchResult);
log.debug("mostSimilarSearchResult:{}", mostSimilarSearchResult);
// 4.optimize the results after the query
if (!mostSimilarSearchResult.isPresent()) {
LOGGER.info("unable to find any information through search , queryCtx:{}", queryCtx);
log.info("unable to find any information through search , queryCtx:{}", queryCtx);
return Lists.newArrayList();
}
Map.Entry<MatchText, List<MapResult>> searchTextEntry = mostSimilarSearchResult.get();
LOGGER.info("searchTextEntry:{},queryCtx:{}", searchTextEntry, queryCtx);
log.info("searchTextEntry:{},queryCtx:{}", searchTextEntry, queryCtx);
Set<SearchResult> searchResults = new LinkedHashSet();
DomainInfoStat domainStat = NatureHelper.getDomainStat(originals);
@@ -98,11 +98,11 @@ public class SearchServiceImpl implements SearchService {
// 4.2 process based on dimension values
MatchText matchText = searchTextEntry.getKey();
Map<String, String> natureToNameMap = getNatureToNameMap(searchTextEntry, new HashSet<>(possibleDomains));
LOGGER.debug("possibleDomains:{},natureToNameMap:{}", possibleDomains, natureToNameMap);
log.debug("possibleDomains:{},natureToNameMap:{}", possibleDomains, natureToNameMap);
for (Map.Entry<String, String> natureToNameEntry : natureToNameMap.entrySet()) {
searchDimensionValue(metricsDb, domainToName, domainStat.getMetricDomainCount(), searchResults,
existMetricAndDimension, matchText, natureToNameMap, natureToNameEntry);
existMetricAndDimension, matchText, natureToNameMap, natureToNameEntry, queryCtx.getQueryFilter());
}
return searchResults.stream().limit(RESULT_SIZE).collect(Collectors.toList());
}
@@ -120,7 +120,7 @@ public class SearchServiceImpl implements SearchService {
Long contextDomain = chatService.getContextDomain(queryCtx.getChatId());
LOGGER.debug("possibleDomains:{},domainStat:{},contextDomain:{}", possibleDomains, domainStat, contextDomain);
log.debug("possibleDomains:{},domainStat:{},contextDomain:{}", possibleDomains, domainStat, contextDomain);
// If nothing is recognized or only metric are present, then add the contextDomain.
if (nothingOrOnlyMetric(domainStat) && effectiveDomain(contextDomain)) {
@@ -141,13 +141,14 @@ public class SearchServiceImpl implements SearchService {
}
private void searchDimensionValue(List<ItemDO> metricsDb,
Map<Integer, String> domainToName,
long metricDomainCount,
Set<SearchResult> searchResults,
boolean existMetricAndDimension,
MatchText matchText,
Map<String, String> natureToNameMap,
Map.Entry<String, String> natureToNameEntry) {
Map<Integer, String> domainToName,
long metricDomainCount,
Set<SearchResult> searchResults,
boolean existMetricAndDimension,
MatchText matchText,
Map<String, String> natureToNameMap,
Map.Entry<String, String> natureToNameEntry,
QueryFilter queryFilter) {
String nature = natureToNameEntry.getKey();
String wordName = natureToNameEntry.getValue();
@@ -159,6 +160,9 @@ public class SearchServiceImpl implements SearchService {
}
// If there are no metric/dimension, complete the metric information
if (metricDomainCount <= 0 && !existMetricAndDimension) {
if (filterByQueryFilter(matchText.getRegText(), queryFilter)) {
return;
}
searchResults.add(
new SearchResult(matchText.getRegText() + wordName, wordName, domainToName.get(domain), domain,
schemaElementType));
@@ -168,7 +172,6 @@ public class SearchServiceImpl implements SearchService {
}
List<String> metrics = filerMetricsByDomain(metricsDb, domain).stream().limit(metricSize).collect(
Collectors.toList());
;
for (String metric : metrics) {
String subRecommend = matchText.getRegText() + wordName + NatureType.SPACE + metric;
searchResults.add(
@@ -182,6 +185,19 @@ public class SearchServiceImpl implements SearchService {
}
}
private boolean filterByQueryFilter(String regText, QueryFilter queryFilter) {
if (queryFilter == null || CollectionUtils.isEmpty(queryFilter.getFilters())) {
return false;
}
List<Filter> filters = queryFilter.getFilters();
for (Filter filter : filters) {
if (regText.equalsIgnoreCase(String.valueOf(filter.getValue()))) {
return false;
}
}
return true;
}
protected List<String> filerMetricsByDomain(List<ItemDO> metricsDb, Integer domain) {
if (CollectionUtils.isEmpty(metricsDb)) {
return Lists.newArrayList();
@@ -249,7 +265,7 @@ public class SearchServiceImpl implements SearchService {
domainToName.get(domain), domain, semanticType));
}
}
LOGGER.info("parseResult:{},dimensionMetricClassIds:{},possibleDomains:{}", mapResult,
log.info("parseResult:{},dimensionMetricClassIds:{},possibleDomains:{}", mapResult,
dimensionMetricClassIds, possibleDomains);
}
return existMetric;

View File

@@ -45,7 +45,7 @@ public class ApplicationStartedInit implements ApplicationListener<ApplicationSt
*/
@Scheduled(cron = "${reload.knowledge.corn:0 0/1 * * * ?}")
public void reloadKnowledge() {
log.debug("reloadKnowledge start");
log.info("reloadKnowledge start");
try {
List<WordNature> wordNatures = wordNatureService.getAllWordNature();
@@ -55,6 +55,7 @@ public class ApplicationStartedInit implements ApplicationListener<ApplicationSt
log.debug("wordNatures is not change, reloadKnowledge end");
return;
}
log.info("wordNatures is change");
wordNatureService.setPreWordNatures(wordNatures);
onlineKnowledgeService.updateOnlineKnowledge(wordNatureService.getAllWordNature());
wordNatureService.getCache().refresh("");
@@ -63,6 +64,6 @@ public class ApplicationStartedInit implements ApplicationListener<ApplicationSt
log.error("reloadKnowledge error", e);
}
log.debug("reloadKnowledge end");
log.info("reloadKnowledge end");
}
}

View File

@@ -29,19 +29,16 @@ import org.springframework.util.CollectionUtils;
@Service
public class DictApplicationService {
@Value("${dict.flush.enable:true}")
private Boolean dictFlushEnable;
@Value("${dict.file.type:txt}")
private String dictFileType;
private String dimValue = "DimValue_%d_%d";
private String dateTimeFormatter = "yyyyMMddHHmmss";
private final DictMetaUtils metaUtils;
private final DictQueryUtils dictQueryUtils;
private final FileHandler fileHandler;
private final DictRepository dictRepository;
@Value("${dict.flush.enable:true}")
private Boolean dictFlushEnable;
@Value("${dict.file.type:txt}")
private String dictFileType;
private String dimValue = "DimValue_%d_%d";
private String dateTimeFormatter = "yyyyMMddHHmmss";
public DictApplicationService(DictMetaUtils metaUtils,
DictQueryUtils dictQueryUtils,

View File

@@ -13,6 +13,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,10 +22,9 @@ import org.slf4j.LoggerFactory;
/**
* nature parse helper
*/
@Slf4j
public class NatureHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(NatureHelper.class);
private static boolean isDomainOrEntity(Term term, Integer domain) {
return (NatureType.NATURE_SPILT + domain).equals(term.nature.toString()) || term.nature.toString()
.endsWith(NatureType.ENTITY.getType());
@@ -47,7 +48,7 @@ public class NatureHelper {
}
return Integer.valueOf(split[1]);
} catch (NumberFormatException e) {
LOGGER.error("", e);
log.error("", e);
}
return null;
}

View File

@@ -3,8 +3,9 @@ package com.tencent.supersonic.chat.application.knowledge;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.tencent.supersonic.chat.api.service.SemanticLayer;
import com.tencent.supersonic.chat.domain.pojo.semantic.DomainInfos;
import com.tencent.supersonic.chat.api.component.SemanticLayer;
import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos;
import com.tencent.supersonic.chat.domain.utils.ComponentFactory;
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
import com.tencent.supersonic.common.nlp.ItemDO;
import com.tencent.supersonic.common.nlp.NatureType;
@@ -13,9 +14,10 @@ import com.tencent.supersonic.knowledge.application.online.WordNatureStrategyFac
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
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;
@@ -23,14 +25,11 @@ import org.springframework.stereotype.Service;
* word nature service
**/
@Service
@Slf4j
public class WordNatureService {
private static final Logger LOGGER = LoggerFactory.getLogger(WordNatureService.class);
@Autowired
private SemanticLayer semanticLayer;
private static final Integer META_CACHE_TIME = 5;
private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
private List<WordNature> preWordNatures = new ArrayList<>();
private LoadingCache<String, DomainInfos> cache = CacheBuilder.newBuilder()
@@ -39,14 +38,14 @@ public class WordNatureService {
new CacheLoader<String, DomainInfos>() {
@Override
public DomainInfos load(String key) {
LOGGER.info("load getDomainSchemaInfo cache [{}]", key);
log.info("load getDomainSchemaInfo cache [{}]", key);
return SchemaInfoConverter.convert(semanticLayer.getDomainSchemaInfo(new ArrayList<>()));
}
}
);
public List<WordNature> getAllWordNature() {
SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
DomainInfos domainInfos = SchemaInfoConverter.convert(semanticLayer.getDomainSchemaInfo(new ArrayList<>()));
List<WordNature> natures = new ArrayList<>();
@@ -64,7 +63,7 @@ public class WordNatureService {
private void addNatureToResult(NatureType value, List<ItemDO> metas, List<WordNature> natures) {
List<WordNature> natureList = WordNatureStrategyFactory.get(value).getWordNatureList(metas);
LOGGER.debug("nature type:{} , nature size:{}", value.name(), natureList.size());
log.debug("nature type:{} , nature size:{}", value.name(), natureList.size());
natures.addAll(natureList);
}

View File

@@ -0,0 +1,143 @@
package com.tencent.supersonic.chat.application.mapper;
import com.hankcs.hanlp.seg.common.Term;
import com.tencent.supersonic.chat.api.component.SchemaMapper;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.application.knowledge.WordNatureService;
import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos;
import com.tencent.supersonic.common.nlp.ItemDO;
import com.tencent.supersonic.common.util.context.ContextUtils;
import com.tencent.supersonic.knowledge.infrastructure.nlp.HanlpHelper;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
@Slf4j
public class DatabaseSchemaMapper implements SchemaMapper {
@Override
public void map(QueryContextReq queryContext) {
log.debug("before db mapper,mapInfo:{}", queryContext.getMapInfo());
List<Term> terms = HanlpHelper.getTerms(queryContext.getQueryText());
WordNatureService wordNatureService = ContextUtils.getBean(WordNatureService.class);
DomainInfos domainInfos = wordNatureService.getCache().getUnchecked("");
detectAndAddToSchema(queryContext, terms, domainInfos.getDimensions(),
SchemaElementType.DIMENSION);
detectAndAddToSchema(queryContext, terms, domainInfos.getMetrics(), SchemaElementType.METRIC);
log.debug("after db mapper,mapInfo:{}", queryContext.getMapInfo());
}
private void detectAndAddToSchema(QueryContextReq queryContext, List<Term> terms, List<ItemDO> domains,
SchemaElementType schemaElementType) {
try {
String queryText = queryContext.getQueryText();
Map<String, Set<ItemDO>> domainResultSet = getResultSet(queryText, terms, domains);
addToSchemaMapInfo(domainResultSet, queryContext.getMapInfo(), schemaElementType);
} catch (Exception e) {
log.error("detectAndAddToSchema error", e);
}
}
private Map<String, Set<ItemDO>> getResultSet(String queryText, List<Term> terms, List<ItemDO> domains) {
MapperHelper mapperHelper = ContextUtils.getBean(MapperHelper.class);
Map<String, Set<ItemDO>> nameToItems = getNameToItems(domains);
Map<Integer, Integer> regOffsetToLength = terms.stream().sorted(Comparator.comparing(Term::length))
.collect(Collectors.toMap(Term::getOffset, term -> term.word.length(), (value1, value2) -> value2));
Map<String, Set<ItemDO>> domainResultSet = new HashMap<>();
for (Integer index = 0; index <= queryText.length() - 1; ) {
for (Integer i = index; i <= queryText.length(); ) {
i = mapperHelper.getStepIndex(regOffsetToLength, i);
if (i <= queryText.length()) {
String detectSegment = queryText.substring(index, i);
nameToItems.forEach(
(name, newItemDOs) -> {
if (name.contains(detectSegment)
&& mapperHelper.getSimilarity(detectSegment, name)
>= mapperHelper.getMetricDimensionThresholdConfig()) {
Set<ItemDO> preItemDOS = domainResultSet.putIfAbsent(detectSegment, newItemDOs);
if (Objects.nonNull(preItemDOS)) {
preItemDOS.addAll(newItemDOs);
}
}
}
);
}
}
index = mapperHelper.getStepIndex(regOffsetToLength, index);
}
return domainResultSet;
}
private Map<String, Set<ItemDO>> getNameToItems(List<ItemDO> domains) {
return domains.stream()
.collect(Collectors.toMap(ItemDO::getName, a -> {
Set<ItemDO> result = new HashSet<>();
result.add(a);
return result;
}, (k1, k2) -> {
k1.addAll(k2);
return k1;
}));
}
private void addToSchemaMapInfo(Map<String, Set<ItemDO>> mapResultRowSet, SchemaMapInfo schemaMap,
SchemaElementType schemaElementType) {
if (Objects.isNull(mapResultRowSet) || mapResultRowSet.size() <= 0) {
return;
}
MapperHelper mapperHelper = ContextUtils.getBean(MapperHelper.class);
for (Map.Entry<String, Set<ItemDO>> entry : mapResultRowSet.entrySet()) {
String detectWord = entry.getKey();
Set<ItemDO> itemDOS = entry.getValue();
for (ItemDO itemDO : itemDOS) {
List<SchemaElementMatch> elements = schemaMap.getMatchedElements(itemDO.getDomain());
if (CollectionUtils.isEmpty(elements)) {
elements = new ArrayList<>();
schemaMap.setMatchedElements(itemDO.getDomain(), elements);
}
Set<Integer> regElementSet = elements.stream()
.filter(elementMatch -> schemaElementType.equals(elementMatch.getElementType()))
.map(elementMatch -> elementMatch.getElementID())
.collect(Collectors.toSet());
if (regElementSet.contains(itemDO.getItemId())) {
continue;
}
SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder()
.elementID(itemDO.getItemId()).word(itemDO.getName()).frequency(10000L)
.elementType(schemaElementType).detectWord(detectWord)
.similarity(mapperHelper.getSimilarity(detectWord, itemDO.getName()))
.build();
log.info("schemaElementType:{},add to schema, elementMatch {}", schemaElementType, schemaElementMatch);
elements.add(schemaElementMatch);
}
}
}
}

View File

@@ -1,17 +1,17 @@
package com.tencent.supersonic.chat.application.mapper;
import com.hankcs.hanlp.seg.common.Term;
import com.tencent.supersonic.chat.api.component.SchemaMapper;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SchemaMapper;
import com.tencent.supersonic.chat.application.knowledge.NatureHelper;
import com.tencent.supersonic.chat.domain.pojo.search.MatchText;
import com.tencent.supersonic.chat.domain.utils.NatureConverter;
import com.tencent.supersonic.common.nlp.MapResult;
import com.tencent.supersonic.common.nlp.NatureType;
import com.tencent.supersonic.common.util.context.ContextUtils;
import com.tencent.supersonic.common.util.json.JsonUtil;
import com.tencent.supersonic.knowledge.application.online.BaseWordNature;
import com.tencent.supersonic.knowledge.application.online.WordNatureStrategyFactory;
import com.tencent.supersonic.knowledge.infrastructure.nlp.HanlpHelper;
@@ -19,38 +19,53 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Slf4j
public class HanlpSchemaMapper implements SchemaMapper {
private static final Logger LOGGER = LoggerFactory.getLogger(HanlpSchemaMapper.class);
@Override
public void map(QueryContextReq queryContext) {
List<Term> terms = HanlpHelper.getSegment().seg(queryContext.getQueryText().toLowerCase()).stream()
.collect(Collectors.toList());
List<Term> terms = HanlpHelper.getTerms(queryContext.getQueryText());
terms.forEach(
item -> LOGGER.info("word:{},nature:{},frequency:{}", item.word, item.nature.toString(),
item -> log.info("word:{},nature:{},frequency:{}", item.word, item.nature.toString(),
item.getFrequency())
);
QueryMatchStrategy matchStrategy = ContextUtils.getBean(QueryMatchStrategy.class);
List<MapResult> matches = matchStrategy.match(queryContext.getQueryText(), terms, queryContext.getDomainId());
Map<MatchText, List<MapResult>> matchResult = matchStrategy.match(queryContext.getQueryText(), terms,
queryContext.getDomainId());
List<MapResult> matches = new ArrayList<>();
if (Objects.nonNull(matchResult)) {
Optional<List<MapResult>> first = matchResult.entrySet().stream()
.filter(entry -> CollectionUtils.isNotEmpty(entry.getValue()))
.map(entry -> entry.getValue()).findFirst();
if (first.isPresent()) {
matches = first.get();
}
}
HanlpHelper.transLetterOriginal(matches);
LOGGER.info("queryContext:{},matches:{}", queryContext, matches);
log.info("queryContext:{},matches:{}", queryContext, matches);
convertTermsToSchemaMapInfo(matches, queryContext.getMapInfo());
convertTermsToSchemaMapInfo(matches, queryContext.getMapInfo(), terms);
}
private void convertTermsToSchemaMapInfo(List<MapResult> mapResults, SchemaMapInfo schemaMap) {
private void convertTermsToSchemaMapInfo(List<MapResult> mapResults, SchemaMapInfo schemaMap, List<Term> terms) {
if (CollectionUtils.isEmpty(mapResults)) {
return;
}
Map<String, Long> wordNatureToFrequency = terms.stream().collect(
Collectors.toMap(entry -> entry.getWord() + entry.getNature(),
term -> Long.valueOf(term.getFrequency()), (value1, value2) -> value2));
for (MapResult mapResult : mapResults) {
for (String nature : mapResult.getNatures()) {
Integer domain = NatureHelper.getDomain(nature);
@@ -61,17 +76,18 @@ public class HanlpSchemaMapper implements SchemaMapper {
if (Objects.isNull(elementType)) {
continue;
}
SchemaElementMatch schemaElementMatch = new SchemaElementMatch();
schemaElementMatch.setElementType(elementType);
BaseWordNature baseWordNature = WordNatureStrategyFactory.get(NatureType.getNatureType(nature));
Integer elementID = baseWordNature.getElementID(nature);
schemaElementMatch.setElementID(elementID);
Long frequency = baseWordNature.getFrequency(nature);
schemaElementMatch.setFrequency(frequency);
schemaElementMatch.setWord(mapResult.getName());
schemaElementMatch.setSimilarity(mapResult.getSimilarity());
schemaElementMatch.setDetectWord(mapResult.getDetectWord());
Long frequency = wordNatureToFrequency.get(mapResult.getName() + nature);
SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder()
.elementType(elementType)
.elementID(elementID)
.frequency(frequency)
.word(mapResult.getName())
.similarity(mapResult.getSimilarity())
.detectWord(mapResult.getDetectWord())
.build();
Map<Integer, List<SchemaElementMatch>> domainElementMatches = schemaMap.getDomainElementMatches();
List<SchemaElementMatch> schemaElementMatches = domainElementMatches.putIfAbsent(domain,

View File

@@ -0,0 +1,83 @@
package com.tencent.supersonic.chat.application.mapper;
import com.hankcs.hanlp.algorithm.EditDistance;
import com.tencent.supersonic.chat.application.knowledge.NatureHelper;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* Mapper helper
*/
@Data
@Service
@Slf4j
public class MapperHelper {
@Value("${one.detection.size:6}")
private Integer oneDetectionSize;
@Value("${one.detection.max.size:20}")
private Integer oneDetectionMaxSize;
@Value("${metric.dimension.threshold:0.3}")
private Double metricDimensionThresholdConfig;
@Value("${dimension.value.threshold:0.5}")
private Double dimensionValueThresholdConfig;
public Integer getStepIndex(Map<Integer, Integer> regOffsetToLength, Integer index) {
Integer subRegLength = regOffsetToLength.get(index);
if (Objects.nonNull(subRegLength)) {
index = index + subRegLength;
} else {
index++;
}
return index;
}
public Integer getStepOffset(List<Integer> termList, Integer index) {
for (int j = 0; j < termList.size() - 1; j++) {
if (termList.get(j) <= index && termList.get(j + 1) > index) {
return termList.get(j);
}
}
return index;
}
public double getThresholdMatch(List<String> natures) {
if (existDimensionValues(natures)) {
return dimensionValueThresholdConfig;
}
return metricDimensionThresholdConfig;
}
/***
* exist dimension values
* @param natures
* @return
*/
public boolean existDimensionValues(List<String> natures) {
for (String nature : natures) {
if (NatureHelper.isDimensionValueClassId(nature)) {
return true;
}
}
return false;
}
/***
* get similarity
* @param detectSegment
* @param matchName
* @return
*/
public double getSimilarity(String detectSegment, String matchName) {
String detectSegmentLower = detectSegment == null ? null : detectSegment.toLowerCase();
String matchNameLower = matchName == null ? null : matchName.toLowerCase();
return 1 - (double) EditDistance.compute(detectSegmentLower, matchNameLower) / Math.max(matchName.length(),
detectSegment.length());
}
}

View File

@@ -1,8 +1,6 @@
package com.tencent.supersonic.chat.application.mapper;
import com.hankcs.hanlp.algorithm.EditDistance;
import com.hankcs.hanlp.seg.common.Term;
import com.tencent.supersonic.chat.application.knowledge.NatureHelper;
import com.tencent.supersonic.chat.domain.pojo.search.MatchText;
import com.tencent.supersonic.common.nlp.MapResult;
import java.util.List;
@@ -13,33 +11,6 @@ import java.util.Map;
*/
public interface MatchStrategy {
List<MapResult> match(String text, List<Term> terms, Integer detectDomainId);
Map<MatchText, List<MapResult>> match(String text, List<Term> terms, Integer detectDomainId);
Map<MatchText, List<MapResult>> matchWithMatchText(String text, List<Term> originals, Integer detectDomainId);
/***
* exist dimension values
* @param natures
* @return
*/
default boolean existDimensionValues(List<String> natures) {
for (String nature : natures) {
if (NatureHelper.isDimensionValueClassId(nature)) {
return true;
}
}
return false;
}
/***
* get similarity
* @param detectSegment
* @param matchName
* @return
*/
default double getSimilarity(String detectSegment, String matchName) {
return 1 - (double) EditDistance.compute(detectSegment, matchName) / Math.max(matchName.length(),
detectSegment.length());
}
}

View File

@@ -0,0 +1,66 @@
package com.tencent.supersonic.chat.application.mapper;
import com.google.common.collect.Lists;
import com.tencent.supersonic.chat.api.component.SchemaMapper;
import com.tencent.supersonic.chat.api.pojo.*;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class QueryFilterMapper implements SchemaMapper {
private Long FREQUENCY = 9999999L;
private double SIMILARITY = 1.0;
@Override
public void map(QueryContextReq queryContext) {
Integer domainId = queryContext.getDomainId();
if (domainId == null || domainId <= 0 || queryContext.getQueryFilter() == null) {
return;
}
QueryFilter queryFilter = queryContext.getQueryFilter();
SchemaMapInfo schemaMapInfo = queryContext.getMapInfo();
List<SchemaElementMatch> schemaElementMatches = schemaMapInfo.getMatchedElements(domainId);
clearOtherSchemaElementMatch(domainId, schemaMapInfo);
convertFilterToSchemaMapInfo(queryFilter.getFilters(), schemaElementMatches);
}
private void convertFilterToSchemaMapInfo(List<Filter> filters, List<SchemaElementMatch> schemaElementMatches) {
log.info("schemaElementMatches before queryFilerMapper:{}", schemaElementMatches);
if (CollectionUtils.isEmpty(schemaElementMatches)) {
schemaElementMatches = Lists.newArrayList();
}
List<String> words = schemaElementMatches.stream().map(SchemaElementMatch::getWord).collect(Collectors.toList());
for (Filter filter : filters) {
SchemaElementMatch schemaElementMatch = SchemaElementMatch.builder()
.elementType(SchemaElementType.VALUE)
.elementID(filter.getElementID().intValue())
.frequency(FREQUENCY)
.word(String.valueOf(filter.getValue()))
.similarity(SIMILARITY)
.detectWord(Constants.EMPTY)
.build();
if (words.contains(schemaElementMatch.getWord())) {
continue;
}
schemaElementMatches.add(schemaElementMatch);
}
log.info("schemaElementMatches after queryFilerMapper:{}", schemaElementMatches);
}
private void clearOtherSchemaElementMatch(Integer domainId, SchemaMapInfo schemaMapInfo) {
for (Map.Entry<Integer, List<SchemaElementMatch>> entry : schemaMapInfo.getDomainElementMatches().entrySet()) {
if (!entry.getKey().equals(domainId)) {
entry.getValue().clear();
}
}
}
}

View File

@@ -5,42 +5,34 @@ import com.tencent.supersonic.chat.domain.pojo.search.MatchText;
import com.tencent.supersonic.common.nlp.MapResult;
import com.tencent.supersonic.common.nlp.NatureType;
import com.tencent.supersonic.knowledge.infrastructure.nlp.Suggester;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* match strategy implement
*/
@Service
@Slf4j
public class QueryMatchStrategy implements MatchStrategy {
private static final Logger LOGGER = LoggerFactory.getLogger(QueryMatchStrategy.class);
public static final double STEP = 0.1;
@Value("${one.detection.size:6}")
private Integer oneDetectionSize;
@Value("${one.detection.max.size:20}")
private Integer oneDetectionMaxSize;
@Value("${metric.dimension.threshold:0.3}")
private Double metricDimensionThresholdConfig;
@Value("${dimension.value.threshold:0.5}")
private Double dimensionValueThresholdConfig;
@Autowired
private MapperHelper mapperHelper;
@Override
public List<MapResult> match(String text, List<Term> terms, Integer detectDomainId) {
public Map<MatchText, List<MapResult>> match(String text, List<Term> terms, Integer detectDomainId) {
if (CollectionUtils.isEmpty(terms) || StringUtils.isEmpty(text)) {
return null;
}
@@ -50,17 +42,14 @@ public class QueryMatchStrategy implements MatchStrategy {
List<Integer> offsetList = terms.stream().sorted(Comparator.comparing(Term::getOffset))
.map(term -> term.getOffset()).collect(Collectors.toList());
LOGGER.debug("retryCount:{},terms:{},regOffsetToLength:{},offsetList:{},detectDomainId:{}", terms,
log.debug("retryCount:{},terms:{},regOffsetToLength:{},offsetList:{},detectDomainId:{}", terms,
regOffsetToLength, offsetList, detectDomainId);
return detect(text, regOffsetToLength, offsetList, detectDomainId);
}
@Override
public Map<MatchText, List<MapResult>> matchWithMatchText(String text, List<Term> originals,
Integer detectDomainId) {
return null;
List<MapResult> detects = detect(text, regOffsetToLength, offsetList, detectDomainId);
Map<MatchText, List<MapResult>> result = new HashMap<>();
MatchText matchText = new MatchText(text, text);
result.put(matchText, detects);
return result;
}
private List<MapResult> detect(String text, Map<Integer, Integer> regOffsetToLength, List<Integer> offsetList,
@@ -72,15 +61,15 @@ public class QueryMatchStrategy implements MatchStrategy {
Set<MapResult> mapResultRowSet = new LinkedHashSet();
for (Integer i = index; i <= text.length(); ) {
int offset = getStepOffset(offsetList, index);
i = getStepIndex(regOffsetToLength, i);
int offset = mapperHelper.getStepOffset(offsetList, index);
i = mapperHelper.getStepIndex(regOffsetToLength, i);
if (i <= text.length()) {
List<MapResult> mapResults = detectByStep(text, detectDomainId, index, i, offset);
mapResultRowSet.addAll(mapResults);
}
}
index = getStepIndex(regOffsetToLength, index);
index = mapperHelper.getStepIndex(regOffsetToLength, index);
results.addAll(mapResultRowSet);
}
return results;
@@ -88,11 +77,13 @@ public class QueryMatchStrategy implements MatchStrategy {
private List<MapResult> detectByStep(String text, Integer detectDomainId, Integer index, Integer i, int offset) {
String detectSegment = text.substring(index, i);
Integer oneDetectionSize = mapperHelper.getOneDetectionSize();
// step1. pre search
LinkedHashSet<MapResult> mapResults = Suggester.prefixSearch(detectSegment, oneDetectionMaxSize)
LinkedHashSet<MapResult> mapResults = Suggester.prefixSearch(detectSegment,
mapperHelper.getOneDetectionMaxSize())
.stream().collect(Collectors.toCollection(LinkedHashSet::new));
// step2. suffix search
LinkedHashSet<MapResult> suffixMapResults = Suggester.suffixSearch(detectSegment, oneDetectionMaxSize)
LinkedHashSet<MapResult> suffixMapResults = Suggester.suffixSearch(detectSegment, oneDetectionSize)
.stream().collect(Collectors.toCollection(LinkedHashSet::new));
mapResults.addAll(suffixMapResults);
@@ -105,7 +96,7 @@ public class QueryMatchStrategy implements MatchStrategy {
.collect(Collectors.toCollection(LinkedHashSet::new));
// step4. filter by classId
if (Objects.nonNull(detectDomainId) && detectDomainId > 0) {
LOGGER.debug("detectDomainId:{}, before parseResults:{}", mapResults);
log.debug("detectDomainId:{}, before parseResults:{}", mapResults);
mapResults = mapResults.stream().map(entry -> {
List<String> natures = entry.getNatures().stream().filter(
nature -> nature.startsWith(NatureType.NATURE_SPILT + detectDomainId) || (nature.startsWith(
@@ -114,25 +105,27 @@ public class QueryMatchStrategy implements MatchStrategy {
entry.setNatures(natures);
return entry;
}).collect(Collectors.toCollection(LinkedHashSet::new));
LOGGER.info("after domainId parseResults:{}", mapResults);
log.info("after domainId parseResults:{}", mapResults);
}
// step5. filter by similarity
mapResults = mapResults.stream()
.filter(term -> getSimilarity(detectSegment, term.getName()) >= getThresholdMatch(term.getNatures()))
.filter(term -> mapperHelper.getSimilarity(detectSegment, term.getName())
>= mapperHelper.getThresholdMatch(term.getNatures()))
.filter(term -> CollectionUtils.isNotEmpty(term.getNatures()))
.collect(Collectors.toCollection(LinkedHashSet::new));
LOGGER.debug("metricDimensionThreshold:{},dimensionValueThreshold:{},after isSimilarity parseResults:{}",
log.debug("metricDimensionThreshold:{},dimensionValueThreshold:{},after isSimilarity parseResults:{}",
mapResults);
mapResults = mapResults.stream().map(parseResult -> {
parseResult.setOffset(offset);
parseResult.setSimilarity(getSimilarity(detectSegment, parseResult.getName()));
parseResult.setSimilarity(mapperHelper.getSimilarity(detectSegment, parseResult.getName()));
return parseResult;
}).collect(Collectors.toCollection(LinkedHashSet::new));
// step6. take only one dimension or 10 metric/dimension value per rond.
List<MapResult> dimensionMetrics = mapResults.stream().filter(entry -> existDimensionValues(entry.getNatures()))
List<MapResult> dimensionMetrics = mapResults.stream()
.filter(entry -> mapperHelper.existDimensionValues(entry.getNatures()))
.collect(Collectors.toList())
.stream()
.limit(1)
@@ -144,31 +137,4 @@ public class QueryMatchStrategy implements MatchStrategy {
return mapResults.stream().limit(oneDetectionSize).collect(Collectors.toList());
}
}
private Integer getStepIndex(Map<Integer, Integer> regOffsetToLength, Integer index) {
Integer subRegLength = regOffsetToLength.get(index);
if (Objects.nonNull(subRegLength)) {
index = index + subRegLength;
} else {
index++;
}
return index;
}
private Integer getStepOffset(List<Integer> termList, Integer index) {
for (int j = 0; j < termList.size() - 1; j++) {
if (termList.get(j) <= index && termList.get(j + 1) > index) {
return termList.get(j);
}
}
return index;
}
private double getThresholdMatch(List<String> natures) {
if (existDimensionValues(natures)) {
return dimensionValueThresholdConfig;
}
return metricDimensionThresholdConfig;
}
}

View File

@@ -23,15 +23,8 @@ public class SearchMatchStrategy implements MatchStrategy {
private static final int SEARCH_SIZE = 3;
@Override
public List<MapResult> match(String text, List<Term> terms, Integer detectDomainId) {
return null;
}
@Override
public Map<MatchText, List<MapResult>> matchWithMatchText(String text, List<Term> originals,
public Map<MatchText, List<MapResult>> match(String text, List<Term> originals,
Integer detectDomainId) {
Map<Integer, Integer> regOffsetToLength = originals.stream()

View File

@@ -1,59 +1,82 @@
package com.tencent.supersonic.chat.application.parser;
import com.tencent.supersonic.chat.api.component.SemanticParser;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticParser;
import com.tencent.supersonic.chat.application.parser.resolver.AggregateTypeResolver;
import com.tencent.supersonic.chat.application.query.MetricCompare;
import com.tencent.supersonic.chat.application.query.MetricFilter;
import com.tencent.supersonic.chat.application.query.EntityListFilter;
import com.tencent.supersonic.chat.application.query.MetricGroupBy;
import com.tencent.supersonic.chat.application.query.MetricOrderBy;
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.common.util.context.ContextUtils;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class AggregateSemanticParser implements SemanticParser {
private final Logger logger = LoggerFactory.getLogger(AggregateSemanticParser.class);
public static final Integer TOPN_LIMIT = 1000;
private AggregateTypeResolver aggregateTypeResolver;
private static Map<AggregateTypeEnum, Pattern> aggregateRegexMap = new HashMap<>();
static {
aggregateRegexMap.put(AggregateTypeEnum.MAX, Pattern.compile("(?i)(最大值|最大|max|峰值|最高|最多)"));
aggregateRegexMap.put(AggregateTypeEnum.MIN, Pattern.compile("(?i)(最小值|最小|min|最低|最少)"));
aggregateRegexMap.put(AggregateTypeEnum.SUM, Pattern.compile("(?i)(汇总|总和|sum)"));
aggregateRegexMap.put(AggregateTypeEnum.AVG, Pattern.compile("(?i)(平均值|日均|平均|avg)"));
aggregateRegexMap.put(AggregateTypeEnum.TOPN, Pattern.compile("(?i)(top)"));
aggregateRegexMap.put(AggregateTypeEnum.DISTINCT, Pattern.compile("(?i)(uv)"));
aggregateRegexMap.put(AggregateTypeEnum.COUNT, Pattern.compile("(?i)(总数|pv)"));
aggregateRegexMap.put(AggregateTypeEnum.NONE, Pattern.compile("(?i)(明细)"));
}
public static AggregateTypeEnum resolveAggregateType(String queryText) {
Map<AggregateTypeEnum, Integer> aggregateCount = new HashMap<>(aggregateRegexMap.size());
for (Map.Entry<AggregateTypeEnum, Pattern> entry : aggregateRegexMap.entrySet()) {
Matcher matcher = entry.getValue().matcher(queryText);
int count = 0;
while (matcher.find()) {
count++;
}
if (count > 0) {
aggregateCount.put(entry.getKey(), count);
}
}
return aggregateCount.entrySet().stream().max(Map.Entry.comparingByValue()).map(entry -> entry.getKey())
.orElse(null);
}
@Override
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
aggregateTypeResolver = ContextUtils.getBean(AggregateTypeResolver.class);
public void parse(QueryContextReq queryContext, ChatContext chatContext) {
AggregateTypeEnum aggregateType = resolveAggregateType(queryContext.getQueryText());
AggregateTypeEnum aggregateType = aggregateTypeResolver.resolve(queryContext.getQueryText());
for (SemanticQuery semanticQuery : queryContext.getCandidateQueries()) {
SemanticParseInfo semanticParse = semanticQuery.getParseInfo();
SemanticParseInfo semanticParse = queryContext.getParseInfo();
Set<SchemaItem> metrics = semanticParse.getMetrics();
semanticParse.setNativeQuery(getNativeQuery(aggregateType, queryContext));
semanticParse.setAggType(aggregateType);
//semanticParse.setOrders(getOrder(aggregateType, metrics));
semanticParse.setLimit(Long.valueOf(TOPN_LIMIT));
resetQueryModeByAggregateType(queryContext, aggregateType);
return false;
semanticParse.setNativeQuery(getNativeQuery(aggregateType, semanticParse));
semanticParse.setAggType(aggregateType);
if (Objects.isNull(semanticParse.getLimit()) || semanticParse.getLimit() <= 0) {
semanticParse.setLimit(Long.valueOf(TOPN_LIMIT));
}
resetQueryModeByAggregateType(semanticParse, aggregateType);
}
}
/**
* query mode reset by the AggregateType
*
* @param queryContext
* @param parseInfo
* @param aggregateType
*/
private void resetQueryModeByAggregateType(QueryContextReq queryContext, AggregateTypeEnum aggregateType) {
private void resetQueryModeByAggregateType(SemanticParseInfo parseInfo,
AggregateTypeEnum aggregateType) {
SemanticParseInfo parseInfo = queryContext.getParseInfo();
String queryMode = parseInfo.getQueryMode();
if (MetricGroupBy.QUERY_MODE.equals(queryMode) || MetricGroupBy.QUERY_MODE.equals(queryMode)) {
if (AggregateTypeEnum.MAX.equals(aggregateType) || AggregateTypeEnum.MIN.equals(aggregateType)
@@ -62,23 +85,18 @@ public class AggregateSemanticParser implements SemanticParser {
} else {
parseInfo.setQueryMode(MetricGroupBy.QUERY_MODE);
}
log.info("queryMode mode [{}]->[{}]", queryMode, parseInfo.getQueryMode());
}
if (MetricFilter.QUERY_MODE.equals(queryMode) || MetricCompare.QUERY_MODE.equals(queryMode)) {
if (aggregateTypeResolver.hasCompareIntentionalWords(queryContext.getQueryText())) {
parseInfo.setQueryMode(MetricCompare.QUERY_MODE);
} else {
parseInfo.setQueryMode(MetricFilter.QUERY_MODE);
}
}
logger.info("queryMode mode [{}]->[{}]", queryMode, parseInfo.getQueryMode());
}
private boolean getNativeQuery(AggregateTypeEnum aggregateType, QueryContextReq queryContext) {
private boolean getNativeQuery(AggregateTypeEnum aggregateType, SemanticParseInfo semanticParse) {
if (AggregateTypeEnum.TOPN.equals(aggregateType)) {
return true;
}
return queryContext.getParseInfo().getNativeQuery();
if (EntityListFilter.QUERY_MODE.equals(semanticParse.getQueryMode()) && (semanticParse.getMetrics() == null
|| semanticParse.getMetrics().isEmpty())) {
return true;
}
return semanticParse.getNativeQuery();
}
}

View File

@@ -1,273 +0,0 @@
package com.tencent.supersonic.chat.application.parser;
import static java.time.LocalDate.now;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticParser;
import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver;
import com.tencent.supersonic.chat.application.query.EntityDetail;
import com.tencent.supersonic.chat.application.query.EntityListFilter;
import com.tencent.supersonic.chat.application.query.EntityMetricFilter;
import com.tencent.supersonic.chat.application.query.MetricDomain;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
import com.tencent.supersonic.chat.domain.pojo.config.DefaultMetric;
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
import com.tencent.supersonic.chat.domain.service.ChatService;
import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils;
import com.tencent.supersonic.common.pojo.DateConf;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.common.util.context.ContextUtils;
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@Slf4j
@Component
public class DefaultMetricSemanticParser implements SemanticParser {
private final Logger logger = LoggerFactory.getLogger(DefaultMetricSemanticParser.class);
private DomainResolver selectStrategy;
private ChatService chatService;
private DefaultSemanticInternalUtils defaultSemanticUtils;
@Override
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
selectStrategy = ContextUtils.getBean(DomainResolver.class);
chatService = ContextUtils.getBean(ChatService.class);
defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class);
String queryMode = queryContext.getParseInfo().getQueryMode();
if (StringUtils.isNotEmpty(queryMode)) {
// QueryMode Selected
if (!EntityListFilter.QUERY_MODE.equals(queryMode)) {
Integer domainId = queryContext.getDomainId().intValue();
List<SchemaElementMatch> matchedElements = queryContext.getMapInfo().getMatchedElements(domainId);
if (!CollectionUtils.isEmpty(matchedElements)) {
long metricCount = matchedElements.stream()
.filter(schemaElementMatch -> schemaElementMatch.getElementType()
.equals(SchemaElementType.METRIC)).count();
if (metricCount <= 0) {
if (chatCtx.getParseInfo() == null
|| chatCtx.getParseInfo().getMetrics() == null
|| chatCtx.getParseInfo().getMetrics().size() <= 0) {
logger.info("fillThemeDefaultMetricLogic");
fillThemeDefaultMetricLogic(queryContext.getParseInfo());
}
}
}
fillDateDomain(chatCtx, queryContext);
}
}
defaultQueryMode(queryContext, chatCtx);
if (EntityDetail.QUERY_MODE.equals(queryMode) || EntityMetricFilter.QUERY_MODE.equals(queryMode)) {
addEntityDetailDimensionMetric(queryContext, chatCtx);
dealNativeQuery(queryContext, true);
}
return false;
}
private void dealNativeQuery(QueryContextReq queryContext, boolean isNativeQuery) {
if (Objects.nonNull(queryContext) && Objects.nonNull(queryContext.getParseInfo())) {
queryContext.getParseInfo().setNativeQuery(isNativeQuery);
}
}
private Set<String> addPrimaryDimension(EntityRichInfo entity, List<SchemaItem> dimensions) {
Set<String> primaryDimensions = new HashSet<>();
if (Objects.isNull(entity) || CollectionUtils.isEmpty(entity.getEntityIds())) {
return primaryDimensions;
}
entity.getEntityIds().stream().forEach(dimSchemaDesc -> {
SchemaItem dimension = new SchemaItem();
BeanUtils.copyProperties(dimSchemaDesc, dimension);
dimensions.add(dimension);
primaryDimensions.add(dimSchemaDesc.getBizName());
});
return primaryDimensions;
}
protected void addEntityDetailDimensionMetric(QueryContextReq queryContext, ChatContext chatCtx) {
if (queryContext.getParseInfo().getDomainId() > 0) {
Long domainId = queryContext.getParseInfo().getDomainId();
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domainId);
if (chaConfigRichDesc != null) {
if (chaConfigRichDesc.getEntity() == null
|| chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() == null) {
return;
}
SemanticParseInfo semanticParseInfo = queryContext.getParseInfo();
Set<SchemaItem> metrics = new LinkedHashSet();
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream()
.forEach(m -> metrics.add(getMetric(m)));
semanticParseInfo.setMetrics(metrics);
List<SchemaElementMatch> schemaElementMatches = queryContext.getMapInfo()
.getMatchedElements(domainId.intValue());
if (CollectionUtils.isEmpty(schemaElementMatches) || schemaElementMatches.stream()
.filter(s -> SchemaElementType.DIMENSION.equals(s.getElementType())).count() <= 0) {
logger.info("addEntityDetailDimensionMetric catch");
Set<SchemaItem> dimensions = new LinkedHashSet();
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getDimensionList().stream()
.forEach(m -> dimensions.add(getDimension(m)));
semanticParseInfo.setDimensions(dimensions);
}
}
}
}
protected void defaultQueryMode(QueryContextReq queryContext, ChatContext chatCtx) {
SchemaMapInfo schemaMap = queryContext.getMapInfo();
SemanticParseInfo parseInfo = queryContext.getParseInfo();
if (StringUtils.isEmpty(parseInfo.getQueryMode())) {
if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getDomainId() > 0) {
//
Long domain = chatCtx.getParseInfo().getDomainId();
String queryMode = chatCtx.getParseInfo().getQueryMode();
if (!CollectionUtils.isEmpty(schemaMap.getMatchedDomains()) && schemaMap.getMatchedDomains()
.contains(domain.intValue())) {
List<SchemaElementMatch> elementMatches = schemaMap.getMatchedElements(domain.intValue());
Long filterNUm = elementMatches.stream()
.filter(e -> e.getElementType().equals(SchemaElementType.VALUE) || e.getElementType()
.equals(SchemaElementType.ID)).count();
Long dimensionNUm = elementMatches.stream()
.filter(e -> e.getElementType().equals(SchemaElementType.DIMENSION)).count();
Long metricrNUm = elementMatches.stream()
.filter(e -> e.getElementType().equals(SchemaElementType.METRIC)).count();
if (filterNUm > 0 && dimensionNUm > 0 && metricrNUm > 0) {
// default as entity detail queryMode
logger.info("defaultQueryMode [{}]", EntityDetail.QUERY_MODE);
parseInfo.setQueryMode(EntityDetail.QUERY_MODE);
parseInfo.setDomainId(domain);
return;
}
Long entityNUm = elementMatches.stream()
.filter(e -> e.getElementType().equals(SchemaElementType.ENTITY)).count();
if (filterNUm <= 0 && dimensionNUm <= 0 && entityNUm <= 0) {
// default as metric domain
if (metricrNUm > 0 || MetricDomain.QUERY_MODE.equals(queryMode)) {
// default as entity detail queryMode
logger.info("defaultQueryMode [{}]", MetricDomain.QUERY_MODE);
parseInfo.setQueryMode(MetricDomain.QUERY_MODE);
parseInfo.setDomainId(domain);
return;
}
}
}
if (CollectionUtils.isEmpty(schemaMap.getMatchedDomains()) && parseInfo != null
&& parseInfo.getDateInfo() != null) {
// only query time
if (MetricDomain.QUERY_MODE.equals(queryMode)) {
// METRIC_DOMAIN context
logger.info("defaultQueryMode [{}]", MetricDomain.QUERY_MODE);
parseInfo.setQueryMode(MetricDomain.QUERY_MODE);
parseInfo.setDomainId(domain);
return;
}
}
}
}
}
private void fillDateDomain(ChatContext chatCtx, QueryContextReq queryContext) {
SemanticParseInfo parseInfo = queryContext.getParseInfo();
if (parseInfo == null || parseInfo.getDateInfo() == null) {
boolean isUpdateTime = false;
if (selectStrategy.isDomainSwitch(chatCtx, queryContext)) {
isUpdateTime = true;
}
if (chatCtx.getParseInfo() == null
|| chatCtx.getParseInfo().getDateInfo() == null) {
isUpdateTime = true;
}
if (isUpdateTime && parseInfo != null && parseInfo.getDomainId() > 0) {
logger.info("fillThemeDefaultTime");
fillThemeDefaultTime(parseInfo.getDomainId(), parseInfo);
}
}
}
private void fillThemeDefaultMetricLogic(SemanticParseInfo semanticParseInfo) {
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(
semanticParseInfo.getDomainId());
if (Objects.isNull(chaConfigRichDesc) || CollectionUtils.isEmpty(chaConfigRichDesc.getDefaultMetrics())) {
log.info("there is no defaultMetricIds info");
return;
}
if (CollectionUtils.isEmpty(semanticParseInfo.getMetrics()) && CollectionUtils.isEmpty(
semanticParseInfo.getDimensions())) {
Set<SchemaItem> metrics = new LinkedHashSet();
chaConfigRichDesc.getDefaultMetrics().stream().forEach(metric -> {
SchemaItem metricTmp = new SchemaItem();
metricTmp.setId(metric.getMetricId());
metricTmp.setBizName(metric.getBizName());
metrics.add(metricTmp);
});
semanticParseInfo.setMetrics(metrics);
}
if (Objects.isNull(semanticParseInfo.getDateInfo()) || Objects.isNull(
semanticParseInfo.getDateInfo().getDateMode())) {
DefaultMetric defaultMetricInfo = chaConfigRichDesc.getDefaultMetrics().get(0);
DateConf dateInfo = new DateConf();
dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS);
dateInfo.setUnit(defaultMetricInfo.getUnit());
dateInfo.setPeriod(defaultMetricInfo.getPeriod());
semanticParseInfo.setDateInfo(dateInfo);
}
}
private void fillThemeDefaultTime(Long domain, SemanticParseInfo semanticParseInfo) {
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(
semanticParseInfo.getDomainId());
if (!Objects.isNull(chaConfigRichDesc) && !CollectionUtils.isEmpty(chaConfigRichDesc.getDefaultMetrics())) {
DefaultMetric defaultMetricInfo = chaConfigRichDesc.getDefaultMetrics().get(0);
DateConf dateInfo = new DateConf();
dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS);
dateInfo.setUnit(defaultMetricInfo.getUnit());
dateInfo.setStartDate(now().minusDays(defaultMetricInfo.getUnit()).toString());
dateInfo.setEndDate(now().minusDays(1).toString());
dateInfo.setPeriod(defaultMetricInfo.getPeriod());
semanticParseInfo.setDateInfo(dateInfo);
}
}
private SchemaItem getMetric(MetricSchemaResp metricSchemaDesc) {
SchemaItem queryMeta = new SchemaItem();
queryMeta.setId(metricSchemaDesc.getId());
queryMeta.setBizName(metricSchemaDesc.getBizName());
queryMeta.setName(metricSchemaDesc.getName());
return queryMeta;
}
private SchemaItem getDimension(DimSchemaResp dimSchemaDesc) {
SchemaItem queryMeta = new SchemaItem();
queryMeta.setId(dimSchemaDesc.getId());
queryMeta.setBizName(dimSchemaDesc.getBizName());
queryMeta.setName(dimSchemaDesc.getName());
return queryMeta;
}
}

View File

@@ -1,10 +1,12 @@
package com.tencent.supersonic.chat.application.parser.resolver;
package com.tencent.supersonic.chat.application.parser;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import java.util.Map;
public interface DomainResolver {
@@ -12,6 +14,6 @@ public interface DomainResolver {
Integer resolve(Map<Integer, SemanticQuery> domainQueryModes, QueryContextReq queryCtx, ChatContext chatCtx,
SchemaMapInfo schemaMap);
boolean isDomainSwitch(ChatContext chatCtx, QueryContextReq queryCtx);
boolean isDomainSwitch(ChatContext chatCtx, SemanticParseInfo semanticParseInfo);
}

View File

@@ -1,135 +1,175 @@
package com.tencent.supersonic.chat.application.parser;
import com.tencent.supersonic.chat.api.component.SemanticLayer;
import com.tencent.supersonic.chat.api.component.SemanticParser;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticLayer;
import com.tencent.supersonic.chat.api.service.SemanticParser;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver;
import com.tencent.supersonic.chat.application.parser.resolver.SemanticQueryResolver;
import com.tencent.supersonic.chat.domain.pojo.semantic.DomainInfos;
import com.tencent.supersonic.chat.application.query.EntitySemanticQuery;
import com.tencent.supersonic.chat.application.query.MetricSemanticQuery;
import com.tencent.supersonic.chat.application.query.RuleSemanticQuery;
import com.tencent.supersonic.chat.application.query.RuleSemanticQueryManager;
import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigResp;
import com.tencent.supersonic.chat.domain.service.ConfigService;
import com.tencent.supersonic.chat.domain.utils.ComponentFactory;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import com.tencent.supersonic.chat.domain.utils.DefaultMetricUtils;
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
import com.tencent.supersonic.common.util.context.ContextUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
@Component
@Slf4j
public class DomainSemanticParser implements SemanticParser {
private final Logger logger = LoggerFactory.getLogger(DomainSemanticParser.class);
private List<DomainResolver> domainResolverList;
private SemanticQueryResolver semanticQueryResolver;
public DomainSemanticParser() {
domainResolverList = SpringFactoriesLoader.loadFactories(DomainResolver.class,
Thread.currentThread().getContextClassLoader());
}
private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
@Override
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
DomainInfos domainInfosDb = SchemaInfoConverter.convert(
ContextUtils.getBean(SemanticLayer.class).getDomainSchemaInfo(new ArrayList<>()));
public void parse(QueryContextReq queryContext, ChatContext chatContext) {
DomainInfos domainInfosDb = SchemaInfoConverter.convert(semanticLayer.getDomainSchemaInfo(new ArrayList<>()));
Map<Integer, String> domainToName = domainInfosDb.getDomainToName();
SchemaMapInfo mapInfo = queryContext.getMapInfo();
SemanticParseInfo parseInfo = queryContext.getParseInfo();
//domainResolver = ContextUtils.getBean(DomainResolver.class);
semanticQueryResolver = ContextUtils.getBean(SemanticQueryResolver.class);
// iterate all schemaElementMatches to resolve semantic query
for (Integer domainId : mapInfo.getMatchedDomains()) {
List<SchemaElementMatch> elementMatches = mapInfo.getMatchedElements(domainId);
List<RuleSemanticQuery> queries = resolveQuery(elementMatches, queryContext);
for (RuleSemanticQuery query : queries) {
Map<Integer, SemanticQuery> domainSemanticQuery = new HashMap<>();
// Round 1: find all domains that can be resolved to any query mode
for (Integer domain : mapInfo.getMatchedDomains()) {
List<SchemaElementMatch> elementMatches = mapInfo.getMatchedElements(domain);
SemanticQuery query = semanticQueryResolver.resolve(elementMatches, queryContext);
if (Objects.nonNull(query)) {
domainSemanticQuery.put(domain, query);
if (useBlackItem(query, domainId)) {
log.info("useBlackItem, skip query:{}", query);
continue;
}
addCandidateQuery(queryContext, chatContext, domainId.longValue(),
domainToName.get(domainId), query);
}
}
// only one domain is found, no need to rank
if (domainSemanticQuery.size() == 1) {
Optional<Map.Entry<Integer, SemanticQuery>> match = domainSemanticQuery.entrySet().stream().findFirst();
if (match.isPresent()) {
logger.info("select by only one [{}:{}]", match.get().getKey(), match.get().getValue());
parseInfo.setDomainId(Long.valueOf(match.get().getKey()));
parseInfo.setDomainName(domainToName.get(Integer.valueOf(match.get().getKey())));
parseInfo.setQueryMode(match.get().getValue().getQueryMode());
return false;
}
} else if (domainSemanticQuery.size() > 1) {
// will choose one by the domain select
Optional<Integer> domainId = domainResolverList.stream()
.map(domainResolver -> domainResolver.resolve(domainSemanticQuery, queryContext, chatCtx, mapInfo))
.filter(d -> d > 0).findFirst();
if (domainId.isPresent() && domainId.get() > 0) {
for (Map.Entry<Integer, SemanticQuery> match : domainSemanticQuery.entrySet()) {
if (match.getKey().equals(domainId.get())) {
logger.info("select by selectStrategy [{}:{}]", domainId.get(), match.getValue());
parseInfo.setDomainId(Long.valueOf(match.getKey()));
parseInfo.setDomainName(domainToName.get(Integer.valueOf(match.getKey())));
parseInfo.setQueryMode(match.getValue().getQueryMode());
return false;
// if no candidates have been found yet, count in chat context and try again
if (queryContext.getCandidateQueries().size() <= 0) {
if (chatContext.getParseInfo() != null && chatContext.getParseInfo().getDomainId() > 0) {
Integer chatDomainId = Integer.valueOf(chatContext.getParseInfo().getDomainId().intValue());
if (mapInfo.getMatchedDomains().contains(chatDomainId)) {
List<SchemaElementMatch> elementMatches = mapInfo.getMatchedElements(chatDomainId);
List<RuleSemanticQuery> queries = tryParseByContext(elementMatches, chatContext, queryContext);
for (RuleSemanticQuery query : queries) {
addCandidateQuery(queryContext, chatContext, chatDomainId.longValue(),
domainToName.get(chatDomainId), query);
}
}
}
}
// Round 2: no domains can be found yet, count in chat context
if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getDomainId() > 0) {
Integer chatDomain = Integer.valueOf(chatCtx.getParseInfo().getDomainId().intValue());
if (mapInfo.getMatchedDomains().contains(chatDomain) || CollectionUtils.isEmpty(
mapInfo.getMatchedDomains())) {
List<SchemaElementMatch> elementMatches = mapInfo.getMatchedElements(chatDomain);
if (CollectionUtils.isEmpty(elementMatches)) {
parseInfo.setDomainId(Long.valueOf(chatDomain));
parseInfo.setDomainName(domainToName.get(chatDomain));
parseInfo.setQueryMode(chatCtx.getParseInfo().getQueryMode());
return false;
}
}
private boolean useBlackItem(RuleSemanticQuery query, Integer domainId) {
if (Objects.isNull(domainId)) {
return false;
}
ConfigService configService = ContextUtils.getBean(ConfigService.class);
ChatConfigResp chatConfigResp = configService.fetchConfigByDomainId(domainId.longValue());
if (Objects.nonNull(chatConfigResp) && Objects.nonNull(query) && Objects.nonNull(query.getParseInfo())) {
List<SchemaElementMatch> elementMatches = query.getParseInfo().getElementMatches();
if (!CollectionUtils.isEmpty(elementMatches)) {
return useBlackItemInternal(elementMatches, chatConfigResp, query);
SemanticQuery query = tryParseByContext(elementMatches, chatCtx, queryContext);
if (Objects.nonNull(query)) {
logger.info("select by context count [{}:{}]", chatDomain, query);
parseInfo.setDomainId(Long.valueOf(chatDomain));
parseInfo.setDomainName(domainToName.get(chatDomain));
parseInfo.setQueryMode(query.getQueryMode());
return false;
}
}
}
// Round 3: no domains can be found yet, count in default metric
return false;
}
private boolean useBlackItemInternal(List<SchemaElementMatch> elementMatches, ChatConfigResp chatConfigResp, RuleSemanticQuery query) {
if (Objects.isNull(chatConfigResp)) {
return false;
}
List<Long> blackDimIdList = new ArrayList<>();
List<Long> blackMetricIdList = new ArrayList<>();
if (query instanceof EntitySemanticQuery
&& Objects.nonNull(chatConfigResp.getChatDetailConfig())
&& Objects.nonNull(chatConfigResp.getChatDetailConfig().getVisibility())) {
log.info("useBlackItem, handle EntitySemanticQuery blackList logic");
blackDimIdList = chatConfigResp.getChatDetailConfig().getVisibility().getBlackDimIdList();
blackMetricIdList = chatConfigResp.getChatDetailConfig().getVisibility().getBlackMetricIdList();
}
if (query instanceof MetricSemanticQuery
&& Objects.nonNull(chatConfigResp.getChatAggConfig())
&& Objects.nonNull(chatConfigResp.getChatAggConfig().getVisibility())) {
log.info("useBlackItem, handle MetricSemanticQuery blackList logic");
blackDimIdList = chatConfigResp.getChatAggConfig().getVisibility().getBlackDimIdList();
blackMetricIdList = chatConfigResp.getChatAggConfig().getVisibility().getBlackMetricIdList();
}
return useBlackItemWithElementMatches(elementMatches, blackDimIdList, blackMetricIdList);
}
private boolean useBlackItemWithElementMatches(List<SchemaElementMatch> elementMatches, List<Long> blackDimIdList, List<Long> blackMetricIdList) {
Set<Long> dimIds = elementMatches.stream()
.filter(element -> SchemaElementType.VALUE.equals(element.getElementType()) || SchemaElementType.DIMENSION.equals(element.getElementType()))
.map(element -> Long.valueOf(element.getElementID())).collect(Collectors.toSet());
Set<Long> metricIds = elementMatches.stream()
.filter(element -> SchemaElementType.METRIC.equals(element.getElementType()))
.map(element -> Long.valueOf(element.getElementID())).collect(Collectors.toSet());
return useBlackItemWithIds(dimIds, metricIds, blackDimIdList, blackMetricIdList);
}
private boolean useBlackItemWithIds(Set<Long> dimIds, Set<Long> metricIds, List<Long> blackDimIdList, List<Long> blackMetricIdList) {
if (!CollectionUtils.isEmpty(blackDimIdList) && !CollectionUtils.isEmpty(dimIds)) {
if (blackDimIdList.stream().anyMatch(dimIds::contains)) {
log.info("useBlackItem, blackDimIdList:{}", blackDimIdList.stream().filter(dimIds::contains).collect(Collectors.toList()));
return true;
}
}
if (!CollectionUtils.isEmpty(blackMetricIdList) && !CollectionUtils.isEmpty(metricIds)) {
if (blackMetricIdList.stream().anyMatch(metricIds::contains)) {
log.info("useBlackItem, blackMetricIdList:{}", blackMetricIdList.stream().filter(metricIds::contains).collect(Collectors.toList()));
return true;
}
}
return false;
}
private void addCandidateQuery(QueryContextReq queryContext, ChatContext chatContext,
Long domainId, String domainName, RuleSemanticQuery semanticQuery) {
if (semanticQuery != null) {
DefaultMetricUtils defaultMetricUtils = ContextUtils.getBean(DefaultMetricUtils.class);
defaultMetricUtils.fillParseInfo(semanticQuery, domainId, domainName);
inheritContext(semanticQuery, chatContext);
defaultMetricUtils.fillDefaultMetric(semanticQuery.getParseInfo(), queryContext, chatContext);
queryContext.getCandidateQueries().add(semanticQuery);
}
}
protected void inheritContext(RuleSemanticQuery semanticQuery, ChatContext chatContext) {
// is domain switch
SemanticParseInfo semanticParse = semanticQuery.getParseInfo();
DomainResolver domainResolver = ComponentFactory.getDomainResolver();
if (!domainResolver.isDomainSwitch(chatContext, semanticParse)) {
semanticQuery.inheritContext(chatContext);
}
}
/**
* try to add ChatContext to SchemaElementMatch and look if match QueryMode
* try to add ChatContext to SchemaMatch and look if match QueryMode
*
* @param elementMatches
* @param chatCtx
* @return
*/
private SemanticQuery tryParseByContext(List<SchemaElementMatch> elementMatches, ChatContext chatCtx,
QueryContextReq searchCt) {
private List<RuleSemanticQuery> tryParseByContext(List<SchemaElementMatch> elementMatches,
ChatContext chatCtx, QueryContextReq queryCtx) {
if (chatCtx.getParseInfo() != null && chatCtx.getParseInfo().getEntity() > 0) {
Long entityCount = elementMatches.stream().filter(i -> SchemaElementType.ENTITY.equals(i.getElementType()))
.count();
@@ -137,27 +177,28 @@ public class DomainSemanticParser implements SemanticParser {
.count();
if (entityCount <= 0 && metricCount <= 0 && ContextHelper.hasEntityId(chatCtx)) {
// try entity parse
SchemaElementMatch entityElementMatch = new SchemaElementMatch();
entityElementMatch.setElementType(SchemaElementType.ENTITY);
List<SchemaElementMatch> newSchemaElementMatch = new ArrayList<>();
SchemaElementMatch entityElementMatch = SchemaElementMatch.builder()
.elementType(SchemaElementType.ENTITY).build();
List<SchemaElementMatch> newSchemaMatches = new ArrayList<>();
if (!CollectionUtils.isEmpty(elementMatches)) {
newSchemaElementMatch.addAll(elementMatches);
newSchemaMatches.addAll(elementMatches);
}
newSchemaElementMatch.add(entityElementMatch);
SemanticQuery semanticQuery = doParseByContext(newSchemaElementMatch, chatCtx, searchCt);
if (Objects.nonNull(semanticQuery)) {
return semanticQuery;
newSchemaMatches.add(entityElementMatch);
List<RuleSemanticQuery> queries = doParseByContext(newSchemaMatches, chatCtx, queryCtx);
if (queries.size() > 0) {
return queries;
}
}
}
return doParseByContext(elementMatches, chatCtx, searchCt);
return doParseByContext(elementMatches, chatCtx, queryCtx);
}
private SemanticQuery doParseByContext(List<SchemaElementMatch> elementMatches, ChatContext chatCtx,
QueryContextReq searchCt) {
SemanticParseInfo contextSemanticParseInfo = chatCtx.getParseInfo();
if (contextSemanticParseInfo != null) {
List<SchemaElementMatch> newSchemaElementMatch = new ArrayList<>();
private List<RuleSemanticQuery> doParseByContext(List<SchemaElementMatch> elementMatches,
ChatContext chatCtx, QueryContextReq queryContext) {
SemanticParseInfo contextSemanticParse = chatCtx.getParseInfo();
if (contextSemanticParse != null) {
List<SchemaElementMatch> newElementMatches = new ArrayList<>();
List<List<SchemaElementType>> trySchemaElementTypes = new LinkedList<>();
// try DIMENSION+METRIC+VALUE
// try DIMENSION+METRIC METRIC+VALUE DIMENSION+VALUE
@@ -174,19 +215,36 @@ public class DomainSemanticParser implements SemanticParser {
trySchemaElementTypes.add(new ArrayList<>(Arrays.asList(SchemaElementType.VALUE)));
trySchemaElementTypes.add(new ArrayList<>(Arrays.asList(SchemaElementType.DIMENSION)));
for (List<SchemaElementType> schemaElementTypes : trySchemaElementTypes) {
newSchemaElementMatch.clear();
for (List<SchemaElementType> schemaTypes : trySchemaElementTypes) {
newElementMatches.clear();
if (!CollectionUtils.isEmpty(elementMatches)) {
newSchemaElementMatch.addAll(elementMatches);
newElementMatches.addAll(elementMatches);
}
ContextHelper.mergeContextSchemaElementMatch(newSchemaElementMatch, elementMatches, schemaElementTypes,
contextSemanticParseInfo);
SemanticQuery semanticQuery = semanticQueryResolver.resolve(newSchemaElementMatch, searchCt);
if (semanticQuery != null) {
return semanticQuery;
ContextHelper.mergeContextSchemaElementMatch(newElementMatches, elementMatches, schemaTypes,
contextSemanticParse);
List<RuleSemanticQuery> queries = resolveQuery(newElementMatches, queryContext);
if (queries.size() > 0) {
return queries;
}
}
}
return null;
return new ArrayList<>();
}
private List<RuleSemanticQuery> resolveQuery(List<SchemaElementMatch> candidateElementMatches,
QueryContextReq queryContext) {
List<RuleSemanticQuery> matchedQueries = new ArrayList<>();
for (RuleSemanticQuery semanticQuery : RuleSemanticQueryManager.getSemanticQueries()) {
List<SchemaElementMatch> matches = semanticQuery.match(candidateElementMatches, queryContext);
if (matches.size() > 0) {
log.info("resolve match [{}:{}] ", semanticQuery.getQueryMode(), matches.size());
RuleSemanticQuery query = RuleSemanticQueryManager.create(semanticQuery.getQueryMode());
query.getParseInfo().getElementMatches().addAll(matches);
matchedQueries.add(query);
}
}
return matchedQueries;
}
}

View File

@@ -1,36 +1,117 @@
package com.tencent.supersonic.chat.application.parser.resolver;
package com.tencent.supersonic.chat.application.parser;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.*;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class BaseDomainResolver implements DomainResolver {
import java.util.*;
import java.util.stream.Collectors;
@Override
public boolean isDomainSwitch(ChatContext chatCtx, QueryContextReq searchCtx) {
Long contextDomain = chatCtx.getParseInfo().getDomainId();
Long currentDomain = searchCtx.getParseInfo().getDomainId();
boolean noSwitch = currentDomain == null || contextDomain == null || contextDomain.equals(currentDomain);
log.info("ChatContext isDomainSwitch [{}]", !noSwitch);
return !noSwitch;
@Slf4j
public class HeuristicDomainResolver implements DomainResolver {
protected static Integer selectDomainBySchemaElementCount(Map<Integer, SemanticQuery> domainQueryModes,
SchemaMapInfo schemaMap) {
Map<Integer, QueryMatchInfo> domainTypeMap = getDomainTypeMap(schemaMap);
if (domainTypeMap.size() == 1) {
Integer domainSelect = domainTypeMap.entrySet().stream().collect(Collectors.toList()).get(0).getKey();
if (domainQueryModes.containsKey(domainSelect)) {
log.info("selectDomain from domainTypeMap not order [{}]", domainSelect);
return domainSelect;
}
} else {
Map.Entry<Integer, QueryMatchInfo> maxDomain = domainTypeMap.entrySet().stream()
.filter(entry -> domainQueryModes.containsKey(entry.getKey()))
.sorted(ContextHelper.DomainStatComparator).findFirst().orElse(null);
if (maxDomain != null) {
log.info("selectDomain from domainTypeMap order [{}]", maxDomain.getKey());
return maxDomain.getKey();
}
}
return 0;
}
public abstract Integer selectDomain(Map<Integer, SemanticQuery> domainQueryModes, QueryContextReq searchCtx,
ChatContext chatCtx, SchemaMapInfo schemaMap);
/**
* to check can switch domain if context exit domain
*
* @return false will use context domain, true will use other domain , maybe include context domain
*/
protected static boolean isAllowSwitch(Map<Integer, SemanticQuery> domainQueryModes, SchemaMapInfo schemaMap,
ChatContext chatCtx, QueryContextReq searchCtx, Integer domainId) {
if (!Objects.nonNull(domainId) || domainId <= 0) {
return true;
}
// except content domain, calculate the number of types for each domain, if numbers<=1 will not switch
Map<Integer, QueryMatchInfo> domainTypeMap = getDomainTypeMap(schemaMap);
log.info("isAllowSwitch domainTypeMap [{}]", domainTypeMap);
long otherDomainTypeNumBigOneCount = domainTypeMap.entrySet().stream()
.filter(entry -> domainQueryModes.containsKey(entry.getKey()) && !entry.getKey().equals(domainId))
.filter(entry -> entry.getValue().getCount() > 1).count();
if (otherDomainTypeNumBigOneCount >= 1) {
return true;
}
// if query text only contain time , will not switch
for (SemanticQuery semanticQuery : domainQueryModes.values()) {
SemanticParseInfo semanticParseInfo = semanticQuery.getParseInfo();
if (semanticParseInfo == null) {
continue;
}
if (searchCtx.getQueryText() != null && semanticParseInfo.getDateInfo() != null) {
if (semanticParseInfo.getDateInfo().getText() != null) {
if (semanticParseInfo.getDateInfo().getText().equalsIgnoreCase(searchCtx.getQueryText())) {
log.info("timeParseResults is not null , can not switch context , timeParseResults:{},",
semanticParseInfo.getDateInfo());
return false;
}
}
}
}
// if context domain not in schemaMap , will switch
if (schemaMap.getMatchedElements(domainId) == null || schemaMap.getMatchedElements(domainId).size() <= 0) {
log.info("domainId not in schemaMap ");
return true;
}
// other will not switch
return false;
}
public static Map<Integer, QueryMatchInfo> getDomainTypeMap(SchemaMapInfo schemaMap) {
Map<Integer, QueryMatchInfo> domainCount = new HashMap<>();
for (Map.Entry<Integer, List<SchemaElementMatch>> entry : schemaMap.getDomainElementMatches().entrySet()) {
List<SchemaElementMatch> schemaElementMatches = schemaMap.getMatchedElements(entry.getKey());
if (schemaElementMatches != null && schemaElementMatches.size() > 0) {
if (!domainCount.containsKey(entry.getKey())) {
domainCount.put(entry.getKey(), new QueryMatchInfo());
}
QueryMatchInfo queryMatchInfo = domainCount.get(entry.getKey());
Set<SchemaElementType> schemaElementTypes = new HashSet<>();
schemaElementMatches.stream()
.forEach(schemaElementMatch -> schemaElementTypes.add(schemaElementMatch.getElementType()));
SchemaElementMatch schemaElementMatchMax = schemaElementMatches.stream()
.sorted(ContextHelper.schemaElementMatchComparatorBySimilarity).findFirst().orElse(null);
if (schemaElementMatchMax != null) {
queryMatchInfo.setMaxSimilarity(schemaElementMatchMax.getSimilarity());
}
queryMatchInfo.setCount(schemaElementTypes.size());
}
}
return domainCount;
}
@Override
public boolean isDomainSwitch(ChatContext chatCtx, SemanticParseInfo semanticParseInfo) {
Long contextDomain = chatCtx.getParseInfo().getDomainId();
Long currentDomain = semanticParseInfo.getDomainId();
boolean noSwitch =
currentDomain == null || contextDomain == null || contextDomain.equals(currentDomain);
log.debug("ChatContext isDomainSwitch [{}] [{}]",
semanticParseInfo.getQueryMode(), !noSwitch);
return !noSwitch;
}
@Override
public Integer resolve(Map<Integer, SemanticQuery> domainQueryModes, QueryContextReq searchCtx,
@@ -44,88 +125,23 @@ public abstract class BaseDomainResolver implements DomainResolver {
return selectDomainBySchemaElementCount(domainQueryModes, schemaMap);
}
protected static Integer selectDomainBySchemaElementCount(Map<Integer, SemanticQuery> domainQueryModes,
public Integer selectDomain(Map<Integer, SemanticQuery> domainQueryModes, QueryContextReq searchCtx,
ChatContext chatCtx,
SchemaMapInfo schemaMap) {
Map<Integer, SchemaElementCount> domainTypeMap = getDomainTypeMap(schemaMap);
if (domainTypeMap.size() == 1) {
Integer domainSelect = domainTypeMap.entrySet().stream().collect(Collectors.toList()).get(0).getKey();
if (domainQueryModes.containsKey(domainSelect)) {
log.info("selectDomain from domainTypeMap not order [{}]", domainSelect);
return domainSelect;
}
} else {
Map.Entry<Integer, SchemaElementCount> maxDomain = domainTypeMap.entrySet().stream()
.filter(entry -> domainQueryModes.containsKey(entry.getKey()))
.sorted(ContextHelper.DomainStatComparator).findFirst().orElse(null);
if (maxDomain != null) {
log.info("selectDomain from domainTypeMap order [{}]", maxDomain.getKey());
return maxDomain.getKey();
// if QueryContext has domainId and in domainQueryModes
if (domainQueryModes.containsKey(searchCtx.getDomainId())) {
log.info("selectDomain from QueryContext [{}]", searchCtx.getDomainId());
return searchCtx.getDomainId();
}
// if ChatContext has domainId and in domainQueryModes
if (chatCtx.getParseInfo().getDomainId() > 0) {
Integer domainId = Integer.valueOf(chatCtx.getParseInfo().getDomainId().intValue());
if (!isAllowSwitch(domainQueryModes, schemaMap, chatCtx, searchCtx, domainId)) {
log.info("selectDomain from ChatContext [{}]", domainId);
return domainId;
}
}
// default 0
return 0;
}
/**
* to check can switch domain if context exit domain
*
* @return false will use context domain, true will use other domain , maybe include context domain
*/
protected static boolean isAllowSwitch(Map<Integer, SemanticQuery> domainQueryModes, SchemaMapInfo schemaMap,
ChatContext chatCtx, QueryContextReq searchCtx, Integer domainId) {
if (!Objects.nonNull(domainId) || domainId <= 0) {
return true;
}
// except content domain, calculate the number of types for each domain, if numbers<=1 will not switch
Map<Integer, SchemaElementCount> domainTypeMap = getDomainTypeMap(schemaMap);
log.info("isAllowSwitch domainTypeMap [{}]", domainTypeMap);
long otherDomainTypeNumBigOneCount = domainTypeMap.entrySet().stream()
.filter(entry -> domainQueryModes.containsKey(entry.getKey()) && !entry.getKey().equals(domainId))
.filter(entry -> entry.getValue().getCount() > 1).count();
if (otherDomainTypeNumBigOneCount >= 1) {
return true;
}
// if query text only contain time , will not switch
if (searchCtx.getQueryText() != null && searchCtx.getParseInfo().getDateInfo() != null) {
if (searchCtx.getParseInfo().getDateInfo().getText() != null) {
if (searchCtx.getParseInfo().getDateInfo().getText().equalsIgnoreCase(searchCtx.getQueryText())) {
log.info("timeParseResults is not null , can not switch context , timeParseResults:{},",
searchCtx.getParseInfo().getDateInfo());
return false;
}
}
}
// if context domain not in schemaMap , will switch
if (schemaMap.getMatchedElements(domainId) == null || schemaMap.getMatchedElements(domainId).size() <= 0) {
log.info("domainId not in schemaMap ");
return true;
}
// other will not switch
return false;
}
protected static Map<Integer, SchemaElementCount> getDomainTypeMap(SchemaMapInfo schemaMap) {
Map<Integer, SchemaElementCount> domainCount = new HashMap<>();
for (Map.Entry<Integer, List<SchemaElementMatch>> entry : schemaMap.getDomainElementMatches().entrySet()) {
List<SchemaElementMatch> schemaElementMatches = schemaMap.getMatchedElements(entry.getKey());
if (schemaElementMatches != null && schemaElementMatches.size() > 0) {
if (!domainCount.containsKey(entry.getKey())) {
domainCount.put(entry.getKey(), new SchemaElementCount());
}
SchemaElementCount schemaElementCount = domainCount.get(entry.getKey());
Set<SchemaElementType> schemaElementTypes = new HashSet<>();
schemaElementMatches.stream()
.forEach(schemaElementMatch -> schemaElementTypes.add(schemaElementMatch.getElementType()));
SchemaElementMatch schemaElementMatchMax = schemaElementMatches.stream()
.sorted(ContextHelper.schemaElementMatchComparatorBySimilarity).findFirst().orElse(null);
if (schemaElementMatchMax != null) {
schemaElementCount.setMaxSimilarity(schemaElementMatchMax.getSimilarity());
}
schemaElementCount.setCount(schemaElementTypes.size());
}
}
return domainCount;
}
}

View File

@@ -0,0 +1,160 @@
package com.tencent.supersonic.chat.application.parser;
import com.tencent.supersonic.chat.api.component.SemanticParser;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.application.knowledge.WordNatureService;
import com.tencent.supersonic.chat.application.query.LLMSemanticQuery;
import com.tencent.supersonic.chat.domain.config.LLMConfig;
import com.tencent.supersonic.chat.domain.pojo.chat.DomainInfos;
import com.tencent.supersonic.chat.domain.pojo.chat.LLMReq;
import com.tencent.supersonic.chat.domain.pojo.chat.LLMResp;
import com.tencent.supersonic.chat.domain.pojo.chat.LLMSchema;
import com.tencent.supersonic.chat.domain.utils.DslToSemanticInfo;
import com.tencent.supersonic.chat.domain.utils.SemanticSatisfactionChecker;
import com.tencent.supersonic.common.nlp.ItemDO;
import com.tencent.supersonic.common.util.context.ContextUtils;
import com.tencent.supersonic.common.util.json.JsonUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
@Slf4j
public class LLMSemanticParser implements SemanticParser {
private DslToSemanticInfo dslToSemanticInfo = new DslToSemanticInfo();
@Override
public void parse(QueryContextReq queryContext, ChatContext chatCtx) {
String queryText = queryContext.getQueryText();
if (SemanticSatisfactionChecker.check(queryContext)) {
log.info("There is no need parse by llm , queryText:{}", queryText);
return;
}
try {
Integer domainId = getDomainId(queryContext, chatCtx);
LLMResp llmResp = requestLLM(queryContext, domainId);
if (Objects.isNull(llmResp)) {
return;
}
LLMSemanticQuery semanticQuery = new LLMSemanticQuery();
SemanticParseInfo parseInfo = semanticQuery.getParseInfo();
String sql = convertToSql(llmResp, parseInfo, domainId);
parseInfo.setInfo(sql);
parseInfo.setDomainId(Long.valueOf(domainId));
parseInfo.setBonus(queryText.length() * 1.0);
parseInfo.setQueryMode(LLMSemanticQuery.QUERY_MODE);
queryContext.getCandidateQueries().add(semanticQuery);
return;
} catch (Exception e) {
log.error("llm parse error , skip the parser. error:", e);
}
}
protected String convertToSql(LLMResp llmResp, SemanticParseInfo parseInfo, Integer domainId)
throws SqlParseException {
return dslToSemanticInfo.convert(parseInfo, llmResp, domainId);
}
protected LLMResp requestLLM(QueryContextReq queryContext, Integer domainId) {
final LLMConfig llmConfig = ContextUtils.getBean(LLMConfig.class);
if (StringUtils.isEmpty(llmConfig.getUrl())) {
log.warn("llmConfig url is null, skip llm parser");
return null;
}
DomainInfos domainInfos = ContextUtils.getBean(WordNatureService.class).getCache().getUnchecked("");
Map<Integer, String> domainIdToName = domainInfos.getDomains().stream()
.collect(Collectors.toMap(ItemDO::getDomain, a -> a.getName(), (k1, k2) -> k1));
Map<Integer, String> itemIdToName = domainInfos.getDimensions().stream()
.filter(entry -> domainId.equals(entry.getDomain()))
.collect(Collectors.toMap(ItemDO::getItemId, ItemDO::getName, (value1, value2) -> value2));
String domainName = domainIdToName.get(domainId);
LLMReq llmReq = new LLMReq();
llmReq.setQueryText(queryContext.getQueryText());
List<SchemaElementMatch> matchedElements = queryContext.getMapInfo().getMatchedElements(domainId);
Set<String> fieldNameList = matchedElements.stream()
.filter(schemaElementMatch ->
SchemaElementType.METRIC.equals(schemaElementMatch.getElementType()) ||
SchemaElementType.DIMENSION.equals(schemaElementMatch.getElementType()) ||
SchemaElementType.VALUE.equals(schemaElementMatch.getElementType()))
.map(schemaElementMatch -> {
if (!SchemaElementType.VALUE.equals(schemaElementMatch.getElementType())) {
return schemaElementMatch.getWord();
}
return itemIdToName.get(schemaElementMatch.getElementID());
})
.filter(name -> StringUtils.isNotEmpty(name) && !name.contains("%"))
.collect(Collectors.toSet());
LLMSchema llmSchema = new LLMSchema();
llmSchema.setDomainName(domainName);
llmSchema.setFieldNameList(new ArrayList<>(fieldNameList));
llmReq.setSchema(llmSchema);
log.info("requestLLM request, domainId:{},llmReq:{}", domainId, llmReq);
String questUrl = llmConfig.getUrl() + llmConfig.getQueryToSqlPath();
RestTemplate restTemplate = ContextUtils.getBean(RestTemplate.class);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(JsonUtil.toString(llmReq), headers);
ResponseEntity<LLMResp> responseEntity = restTemplate.exchange(questUrl, HttpMethod.POST, entity,
LLMResp.class);
log.info("requestLLM response, questUrl:{} \n entity:{} \n body:{}", questUrl, entity,
responseEntity.getBody());
return responseEntity.getBody();
}
protected Integer getDomainId(QueryContextReq queryContext, ChatContext chatCtx) {
SchemaMapInfo mapInfo = queryContext.getMapInfo();
Set<Integer> matchedDomains = mapInfo.getMatchedDomains();
Map<Integer, SemanticQuery> domainQueryModes = new HashMap<>();
for (Integer matchedDomain : matchedDomains) {
domainQueryModes.put(matchedDomain, new LLMSemanticQuery());
}
List<DomainResolver> domainResolverList = SpringFactoriesLoader.loadFactories(DomainResolver.class,
Thread.currentThread().getContextClassLoader());
Optional<Integer> domainId = domainResolverList.stream()
.map(domainResolver -> domainResolver.resolve(domainQueryModes, queryContext, chatCtx,
queryContext.getMapInfo())).filter(d -> d > 0).findFirst();
if (domainId.isPresent()) {
return domainId.get();
}
return 0;
}
}

View File

@@ -1,130 +0,0 @@
package com.tencent.supersonic.chat.application.parser;
import static com.tencent.supersonic.common.constant.Constants.DAY;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticParser;
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
import com.tencent.supersonic.chat.application.query.EntityListFilter;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils;
import com.tencent.supersonic.common.constant.Constants;
import com.tencent.supersonic.common.pojo.DateConf;
import com.tencent.supersonic.common.pojo.Order;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.common.util.context.ContextUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@Component
public class ListFilterParser implements SemanticParser {
private DefaultSemanticInternalUtils defaultSemanticUtils;
@Override
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class);
String queryMode = queryContext.getParseInfo().getQueryMode();
if (!EntityListFilter.QUERY_MODE.equals(queryMode)) {
return false;
}
this.fillDateEntityFilter(queryContext.getParseInfo());
this.addEntityDetailAndOrderByMetric(queryContext, chatCtx);
this.dealNativeQuery(queryContext, true);
return true;
}
private void fillDateEntityFilter(SemanticParseInfo semanticParseInfo) {
DateConf dateInfo = new DateConf();
dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS);
dateInfo.setUnit(1);
dateInfo.setPeriod(DAY);
dateInfo.setText(String.format("近1天"));
semanticParseInfo.setDateInfo(dateInfo);
}
private void addEntityDetailAndOrderByMetric(QueryContextReq queryContext, ChatContext chatCtx) {
if (queryContext.getParseInfo().getDomainId() > 0L) {
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(
queryContext.getParseInfo().getDomainId());
if (chaConfigRichDesc != null) {
SemanticParseInfo semanticParseInfo = queryContext.getParseInfo();
Set<SchemaItem> dimensions = new LinkedHashSet();
Set<String> primaryDimensions = this.addPrimaryDimension(chaConfigRichDesc.getEntity(), dimensions);
Set<SchemaItem> metrics = new LinkedHashSet();
if (chaConfigRichDesc.getEntity() != null
&& chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) {
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream()
.forEach((m) -> metrics.add(this.getMetric(m)));
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getDimensionList().stream()
.filter((m) -> !primaryDimensions.contains(m.getBizName()))
.forEach((m) -> dimensions.add(this.getDimension(m)));
}
semanticParseInfo.setDimensions(dimensions);
semanticParseInfo.setMetrics(metrics);
Set<Order> orders = new LinkedHashSet();
if (chaConfigRichDesc.getEntity() != null
&& chaConfigRichDesc.getEntity().getEntityInternalDetailDesc() != null) {
chaConfigRichDesc.getEntity().getEntityInternalDetailDesc().getMetricList().stream()
.forEach((metric) -> orders.add(new Order(metric.getBizName(), Constants.DESC_UPPER)));
}
semanticParseInfo.setOrders(orders);
}
}
}
private Set<String> addPrimaryDimension(EntityRichInfo entity, Set<SchemaItem> dimensions) {
Set<String> primaryDimensions = new HashSet();
if (!Objects.isNull(entity) && !CollectionUtils.isEmpty(entity.getEntityIds())) {
entity.getEntityIds().stream().forEach((dimSchemaDesc) -> {
SchemaItem dimension = new SchemaItem();
BeanUtils.copyProperties(dimSchemaDesc, dimension);
dimensions.add(dimension);
primaryDimensions.add(dimSchemaDesc.getBizName());
});
return primaryDimensions;
} else {
return primaryDimensions;
}
}
private SchemaItem getMetric(MetricSchemaResp metricSchemaDesc) {
SchemaItem queryMeta = new SchemaItem();
queryMeta.setId(metricSchemaDesc.getId());
queryMeta.setBizName(metricSchemaDesc.getBizName());
queryMeta.setName(metricSchemaDesc.getName());
return queryMeta;
}
private SchemaItem getDimension(DimSchemaResp dimSchemaDesc) {
SchemaItem queryMeta = new SchemaItem();
queryMeta.setId(dimSchemaDesc.getId());
queryMeta.setBizName(dimSchemaDesc.getBizName());
queryMeta.setName(dimSchemaDesc.getName());
return queryMeta;
}
private void dealNativeQuery(QueryContextReq queryContext, boolean isNativeQuery) {
if (Objects.nonNull(queryContext) && Objects.nonNull(queryContext.getParseInfo())) {
queryContext.getParseInfo().setNativeQuery(isNativeQuery);
}
}
}

View File

@@ -1,22 +1,30 @@
package com.tencent.supersonic.chat.application.parser;
import com.tencent.supersonic.chat.api.component.SemanticParser;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticParser;
import com.tencent.supersonic.chat.application.query.MetricSemanticQuery;
import com.tencent.supersonic.chat.application.query.RuleSemanticQuery;
import com.tencent.supersonic.chat.application.query.RuleSemanticQueryManager;
import com.tencent.supersonic.common.constant.Constants;
import com.tencent.supersonic.common.pojo.DateConf;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.semantic.api.core.enums.TimeDimensionEnum;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.collections.CollectionUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;
@Component
public class TimeSemanticParser implements SemanticParser {
public TimeSemanticParser() {
}
private static final Pattern recentPeriodPattern = Pattern.compile(
".*(?<periodStr>(近|过去)((?<enNum>\\d+)|(?<zhNum>[一二三四五六七八九十百千万亿]+))个?(?<zhPeriod>[天周月年])).*");
private int zhNumParse(String zhNumStr) {
Stack<Integer> stack = new Stack<>();
@@ -42,11 +50,8 @@ public class TimeSemanticParser implements SemanticParser {
return stack.stream().mapToInt(s -> s).sum();
}
private static final Pattern recentPeriodPattern = Pattern.compile(
".*(?<periodStr>(近|过去)((?<enNum>\\d+)|(?<zhNum>[一二三四五六七八九十百千万亿]+))个?(?<zhPeriod>[天周月年])).*");
@Override
public boolean parse(QueryContextReq queryContext, ChatContext chatCtx) {
public void parse(QueryContextReq queryContext, ChatContext chatContext) {
Matcher m = recentPeriodPattern.matcher(queryContext.getQueryText());
if (m.matches()) {
int num = 0;
@@ -86,10 +91,54 @@ public class TimeSemanticParser implements SemanticParser {
}
info.setText(text);
info.setStartDate(LocalDate.now().minusDays(days).toString());
info.setUnit(days);
queryContext.getParseInfo().setDateInfo(info);
info.setUnit(num);
//queryContext.getParseInfo().setDateInfo(info);
for (SemanticQuery query : queryContext.getCandidateQueries()) {
if (query instanceof MetricSemanticQuery) {
query.getParseInfo().setDateInfo(info);
}
}
doParseOnlyTime(queryContext, chatContext, info);
}
}
}
protected void doParseOnlyTime(QueryContextReq queryContext, ChatContext chatContext, DateConf info) {
if (!queryContext.getCandidateQueries().isEmpty() || chatContext.getParseInfo() == null || Objects.isNull(
info.getText())) {
return;
}
if (info.getText().equals(queryContext.getQueryText()) && queryContext.getMapInfo().getDomainElementMatches()
.isEmpty()
) {
if (Objects.nonNull(chatContext.getParseInfo().getQueryMode()) && Objects.nonNull(
chatContext.getParseInfo().getDomainId()) && chatContext.getParseInfo().getDomainId() > 0) {
if (Objects.nonNull(chatContext.getParseInfo().getDateInfo()) && !chatContext.getParseInfo()
.getDateInfo().getPeriod().equals(info.getPeriod())) {
if (!CollectionUtils.isEmpty(chatContext.getParseInfo().getDimensions())) {
String dateField = TimeDimensionEnum.DAY.getName();
if (Constants.MONTH.equals(chatContext.getParseInfo().getDateInfo().getPeriod())) {
dateField = TimeDimensionEnum.MONTH.getName();
}
if (Constants.WEEK.equals(chatContext.getParseInfo().getDateInfo().getPeriod())) {
dateField = TimeDimensionEnum.WEEK.getName();
}
Set<SchemaItem> dimensions = new HashSet<>();
for (SchemaItem schemaItem : chatContext.getParseInfo().getDimensions()) {
if (schemaItem.getBizName().equals(dateField)) {
continue;
}
dimensions.add(schemaItem);
}
chatContext.getParseInfo().setDimensions(dimensions);
}
}
chatContext.getParseInfo().setDateInfo(info);
RuleSemanticQuery semanticQuery = RuleSemanticQueryManager.create(
chatContext.getParseInfo().getQueryMode());
semanticQuery.setParseInfo(chatContext.getParseInfo());
queryContext.getCandidateQueries().add(semanticQuery);
}
}
return false;
}
}

View File

@@ -1,14 +0,0 @@
package com.tencent.supersonic.chat.application.parser.resolver;
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
/***
* aggregate parser
*/
public interface AggregateTypeResolver {
AggregateTypeEnum resolve(String queryText);
boolean hasCompareIntentionalWords(String queryText);
}

View File

@@ -1,45 +0,0 @@
package com.tencent.supersonic.chat.application.parser.resolver;
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import com.tencent.supersonic.chat.application.query.SemanticQueryFactory;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class DomainSemanticQueryResolver implements SemanticQueryResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(DomainSemanticQueryResolver.class);
@Override
public SemanticQuery resolve(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx) {
Map<SemanticQuery, SchemaElementCount> matchMap = new HashMap<>();
for (SemanticQuery semanticQuery : SemanticQueryFactory.getSemanticQueries()) {
SchemaElementCount match = semanticQuery.match(elementMatches, queryCtx);
if (match != null && match.getCount() > 0 && match.getMaxSimilarity() > 0) {
LOGGER.info("resolve match [{}:{}] ", semanticQuery.getQueryMode(), match);
matchMap.put(semanticQuery, match);
}
}
// get the similarity max
Map.Entry<SemanticQuery, SchemaElementCount> matchMax = matchMap.entrySet().stream()
.sorted(ContextHelper.SemanticQueryStatComparator).findFirst().orElse(null);
if (matchMax != null) {
LOGGER.info("resolve max [{}:{}] ", matchMax.getKey().getQueryMode(), matchMax.getValue());
return matchMax.getKey();
}
return null;
}
}

View File

@@ -1,50 +0,0 @@
package com.tencent.supersonic.chat.application.parser.resolver;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@Service("DomainResolver")
@Slf4j
public class HeuristicDomainResolver extends BaseDomainResolver {
@Override
public Integer selectDomain(Map<Integer, SemanticQuery> domainQueryModes, QueryContextReq searchCtx,
ChatContext chatCtx,
SchemaMapInfo schemaMap) {
// if QueryContext has domainId and in domainQueryModes
if (domainQueryModes.containsKey(searchCtx.getDomainId())) {
log.info("selectDomain from QueryContext [{}]", searchCtx.getDomainId());
return searchCtx.getDomainId();
}
// if ChatContext has domainId and in domainQueryModes
if (chatCtx.getParseInfo().getDomainId() > 0) {
Integer domainId = Integer.valueOf(chatCtx.getParseInfo().getDomainId().intValue());
if (!isAllowSwitch(domainQueryModes, schemaMap, chatCtx, searchCtx, domainId)) {
log.info("selectDomain from ChatContext [{}]", domainId);
return domainId;
}
}
// default 0
return 0;
}
}

View File

@@ -1,55 +0,0 @@
package com.tencent.supersonic.chat.application.parser.resolver;
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@Primary
public class RegexAggregateTypeResolver implements AggregateTypeResolver {
private static Map<AggregateTypeEnum, Pattern> aggregateRegexMap = new HashMap<>();
private static Pattern compareIntentionalWord = Pattern.compile("(?i)(比较|对比)");
static {
aggregateRegexMap.put(AggregateTypeEnum.MAX, Pattern.compile("(?i)(最大值|最大|max|峰值|最高)"));
aggregateRegexMap.put(AggregateTypeEnum.MIN, Pattern.compile("(?i)(最小值|最小|min|最低)"));
aggregateRegexMap.put(AggregateTypeEnum.SUM, Pattern.compile("(?i)(汇总|总和|sum)"));
aggregateRegexMap.put(AggregateTypeEnum.AVG, Pattern.compile("(?i)(平均值|日均|平均|avg)"));
aggregateRegexMap.put(AggregateTypeEnum.TOPN, Pattern.compile("(?i)(top)"));
aggregateRegexMap.put(AggregateTypeEnum.DISTINCT, Pattern.compile("(?i)(uv)"));
aggregateRegexMap.put(AggregateTypeEnum.COUNT, Pattern.compile("(?i)(总数|pv)"));
aggregateRegexMap.put(AggregateTypeEnum.NONE, Pattern.compile("(?i)(明细)"));
}
@Override
public AggregateTypeEnum resolve(String text) {
Map<AggregateTypeEnum, Integer> aggregateCount = new HashMap<>(aggregateRegexMap.size());
for (Entry<AggregateTypeEnum, Pattern> entry : aggregateRegexMap.entrySet()) {
Matcher matcher = entry.getValue().matcher(text);
int count = 0;
while (matcher.find()) {
count++;
}
if (count > 0) {
aggregateCount.put(entry.getKey(), count);
}
}
return aggregateCount.entrySet().stream().max(Map.Entry.comparingByValue()).map(entry -> entry.getKey())
.orElse(null);
}
@Override
public boolean hasCompareIntentionalWords(String queryText) {
return compareIntentionalWord.matcher(queryText).find();
}
}

View File

@@ -1,14 +0,0 @@
package com.tencent.supersonic.chat.application.parser.resolver;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import java.util.List;
/**
* Base interface for resolving query mode.
*/
public interface SemanticQueryResolver {
SemanticQuery resolve(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx);
}

View File

@@ -1,123 +0,0 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.EntityInfo;
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.response.QueryResultResp;
import com.tencent.supersonic.chat.api.service.SemanticLayer;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn;
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
import com.tencent.supersonic.chat.application.DomainEntityService;
import com.tencent.supersonic.chat.application.parser.resolver.DomainResolver;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
import com.tencent.supersonic.chat.domain.pojo.search.QueryState;
import com.tencent.supersonic.chat.domain.service.ChatService;
import com.tencent.supersonic.chat.domain.utils.DefaultSemanticInternalUtils;
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
import com.tencent.supersonic.common.util.context.ContextUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public abstract class BaseSemanticQuery implements SemanticQuery {
protected QueryModeOption queryModeOption = QueryModeOption.build();
private static final Logger LOGGER = LoggerFactory.getLogger(BaseSemanticQuery.class);
@Override
public QueryResultResp execute(QueryContextReq queryCtx, ChatContext chatCtx) {
DomainResolver domainResolver = ContextUtils.getBean(DomainResolver.class);
ChatService chatService = ContextUtils.getBean(ChatService.class);
SemanticLayer semanticLayer = ContextUtils.getBean(SemanticLayer.class);
SemanticParseInfo semanticParse = queryCtx.getParseInfo();
String queryMode = semanticParse.getQueryMode();
if (semanticParse.getDomainId() < 0 || StringUtils.isEmpty(queryMode)) {
// reach here some error may happen
LOGGER.error("not find QueryMode");
throw new RuntimeException("not find QueryMode");
}
supplyMetadata(semanticLayer, queryCtx);
// is domain switch
if (domainResolver.isDomainSwitch(chatCtx, queryCtx)) {
chatService.switchContext(chatCtx);
}
// submit semantic query based on the result of semantic parsing
SemanticParseInfo semanticParseInfo = getContext(chatCtx, queryCtx);
QueryResultResp queryResponse = new QueryResultResp();
QueryResultWithSchemaResp queryResult = semanticLayer.queryByStruct(
SchemaInfoConverter.convertTo(semanticParseInfo), queryCtx.getUser());
if (queryResult != null) {
queryResponse.setQueryAuthorization(queryResult.getQueryAuthorization());
}
String sql = queryResult == null ? null : queryResult.getSql();
List<Map<String, Object>> resultList = queryResult == null ? new ArrayList<>()
: queryResult.getResultList();
List<QueryColumn> columns = queryResult == null ? new ArrayList<>() : queryResult.getColumns();
queryResponse.setQuerySql(sql);
queryResponse.setQueryResults(resultList);
queryResponse.setQueryColumns(columns);
queryResponse.setQueryMode(queryMode);
// add domain info
EntityInfo entityInfo = ContextUtils.getBean(DomainEntityService.class)
.getEntityInfo(queryCtx, chatCtx, queryCtx.getUser());
queryResponse.setEntityInfo(entityInfo);
return queryResponse;
}
private void supplyMetadata(SemanticLayer semanticLayer, QueryContextReq queryCtx) {
DefaultSemanticInternalUtils defaultSemanticUtils = ContextUtils.getBean(DefaultSemanticInternalUtils.class);
SchemaMapInfo mapInfo = queryCtx.getMapInfo();
SemanticParseInfo semanticParse = queryCtx.getParseInfo();
Long domain = semanticParse.getDomainId();
List<SchemaElementMatch> schemaElementMatches = mapInfo.getMatchedElements(domain.intValue());
DomainSchemaResp domainSchemaDesc = semanticLayer.getDomainSchemaInfo(domain);
ChatConfigRichInfo chaConfigRichDesc = defaultSemanticUtils.getChatConfigRichInfo(domain);
// supply metadata
if (!CollectionUtils.isEmpty(schemaElementMatches)) {
this.queryModeOption.addQuerySemanticParseInfo(schemaElementMatches, domainSchemaDesc,
chaConfigRichDesc, semanticParse);
}
}
@Override
public void updateContext(QueryResultResp queryResponse, ChatContext chatCtx, QueryContextReq queryCtx) {
if (queryCtx.isSaveAnswer() && queryResponse != null
&& queryResponse.getQueryState() == QueryState.NORMAL.getState()) {
chatCtx.setParseInfo(getParseInfo(queryCtx, chatCtx));
chatCtx.setQueryText(queryCtx.getQueryText());
ContextUtils.getBean(ChatService.class).updateContext(chatCtx);
}
queryResponse.setChatContext(queryCtx.getParseInfo());
}
public abstract SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx);
@Override
public SchemaElementCount match(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx) {
return queryModeOption.match(elementMatches, queryCtx);
}
}

View File

@@ -1,26 +1,22 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Service;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED;
@Service
public class EntityDetail extends BaseSemanticQuery {
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Component;
@Component
public class EntityDetail extends EntitySemanticQuery {
public static String QUERY_MODE = "ENTITY_DETAIL";
public EntityDetail() {
queryModeOption.setAggregation(QueryModeElementOption.unused());
queryModeOption.setDate(QueryModeElementOption.unused());
queryModeOption.setDimension(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST,
1);
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setMetric(QueryModeElementOption.unused());
queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_MOST, 1);
queryModeOption.setDomain(QueryModeElementOption.optional());
super();
queryMatcher.addOption(DIMENSION, REQUIRED, AT_LEAST, 1)
.addOption(VALUE, REQUIRED, AT_LEAST, 1);
}
@Override
@@ -29,21 +25,9 @@ public class EntityDetail extends BaseSemanticQuery {
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(),
semanticParseInfo.getDimensionFilters());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
ContextHelper.addIfEmpty(chatContext.getParseInfo().getDimensionFilters(),
parseInfo.getDimensionFilters());
}
}

View File

@@ -1,50 +1,128 @@
package com.tencent.supersonic.chat.application.query;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.*;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.*;
import static com.tencent.supersonic.common.constant.Constants.DAY;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichResp;
import com.tencent.supersonic.chat.domain.pojo.config.ChatDefaultRichConfig;
import com.tencent.supersonic.chat.domain.pojo.config.EntityRichInfo;
import com.tencent.supersonic.chat.domain.service.ConfigService;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Service;
import com.tencent.supersonic.common.constant.Constants;
import com.tencent.supersonic.common.pojo.DateConf;
import com.tencent.supersonic.common.pojo.Order;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.common.util.context.ContextUtils;
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
@Service
public class EntityListFilter extends BaseSemanticQuery {
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class EntityListFilter extends EntitySemanticQuery {
public static String QUERY_MODE = "ENTITY_LIST_FILTER";
private static Long entityListLimit = 200L;
public EntityListFilter() {
queryModeOption.setAggregation(QueryModeElementOption.unused());
queryModeOption.setDate(QueryModeElementOption.unused());
queryModeOption.setDimension(QueryModeElementOption.unused());
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setMetric(QueryModeElementOption.unused());
queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setDomain(QueryModeElementOption.optional());
super();
queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 1)
.addOption(ENTITY, REQUIRED, AT_LEAST, 1);
}
@Override
public String getQueryMode() {
return QUERY_MODE;
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(),
semanticParseInfo.getDimensionFilters());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
semanticParseInfo.setLimit(entityListLimit);
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
SemanticParseInfo chatParseInfo = chatContext.getParseInfo();
ContextHelper.addIfEmpty(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters());
parseInfo.setLimit(entityListLimit);
this.fillDateEntityFilter(parseInfo);
this.addEntityDetailAndOrderByMetric(parseInfo);
this.dealNativeQuery(parseInfo, true);
}
private void fillDateEntityFilter(SemanticParseInfo semanticParseInfo) {
DateConf dateInfo = new DateConf();
dateInfo.setDateMode(DateConf.DateMode.RECENT_UNITS);
dateInfo.setUnit(1);
dateInfo.setPeriod(DAY);
dateInfo.setText(String.format("近1天"));
semanticParseInfo.setDateInfo(dateInfo);
}
private void addEntityDetailAndOrderByMetric(SemanticParseInfo semanticParseInfo) {
if (semanticParseInfo.getDomainId() > 0L) {
ConfigService configService = ContextUtils.getBean(ConfigService.class);
ChatConfigRichResp chaConfigRichDesc = configService.getConfigRichInfo(
semanticParseInfo.getDomainId());
if (chaConfigRichDesc != null && chaConfigRichDesc.getChatDetailRichConfig() != null
&& chaConfigRichDesc.getChatDetailRichConfig().getEntity() != null) {
// SemanticParseInfo semanticParseInfo = queryContext.getParseInfo();
// EntityRichInfo entity = chaConfigRichDesc.getChatDetailRichConfig().getEntity();
Set<SchemaItem> dimensions = new LinkedHashSet();
// Set<String> primaryDimensions = this.addPrimaryDimension(entity, dimensions);
Set<SchemaItem> metrics = new LinkedHashSet();
Set<Order> orders = new LinkedHashSet();
ChatDefaultRichConfig chatDefaultConfig = chaConfigRichDesc.getChatDetailRichConfig().getChatDefaultConfig();
if (chatDefaultConfig != null) {
chatDefaultConfig.getMetrics().stream()
.forEach(metric -> {
metrics.add(metric);
orders.add(new Order(metric.getBizName(), Constants.DESC_UPPER));
});
chatDefaultConfig.getDimensions().stream()
// .filter((m) -> !primaryDimensions.contains(m.getBizName()))
.forEach(dimension -> dimensions.add(dimension));
}
semanticParseInfo.setDimensions(dimensions);
semanticParseInfo.setMetrics(metrics);
semanticParseInfo.setOrders(orders);
}
}
}
private Set<String> addPrimaryDimension(EntityRichInfo entity, Set<SchemaItem> dimensions) {
Set<String> primaryDimensions = new HashSet();
DimSchemaResp dimItem = entity.getDimItem();
if (Objects.nonNull(entity) && Objects.nonNull(dimItem)) {
SchemaItem dimension = new SchemaItem();
BeanUtils.copyProperties(dimItem, dimension);
dimensions.add(dimension);
primaryDimensions.add(dimItem.getBizName());
return primaryDimensions;
} else {
return primaryDimensions;
}
}
private void dealNativeQuery(SemanticParseInfo semanticParseInfo, boolean isNativeQuery) {
if (Objects.nonNull(semanticParseInfo)) {
semanticParseInfo.setNativeQuery(isNativeQuery);
}
}
}

View File

@@ -1,27 +1,23 @@
package com.tencent.supersonic.chat.application.query;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
@Service
public class EntityListTopN extends BaseSemanticQuery {
@Component
public class EntityListTopN extends EntitySemanticQuery {
public static String QUERY_MODE = "ENTITY_LIST_TOPN";
public EntityListTopN() {
queryModeOption.setAggregation(QueryModeElementOption.optional());
queryModeOption.setDate(QueryModeElementOption.optional());
queryModeOption.setDimension(QueryModeElementOption.unused());
queryModeOption.setFilter(QueryModeElementOption.unused());
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setDomain(QueryModeElementOption.optional());
queryModeOption.setSupportOrderBy(true);
super();
queryMatcher.addOption(METRIC, REQUIRED, AT_LEAST, 1)
.setSupportOrderBy(true);
}
@Override
@@ -30,22 +26,10 @@ public class EntityListTopN extends BaseSemanticQuery {
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo);
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
SemanticParseInfo chatParseInfo = chatContext.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo);
ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics());
}
}

View File

@@ -1,26 +1,24 @@
package com.tencent.supersonic.chat.application.query;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.METRIC;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.VALUE;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
@Service
public class EntityMetricFilter extends BaseSemanticQuery {
@Component
public class EntityMetricFilter extends EntitySemanticQuery {
public static String QUERY_MODE = "ENTITY_METRIC_FILTER";
public EntityMetricFilter() {
queryModeOption.setAggregation(QueryModeElementOption.optional());
queryModeOption.setDate(QueryModeElementOption.optional());
queryModeOption.setDimension(QueryModeElementOption.unused());
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setEntity(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setDomain(QueryModeElementOption.optional());
super();
queryMatcher.addOption(METRIC, REQUIRED, AT_LEAST, 1)
.addOption(VALUE, REQUIRED, AT_LEAST, 1);
}
@Override
@@ -29,21 +27,9 @@ public class EntityMetricFilter extends BaseSemanticQuery {
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(),
semanticParseInfo.getDimensionFilters());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
SemanticParseInfo chatParseInfo = chatContext.getParseInfo();
ContextHelper.addIfEmpty(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters());
}
}

View File

@@ -0,0 +1,13 @@
package com.tencent.supersonic.chat.application.query;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.ENTITY;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED;
public abstract class EntitySemanticQuery extends RuleSemanticQuery {
public EntitySemanticQuery() {
super();
queryMatcher.addOption(ENTITY, REQUIRED, AT_LEAST, 1);
}
}

View File

@@ -0,0 +1,63 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.common.constant.Constants;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HeuristicQuerySelector implements QuerySelector {
@Override
public SemanticQuery select(List<SemanticQuery> candidateQueries) {
double maxScore = 0;
SemanticQuery pickedQuery = null;
for (SemanticQuery query : candidateQueries) {
if (query instanceof LLMSemanticQuery) {
log.info("force to use LLM if existed");
return query;
}
SemanticParseInfo semanticParse = query.getParseInfo();
double score = computeScore(semanticParse);
if (score > maxScore) {
maxScore = score;
pickedQuery = query;
}
log.info("candidate query (domain={}, queryMode={}) with score={}",
semanticParse.getDomainName(), semanticParse.getQueryMode(), score);
}
return pickedQuery;
}
private double computeScore(SemanticParseInfo semanticParse) {
double score = 0;
Map<SchemaElementType, SchemaElementMatch> maxSimilarityMatch = new HashMap<>();
for (SchemaElementMatch match : semanticParse.getElementMatches()) {
SchemaElementType type = match.getElementType();
if (!maxSimilarityMatch.containsKey(type) ||
match.getSimilarity() > maxSimilarityMatch.get(type).getSimilarity()) {
maxSimilarityMatch.put(type, match);
}
}
for (SchemaElementMatch match : maxSimilarityMatch.values()) {
score +=
Optional.ofNullable(match.getDetectWord()).orElse(Constants.EMPTY).length() * match.getSimilarity();
}
// bonus is a special construct to control the final score
score += semanticParse.getBonus();
return score;
}
}

View File

@@ -0,0 +1,71 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.chat.api.component.SemanticLayer;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import com.tencent.supersonic.chat.api.pojo.EntityInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.response.QueryResultResp;
import com.tencent.supersonic.chat.application.DomainEntityService;
import com.tencent.supersonic.chat.domain.utils.ComponentFactory;
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
import com.tencent.supersonic.common.util.context.ContextUtils;
import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn;
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
public class LLMSemanticQuery implements SemanticQuery {
public static String QUERY_MODE = "DSL";
private SemanticParseInfo semanticParse = new SemanticParseInfo();
private SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
@Override
public String getQueryMode() {
return QUERY_MODE;
}
@Override
public QueryResultResp execute(User user) {
String queryMode = semanticParse.getQueryMode();
if (semanticParse.getDomainId() < 0 || StringUtils.isEmpty(queryMode)) {
// reach here some error may happen
log.error("not find QueryMode");
throw new RuntimeException("not find QueryMode");
}
QueryResultResp queryResponse = new QueryResultResp();
QueryResultWithSchemaResp queryResult = semanticLayer.queryBySql(
SchemaInfoConverter.convertToQuerySqlReq(semanticParse), user);
if (queryResult != null) {
queryResponse.setQueryAuthorization(queryResult.getQueryAuthorization());
}
String sql = queryResult == null ? null : queryResult.getSql();
List<Map<String, Object>> resultList = queryResult == null ? new ArrayList<>()
: queryResult.getResultList();
List<QueryColumn> columns = queryResult == null ? new ArrayList<>() : queryResult.getColumns();
queryResponse.setQuerySql(sql);
queryResponse.setQueryResults(resultList);
queryResponse.setQueryColumns(columns);
queryResponse.setQueryMode(queryMode);
// add domain info
EntityInfo entityInfo = ContextUtils.getBean(DomainEntityService.class)
.getEntityInfo(semanticParse, user);
queryResponse.setEntityInfo(entityInfo);
return queryResponse;
}
@Override
public SemanticParseInfo getParseInfo() {
return semanticParse;
}
}

View File

@@ -1,39 +1,44 @@
package com.tencent.supersonic.chat.application.query;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.ENTITY;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.VALUE;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_MOST;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.OPTIONAL;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.Filter;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
@Service
@Slf4j
public class MetricCompare extends BaseSemanticQuery {
@Component
public class MetricCompare extends MetricSemanticQuery {
public static String QUERY_MODE = "METRIC_COMPARE";
public static Pattern intentWordPattern = Pattern.compile("(?i)(比较|对比)");
public MetricCompare() {
queryModeOption.setAggregation(QueryModeElementOption.optional());
queryModeOption.setDate(QueryModeElementOption.optional());
queryModeOption.setDimension(QueryModeElementOption.unused());
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setEntity(QueryModeElementOption.unused());
queryModeOption.setDomain(QueryModeElementOption.optional());
queryModeOption.setSupportCompare(true);
queryModeOption.setSupportOrderBy(true);
super();
queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 2)
.addOption(ENTITY, OPTIONAL, AT_MOST, 1);
queryMatcher.setSupportCompare(true);
queryMatcher.setSupportOrderBy(true);
}
@Override
@@ -42,26 +47,22 @@ public class MetricCompare extends BaseSemanticQuery {
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCt) {
SemanticParseInfo semanticParseInfo = chatCt.getParseInfo();
ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.addIfEmpty(queryCtx.getParseInfo().getDimensionFilters(),
semanticParseInfo.getDimensionFilters());
ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
public List<SchemaElementMatch> match(List<SchemaElementMatch> candidateElementMatches, QueryContextReq queryCtx) {
if (intentWordPattern.matcher(queryCtx.getQueryText()).find()) {
return super.match(candidateElementMatches, queryCtx);
} else {
return new ArrayList<>();
}
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo);
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
mergeAppend(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
addCompareDimension(semanticParseInfo);
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
SemanticParseInfo chatParseInfo = chatContext.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo);
ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics());
mergeAppend(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters());
addCompareDimension(parseInfo);
parseInfo.setBonus(2 * 1.0);
}
private void addCompareDimension(SemanticParseInfo semanticParseInfo) {
@@ -95,7 +96,7 @@ public class MetricCompare extends BaseSemanticQuery {
if (toAdd.isPresent()) {
if (FilterOperatorEnum.EQUALS.equals(toAdd.get().getOperator()) || FilterOperatorEnum.IN.equals(
toAdd.get().getOperator())) {
List<Object> vals = new ArrayList<>();
Set<Object> vals = new HashSet<>();
if (toAdd.get().getOperator().equals(FilterOperatorEnum.IN)) {
vals.addAll((List<Object>) (toAdd.get().getValue()));
} else {
@@ -106,7 +107,7 @@ public class MetricCompare extends BaseSemanticQuery {
} else {
vals.add(filter.getValue());
}
toAdd.get().setValue(vals);
toAdd.get().setValue(new ArrayList<>(vals));
toAdd.get().setOperator(FilterOperatorEnum.IN);
continue;
}

View File

@@ -1,26 +1,23 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
@Service
public class MetricDomain extends BaseSemanticQuery {
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.*;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.*;
@Component
public class MetricDomain extends MetricSemanticQuery {
public static String QUERY_MODE = "METRIC_DOMAIN";
public MetricDomain() {
queryModeOption.setAggregation(QueryModeElementOption.optional());
queryModeOption.setDate(QueryModeElementOption.optional());
queryModeOption.setDimension(QueryModeElementOption.unused());
queryModeOption.setFilter(QueryModeElementOption.unused());
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setEntity(QueryModeElementOption.unused());
queryModeOption.setDomain(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
super();
queryMatcher.addOption(DOMAIN, REQUIRED, AT_LEAST, 1);
}
@Override
@@ -29,22 +26,10 @@ public class MetricDomain extends BaseSemanticQuery {
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo);
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
SemanticParseInfo chatParseInfo = chatContext.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo);
ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics());
}
}

View File

@@ -1,25 +1,25 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
@Service
public class MetricFilter extends BaseSemanticQuery {
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.*;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.*;
@Component
public class MetricFilter extends MetricSemanticQuery {
public static String QUERY_MODE = "METRIC_FILTER";
public MetricFilter() {
queryModeOption.setAggregation(QueryModeElementOption.optional());
queryModeOption.setDate(QueryModeElementOption.optional());
queryModeOption.setDimension(QueryModeElementOption.unused());
queryModeOption.setFilter(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setEntity(QueryModeElementOption.unused());
queryModeOption.setDomain(QueryModeElementOption.optional());
super();
queryMatcher.addOption(VALUE, REQUIRED, AT_LEAST, 1)
.addOption(ENTITY, OPTIONAL, AT_MOST, 1);
}
@Override
@@ -28,25 +28,10 @@ public class MetricFilter extends BaseSemanticQuery {
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateList(queryCtx.getParseInfo().getDimensionFilters(),
semanticParseInfo.getDimensionFilters());
ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo);
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensionFilters(), semanticParseInfo.getDimensionFilters());
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
SemanticParseInfo chatParseInfo = chatContext.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo);
ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics());
ContextHelper.addIfEmpty(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters());
}
}

View File

@@ -2,26 +2,21 @@ package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
@Service
public class MetricGroupBy extends BaseSemanticQuery {
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.*;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.*;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.*;
@Component
public class MetricGroupBy extends MetricSemanticQuery {
public static String QUERY_MODE = "METRIC_GROUPBY";
public MetricGroupBy() {
queryModeOption.setAggregation(QueryModeElementOption.optional());
queryModeOption.setDate(QueryModeElementOption.optional());
queryModeOption.setDimension(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST,
1);
queryModeOption.setFilter(QueryModeElementOption.unused());
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setEntity(QueryModeElementOption.unused());
queryModeOption.setDomain(QueryModeElementOption.optional());
super();
queryMatcher.addOption(DIMENSION, REQUIRED, AT_LEAST, 1);
}
@Override
@@ -30,24 +25,11 @@ public class MetricGroupBy extends BaseSemanticQuery {
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.updateList(queryCtx.getParseInfo().getDimensions(), semanticParseInfo.getDimensions());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo);
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensions(), semanticParseInfo.getDimensions());
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
SemanticParseInfo chatParseInfo = chatContext.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo);
ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics());
ContextHelper.addIfEmpty(chatParseInfo.getDimensions(), parseInfo.getDimensions());
}
}

View File

@@ -1,27 +1,18 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.pojo.ChatContext;
import com.tencent.supersonic.chat.api.pojo.SchemaMapInfo;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Component;
@Service
public class MetricOrderBy extends BaseSemanticQuery {
@Component
public class MetricOrderBy extends RuleSemanticQuery {
public static String QUERY_MODE = "METRIC_ORDERBY";
public MetricOrderBy() {
queryModeOption.setAggregation(QueryModeElementOption.optional());
queryModeOption.setDate(QueryModeElementOption.optional());
queryModeOption.setDimension(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST,
1);
queryModeOption.setFilter(QueryModeElementOption.optional());
queryModeOption.setMetric(SchemaElementOption.REQUIRED, QueryModeElementOption.RequireNumberType.AT_LEAST, 1);
queryModeOption.setEntity(QueryModeElementOption.unused());
queryModeOption.setDomain(QueryModeElementOption.optional());
queryModeOption.setSupportOrderBy(true);
super();
}
@Override
@@ -30,24 +21,12 @@ public class MetricOrderBy extends BaseSemanticQuery {
}
@Override
public SemanticParseInfo getParseInfo(QueryContextReq queryCtx, ChatContext chatCtx) {
SemanticParseInfo semanticParseInfo = chatCtx.getParseInfo();
ContextHelper.updateTime(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateDomain(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateSemanticQuery(queryCtx.getParseInfo(), semanticParseInfo);
ContextHelper.updateList(queryCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.updateList(queryCtx.getParseInfo().getDimensions(), semanticParseInfo.getDimensions());
ContextHelper.updateEntity(queryCtx.getParseInfo(), semanticParseInfo);
return semanticParseInfo;
}
@Override
public SemanticParseInfo getContext(ChatContext chatCtx, QueryContextReq queryCtx) {
SemanticParseInfo semanticParseInfo = queryCtx.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatCtx.getParseInfo(), semanticParseInfo);
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getMetrics(), semanticParseInfo.getMetrics());
ContextHelper.addIfEmpty(chatCtx.getParseInfo().getDimensions(), semanticParseInfo.getDimensions());
return semanticParseInfo;
public void inheritContext(ChatContext chatContext) {
SemanticParseInfo chatParseInfo = chatContext.getParseInfo();
ContextHelper.updateTimeIfEmpty(chatParseInfo, parseInfo);
ContextHelper.addIfEmpty(chatParseInfo.getMetrics(), parseInfo.getMetrics());
ContextHelper.addIfEmpty(chatParseInfo.getDimensions(), parseInfo.getDimensions());
ContextHelper.addIfEmpty(chatParseInfo.getDimensionFilters(), parseInfo.getDimensionFilters());
}
}

View File

@@ -0,0 +1,13 @@
package com.tencent.supersonic.chat.application.query;
import static com.tencent.supersonic.chat.api.pojo.SchemaElementType.METRIC;
import static com.tencent.supersonic.chat.application.query.QueryMatchOption.RequireNumberType.AT_LEAST;
import static com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption.REQUIRED;
public abstract class MetricSemanticQuery extends RuleSemanticQuery {
public MetricSemanticQuery() {
super();
queryMatcher.addOption(METRIC, REQUIRED, AT_LEAST, 1);
}
}

View File

@@ -0,0 +1,43 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import lombok.Data;
@Data
public class QueryMatchOption {
private SchemaElementOption schemaElementOption;
private RequireNumberType requireNumberType;
private Integer requireNumber;
public static QueryMatchOption build(SchemaElementOption schemaElementOption,
RequireNumberType requireNumberType, Integer requireNumber) {
QueryMatchOption queryMatchOption = new QueryMatchOption();
queryMatchOption.requireNumber = requireNumber;
queryMatchOption.requireNumberType = requireNumberType;
queryMatchOption.schemaElementOption = schemaElementOption;
return queryMatchOption;
}
public static QueryMatchOption optional() {
QueryMatchOption queryMatchOption = new QueryMatchOption();
queryMatchOption.setSchemaElementOption(SchemaElementOption.OPTIONAL);
queryMatchOption.setRequireNumber(0);
queryMatchOption.setRequireNumberType(RequireNumberType.AT_LEAST);
return queryMatchOption;
}
public static QueryMatchOption unused() {
QueryMatchOption queryMatchOption = new QueryMatchOption();
queryMatchOption.setSchemaElementOption(SchemaElementOption.UNUSED);
queryMatchOption.setRequireNumber(0);
queryMatchOption.setRequireNumberType(RequireNumberType.EQUAL);
return queryMatchOption;
}
public enum RequireNumberType {
AT_MOST, AT_LEAST, EQUAL
}
}

View File

@@ -0,0 +1,108 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class QueryMatcher {
private HashMap<SchemaElementType, QueryMatchOption> elementOptionMap = new HashMap<>();
private boolean supportCompare;
private boolean supportOrderBy;
private List<AggregateTypeEnum> orderByTypes = Arrays.asList(AggregateTypeEnum.MAX, AggregateTypeEnum.MIN,
AggregateTypeEnum.TOPN);
public QueryMatcher() {
for (SchemaElementType type : SchemaElementType.values()) {
if (type.equals(SchemaElementType.DOMAIN)) {
elementOptionMap.put(type, QueryMatchOption.optional());
} else {
elementOptionMap.put(type, QueryMatchOption.unused());
}
}
}
public QueryMatcher addOption(SchemaElementType type, SchemaElementOption option,
QueryMatchOption.RequireNumberType requireNumberType, Integer requireNumber) {
elementOptionMap.put(type, QueryMatchOption.build(option, requireNumberType, requireNumber));
return this;
}
/**
* Match schema element with current query according to the options.
*
* @param candidateElementMatches
* @return a list of all matched schema elements,
* empty list if no matches can be found
*/
public List<SchemaElementMatch> match(List<SchemaElementMatch> candidateElementMatches) {
List<SchemaElementMatch> elementMatches = new ArrayList<>();
HashMap<SchemaElementType, Integer> schemaElementTypeCount = new HashMap<>();
for (SchemaElementMatch schemaElementMatch : candidateElementMatches) {
SchemaElementType schemaElementType = schemaElementMatch.getElementType();
if (schemaElementTypeCount.containsKey(schemaElementType)) {
schemaElementTypeCount.put(schemaElementType, schemaElementTypeCount.get(schemaElementType) + 1);
} else {
schemaElementTypeCount.put(schemaElementType, 1);
}
}
// check if current query options are satisfied, return immediately if not
for (Map.Entry<SchemaElementType, QueryMatchOption> e : elementOptionMap.entrySet()) {
SchemaElementType elementType = e.getKey();
QueryMatchOption elementOption = e.getValue();
if (!isMatch(elementOption, getCount(schemaElementTypeCount, elementType))) {
return new ArrayList<>();
}
}
// add element match if its element type is not declared as unused
for (SchemaElementMatch elementMatch : candidateElementMatches) {
QueryMatchOption elementOption = elementOptionMap.get(elementMatch.getElementType());
if (Objects.nonNull(elementOption) && !elementOption.getSchemaElementOption()
.equals(SchemaElementOption.UNUSED)) {
elementMatches.add(elementMatch);
}
}
return elementMatches;
}
private int getCount(HashMap<SchemaElementType, Integer> schemaElementTypeCount,
SchemaElementType schemaElementType) {
if (schemaElementTypeCount.containsKey(schemaElementType)) {
return schemaElementTypeCount.get(schemaElementType);
}
return 0;
}
private boolean isMatch(QueryMatchOption queryMatchOption, int count) {
// check if required but empty
if (queryMatchOption.getSchemaElementOption().equals(SchemaElementOption.REQUIRED) && count <= 0) {
return false;
}
if (queryMatchOption.getRequireNumberType().equals(QueryMatchOption.RequireNumberType.AT_LEAST)
&& count < queryMatchOption.getRequireNumber()) {
return false;
}
if (queryMatchOption.getRequireNumberType().equals(QueryMatchOption.RequireNumberType.AT_MOST)
&& count > queryMatchOption.getRequireNumber()) {
return false;
}
return true;
}
}

View File

@@ -1,43 +0,0 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import lombok.Data;
@Data
public class QueryModeElementOption {
public enum RequireNumberType {
AT_MOST, AT_LEAST, EQUAL
}
private SchemaElementOption schemaElementOption;
private RequireNumberType requireNumberType;
private Integer requireNumber;
public static QueryModeElementOption build(SchemaElementOption schemaElementOption,
RequireNumberType requireNumberType, Integer requireNumber) {
QueryModeElementOption queryModeElementOption = new QueryModeElementOption();
queryModeElementOption.requireNumber = requireNumber;
queryModeElementOption.requireNumberType = requireNumberType;
queryModeElementOption.schemaElementOption = schemaElementOption;
return queryModeElementOption;
}
public static QueryModeElementOption optional() {
QueryModeElementOption queryModeElementOption = new QueryModeElementOption();
queryModeElementOption.setSchemaElementOption(SchemaElementOption.OPTIONAL);
queryModeElementOption.setRequireNumber(0);
queryModeElementOption.setRequireNumberType(RequireNumberType.AT_LEAST);
return queryModeElementOption;
}
public static QueryModeElementOption unused() {
QueryModeElementOption queryModeElementOption = new QueryModeElementOption();
queryModeElementOption.setSchemaElementOption(SchemaElementOption.UNUSED);
queryModeElementOption.setRequireNumber(0);
queryModeElementOption.setRequireNumberType(RequireNumberType.EQUAL);
return queryModeElementOption;
}
}

View File

@@ -1,307 +0,0 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.pojo.Filter;
import com.tencent.supersonic.chat.api.pojo.SchemaElementCount;
import com.tencent.supersonic.chat.api.pojo.SchemaElementMatch;
import com.tencent.supersonic.chat.api.pojo.SchemaElementType;
import com.tencent.supersonic.chat.api.pojo.SemanticParseInfo;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.semantic.api.core.response.DimSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.DomainSchemaResp;
import com.tencent.supersonic.semantic.api.core.response.MetricSchemaResp;
import com.tencent.supersonic.semantic.api.query.enums.FilterOperatorEnum;
import com.tencent.supersonic.chat.application.parser.resolver.AggregateTypeResolver;
import com.tencent.supersonic.chat.domain.pojo.chat.SchemaElementOption;
import com.tencent.supersonic.chat.domain.pojo.config.ChatConfigRichInfo;
import com.tencent.supersonic.chat.domain.utils.ContextHelper;
import com.tencent.supersonic.common.enums.AggregateTypeEnum;
import com.tencent.supersonic.common.pojo.SchemaItem;
import com.tencent.supersonic.common.util.context.ContextUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class QueryModeOption {
private QueryModeElementOption domain;
private QueryModeElementOption entity;
private QueryModeElementOption metric;
private QueryModeElementOption dimension;
private QueryModeElementOption filter;
private QueryModeElementOption date;
private QueryModeElementOption aggregation;
private boolean supportCompare = false;
private boolean supportOrderBy = false;
List<AggregateTypeEnum> orderByTypes = Arrays.asList(AggregateTypeEnum.MAX, AggregateTypeEnum.MIN,
AggregateTypeEnum.TOPN);
public static QueryModeOption build() {
return new QueryModeOption();
}
public QueryModeOption setDimension(SchemaElementOption dimension,
QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) {
this.dimension = QueryModeElementOption.build(dimension, requireNumberType, requireNumber);
return this;
}
public QueryModeOption setDomain(SchemaElementOption domain,
QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) {
this.domain = QueryModeElementOption.build(domain, requireNumberType, requireNumber);
return this;
}
public QueryModeOption setEntity(SchemaElementOption entity,
QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) {
this.entity = QueryModeElementOption.build(entity, requireNumberType, requireNumber);
return this;
}
public QueryModeOption setFilter(SchemaElementOption filter,
QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) {
this.filter = QueryModeElementOption.build(filter, requireNumberType, requireNumber);
return this;
}
public QueryModeOption setMetric(SchemaElementOption metric,
QueryModeElementOption.RequireNumberType requireNumberType, Integer requireNumber) {
this.metric = QueryModeElementOption.build(metric, requireNumberType, requireNumber);
return this;
}
/**
* add query semantic parse info
* @param schemaElementMatches
* @param domainSchemaDesc
* @param chaConfigRichDesc
* @param semanticParseInfo
*/
public void addQuerySemanticParseInfo(List<SchemaElementMatch> schemaElementMatches,
DomainSchemaResp domainSchemaDesc, ChatConfigRichInfo chaConfigRichDesc,
SemanticParseInfo semanticParseInfo) {
Map<Long, DimSchemaResp> dimensionDescMap = domainSchemaDesc.getDimensions().stream()
.collect(Collectors.toMap(DimSchemaResp::getId, Function.identity()));
Map<Long, MetricSchemaResp> metricDescMap = domainSchemaDesc.getMetrics().stream()
.collect(Collectors.toMap(MetricSchemaResp::getId, Function.identity()));
Map<Long, List<SchemaElementMatch>> values = getLinkSchemaElementMatch(schemaElementMatches, dimensionDescMap,
semanticParseInfo, metricDescMap);
if (!values.isEmpty()) {
for (Map.Entry<Long, List<SchemaElementMatch>> entry : values.entrySet()) {
DimSchemaResp dimensionDesc = dimensionDescMap.get(entry.getKey());
if (entry.getValue().size() == 1) {
SchemaElementMatch schemaElementMatch = entry.getValue().get(0);
Filter chatFilter = new Filter();
chatFilter.setValue(schemaElementMatch.getWord());
chatFilter.setBizName(dimensionDesc.getBizName());
chatFilter.setName(dimensionDesc.getName());
chatFilter.setOperator(FilterOperatorEnum.EQUALS);
chatFilter.setElementID(Long.valueOf(schemaElementMatch.getElementID()));
semanticParseInfo.getDimensionFilters().add(chatFilter);
ContextHelper.setEntityId(entry.getKey(), schemaElementMatch.getWord(), chaConfigRichDesc,
semanticParseInfo);
} else {
Filter chatFilter = new Filter();
List<String> vals = new ArrayList<>();
entry.getValue().stream().forEach(i -> vals.add(i.getWord()));
chatFilter.setValue(vals);
chatFilter.setBizName(dimensionDesc.getBizName());
chatFilter.setName(dimensionDesc.getName());
chatFilter.setOperator(FilterOperatorEnum.IN);
chatFilter.setElementID(entry.getKey());
semanticParseInfo.getDimensionFilters().add(chatFilter);
}
}
}
}
private Map<Long, List<SchemaElementMatch>> getLinkSchemaElementMatch(List<SchemaElementMatch> schemaElementMatches,
Map<Long, DimSchemaResp> dimensionDescMap, SemanticParseInfo semanticParseInfo,
Map<Long, MetricSchemaResp> metricDescMap) {
Map<Long, List<SchemaElementMatch>> values = new HashMap<>();
for (SchemaElementMatch schemaElementMatch : schemaElementMatches) {
Long elementID = Long.valueOf(schemaElementMatch.getElementID());
switch (schemaElementMatch.getElementType()) {
case DATE:
case DOMAIN:
case ENTITY:
break;
case ID:
case VALUE:
case DIMENSION:
if (dimensionDescMap.containsKey(elementID)) {
DimSchemaResp dimensionDesc = dimensionDescMap.get(elementID);
SchemaItem dimensionParseInfo = new SchemaItem();
dimensionParseInfo.setBizName(dimensionDesc.getBizName());
dimensionParseInfo.setName(dimensionDesc.getName());
dimensionParseInfo.setId(dimensionDesc.getId());
if (!dimension.getSchemaElementOption().equals(SchemaElementOption.UNUSED)
&& schemaElementMatch.getElementType().equals(SchemaElementType.DIMENSION)) {
semanticParseInfo.getDimensions().add(dimensionParseInfo);
}
if (!filter.getSchemaElementOption().equals(SchemaElementOption.UNUSED) && (
schemaElementMatch.getElementType().equals(SchemaElementType.VALUE)
|| schemaElementMatch.getElementType().equals(SchemaElementType.ID))) {
if (values.containsKey(elementID)) {
values.get(elementID).add(schemaElementMatch);
} else {
values.put(elementID, new ArrayList<>(Arrays.asList(schemaElementMatch)));
}
}
}
break;
case METRIC:
if (!metric.getSchemaElementOption().equals(SchemaElementOption.UNUSED)) {
if (metricDescMap.containsKey(elementID)) {
MetricSchemaResp metricDesc = metricDescMap.get(elementID);
SchemaItem metric = new SchemaItem();
metric.setBizName(metricDesc.getBizName());
metric.setName(metricDesc.getName());
metric.setId(metricDesc.getId());
semanticParseInfo.getMetrics().add(metric);
}
}
break;
default:
}
}
return values;
}
/**
* math
* @param elementMatches
* @param queryCtx
* @return
*/
public SchemaElementCount match(List<SchemaElementMatch> elementMatches, QueryContextReq queryCtx) {
AggregateTypeResolver aggregateTypeResolver = ContextUtils.getBean(AggregateTypeResolver.class);
boolean isCompareType = aggregateTypeResolver.hasCompareIntentionalWords(queryCtx.getQueryText());
boolean isOrderByType = orderByTypes.contains(aggregateTypeResolver.resolve(queryCtx.getQueryText()));
if ((isOrderByType && !supportOrderBy) || (isCompareType && !supportCompare)) {
return new SchemaElementCount();
}
SchemaElementCount schemaElementCount = new SchemaElementCount();
schemaElementCount.setCount(0);
schemaElementCount.setMaxSimilarity(0);
HashMap<SchemaElementType, Integer> schemaElementTypeCount = new HashMap<>();
for (SchemaElementMatch schemaElementMatch : elementMatches) {
SchemaElementType schemaElementType = schemaElementMatch.getElementType();
if (schemaElementTypeCount.containsKey(schemaElementType)) {
schemaElementTypeCount.put(schemaElementType, schemaElementTypeCount.get(schemaElementType) + 1);
} else {
schemaElementTypeCount.put(schemaElementType, 1);
}
}
// test each element
if (!isMatch(domain, getCount(schemaElementTypeCount, SchemaElementType.DOMAIN))) {
return schemaElementCount;
}
if (!isMatch(dimension, getCount(schemaElementTypeCount, SchemaElementType.DIMENSION))) {
return schemaElementCount;
}
if (!isMatch(metric, getCount(schemaElementTypeCount, SchemaElementType.METRIC))) {
return schemaElementCount;
}
int filterCount = getCount(schemaElementTypeCount, SchemaElementType.VALUE) + getCount(schemaElementTypeCount,
SchemaElementType.ID);
if (!isMatch(filter, filterCount)) {
return schemaElementCount;
}
if (!isMatch(entity, getCount(schemaElementTypeCount, SchemaElementType.ENTITY))) {
return schemaElementCount;
}
if (!isMatch(date, getCount(schemaElementTypeCount, SchemaElementType.DATE))) {
return schemaElementCount;
}
// count the max similarity
double similarity = 0;
Set<SchemaElementType> schemaElementTypeSet = new HashSet<>();
for (SchemaElementMatch schemaElementMatch : elementMatches) {
double schemaElementMatchSimilarity = getSimilarity(schemaElementMatch);
if (schemaElementMatchSimilarity > similarity) {
similarity = schemaElementMatchSimilarity;
}
schemaElementTypeSet.add(schemaElementMatch.getElementType());
}
schemaElementCount.setCount(schemaElementTypeSet.size());
schemaElementCount.setMaxSimilarity(similarity);
return schemaElementCount;
}
private int getCount(HashMap<SchemaElementType, Integer> schemaElementTypeCount,
SchemaElementType schemaElementType) {
if (schemaElementTypeCount.containsKey(schemaElementType)) {
return schemaElementTypeCount.get(schemaElementType);
}
return 0;
}
private double getSimilarity(SchemaElementMatch schemaElementMatch) {
switch (schemaElementMatch.getElementType()) {
case DATE:
return getSimilarity(date, schemaElementMatch.getSimilarity());
case DOMAIN:
return getSimilarity(domain, schemaElementMatch.getSimilarity());
case ENTITY:
return getSimilarity(entity, schemaElementMatch.getSimilarity());
case DIMENSION:
return getSimilarity(dimension, schemaElementMatch.getSimilarity());
case METRIC:
return getSimilarity(metric, schemaElementMatch.getSimilarity());
case ID:
case VALUE:
return getSimilarity(filter, schemaElementMatch.getSimilarity());
default:
return 0;
}
}
private double getSimilarity(QueryModeElementOption queryModeElementOption, double similarity) {
if (queryModeElementOption.getSchemaElementOption().equals(SchemaElementOption.REQUIRED)) {
return similarity;
}
return 0;
}
private boolean isMatch(QueryModeElementOption queryModeElementOption, int count) {
// first find if unused but not empty
if (queryModeElementOption.getSchemaElementOption().equals(SchemaElementOption.UNUSED) && count > 0) {
return false;
}
// find if required but empty
if (queryModeElementOption.getSchemaElementOption().equals(SchemaElementOption.REQUIRED) && count <= 0) {
return false;
}
// find if count no satisfy
if (queryModeElementOption.getRequireNumberType().equals(QueryModeElementOption.RequireNumberType.EQUAL)
&& queryModeElementOption.getRequireNumber() != count) {
return false;
}
if (queryModeElementOption.getRequireNumberType().equals(QueryModeElementOption.RequireNumberType.AT_LEAST)
&& count < queryModeElementOption.getRequireNumber()) {
return false;
}
if (queryModeElementOption.getRequireNumberType().equals(QueryModeElementOption.RequireNumberType.AT_MOST)
&& count > queryModeElementOption.getRequireNumber()) {
return false;
}
// here default satisfy
return true;
}
}

View File

@@ -0,0 +1,13 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import java.util.List;
/**
* This interface defines the contract for a selector that picks the most suitable semantic query.
**/
public interface QuerySelector {
SemanticQuery select(List<SemanticQuery> candidateQueries);
}

View File

@@ -0,0 +1,89 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.auth.api.authentication.pojo.User;
import com.tencent.supersonic.chat.api.component.SemanticLayer;
import com.tencent.supersonic.chat.api.component.SemanticQuery;
import com.tencent.supersonic.chat.api.pojo.*;
import com.tencent.supersonic.chat.api.request.QueryContextReq;
import com.tencent.supersonic.chat.api.response.QueryResultResp;
import com.tencent.supersonic.chat.application.DomainEntityService;
import com.tencent.supersonic.chat.domain.utils.ComponentFactory;
import com.tencent.supersonic.chat.domain.utils.SchemaInfoConverter;
import com.tencent.supersonic.common.util.context.ContextUtils;
import com.tencent.supersonic.semantic.api.core.pojo.QueryColumn;
import com.tencent.supersonic.semantic.api.core.response.QueryResultWithSchemaResp;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.*;
@Slf4j
@ToString
public abstract class RuleSemanticQuery implements SemanticQuery, Serializable {
protected SemanticParseInfo parseInfo = new SemanticParseInfo();
protected QueryMatcher queryMatcher = new QueryMatcher();
protected SemanticLayer semanticLayer = ComponentFactory.getSemanticLayer();
public RuleSemanticQuery() {
RuleSemanticQueryManager.register(this);
}
public List<SchemaElementMatch> match(List<SchemaElementMatch> candidateElementMatches,
QueryContextReq queryCtx) {
return queryMatcher.match(candidateElementMatches);
}
public abstract void inheritContext(ChatContext chatContext);
@Override
public QueryResultResp execute(User user) {
String queryMode = parseInfo.getQueryMode();
if (parseInfo.getDomainId() < 0 || StringUtils.isEmpty(queryMode)) {
// reach here some error may happen
log.error("not find QueryMode");
throw new RuntimeException("not find QueryMode");
}
List<String> semanticQueryModes = RuleSemanticQueryManager.getSemanticQueryModes();
if (!semanticQueryModes.contains(parseInfo.getQueryMode())) {
return null;
}
QueryResultResp queryResponse = new QueryResultResp();
QueryResultWithSchemaResp queryResult = semanticLayer.queryByStruct(
SchemaInfoConverter.convertTo(parseInfo), user);
if (queryResult != null) {
queryResponse.setQueryAuthorization(queryResult.getQueryAuthorization());
}
String sql = queryResult == null ? null : queryResult.getSql();
List<Map<String, Object>> resultList = queryResult == null ? new ArrayList<>()
: queryResult.getResultList();
List<QueryColumn> columns = queryResult == null ? new ArrayList<>() : queryResult.getColumns();
queryResponse.setQuerySql(sql);
queryResponse.setQueryResults(resultList);
queryResponse.setQueryColumns(columns);
queryResponse.setQueryMode(queryMode);
// add domain info
EntityInfo entityInfo = ContextUtils.getBean(DomainEntityService.class)
.getEntityInfo(parseInfo, user);
queryResponse.setEntityInfo(entityInfo);
return queryResponse;
}
@Override
public SemanticParseInfo getParseInfo() {
return parseInfo;
}
public void setParseInfo(SemanticParseInfo parseInfo) {
this.parseInfo = parseInfo;
}
}

View File

@@ -0,0 +1,39 @@
package com.tencent.supersonic.chat.application.query;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* RuleSemanticQueryManager
*/
public class RuleSemanticQueryManager {
private static Map<String, RuleSemanticQuery> semanticQueryMap = new ConcurrentHashMap<>();
public static RuleSemanticQuery create(String queryMode) {
RuleSemanticQuery semanticQuery = semanticQueryMap.get(queryMode);
if (Objects.isNull(semanticQuery)) {
throw new RuntimeException("no supported queryMode :" + queryMode);
}
try {
return semanticQuery.getClass().getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("no supported queryMode :" + queryMode);
}
}
public static void register(RuleSemanticQuery query) {
semanticQueryMap.put(query.getQueryMode(), query);
}
public static List<RuleSemanticQuery> getSemanticQueries() {
return new ArrayList<>(semanticQueryMap.values());
}
public static List<String> getSemanticQueryModes() {
return new ArrayList<>(semanticQueryMap.keySet());
}
}

View File

@@ -1,46 +0,0 @@
package com.tencent.supersonic.chat.application.query;
import com.tencent.supersonic.chat.api.service.SemanticQuery;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.CollectionUtils;
/**
* SemanticQueryFactory
*/
public class SemanticQueryFactory {
private static Map<String, SemanticQuery> strategyFactory = new ConcurrentHashMap<>();
private static List<SemanticQuery> semanticQueries;
public static SemanticQuery get(String queryMode) {
if (CollectionUtils.isEmpty(strategyFactory)) {
init();
}
SemanticQuery semanticQuery = strategyFactory.get(queryMode);
if (Objects.isNull(semanticQuery)) {
throw new RuntimeException("not support queryMode :" + queryMode);
}
return semanticQuery;
}
private static void init() {
for (SemanticQuery semanticQuery : getSemanticQueries()) {
strategyFactory.put(semanticQuery.getQueryMode(), semanticQuery);
}
}
public static List<SemanticQuery> getSemanticQueries() {
if (CollectionUtils.isEmpty(semanticQueries)) {
semanticQueries = SpringFactoriesLoader.loadFactories(SemanticQuery.class,
Thread.currentThread().getContextClassLoader());
}
return semanticQueries;
}
}

View File

@@ -0,0 +1,19 @@
package com.tencent.supersonic.chat.domain.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class LLMConfig {
@Value("${llm.url:}")
private String url;
@Value("${query2sql.path:query2sql}")
private String queryToSqlPath;
}

View File

@@ -15,29 +15,11 @@ public class ChatConfigDO {
private Long id;
private Long domainId;
/**
* default metrics information about the domain
*/
private String defaultMetrics;
/**
* invisible dimensions/metrics
*/
private String visibility;
private String chatDetailConfig;
/**
* the entity info about the domain
*/
private String entity;
private String chatAggConfig;
/**
* information about dictionary about the domain
*/
private String knowledgeInfo;
/**
* available status
*/
private Integer status;
/**

View File

@@ -5,6 +5,7 @@ import java.util.Date;
import java.util.List;
public class ChatQueryDOExample {
protected String orderByClause;
protected boolean distinct;
protected List<Criteria> oredCriteria;
@@ -15,23 +16,22 @@ public class ChatQueryDOExample {
oredCriteria = new ArrayList<Criteria>();
}
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
public String getOrderByClause() {
return orderByClause;
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
}
public boolean isDistinct() {
return distinct;
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
public List<Criteria> getOredCriteria() {
return oredCriteria;
}
@@ -65,22 +65,22 @@ public class ChatQueryDOExample {
distinct = false;
}
public void setLimitStart(Integer limitStart) {
this.limitStart = limitStart;
}
public Integer getLimitStart() {
return limitStart;
}
public void setLimitEnd(Integer limitEnd) {
this.limitEnd = limitEnd;
public void setLimitStart(Integer limitStart) {
this.limitStart = limitStart;
}
public Integer getLimitEnd() {
return limitEnd;
}
public void setLimitEnd(Integer limitEnd) {
this.limitEnd = limitEnd;
}
protected abstract static class GeneratedCriteria {
protected List<Criterion> criteria;
@@ -589,38 +589,6 @@ public class ChatQueryDOExample {
private String typeHandler;
public String getCondition() {
return condition;
}
public Object getValue() {
return value;
}
public Object getSecondValue() {
return secondValue;
}
public boolean isNoValue() {
return noValue;
}
public boolean isSingleValue() {
return singleValue;
}
public boolean isBetweenValue() {
return betweenValue;
}
public boolean isListValue() {
return listValue;
}
public String getTypeHandler() {
return typeHandler;
}
protected Criterion(String condition) {
super();
this.condition = condition;
@@ -656,5 +624,37 @@ public class ChatQueryDOExample {
protected Criterion(String condition, Object value, Object secondValue) {
this(condition, value, secondValue, null);
}
public String getCondition() {
return condition;
}
public Object getValue() {
return value;
}
public Object getSecondValue() {
return secondValue;
}
public boolean isNoValue() {
return noValue;
}
public boolean isSingleValue() {
return singleValue;
}
public boolean isBetweenValue() {
return betweenValue;
}
public boolean isListValue() {
return listValue;
}
public String getTypeHandler() {
return typeHandler;
}
}
}

View File

@@ -5,6 +5,9 @@ import lombok.Data;
@Data
public class QueryDO {
public String aggregator = "trend";
public String startTime;
public String endTime;
private long id;
private long questionId;
private String createTime;
@@ -19,10 +22,7 @@ public class QueryDO {
private int isDeleted;
private String module;
private long chatId;
public String aggregator = "trend";
private int topNum;
public String startTime;
public String endTime;
private String querySql;
private Object queryColumn;
private Object entityInfo;

View File

@@ -1,4 +1,4 @@
package com.tencent.supersonic.chat.domain.pojo.semantic;
package com.tencent.supersonic.chat.domain.pojo.chat;
import com.tencent.supersonic.common.nlp.ItemDO;
import java.io.Serializable;

View File

@@ -0,0 +1,11 @@
package com.tencent.supersonic.chat.domain.pojo.chat;
import lombok.Data;
@Data
public class LLMReq {
private String queryText;
private LLMSchema schema;
}

View File

@@ -0,0 +1,20 @@
package com.tencent.supersonic.chat.domain.pojo.chat;
import java.util.List;
import lombok.Data;
@Data
public class LLMResp {
private String query;
private String domainName;
private String sqlOutput;
private List<String> fields;
private String schemaLinkingOutput;
private String schemaLinkStr;
}

View File

@@ -0,0 +1,13 @@
package com.tencent.supersonic.chat.domain.pojo.chat;
import java.util.List;
import lombok.Data;
@Data
public class LLMSchema {
private String domainName;
private List<String> fieldNameList;
}

View File

@@ -15,20 +15,20 @@ public class PageQueryInfoReq {
return pageSize;
}
public int getCurrent() {
return current;
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public String getUserName() {
return userName;
public int getCurrent() {
return current;
}
public void setCurrent(int current) {
this.current = current;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {

Some files were not shown because too many files have changed in this diff Show More