Skip to content

Commit 6a5fd29

Browse files
committed
add ontology for TIS ,refactor DefaultChatBIService to nor single instance
1 parent 8f13ae0 commit 6a5fd29

14 files changed

Lines changed: 192 additions & 79 deletions

File tree

tis-ontology-plugin/src/main/java/com/qlangtech/tis/plugin/ontology/EnableChatBI.java

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@
2424
import com.qlangtech.tis.extension.Descriptor;
2525
import com.qlangtech.tis.extension.DescriptorUseableShortComment;
2626
import com.qlangtech.tis.extension.TISExtension;
27+
import com.qlangtech.tis.lang.PayloadLink;
28+
import com.qlangtech.tis.plugin.IPluginStore;
2729
import com.qlangtech.tis.plugin.IdentityDesc;
30+
import com.qlangtech.tis.plugin.IdentityName;
2831
import com.qlangtech.tis.plugin.annotation.FormField;
2932
import com.qlangtech.tis.plugin.annotation.FormFieldType;
3033
import com.qlangtech.tis.plugin.annotation.Validator;
3134
import com.qlangtech.tis.plugin.ds.manipulate.ManipulateItemsProcessor;
35+
import com.qlangtech.tis.plugin.manipulate.ManipulatePluginCacheRegister;
36+
import com.qlangtech.tis.plugin.ontology.chatbi.ChatBIService;
3237
import com.qlangtech.tis.plugin.ontology.chatbi.DefaultChatBIService;
3338
import com.qlangtech.tis.plugin.ontology.chatbi.config.ValidationConfig;
3439
import com.qlangtech.tis.plugin.ontology.impl.OntologyPluginMeta;
@@ -38,6 +43,7 @@
3843

3944
import java.time.Duration;
4045
import java.util.Collections;
46+
import java.util.Objects;
4147
import java.util.Optional;
4248

4349
import static com.qlangtech.tis.manage.common.UserProfile.KEY_FIELD_LLM_NAME;
@@ -47,8 +53,18 @@
4753
*
4854
* @author 百岁 (baisui@qlangtech.com)
4955
* @date 2026/5/28
56+
* @see DefaultChatBIService
5057
*/
51-
public class EnableChatBI extends OntologyDomainManipulate implements IManipulateStatus, IdentityDesc<JSONObject> {
58+
public class EnableChatBI extends OntologyDomainManipulate implements IManipulateStatus, IdentityDesc<JSONObject>, IPluginStore.BeforePluginSaved, IPluginStore.AfterPluginSaved {
59+
60+
public static final String KEY_ID_NAME = "chat_bi";
61+
private String ontologyDomain;
62+
private transient ChatBIService _chatBIService;
63+
64+
public static EnableChatBI load(String ontologyDomain) {
65+
ManipulatePluginCacheRegister.TemplateManipulateStore<OntologyDomainManipulate> manipulateStore = getManipulateStore(ontologyDomain, false);
66+
return manipulateStore.getManipuldate(IdentityName.create(KEY_ID_NAME), EnableChatBI.class);
67+
}
5268

5369
/**
5470
* 大模型接口
@@ -86,8 +102,28 @@ public class EnableChatBI extends OntologyDomainManipulate implements IManipulat
86102
@FormField(ordinal = 7, validate = {Validator.require})
87103
public com.qlangtech.tis.plugin.ontology.chatbi.config.TraceConfig traceConfig;
88104

89-
private LLMProvider getLlmProvider(IPluginContext pluginContext) {
90-
return LLMProvider.load(pluginContext, llm);
105+
@Override
106+
public void afterSaved(IPluginContext pluginContext, Optional<Context> context) {
107+
_chatBIService = null;
108+
}
109+
110+
/**
111+
* 获取当前域对应的 ChatBIService 实例(懒加载,每个 EnableChatBI 实例独立持有)。
112+
*/
113+
public ChatBIService getChatBIService() {
114+
if (_chatBIService == null) {
115+
DefaultChatBIService svc = new DefaultChatBIService();
116+
svc.setLlmProvider(LLMProvider.load(
117+
IPluginContext.namedContext(this.ontologyDomain).setLoginUser(() -> "system"), this.llm));
118+
svc.setConfigs(
119+
retryConfig != null ? retryConfig : createDefaultRetryConfig(),
120+
validationConfig != null ? validationConfig : createDefaultValidationConfig(),
121+
executionConfig != null ? executionConfig : createDefaultExecutionConfig(),
122+
retrievalConfig != null ? retrievalConfig : createDefaultRetrievalConfig()
123+
);
124+
_chatBIService = svc;
125+
}
126+
return _chatBIService;
91127
}
92128

93129
@Override
@@ -97,15 +133,6 @@ protected void afterManipuldateProcess(IPluginContext pluginContext, Optional<Co
97133
if (itemsProcessor.isDeleteProcess()) {
98134
return;
99135
}
100-
DefaultChatBIService chatBIService = DefaultChatBIService.getInstance();
101-
chatBIService.setLlmProvider(this.getLlmProvider(pluginContext));
102-
103-
// 初始化配置到服务
104-
chatBIService.setConfigs(
105-
this.retryConfig != null ? this.retryConfig : createDefaultRetryConfig(),
106-
this.validationConfig != null ? this.validationConfig : createDefaultValidationConfig(),
107-
this.executionConfig != null ? this.executionConfig : createDefaultExecutionConfig()
108-
);
109136

110137
// 初始化 Trace 清理服务
111138
com.qlangtech.tis.plugin.ontology.chatbi.trace.TraceCleanupService.getInstance()
@@ -132,9 +159,9 @@ private com.qlangtech.tis.plugin.ontology.chatbi.config.ValidationConfig createD
132159
config.enableExplain = true;
133160
config.enableKeywordCheck = true;
134161
config.enableAstCheck = true;
135-
config.allowedFirstKeywords = ValidationConfig.dftAllowedFirstKeywords();// "SELECT,WITH,EXPLAIN,SHOW,DESC,DESCRIBE";
136-
config.forbiddenKeywords = ValidationConfig.dftForbiddenKeywords();// "DROP,DELETE,TRUNCATE,ALTER,INSERT,UPDATE,GRANT,REVOKE,EXEC,EXECUTE,CREATE,REPLACE";
137-
config.safeFunctions = ValidationConfig.dftSafeFunctions();// "REPLACE,TRIM,SUBSTRING,CONCAT,CAST,CONVERT";
162+
config.allowedFirstKeywords = ValidationConfig.dftAllowedFirstKeywords();
163+
config.forbiddenKeywords = ValidationConfig.dftForbiddenKeywords();
164+
config.safeFunctions = ValidationConfig.dftSafeFunctions();
138165
return config;
139166
}
140167

@@ -147,6 +174,16 @@ private com.qlangtech.tis.plugin.ontology.chatbi.config.ExecutionConfig createDe
147174
return config;
148175
}
149176

177+
private com.qlangtech.tis.plugin.ontology.chatbi.config.RetrievalConfig createDefaultRetrievalConfig() {
178+
com.qlangtech.tis.plugin.ontology.chatbi.config.RetrievalConfig config =
179+
new com.qlangtech.tis.plugin.ontology.chatbi.config.RetrievalConfig();
180+
config.topKSeeds = 5;
181+
config.maxHops = 2;
182+
config.tokenBudget = 3000;
183+
config.includeValueExamples = false;
184+
return config;
185+
}
186+
150187
private com.qlangtech.tis.plugin.ontology.chatbi.config.TraceConfig createDefaultTraceConfig() {
151188
com.qlangtech.tis.plugin.ontology.chatbi.config.TraceConfig config =
152189
new com.qlangtech.tis.plugin.ontology.chatbi.config.TraceConfig();
@@ -169,6 +206,17 @@ public ManipulateStateSummary manipulateStatusSummary() {
169206
, summary.toString(), true);
170207
}
171208

209+
@Override
210+
public Optional<PayloadLink> manipulateManagerPath() {
211+
return Optional.of(new PayloadLink("查看状态", "/base/ontology/" + this.ontologyDomain + "/chat-bi"));
212+
}
213+
214+
@Override
215+
public void beforeSaved(IPluginContext pluginContext, Optional<Context> context) {
216+
OntologyPluginMeta pluginMeta = OntologyPluginMeta.createPluginMeta();
217+
this.ontologyDomain = Objects.requireNonNull(pluginMeta, "pluginMeta can not be null").getDomain();
218+
}
219+
172220
@TISExtension
173221
public static final class DftDesc extends OntologyDomainManipulate.BasicDesc implements DescriptorUseableShortComment {
174222
public DftDesc() {
@@ -201,4 +249,4 @@ public String shortComment() {
201249
return "开启自然语言统计问数功能";
202250
}
203251
}
204-
}
252+
}

tis-ontology-plugin/src/main/java/com/qlangtech/tis/plugin/ontology/chatbi/ChatBIService.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
* ChatBI 服务接口:NL2SQL 主流程。
2222
* <p>
2323
* 设计参见 {@code design/chat-bi/04-nl-to-sql.md}。
24+
* <p>
25+
* 每个 ontology 域对应一个独立实例,由 {@link com.qlangtech.tis.plugin.ontology.EnableChatBI} 持有。
2426
*
2527
* @author 百岁 (baisui@qlangtech.com)
2628
* @date 2026/6/2
@@ -30,18 +32,12 @@ public interface ChatBIService {
3032

3133
/**
3234
* 给定自然语言问句,生成并执行 SQL。
35+
* <p>
36+
* 检索参数、是否执行、是否 EXPLAIN 校验等选项均由实现类从自身配置中读取。
3337
*
3438
* @param domain 本体域名
3539
* @param nlq 自然语言问句
36-
* @param opts 查询选项(检索参数、是否执行、是否 EXPLAIN 校验)
3740
* @return ChatBI 结果(含 SQL、执行结果、trace、错误)
3841
*/
39-
ChatBIResult ask(String domain, String nlq, ChatBIOptions opts);
40-
41-
/**
42-
* 获取单例实例。
43-
*/
44-
static ChatBIService getInstance() {
45-
return DefaultChatBIService.getInstance();
46-
}
42+
ChatBIResult ask(String domain, String nlq);
4743
}

tis-ontology-plugin/src/main/java/com/qlangtech/tis/plugin/ontology/chatbi/DefaultChatBIService.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
* ChatBI 服务默认实现(§5 T5:重试编排)。
4242
* <p>
4343
* 流水线:检索 → 拼装 Prompt → LLM 调用 → 静态校验 → EXPLAIN 校验(可选)→ 执行(可选)。
44+
* <p>
45+
* 每个 ontology 域对应一个独立实例,由 {@link com.qlangtech.tis.plugin.ontology.EnableChatBI} 持有并懒加载。
4446
*
4547
* @author 百岁 (baisui@qlangtech.com)
4648
* @date 2026/6/2
@@ -49,8 +51,6 @@ public class DefaultChatBIService implements ChatBIService {
4951

5052
private static final Logger logger = LoggerFactory.getLogger(DefaultChatBIService.class);
5153

52-
private static final DefaultChatBIService INSTANCE = new DefaultChatBIService();
53-
5454
private final GraphRAGService graphRAGService = DefaultGraphRAGService.getInstance();
5555
private final KeywordWhitelistValidator keywordValidator = new KeywordWhitelistValidator();
5656
private final AstValidator astValidator = new AstValidator();
@@ -61,35 +61,37 @@ public class DefaultChatBIService implements ChatBIService {
6161
private com.qlangtech.tis.plugin.ontology.chatbi.config.RetryConfig retryConfig;
6262
private com.qlangtech.tis.plugin.ontology.chatbi.config.ValidationConfig validationConfig;
6363
private com.qlangtech.tis.plugin.ontology.chatbi.config.ExecutionConfig executionConfig;
64+
private com.qlangtech.tis.plugin.ontology.chatbi.config.RetrievalConfig retrievalConfig;
6465

65-
public static DefaultChatBIService getInstance() {
66-
return INSTANCE;
67-
}
68-
69-
private DefaultChatBIService() {
66+
public DefaultChatBIService() {
7067
}
7168

7269
public void setLlmProvider(LLMProvider llmProvider) {
7370
this.llmProvider = Objects.requireNonNull(llmProvider, "param llmProvider can not be null");
7471
}
7572

7673
public void setConfigs(com.qlangtech.tis.plugin.ontology.chatbi.config.RetryConfig retryConfig,
77-
com.qlangtech.tis.plugin.ontology.chatbi.config.ValidationConfig validationConfig,
78-
com.qlangtech.tis.plugin.ontology.chatbi.config.ExecutionConfig executionConfig) {
74+
com.qlangtech.tis.plugin.ontology.chatbi.config.ValidationConfig validationConfig,
75+
com.qlangtech.tis.plugin.ontology.chatbi.config.ExecutionConfig executionConfig,
76+
com.qlangtech.tis.plugin.ontology.chatbi.config.RetrievalConfig retrievalConfig) {
7977
this.retryConfig = Objects.requireNonNull(retryConfig, "retryConfig can not be null");
8078
this.validationConfig = Objects.requireNonNull(validationConfig, "validationConfig can not be null");
8179
this.executionConfig = Objects.requireNonNull(executionConfig, "executionConfig can not be null");
80+
this.retrievalConfig = Objects.requireNonNull(retrievalConfig, "retrievalConfig can not be null");
8281
}
8382

8483
@Override
85-
public ChatBIResult ask(String domain, String nlq, ChatBIOptions opts) {
84+
public ChatBIResult ask(String domain, String nlq) {
8685
List<TraceStep> trace = new ArrayList<>();
8786
long startTime = System.currentTimeMillis();
8887

8988
try {
9089
// Step 1: 检索
9190
long t1 = System.currentTimeMillis();
92-
RetrievalResult retrievalResult = graphRAGService.retrieve(domain, nlq, opts.retrievalOptions());
91+
com.qlangtech.tis.plugin.ontology.graphrag.RetrievalOptions retrievalOptions =
92+
Objects.requireNonNull(retrievalConfig, "retrievalConfig can not be null").toRetrievalOptions();
93+
// : com.qlangtech.tis.plugin.ontology.graphrag.RetrievalOptions.defaults();
94+
RetrievalResult retrievalResult = graphRAGService.retrieve(domain, nlq, retrievalOptions);
9395
long t2 = System.currentTimeMillis();
9496
trace.add(TraceStep.retrieve(retrievalResult.objectTypes().size(), retrievalResult.linkers().size(), t2 - t1));
9597

@@ -112,6 +114,7 @@ public ChatBIResult ask(String domain, String nlq, ChatBIOptions opts) {
112114
ValidationResult validationResult = null;
113115
int attempt = 0;
114116
int maxRetry = retryConfig != null ? retryConfig.getMaxRetry() : 2;
117+
boolean enableExplain = validationConfig != null && validationConfig.isEnableExplain();
115118

116119
while (attempt <= maxRetry) {
117120
attempt++;
@@ -142,7 +145,7 @@ public ChatBIResult ask(String domain, String nlq, ChatBIOptions opts) {
142145
trace.add(TraceStep.extract(candidateSql));
143146

144147
// Step 4: 静态校验
145-
validationResult = validateSql(domain, candidateSql, retrievalResult, opts.enableExplain());
148+
validationResult = validateSql(domain, candidateSql, retrievalResult, enableExplain);
146149
JSONObject issues = new JSONObject();
147150
issues.put("issues", validationResult.issues());
148151
trace.add(TraceStep.validate(validationResult.valid(), validationResult.reason(), issues));
@@ -170,7 +173,8 @@ public ChatBIResult ask(String domain, String nlq, ChatBIOptions opts) {
170173

171174
// Step 6: 执行(可选)
172175
QueryResult queryResult = null;
173-
if (validationResult.valid() && opts.executeQuery()) {
176+
boolean executeQuery = executionConfig == null || executionConfig.isExecuteQuery();
177+
if (validationResult.valid() && executeQuery) {
174178
long t5 = System.currentTimeMillis();
175179
queryResult = executeQuery(domain, candidateSql);
176180
long t6 = System.currentTimeMillis();
@@ -183,7 +187,7 @@ public ChatBIResult ask(String domain, String nlq, ChatBIOptions opts) {
183187
if (validationResult.valid()) {
184188
return ChatBIResult.success(candidateSql, queryResult, trace);
185189
} else {
186-
return ChatBIResult.fail(validationResult.reasonAndIssue() ,
190+
return ChatBIResult.fail(validationResult.reasonAndIssue(),
187191
trace, validationResult.exception());
188192
}
189193

@@ -196,9 +200,6 @@ public ChatBIResult ask(String domain, String nlq, ChatBIOptions opts) {
196200
}
197201

198202
private LLMProvider.LLMResponse invokeLLM(String domain, UserPrompt userPrompt, List<String> systemPrompt) {
199-
// TODO: 从 EnableChatBI 配置中加载 LLM Provider
200-
// 暂时抛异常,提示未配置
201-
// throw new UnsupportedOperationException("LLM Provider not configured. Please enable ChatBI for domain: " + domain);
202203
if (this.llmProvider == null) {
203204
throw new IllegalStateException("llmProvider can not be null");
204205
}

tis-ontology-plugin/src/main/java/com/qlangtech/tis/plugin/ontology/impl/glossary/DefaultOntologyGlossary.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ public class DefaultOntologyGlossary extends OntologyGlossary implements IPlugin
4343

4444
@Override
4545
public void afterSaved(IPluginContext pluginContext, Optional<Context> context) {
46-
String domain = OntologyPluginMeta.createPluginMeta(pluginContext.getContext()).getDomain();
46+
OntologyPluginMeta meta = OntologyPluginMeta.createPluginMeta(pluginContext.getContext());
4747
final DefaultOntologyGlossary self = this;
48-
OntologySyncQueue.enqueue(new OntologySyncQueue.OntologySyncTask(pluginContext) {
48+
OntologySyncQueue.enqueue(new OntologySyncQueue.OntologySyncTask(meta) {
4949
@Override
50-
protected void sync() {
51-
OntologyNeo4jSyncService.getInstance().syncGlossary(domain, self);
50+
protected void sync(OntologyPluginMeta meta) {
51+
OntologyNeo4jSyncService.getInstance().syncGlossary(meta.getDomain(), self);
5252
}
5353
});
5454
}

tis-ontology-plugin/src/main/java/com/qlangtech/tis/plugin/ontology/impl/linker/DefaultOntologyLinker.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ public class DefaultOntologyLinker extends OntologyLinker implements IPluginStor
5555

5656
@Override
5757
public void afterSaved(IPluginContext pluginContext, Optional<Context> context) {
58-
String domain = OntologyPluginMeta.createPluginMeta(pluginContext.getContext()).getDomain();
58+
OntologyPluginMeta domain = OntologyPluginMeta.createPluginMeta(pluginContext.getContext());
5959
final DefaultOntologyLinker self = this;
60-
OntologySyncQueue.enqueue(new OntologySyncQueue.OntologySyncTask(pluginContext) {
60+
OntologySyncQueue.enqueue(new OntologySyncQueue.OntologySyncTask(domain) {
6161
@Override
62-
protected void sync() {
63-
OntologyNeo4jSyncService.getInstance().syncLinker(domain, self);
62+
protected void sync(OntologyPluginMeta domain) {
63+
OntologyNeo4jSyncService.getInstance().syncLinker(domain.getDomain(), self);
6464
}
6565
});
6666
}

tis-ontology-plugin/src/main/java/com/qlangtech/tis/plugin/ontology/impl/objtype/DefaultOntologyObjectType.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ public class DefaultOntologyObjectType extends OntologyObjectType implements IPl
5555

5656
@Override
5757
public void afterSaved(IPluginContext pluginContext, Optional<Context> context) {
58-
String domain = OntologyPluginMeta.createPluginMeta(pluginContext.getContext()).getDomain();
58+
OntologyPluginMeta meta = OntologyPluginMeta.createPluginMeta(pluginContext.getContext());
5959
final DefaultOntologyObjectType self = this;
60-
OntologySyncQueue.enqueue(new OntologySyncQueue.OntologySyncTask(pluginContext) {
60+
OntologySyncQueue.enqueue(new OntologySyncQueue.OntologySyncTask(meta) {
6161
@Override
62-
protected void sync() {
63-
OntologyNeo4jSyncService.getInstance().syncObjectType(domain, self);
62+
protected void sync(OntologyPluginMeta meta) {
63+
OntologyNeo4jSyncService.getInstance().syncObjectType(meta.getDomain(), self);
6464
}
6565
});
6666
}

tis-ontology-plugin/src/main/java/com/qlangtech/tis/plugin/ontology/impl/sharedproperty/DefaultOntologySharedProperty.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ public class DefaultOntologySharedProperty extends OntologySharedProperty implem
4343

4444
@Override
4545
public void afterSaved(IPluginContext pluginContext, Optional<Context> context) {
46-
String domain = OntologyPluginMeta.createPluginMeta(pluginContext.getContext()).getDomain();
46+
OntologyPluginMeta meta = OntologyPluginMeta.createPluginMeta(pluginContext.getContext());
4747
final DefaultOntologySharedProperty self = this;
48-
OntologySyncQueue.enqueue(new OntologySyncQueue.OntologySyncTask(pluginContext) {
48+
OntologySyncQueue.enqueue(new OntologySyncQueue.OntologySyncTask(meta) {
4949
@Override
50-
protected void sync() {
51-
OntologyNeo4jSyncService.getInstance().syncSharedProperty(domain, self);
50+
protected void sync(OntologyPluginMeta meta) {
51+
OntologyNeo4jSyncService.getInstance().syncSharedProperty(meta.getDomain(), self);
5252
}
5353
});
5454
}

0 commit comments

Comments
 (0)