Skip to content

Commit 15c136c

Browse files
lzq986VLSMB
andauthored
refactor: enhance schema recall (#108)
* refactor: enhance schema recall - adjust workflow before plannerNode - call hybrid search in AgentVectorStoreService - add aop for log - clean unsed code * clean unsed node * feat: adapt flux output * add semantic support * fix: python bug * fix unit test * fix TableRelationDispatcher unit test * fix method buildBusinessKnowledgePrompt * delete unused code * add metadata constant * add semantic support * update semantic crud cotroller * fix MappersTest.java * feat: add semantic frontend * fix bugs * feat: add search text-edit * fix: bugs in schema.sql * adjust semantic and business knowledge * adjust by copilot feedback * fix MapperTest type error. * refactor: workflow * chore update README.md --------- Co-authored-by: VLSMB <2047857654@qq.com> Co-authored-by: VLSMB <36068250+VLSMB@users.noreply.github.com>
1 parent 3a8b5ac commit 15c136c

92 files changed

Lines changed: 3295 additions & 2261 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,13 @@ spring-ai-alibaba-nl2sql/
117117

118118
将表和数据导入到你的MySQL数据库中。
119119

120-
### 2. 配置management数据库
120+
121+
122+
### 2. 配置
123+
124+
#### 2.1 配置management数据库
125+
126+
121127

122128
在`spring-ai-alibaba-nl2sql-management/src/main/resources/application.yml`中配置你的MySQL数据库连接信息。
123129

@@ -133,7 +139,9 @@ spring:
133139
type: com.alibaba.druid.pool.DruidDataSource
134140
```
135141
136-
### 2.1 可选:启用/关闭自动初始化(schema.sql + data.sql)
142+
#### 2.2 可选:启用/关闭自动初始化(schema.sql + data.sql)
143+
144+
137145
138146
- 默认配置:`application.yml` 中已设置为开启
139147

@@ -162,7 +170,7 @@ spring:
162170
![Snipaste_2025-09-18_14-35-29.jpg](img/Snipaste_2025-09-18_14-35-29.jpg)
163171

164172

165-
### 3. 配置 API Key
173+
#### 2.3 配置 API Key
166174

167175
```yaml
168176
spring:
@@ -178,15 +186,52 @@ spring:
178186
```
179187

180188

181-
### 4. 启动管理端
189+
190+
#### 2.4 嵌入模型批处理策略配置
191+
192+
193+
194+
| 属性 | 说明 | 默认值 |
195+
| ----------------------------------------------------------- | ------------------------------------------------------------ | ----------- |
196+
| spring.ai.alibaba.nl2sql.embedding-batch.encoding-type | 文本编码类型,可参考com.knuddels.jtokkit.api.EncodingType | cl100k_base |
197+
| spring.ai.alibaba.nl2sql.embedding-batch.max-token-count | 每批次最大令牌数 值越小,每批次文档越少,但更安全 值越大,处理效率越高,但可能超出API限制 建议值:2000-8000,根据实际API限制调整 | 2000 |
198+
| spring.ai.alibaba.nl2sql.embedding-batch.reserve-percentage | 预留百分比 用于预留缓冲空间,避免超出限制 建议值:0.1-0.2(10%-20%) | 0.2 |
199+
| spring.ai.alibaba.nl2sql.embedding-batch.max-text-count | 每批次最大文本数量 适用于DashScope等有文本数量限制的API DashScope限制为10 | 10 |
200+
201+
202+
203+
#### 2.5 向量库配置
204+
205+
206+
207+
| 属性 | 说明 | 默认值 |
208+
| ------------------------------------------------------------ | ------------------------------------------------------------ | --------- |
209+
| spring.ai.alibaba.nl2sql.vector-store.similarity-threshold | 相似度阈值配置,用于过滤相似度分数大于等于此阈值的文档 | 0.2 |
210+
| spring.ai.alibaba.nl2sql.vector-store.batch-del-topk-limit | 一次删除操作中,最多删除的文档数量 | 5000 |
211+
| spring.ai.alibaba.nl2sql.vector-store.topk-limit | 查询返回最大文档数 | 30 |
212+
| **spring.ai.alibaba.nl2sql.vector-store.enable-hybrid-search** | 是否启用混合搜索。**注意**:**项目目前默认只提供ES的混合检索能力,<br />如需要扩展其他向量库可自行继承重写 com.alibaba.cloud.ai.service.hybrid.retrieval<br />.AbstractHybridRetrievalStrategy#retrieve 该方法**<br />**并且修改com.alibaba.cloud.ai.service.hybrid.<br />factory.HybridRetrievalStrategyFactory#getObject<br />注册相应的bean** | **false** |
213+
| spring.ai.alibaba.nl2sql.vector-store.elasticsearch-min-score | Elasticsearch最小分数阈值,用于es执行关键词搜索时过滤相关性较低的文档。<br />**开发时使用的es服务端版本 8.15.0** | 0.5 |
214+
215+
216+
217+
#### 2.6 检索融合策略
218+
219+
| 属性 | 说明 | 默认值 |
220+
| ---------------------------------------- | -------------------- | ------ |
221+
| spring.ai.alibaba.nl2sql.fusion-strategy | 多路召回结果融合策略 | rrf |
222+
| | | |
223+
224+
225+
226+
### 3. 启动管理端
182227

183228
在`spring-ai-alibaba-nl2sql-management`目录下,运行 `spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-management/src/main/java/com/alibaba/cloud/ai/DataAgentApplication.java` 类。
184229

185-
### 5. 启动WEB页面
230+
### 4. 启动WEB页面
186231

187232
进入 `spring-ai-alibaba-nl2sql/spring-ai-alibaba-nl2sql-web-ui` 目录
188233

189-
#### 安装依赖
234+
#### 4.1 安装依赖
190235

191236

192237
```bash
@@ -197,7 +242,7 @@ npm install
197242
yarn install
198243
```
199244

200-
### 启动服务
245+
#### 4.2 启动服务
201246

202247
```bash
203248
# 使用 npm

‎spring-ai-alibaba-data-agent-chat/pom.xml‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
<artifactId>spring-boot-starter-web</artifactId>
4040
</dependency>
4141

42+
<!-- Spring AOP依赖 -->
43+
<dependency>
44+
<groupId>org.springframework.boot</groupId>
45+
<artifactId>spring-boot-starter-aop</artifactId>
46+
</dependency>
47+
4248
<dependency>
4349
<groupId>org.springframework.boot</groupId>
4450
<artifactId>spring-boot-starter-test</artifactId>
@@ -199,7 +205,10 @@
199205
<artifactId>elasticsearch-rest-client</artifactId>
200206
<version>${elasticsearch-client.version}</version>
201207
</dependency>
202-
208+
<dependency>
209+
<groupId>jakarta.validation</groupId>
210+
<artifactId>jakarta.validation-api</artifactId>
211+
</dependency>
203212
</dependencies>
204213
<repositories>
205214
<repository>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.alibaba.cloud.ai.aop;
18+
19+
import com.alibaba.cloud.ai.graph.OverAllState;
20+
import lombok.extern.slf4j.Slf4j;
21+
import org.aspectj.lang.JoinPoint;
22+
import org.aspectj.lang.annotation.Aspect;
23+
import org.aspectj.lang.annotation.Before;
24+
import org.aspectj.lang.annotation.Pointcut;
25+
import org.springframework.stereotype.Component;
26+
27+
/**
28+
* AOP切面类,用于记录所有Node类的入口日志
29+
*
30+
*
31+
*/
32+
@Aspect
33+
@Component
34+
@Slf4j
35+
public class NodeEntryLoggingAspect {
36+
37+
@Pointcut("execution(* com.alibaba.cloud.ai.node..*.apply(com.alibaba.cloud.ai.graph.OverAllState))")
38+
public void nodeEntry() {
39+
}
40+
41+
/**
42+
* 在所有实现NodeAction接口的类的apply方法执行前记录日志
43+
* @param joinPoint 连接点
44+
*/
45+
@Before("nodeEntry()")
46+
public void logNodeEntry(JoinPoint joinPoint) {
47+
String className = joinPoint.getTarget().getClass().getSimpleName();
48+
log.info("Entering {} node", className);
49+
50+
// 获取方法参数并打印状态信息
51+
Object[] args = joinPoint.getArgs();
52+
if (args != null && args.length > 0 && args[0] instanceof OverAllState state) {
53+
log.debug("State: {}", state);
54+
}
55+
}
56+
57+
}

‎spring-ai-alibaba-data-agent-chat/src/main/java/com/alibaba/cloud/ai/config/DataAgentConfiguration.java‎

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -108,22 +108,23 @@ public StateGraph nl2sqlGraph(NodeBeanUtil nodeBeanUtil) throws GraphStateExcept
108108
keyStrategyHashMap.put(INPUT_KEY, new ReplaceStrategy());
109109
// Agent ID
110110
keyStrategyHashMap.put(AGENT_ID, new ReplaceStrategy());
111-
// Business knowledge
112-
keyStrategyHashMap.put(BUSINESS_KNOWLEDGE, new ReplaceStrategy());
111+
// Intent recognition
112+
keyStrategyHashMap.put(INTENT_RECOGNITION_NODE_OUTPUT, new ReplaceStrategy());
113+
// QUERY_ENHANCE_NODE节点输出
114+
keyStrategyHashMap.put(QUERY_ENHANCE_NODE_OUTPUT, new ReplaceStrategy());
113115
// Semantic model
114-
keyStrategyHashMap.put(SEMANTIC_MODEL, new ReplaceStrategy());
115-
// queryWrite节点输出
116-
keyStrategyHashMap.put(QUERY_REWRITE_NODE_OUTPUT, new ReplaceStrategy());
117-
// keyword extract节点输出
118-
keyStrategyHashMap.put(KEYWORD_EXTRACT_NODE_OUTPUT, new ReplaceStrategy());
119-
keyStrategyHashMap.put(EVIDENCES, new ReplaceStrategy());
116+
keyStrategyHashMap.put(GENEGRATED_SEMANTIC_MODEL_PROMPT, new ReplaceStrategy());
117+
// EVIDENCE节点输出
118+
keyStrategyHashMap.put(EVIDENCE, new ReplaceStrategy());
120119
// schema recall节点输出
121120
keyStrategyHashMap.put(TABLE_DOCUMENTS_FOR_SCHEMA_OUTPUT, new ReplaceStrategy());
122-
keyStrategyHashMap.put(COLUMN_DOCUMENTS_BY_KEYWORDS_OUTPUT, new ReplaceStrategy());
121+
keyStrategyHashMap.put(COLUMN_DOCUMENTS__FOR_SCHEMA_OUTPUT, new ReplaceStrategy());
123122
// table relation节点输出
124123
keyStrategyHashMap.put(TABLE_RELATION_OUTPUT, new ReplaceStrategy());
125124
keyStrategyHashMap.put(TABLE_RELATION_EXCEPTION_OUTPUT, new ReplaceStrategy());
126125
keyStrategyHashMap.put(TABLE_RELATION_RETRY_COUNT, new ReplaceStrategy());
126+
// Feasibility Assessment 节点输出
127+
keyStrategyHashMap.put(FEASIBILITY_ASSESSMENT_NODE_OUTPUT, new ReplaceStrategy());
127128
// sql generate节点输出
128129
keyStrategyHashMap.put(SQL_GENERATE_SCHEMA_MISSING_ADVICE, new ReplaceStrategy());
129130
keyStrategyHashMap.put(SQL_GENERATE_OUTPUT, new ReplaceStrategy());
@@ -160,10 +161,12 @@ public StateGraph nl2sqlGraph(NodeBeanUtil nodeBeanUtil) throws GraphStateExcept
160161
};
161162

162163
StateGraph stateGraph = new StateGraph(NL2SQL_GRAPH_NAME, keyStrategyFactory)
163-
.addNode(QUERY_REWRITE_NODE, nodeBeanUtil.getNodeBeanAsync(QueryRewriteNode.class))
164-
.addNode(KEYWORD_EXTRACT_NODE, nodeBeanUtil.getNodeBeanAsync(KeywordExtractNode.class))
164+
.addNode(INTENT_RECOGNITION_NODE, nodeBeanUtil.getNodeBeanAsync(IntentRecognitionNode.class))
165+
.addNode(EVIDENCE_RECALL_NODE, nodeBeanUtil.getNodeBeanAsync(EvidenceRecallNode.class))
166+
.addNode(QUERY_ENHANCE_NODE, nodeBeanUtil.getNodeBeanAsync(QueryEnhanceNode.class))
165167
.addNode(SCHEMA_RECALL_NODE, nodeBeanUtil.getNodeBeanAsync(SchemaRecallNode.class))
166168
.addNode(TABLE_RELATION_NODE, nodeBeanUtil.getNodeBeanAsync(TableRelationNode.class))
169+
.addNode(FEASIBILITY_ASSESSMENT_NODE, nodeBeanUtil.getNodeBeanAsync(FeasibilityAssessmentNode.class))
167170
.addNode(SQL_GENERATE_NODE, nodeBeanUtil.getNodeBeanAsync(SqlGenerateNode.class))
168171
.addNode(PLANNER_NODE, nodeBeanUtil.getNodeBeanAsync(PlannerNode.class))
169172
.addNode(PLAN_EXECUTOR_NODE, nodeBeanUtil.getNodeBeanAsync(PlanExecutorNode.class))
@@ -175,13 +178,19 @@ public StateGraph nl2sqlGraph(NodeBeanUtil nodeBeanUtil) throws GraphStateExcept
175178
.addNode(SEMANTIC_CONSISTENCY_NODE, nodeBeanUtil.getNodeBeanAsync(SemanticConsistencyNode.class))
176179
.addNode(HUMAN_FEEDBACK_NODE, nodeBeanUtil.getNodeBeanAsync(HumanFeedbackNode.class));
177180

178-
stateGraph.addEdge(START, QUERY_REWRITE_NODE)
179-
.addConditionalEdges(QUERY_REWRITE_NODE, edge_async(new QueryRewriteDispatcher()),
180-
Map.of(KEYWORD_EXTRACT_NODE, KEYWORD_EXTRACT_NODE, END, END))
181-
.addEdge(KEYWORD_EXTRACT_NODE, SCHEMA_RECALL_NODE)
181+
stateGraph.addEdge(START, INTENT_RECOGNITION_NODE)
182+
.addConditionalEdges(INTENT_RECOGNITION_NODE, edge_async(new IntentRecognitionDispatcher()),
183+
Map.of(EVIDENCE_RECALL_NODE, EVIDENCE_RECALL_NODE, END, END))
184+
.addEdge(EVIDENCE_RECALL_NODE, QUERY_ENHANCE_NODE)
185+
.addConditionalEdges(QUERY_ENHANCE_NODE, edge_async(new QueryEnhanceDispatcher()),
186+
Map.of(SCHEMA_RECALL_NODE, SCHEMA_RECALL_NODE, END, END))
182187
.addEdge(SCHEMA_RECALL_NODE, TABLE_RELATION_NODE)
183188
.addConditionalEdges(TABLE_RELATION_NODE, edge_async(new TableRelationDispatcher()),
184-
Map.of(PLANNER_NODE, PLANNER_NODE, END, END, TABLE_RELATION_NODE, TABLE_RELATION_NODE)) // retry
189+
Map.of(FEASIBILITY_ASSESSMENT_NODE, FEASIBILITY_ASSESSMENT_NODE, END, END, TABLE_RELATION_NODE,
190+
TABLE_RELATION_NODE)) // retry
191+
.addConditionalEdges(FEASIBILITY_ASSESSMENT_NODE, edge_async(new FeasibilityAssessmentDispatcher()),
192+
Map.of(PLANNER_NODE, PLANNER_NODE, END, END))
193+
185194
// The edge from PlannerNode now goes to PlanExecutorNode for validation and
186195
// execution
187196
.addEdge(PLANNER_NODE, PLAN_EXECUTOR_NODE)
@@ -214,16 +223,15 @@ public StateGraph nl2sqlGraph(NodeBeanUtil nodeBeanUtil) throws GraphStateExcept
214223
.addConditionalEdges(SQL_EXECUTE_NODE, edge_async(new SQLExecutorDispatcher()),
215224
Map.of(SQL_GENERATE_NODE, SQL_GENERATE_NODE, SEMANTIC_CONSISTENCY_NODE, SEMANTIC_CONSISTENCY_NODE))
216225
.addConditionalEdges(SQL_GENERATE_NODE, edge_async(new SqlGenerateDispatcher()),
217-
Map.of(KEYWORD_EXTRACT_NODE, KEYWORD_EXTRACT_NODE, END, END, SQL_EXECUTE_NODE, SQL_EXECUTE_NODE))
226+
Map.of(FEASIBILITY_ASSESSMENT_NODE, FEASIBILITY_ASSESSMENT_NODE, END, END, SQL_EXECUTE_NODE,
227+
SQL_EXECUTE_NODE))
218228
.addConditionalEdges(SEMANTIC_CONSISTENCY_NODE, edge_async(new SemanticConsistenceDispatcher()),
219229
Map.of(SQL_GENERATE_NODE, SQL_GENERATE_NODE, PLAN_EXECUTOR_NODE, PLAN_EXECUTOR_NODE));
220230

221231
GraphRepresentation graphRepresentation = stateGraph.getGraph(GraphRepresentation.Type.PLANTUML,
222232
"workflow graph");
223233

224-
log.info("\n\n");
225-
log.info(graphRepresentation.content());
226-
log.info("\n\n");
234+
log.info("workflow in PlantUML format as follows \n\n" + graphRepresentation.content() + "\n\n");
227235

228236
return stateGraph;
229237
}

‎spring-ai-alibaba-data-agent-chat/src/main/java/com/alibaba/cloud/ai/config/DataAgentProperties.java‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public class DataAgentProperties {
3737
*/
3838
private EmbeddingBatch embeddingBatch = new EmbeddingBatch();
3939

40+
private VectorStoreProperties vectorStore = new VectorStoreProperties();
41+
4042
@Getter
4143
@Setter
4244
public static class EmbeddingBatch {
@@ -63,4 +65,35 @@ public static class EmbeddingBatch {
6365

6466
}
6567

68+
@Getter
69+
@Setter
70+
public static class VectorStoreProperties {
71+
72+
/**
73+
* 相似度阈值配置,用于过滤相似度分数大于等于此阈值的文档
74+
*/
75+
private double similarityThreshold = 0.2;
76+
77+
/**
78+
* 一次删除操作中,最多删除的文档数量
79+
*/
80+
private int batchDelTopkLimit = 5000;
81+
82+
/**
83+
* 查询时返回的最大文档数量
84+
*/
85+
private int topkLimit = 30;
86+
87+
/**
88+
* 是否启用混合搜索
89+
*/
90+
private boolean enableHybridSearch = false;
91+
92+
/**
93+
* Elasticsearch最小分数阈值,用于es执行关键词搜索时过滤相关性较低的文档
94+
*/
95+
private double elasticsearchMinScore = 0.5;
96+
97+
}
98+
6699
}

‎spring-ai-alibaba-data-agent-chat/src/main/java/com/alibaba/cloud/ai/constant/Constant.java‎

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,27 @@ private Constant() {
3535

3636
public static final String NL2SQL_GRAPH_NAME = "nl2sqlGraph";
3737

38-
public static final String QUERY_REWRITE_NODE_OUTPUT = "QUERY_REWRITE_NODE_OUTPUT";
38+
public static final String INTENT_RECOGNITION_NODE_OUTPUT = "INTENT_RECOGNITION_NODE_OUTPUT";
3939

40-
public static final String KEYWORD_EXTRACT_NODE_OUTPUT = "KEYWORD_EXTRACT_NODE_OUTPUT";
40+
public static final String QUERY_ENHANCE_NODE_OUTPUT = "QUERY_ENHANCE_NODE_OUTPUT";
4141

42-
public static final String EVIDENCES = "EVIDENCES";
42+
public static final String FEASIBILITY_ASSESSMENT_NODE_OUTPUT = "FEASIBILITY_ASSESSMENT_NODE_OUTPUT";
43+
44+
public static final String EVIDENCE = "EVIDENCE";
4345

4446
public static final String TABLE_DOCUMENTS_FOR_SCHEMA_OUTPUT = "TABLE_DOCUMENTS_FOR_SCHEMA";
4547

4648
public static final String SCHEMA_RECALL_NODE_OUTPUT = "SCHEMA_RECALL_NODE_OUTPUT";
4749

48-
public static final String COLUMN_DOCUMENTS_BY_KEYWORDS_OUTPUT = "COLUMN_DOCUMENTS_BY_KEYWORDS_OUTPUT";
50+
public static final String COLUMN_DOCUMENTS__FOR_SCHEMA_OUTPUT = "COLUMN_DOCUMENTS__FOR_SCHEMA_OUTPUT";
4951

5052
public static final String TABLE_RELATION_OUTPUT = "TABLE_RELATION_OUTPUT";
5153

5254
public static final String TABLE_RELATION_EXCEPTION_OUTPUT = "TABLE_RELATION_EXCEPTION_OUTPUT";
5355

5456
public static final String TABLE_RELATION_RETRY_COUNT = "TABLE_RELATION_RETRY_COUNT";
5557

56-
public static final String BUSINESS_KNOWLEDGE = "BUSINESS_KNOWLEDGE";
57-
58-
public static final String SEMANTIC_MODEL = "SEMANTIC_MODEL";
58+
public static final String GENEGRATED_SEMANTIC_MODEL_PROMPT = "GENEGRATED_SEMANTIC_MODEL_PROMPT";
5959

6060
public static final String SQL_GENERATE_OUTPUT = "SQL_GENERATE_OUTPUT";
6161

@@ -93,6 +93,14 @@ private Constant() {
9393

9494
public static final String PLAN_EXECUTOR_NODE = "PLAN_EXECUTOR_NODE";
9595

96+
public static final String INTENT_RECOGNITION_NODE = "INTENT_RECOGNITION_NODE";
97+
98+
public static final String EVIDENCE_RECALL_NODE = "EVIDENCE_RECALL_NODE";
99+
100+
public static final String QUERY_ENHANCE_NODE = "QUERY_ENHANCE_NODE";
101+
102+
public static final String FEASIBILITY_ASSESSMENT_NODE = "FEASIBILITY_ASSESSMENT_NODE";
103+
96104
public static final String QUERY_REWRITE_NODE = "QUERY_REWRITE_NODE";
97105

98106
public static final String REPORT_GENERATOR_NODE = "REPORT_GENERATOR_NODE";
@@ -144,16 +152,4 @@ private Constant() {
144152
// 人类复核相关
145153
public static final String HUMAN_REVIEW_ENABLED = "HUMAN_REVIEW_ENABLED";
146154

147-
// column
148-
public static final String COLUMN = "column";
149-
150-
// table
151-
public static final String TABLE = "table";
152-
153-
// vectorType
154-
public static final String VECTOR_TYPE = "vectorType";
155-
156-
// knowledgeId
157-
public static final String KNOWLEDGE_ID = "knowledgeId";
158-
159155
}

0 commit comments

Comments
 (0)