mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-10 11:07:06 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
041daad1e4 | ||
|
|
a0869dc7bd | ||
|
|
c40efebc7a | ||
|
|
75e15f4c50 | ||
|
|
d551fcd6d2 | ||
|
|
404163f391 | ||
|
|
5ffd617431 | ||
|
|
ef4fca9671 | ||
|
|
291187229c | ||
|
|
ad83e752b9 | ||
|
|
805a59dddd | ||
|
|
8639c23dc4 | ||
|
|
7c57a1cb33 | ||
|
|
bab8b67217 | ||
|
|
b9890d1fd3 | ||
|
|
358d5e5288 | ||
|
|
8adecd0b58 | ||
|
|
df74c62ac9 | ||
|
|
9c9d7382fe | ||
|
|
e4266d37ab | ||
|
|
3f6af3b1fb | ||
|
|
c97456c718 | ||
|
|
95a698b875 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,3 +13,5 @@ assembly/runtime/*
|
||||
**/dist/
|
||||
*.umi/
|
||||
/assembly/deploy
|
||||
/runtime
|
||||
**/.flattened-pom.xml
|
||||
11
CHANGELOG
11
CHANGELOG
@@ -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
|
||||
48
README.md
48
README.md
@@ -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.
|
||||
|
||||
33
README_CN.md
33
README_CN.md
@@ -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
12
assembly/bin/build-chat.sh
Executable 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
|
||||
|
||||
@@ -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
|
||||
23
assembly/bin/build-standalone.sh
Executable file
23
assembly/bin/build-standalone.sh
Executable 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
35
assembly/bin/start-chat.sh
Executable 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
32
assembly/bin/start-standalone.sh
Executable file
32
assembly/bin/start-standalone.sh
Executable 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
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<>();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
public ConfigServiceImpl(ChatConfigRepository chatConfigRepository,
|
||||
ChatConfigUtils chatConfigUtils) {
|
||||
this.chaConfigRepository = chaConfigRepository;
|
||||
this.semanticLayer = semanticLayer;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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,27 +76,36 @@ 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) {
|
||||
private EntityInfo getEntityInfo(ChatConfigRichResp chaConfigRichDesc) {
|
||||
|
||||
EntityInfo entityInfo = new EntityInfo();
|
||||
|
||||
if (entityDesc != null) {
|
||||
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()) {
|
||||
|
||||
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());
|
||||
@@ -109,7 +113,10 @@ public class DomainEntityService {
|
||||
dimensions.add(mainEntityDimension);
|
||||
}
|
||||
entityInfo.setDimensions(dimensions);
|
||||
for (MetricSchemaResp metricDesc : entityDesc.getEntityInternalDetailDesc().getMetricList()) {
|
||||
}
|
||||
|
||||
if(!CollectionUtils.isEmpty(chatDefaultConfig.getMetrics())){
|
||||
for (SchemaItem metricDesc : chatDefaultConfig.getMetrics()) {
|
||||
DataInfo dataInfo = new DataInfo();
|
||||
dataInfo.setName(metricDesc.getName());
|
||||
dataInfo.setBizName(metricDesc.getBizName());
|
||||
@@ -119,6 +126,7 @@ public class DomainEntityService {
|
||||
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) {
|
||||
|
||||
@@ -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,25 +48,29 @@ 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);
|
||||
}
|
||||
}
|
||||
// submit semantic query based on the result of semantic parsing
|
||||
SemanticQuery query = SemanticQueryFactory.get(queryCtx.getParseInfo().getQueryMode());
|
||||
|
||||
QueryResultResp queryResponse = query.execute(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
|
||||
query.updateContext(queryResponse, chatCtx, queryCtx);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SemanticParseInfo queryContext(QueryContextReq queryCtx) {
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)) {
|
||||
@@ -147,7 +147,8 @@ public class SearchServiceImpl implements SearchService {
|
||||
boolean existMetricAndDimension,
|
||||
MatchText matchText,
|
||||
Map<String, String> natureToNameMap,
|
||||
Map.Entry<String, String> natureToNameEntry) {
|
||||
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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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());
|
||||
|
||||
SemanticParseInfo semanticParse = queryContext.getParseInfo();
|
||||
|
||||
Set<SchemaItem> metrics = semanticParse.getMetrics();
|
||||
|
||||
semanticParse.setNativeQuery(getNativeQuery(aggregateType, queryContext));
|
||||
for (SemanticQuery semanticQuery : queryContext.getCandidateQueries()) {
|
||||
SemanticParseInfo semanticParse = semanticQuery.getParseInfo();
|
||||
|
||||
semanticParse.setNativeQuery(getNativeQuery(aggregateType, semanticParse));
|
||||
semanticParse.setAggType(aggregateType);
|
||||
//semanticParse.setOrders(getOrder(aggregateType, metrics));
|
||||
if (Objects.isNull(semanticParse.getLimit()) || semanticParse.getLimit() <= 0) {
|
||||
semanticParse.setLimit(Long.valueOf(TOPN_LIMIT));
|
||||
resetQueryModeByAggregateType(queryContext, aggregateType);
|
||||
return false;
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean useBlackItem(RuleSemanticQuery query, Integer domainId) {
|
||||
if (Objects.isNull(domainId)) {
|
||||
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;
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
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());
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Round 3: no domains can be found yet, count in default metric
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
// if QueryContext has domainId and in domainQueryModes
|
||||
if (domainQueryModes.containsKey(searchCtx.getDomainId())) {
|
||||
log.info("selectDomain from QueryContext [{}]", searchCtx.getDomainId());
|
||||
return searchCtx.getDomainId();
|
||||
}
|
||||
} 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 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.tencent.supersonic.chat.domain.pojo.config;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ChatAggConfig {
|
||||
|
||||
/**
|
||||
* invisible dimensions/metrics
|
||||
*/
|
||||
private ItemVisibility visibility;
|
||||
|
||||
/**
|
||||
* information about dictionary about the domain
|
||||
*/
|
||||
private List<KnowledgeInfo> knowledgeInfos;
|
||||
|
||||
private KnowledgeAdvancedConfig globalKnowledgeConfig;
|
||||
|
||||
private ChatDefaultConfig chatDefaultConfig;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user