09-DataAgent SQL执行步骤

基于源码版本:1.0.0-SNAPSHOT,核心代码位置 workflow/node/SqlGenerateNode.java(211行)+ workflow/node/SemanticConsistencyNode.java(123行)+ workflow/node/SqlExecuteNode.java(307行)+ resources/prompts/new-sql-generate.txt + resources/prompts/semantic-consistency.txt + resources/prompts/sql-error-fixer.txt

贯穿示例:用户输入「统计上月各部门销售额」


一、模块定位与架构

1.1 在执行链路中的位置

SQL执行步骤是 PlanExecutor 路由的三种步骤类型之一,当 Planner 生成的计划中某个步骤的 toolToUse = "SQL_GENERATE_NODE" 时,PlanExecutor 就会路由到 SQL 执行步骤。

重要前提 :SQL步骤不是独立存在的,它是 Plan 计划中的一个步骤。只有 Planner 生成的计划里包含 SQL_GENERATE_NODE 类型的步骤时,才会执行这条链路。PlanExecutor 负责按计划顺序调度,SQL步骤执行完后结果存入 state,然后回到 PlanExecutor 继续下一个步骤。

复制代码
Planner(生成计划,计划中包含SQL步骤)
    │
    ▼
PlanExecutor(读取当前步骤,toolToUse=SQL_GENERATE_NODE)
    │
    ▼
┌─────────────────────────────────────────────────┐
│              SQL执行步骤(本文档)                    │
│                                                   │
│  SQL生成 → 语义一致性校验 → SQL执行                  │
│    │           │ 不通过      │ 失败                │
│    │           ▼            ▼                    │
│    │      重新生成SQL    重新生成SQL                │
│    │      (最多N次)     (最多N次)               │
│    │                                            │
│    └─────────── 通过 ──────► 成功 ──► 结果存入state │
│                                          │          │
└──────────────────────────────────────────┼──────────┘
                                           │
                                           ▼
                                    PlanExecutor(下一步)

1.2 三个节点的职责

节点 行数 职责
SqlGenerateNode 211 根据当前步骤的 instruction 生成 SQL,支持重试生成
SemanticConsistencyNode 123 校验生成的 SQL 与用户需求的语义一致性,不通过则回到 SQL 生成
SqlExecuteNode 307 执行 SQL,存储结果,生成图表配置,失败则回到 SQL 生成

1.3 与 Plan 和 PlanExecutor 的紧密关联

  1. 步骤来源 :SQL步骤的 instruction 来自 Planner 生成的 Plan 中当前步骤的 toolParameters.instruction,不是用户原始问题
  2. 步骤号维护PLAN_CURRENT_STEP 由 PlanExecutor 维护,SQL执行成功后递增
  3. 结果存储 :SQL执行结果以 step_N 为 key 存入 SQL_EXECUTE_NODE_OUTPUT,供后续 Python/报告步骤读取
  4. sql_query回填 :SQL生成后回填到 Plan 当前步骤的 toolParameters.sqlQuery 字段
  5. 重试计数SQL_GENERATE_COUNT 记录当前步骤的 SQL 生成次数,超过最大次数则 END
  6. 回到 PlanExecutor :SQL执行成功后通过 SQLExecutorDispatcher 路由回 PLAN_EXECUTOR_NODE,继续下一个步骤

二、完整执行链路(用「统计上月各部门销售额」展开)

2.1 前置状态

PlanExecutor 路由到 SQL 步骤时,state 中的关键状态:

复制代码
PLAN_CURRENT_STEP = 1(当前是第1步)
PLANNER_NODE_OUTPUT = {
  "execution_plan": [
    { "step": 1, "tool_to_use": "SQL_GENERATE_NODE",
      "tool_parameters": { "instruction": "从 sales_order 表关联 department 表..." } },
    { "step": 2, "tool_to_use": "REPORT_GENERATOR_NODE", ... }
  ]
}
TABLE_RELATION_OUTPUT = 精选后的 Schema(sales_order + department)
EVIDENCE = 证据召回的输出
DB_DIALECT_TYPE = "mysql"
SQL_GENERATE_COUNT = 0
SQL_REGENERATE_REASON = empty()

2.2 第一步:SQL生成(SqlGenerateNode)

2.2.1 检查重试次数
java 复制代码
int count = state.value(SQL_GENERATE_COUNT, 0);
if (count >= properties.getMaxSqlRetryCount()) {
    // 超过最大重试次数,返回错误并 END
    return Map.of(SQL_GENERATE_OUTPUT, generator);  // generator 中设置 SQL_GENERATE_OUTPUT = END
}

本例:count = 0 < 最大次数 → 继续

2.2.2 获取当前步骤的 instruction
java 复制代码
// 获取planner分配的当前执行步骤的sql任务要求,每个步骤的sql任务是不同的。
// 不要拿 user query 这个总体的大任务。
String promptForSql = getCurrentExecutionStepInstruction(state);
// promptForSql = "从 sales_order 表关联 department 表(sales_order.dept_id = department.dept_id),
//                筛选 ship_time >= '2026-07-01' 且 ship_time < '2026-08-01'、status = '已发货' 的记录,
//                按 department.dept_name 分组,返回 department.dept_name AS 部门名称 和 SUM(sales_order.amount) AS 销售总额。"

关键:这里用的是当前步骤的 instruction,不是用户原始问题。Planner 在生成计划时已经把大任务拆解成每个步骤的具体 SQL 需求。

2.2.3 判断是否为重试生成
java 复制代码
SqlRetryDto retryDto = StateUtil.getObjectValue(state, SQL_REGENERATE_REASON, SqlRetryDto.class, SqlRetryDto.empty());
String failedSql = StateUtil.getStringValue(state, SQL_GENERATE_OUTPUT, "");

if (retryDto.sqlExecuteFail()) {
    // SQL执行失败重试
    sqlFlux = handleRetryGenerateSql(state, failedSql, retryDto.reason(), promptForSql);
} else if (retryDto.semanticFail()) {
    // 语义校验失败重试
    sqlFlux = handleRetryGenerateSql(state, failedSql, retryDto.reason(), promptForSql);
} else {
    // 首次生成
    sqlFlux = handleGenerateSql(state, promptForSql);
}

本例:retryDto = empty() → 首次生成 → handleGenerateSql

2.2.4 构建 SQL 生成参数

handleGenerateSql 调用 handleRetryGenerateSql(state, null, null, promptForSql)

java 复制代码
private Flux<String> handleRetryGenerateSql(OverAllState state, String originalSql, String errorMsg, String executionDescription) {
    String evidence = StateUtil.getStringValue(state, EVIDENCE);
    SchemaDTO schemaDTO = StateUtil.getObjectValue(state, TABLE_RELATION_OUTPUT, SchemaDTO.class);
    String userQuery = StateUtil.getCanonicalQuery(state);
    String dialect = StateUtil.getStringValue(state, DB_DIALECT_TYPE);
    String previousStepResults = buildPreviousStepResults(state);  // 前序步骤结果,本例 currentStep=1 → "无"

    SqlGenerationDTO sqlGenerationDTO = SqlGenerationDTO.builder()
        .evidence(evidence)
        .query(userQuery)
        .schemaDTO(schemaDTO)
        .previousStepResults(previousStepResults)
        .sql(originalSql)  // 首次生成时为 null
        .exceptionMessage(errorMsg)  // 首次生成时为 null
        .executionDescription(executionDescription)  // 当前步骤的 instruction
        .dialect(dialect)
        .build();

    return nl2SqlService.generateSql(sqlGenerationDTO);
}
2.2.5 LLM 生成 SQL

nl2SqlService.generateSql 使用 new-sql-generate.txt 模板调用 LLM,生成 SQL。

本例生成的 SQL:

sql 复制代码
SELECT d.dept_name AS 部门名称, SUM(o.amount) AS 销售总额
FROM sales_order o
JOIN department d ON o.dept_id = d.dept_id
WHERE o.ship_time >= '2026-07-01' AND o.ship_time < '2026-08-01' AND o.status = '已发货'
GROUP BY d.dept_name
ORDER BY 销售总额 DESC
2.2.6 防重复执行检测
java 复制代码
private boolean isUnchangedExecutionRetry(SqlRetryDto retryDto, String failedSql, String generatedSql) {
    return retryDto.sqlExecuteFail() && StringUtils.isNotBlank(failedSql) && StringUtils.isNotBlank(generatedSql)
        && normalizeSql(failedSql).equals(normalizeSql(generatedSql));
}

如果是执行失败重试,且新生成的 SQL 和失败的 SQL 完全相同(去掉末尾分号后比较),则停止重试,避免无限循环。

本例:首次生成,不触发此检测。

2.2.7 写入 state
java 复制代码
Map<String, Object> result = new HashMap<>(Map.of(
    SQL_GENERATE_OUTPUT, StateGraph.END,  // 临时值,后面会被覆盖
    SQL_GENERATE_COUNT, count + 1,  // 0 → 1
    SQL_REGENERATE_REASON, SqlRetryDto.empty()  // 清空重试原因
));

// 流式输出完成后,设置 SQL_GENERATE_OUTPUT = 生成的SQL
result.put(SQL_GENERATE_OUTPUT, sql);

2.3 第二步:语义一致性校验(SemanticConsistencyNode)

2.3.1 结构校验(非 LLM)
java 复制代码
Optional<String> structuralValidationError = SqlUtil.findGeneratedSqlValidationError(sql, dialect);
if (structuralValidationError.isPresent()) {
    return buildStructuralValidationFailure(state, sql, structuralValidationError.get());
}

先做 SQL 结构校验(语法、写操作检测等),不通过则直接回到 SQL 生成,不调用 LLM。

本例:SQL 结构合法 → 继续

2.3.2 构建语义校验参数
java 复制代码
SemanticConsistencyDTO semanticConsistencyDTO = SemanticConsistencyDTO.builder()
    .dialect(dialect)
    .sql(sql)  // 生成的 SQL
    .executionDescription(getCurrentExecutionStepInstruction(state))  // 当前步骤的 instruction
    .schemaInfo(buildMixMacSqlDbPrompt(schemaDTO, true))  // Schema
    .userQuery(userQuery)  // 规范化查询
    .evidence(evidence)  // 证据
    .build();
2.3.3 LLM 语义校验

使用 semantic-consistency.txt 模板调用 LLM,输出 SemanticConsistencyOutputDTO

  • isPassed:是否通过
  • reason:不通过的原因

本例:SQL 与 instruction 语义一致 → isPassed = true

2.3.4 校验结果处理
java 复制代码
private Map<String, Object> buildValidationResult(boolean passed, String validationResult) {
    if (passed) {
        return Map.of(SEMANTIC_CONSISTENCY_NODE_OUTPUT, true);
    } else {
        return Map.of(
            SEMANTIC_CONSISTENCY_NODE_OUTPUT, false,
            SQL_REGENERATE_REASON, SqlRetryDto.semantic(validationResult)  // 设置语义失败原因
        );
    }
}
  • 通过 → SEMANTIC_CONSISTENCY_NODE_OUTPUT = true → 进入 SQL 执行
  • 不通过 → 设置 SQL_REGENERATE_REASON = semantic(reason) → 回到 SQL 生成(重试)

本例:通过 → 进入 SQL 执行

2.4 第三步:SQL执行(SqlExecuteNode)

2.4.1 获取 SQL 和数据源配置
java 复制代码
Integer currentStep = PlanProcessUtil.getCurrentStepNumber(state);  // 1
String sqlQuery = StateUtil.getStringValue(state, SQL_GENERATE_OUTPUT);  // 生成的 SQL
sqlQuery = nl2SqlService.sqlTrim(sqlQuery);  // 去掉代码围栏、末尾分号等

String agentIdStr = StateUtil.getStringValue(state, Constant.AGENT_ID);
Long agentId = Long.valueOf(agentIdStr);
DbConfigBO dbConfig = databaseUtil.getAgentDbConfig(agentId);  // 动态获取数据源配置
2.4.2 执行业务逻辑(先执行 SQL,再做流式输出)
java 复制代码
// 业务逻辑优先:先实际执行 SQL
Mono<ExecutedSqlResult> sqlExecution = Mono.fromCallable(() ->
    executeAndStoreResult(state, currentStep, sqlQuery, dbConfig, dbQueryParameter, dbAccessor, result)
).subscribeOn(Schedulers.boundedElastic());

设计亮点:采用"业务逻辑优先"模式------先实际执行 SQL 并存储结果,再创建流式输出给用户看。这样即使流式输出中断,业务结果已经存好了。

2.4.3 executeAndStoreResult(核心)
java 复制代码
private ExecutedSqlResult executeAndStoreResult(...) throws Exception {
    // 1. 执行 SQL
    ResultSetBO resultSetBO = dbAccessor.executeSqlAndReturnObject(dbConfig, dbQueryParameter);
    String strResultSetJson = JsonUtil.getObjectMapper().writeValueAsString(resultSetBO);

    // 2. 写入 state
    result.put(SQL_REGENERATE_REASON, SqlRetryDto.empty());  // 清空重试原因
    result.put(SQL_RESULT_LIST_MEMORY, resultSetBO.getData());  // 结果数据(供Python读取)
    result.put(PLAN_CURRENT_STEP, currentStep + 1);  // 1 → 2,步骤号递增
    result.put(SQL_GENERATE_COUNT, 0);  // 重置SQL生成计数

    // 3. 存入步骤结果集合
    Map<String, String> existingResults = StateUtil.getObjectValue(state, SQL_EXECUTE_NODE_OUTPUT, Map.class, new HashMap<>());
    Map<String, String> updatedResults = PlanProcessUtil.addStepResult(existingResults, currentStep, strResultSetJson);
    result.put(SQL_EXECUTE_NODE_OUTPUT, updatedResults);
    // SQL_EXECUTE_NODE_OUTPUT = { "step_1": "{结果JSON}" }

    // 4. 回填 sql_query 到 Plan 的当前步骤
    ExecutionStep.ToolParameters currentStepParams = PlanProcessUtil.getCurrentExecutionStep(state).getToolParameters();
    currentStepParams.setSqlQuery(sqlQuery);
}

本例执行结果:

复制代码
step_1 = {
  "columns": ["部门名称", "销售总额"],
  "data": [
    ["销售一部", 1250000.00],
    ["销售二部", 980000.00],
    ["销售三部", 760000.00]
  ]
}
PLAN_CURRENT_STEP = 2
2.4.4 图表配置生成

SQL 执行成功后,还会调用 LLM 生成图表配置信息(DisplayStyleBO),填充到 ResultSetBO 中,用于前端展示。

2.4.5 执行失败处理
java 复制代码
.onErrorResume(e -> {
    String errorMessage = e.getMessage();
    result.put(SQL_REGENERATE_REASON, SqlRetryDto.sqlExecute(errorMessage));  // 设置执行失败原因
    return Flux.just(ChatResponseUtil.createResponse("SQL执行失败: " + errorMessage));
});

执行失败时设置 SQL_REGENERATE_REASON = sqlExecute(errorMessage),然后通过 SQLExecutorDispatcher 回到 SQL 生成(重试)。

2.5 回到 PlanExecutor

SQL 执行成功后,SQLExecutorDispatcher 路由:

java 复制代码
public String apply(OverAllState state) {
    SqlRetryDto retryDto = StateUtil.getObjectValue(state, SQL_REGENERATE_REASON, SqlRetryDto.class);
    if (retryDto.sqlExecuteFail()) {
        return SQL_GENERATE_NODE;  // 执行失败 → 重新生成 SQL
    } else {
        return PLAN_EXECUTOR_NODE;  // 执行成功 → 回到 PlanExecutor
    }
}

本例:执行成功 → 回到 PlanExecutor,PLAN_CURRENT_STEP = 2 → 路由到报告生成步骤。


三、SQL生成节点详解

3.1 new-sql-generate.txt 模板解析

模板分为 6 个部分:

部分 作用
# 角色 定义 LLM 为精通指定方言的 SQL 查询工程师
# 指令边界 防 Prompt 注入,Schema 是唯一依据
# 输入 6 个输入:Schema、Evidence、用户原始问题、当前执行步骤、前序步骤结果、方言
# SQL 约束 8 条约束:只读、表名列名必须存在、精确完成当前步骤、不使用 SELECT * 等
# 输出 只输出 SQL 语句本身

3.2 关键约束

  1. 只读限制 :只允许 SELECTWITH ... SELECT,禁止 INSERT/UPDATE/DELETE/DDL 等
  2. Schema 唯一依据:所有表名、列名、关联关系必须存在于 Schema,不得猜测
  3. 精确完成当前步骤:不遗漏过滤条件,不额外解决其他步骤
  4. 半开区间 :时间列包含完整结束日时,优先使用 < 下一日
  5. 方言一致性:MySQL 用反引号,PG/Oracle 用双引号,SQL Server 用方括号
  6. 不输出末尾分号:为兼容执行器

3.3 前序步骤结果的使用

java 复制代码
private String buildPreviousStepResults(OverAllState state) {
    int currentStep = PlanProcessUtil.getCurrentStepNumber(state);
    if (currentStep <= 1) return "无";  // 第一步没有前序结果

    // 遍历 step_1 到 step_{currentStep-1},拼接结果
    // 最多 8000 字符,超过则截断
}

如果当前步骤依赖前序 SQL 结果(比如第二步需要用第一步的结果做过滤),LLM 会使用前序结果中真实存在的列和值替换占位符。

3.4 重试生成的区别

首次生成和重试生成使用同一个模板,但重试时多传入两个参数:

  • sql:上一版失败的 SQL
  • exceptionMessage:失败原因(执行错误或语义校验不通过原因)

LLM 会根据失败原因修正 SQL,而不是重新生成。


四、语义一致性校验节点详解

4.1 两层校验

语义一致性校验节点做了两层校验

  1. 结构校验(非 LLM)SqlUtil.findGeneratedSqlValidationError(sql, dialect)

    • 语法检查
    • 写操作检测(INSERT/UPDATE/DELETE 等)
    • 不通过则直接回到 SQL 生成,不调用 LLM(节省成本)
  2. 语义校验(LLM) :使用 semantic-consistency.txt 模板

    • 校验 SQL 是否与当前步骤的 instruction 语义一致
    • 校验 SQL 是否与用户原始问题一致
    • 输出 isPassed + reason

4.2 校验输入

语义校验传入 6 个参数:

  • dialect:数据库方言
  • sql:生成的 SQL
  • executionDescription:当前步骤的 instruction(核心对比对象)
  • schemaInfo:Schema 信息
  • userQuery:规范化查询(全局背景)
  • evidence:业务知识

4.3 校验结果路由

  • 通过SEMANTIC_CONSISTENCY_NODE_OUTPUT = true → 进入 SQL 执行
  • 不通过 → 设置 SQL_REGENERATE_REASON = semantic(reason) → 回到 SQL 生成(带原因重试)

五、SQL执行节点详解

5.1 业务逻辑优先模式

SqlExecuteNode 采用"业务逻辑优先"的设计模式:

复制代码
1. 先实际执行 SQL(Mono.fromCallable,在 boundedElastic 线程池)
2. 处理和存储结果(写入 state)
3. 创建流式输出给用户看(Flux)

这样即使流式输出中断,业务结果已经存好了,不会丢失。

5.2 结果存储的 4 个状态 Key

SQL 执行成功后,写入 4 个状态 Key:

Key 作用
SQL_REGENERATE_REASON empty() 清空重试原因
SQL_RESULT_LIST_MEMORY resultSetBO.getData() 最近一次 SQL 结果数据(供 Python 步骤读取)
PLAN_CURRENT_STEP currentStep + 1 步骤号递增,告诉 PlanExecutor 执行下一步
SQL_GENERATE_COUNT 0 重置 SQL 生成计数

另外还有两个重要操作:

  • SQL_EXECUTE_NODE_OUTPUT:添加 step_N 结果到集合(供后续步骤读取所有历史结果)
  • toolParameters.sqlQuery:回填生成的 SQL 到 Plan 的当前步骤

5.3 图表配置生成

SQL 执行成功后,还会调用 LLM 生成图表配置信息(DisplayStyleBO),包括:

  • 图表类型(柱状图/折线图/饼图等)
  • X 轴/Y 轴映射
  • 排序方式
  • 展示样式

这些配置填充到 ResultSetBO 中,用于前端自动渲染图表。

5.4 执行失败处理

执行失败时:

  1. 设置 SQL_REGENERATE_REASON = sqlExecute(errorMessage)
  2. 流式输出错误信息给用户
  3. SQLExecutorDispatcher 检测到执行失败 → 路由回 SQL_GENERATE_NODE(重试)

六、三层失败重试机制

SQL 执行步骤有三层失败重试,每一层失败都会回到 SQL 生成节点重新生成:

复制代码
SQL生成
  │
  ├─ 超过最大重试次数 → END(放弃)
  │
  ▼
语义一致性校验
  │
  ├─ 不通过 → SQL_REGENERATE_REASON = semantic(reason) → 回到 SQL生成
  │
  ▼
SQL执行
  │
  ├─ 失败 → SQL_REGENERATE_REASON = sqlExecute(error) → 回到 SQL生成
  │
  ▼
成功 → 回到 PlanExecutor

6.1 第一层:SQL生成重试计数

SQL_GENERATE_COUNT 记录当前步骤的 SQL 生成次数,每次进入 SqlGenerateNode 递增。超过 properties.getMaxSqlRetryCount() 则 END。

注意:这个计数是每个步骤独立的,SQL 执行成功后重置为 0。

6.2 第二层:语义校验不通过

语义校验不通过时,设置 SQL_REGENERATE_REASON = semantic(reason),SqlGenerateNode 检测到 retryDto.semanticFail(),使用重试模式生成 SQL(传入失败 SQL + 原因)。

6.3 第三层:SQL执行失败

SQL 执行失败时,设置 SQL_REGENERATE_REASON = sqlExecute(errorMessage),SqlGenerateNode 检测到 retryDto.sqlExecuteFail(),使用重试模式生成 SQL。

6.4 防重复执行检测

如果是执行失败重试,且新生成的 SQL 和失败的 SQL 完全相同(normalizeSql 比较,去掉末尾分号),则停止重试,避免无限循环:

java 复制代码
if (isUnchangedExecutionRetry(retryDto, failedSql, generatedSql)) {
    result.put(SQL_GENERATE_OUTPUT, StateGraph.END);  // 停止重试
}

七、结果传递机制

7.1 SQL结果如何传递给后续步骤

SQL 执行成功后,结果通过两个渠道传递:

  1. SQL_EXECUTE_NODE_OUTPUT(Map) :所有步骤的历史结果集合,key = step_N,value = 结果 JSON

    • 报告生成节点读取所有步骤结果
    • Python 生成节点读取前序步骤结果
  2. SQL_RESULT_LIST_MEMORY(Object):最近一次 SQL 执行的结果数据

    • Python 执行节点直接读取这个字段

7.2 sql_query 回填

SQL 生成后,回填到 Plan 当前步骤的 toolParameters.sqlQuery 字段:

java 复制代码
ExecutionStep.ToolParameters currentStepParams = PlanProcessUtil.getCurrentExecutionStep(state).getToolParameters();
currentStepParams.setSqlQuery(sqlQuery);

这样 Plan 中就记录了每个 SQL 步骤实际生成的 SQL,便于追溯和展示。

7.3 步骤号递增

PLAN_CURRENT_STEP 递增后,PlanExecutor 下一次进入时就会读取下一个步骤:

java 复制代码
int currentStep = PlanProcessUtil.getCurrentStepNumber(state);  // 递增后的值
ExecutionStep executionStep = executionPlan.get(currentStep - 1);  // 下一个步骤

八、设计亮点

  1. 业务逻辑优先模式:先执行 SQL 存储结果,再做流式输出,即使输出中断也不丢失结果
  2. 三层重试机制:生成计数 + 语义校验 + 执行失败,每层失败都回到 SQL 生成,提高成功率
  3. 防重复执行检测:执行失败重试时,如果新 SQL 和旧 SQL 相同则停止,避免无限循环
  4. 结构校验前置:语义校验前先做非 LLM 的结构校验,节省成本
  5. 步骤级 instruction:SQL 生成使用当前步骤的 instruction,不是用户原始问题,确保每个步骤只完成自己的任务
  6. 前序结果注入:多步骤 SQL 时,前序结果注入到生成 Prompt 中,支持跨步骤依赖
  7. 结果双渠道传递:历史结果集合 + 最近结果内存,兼顾多步骤读取和单步快速访问
  8. sql_query 回填:生成的 SQL 回填到 Plan 中,便于追溯和前端展示

九、潜在问题

  1. 重试次数配置不透明properties.getMaxSqlRetryCount() 的默认值和配置方式需要查看配置类,文档中未明确
  2. 语义校验可能误判:LLM 语义校验可能误判正确的 SQL 为不通过,导致不必要的重试
  3. 前序结果截断:前序结果最多 8000 字符,超过则截断,可能导致后续步骤缺少必要数据
  4. 图表配置生成增加延迟:SQL 执行后还要调用 LLM 生成图表配置,增加响应时间
  5. 结构校验覆盖有限SqlUtil.findGeneratedSqlValidationError 的校验规则可能不全面,某些写操作可能漏检
  6. 重试时未区分失败类型:语义失败和执行失败都回到同一个 SQL 生成节点,虽然传入了原因,但处理逻辑相同
  7. PLAN_CURRENT_STEP 递增时机:在 SQL 执行成功后立即递增,如果后续图表配置生成失败,步骤号已经递增,可能导致状态不一致
  8. SQL_RESULT_LIST_MEMORY 只存最近一次:如果有多个 SQL 步骤,Python 步骤只能读到最近一次的结果,需要从 SQL_EXECUTE_NODE_OUTPUT 读历史结果

十、总结

SQL执行步骤是 PlanExecutor 路由的三种步骤类型之一,由 SQL生成 → 语义一致性校验 → SQL执行 三个节点组成:

  • 承上 :接收 PlanExecutor 路由,从 Plan 当前步骤的 instruction 获取 SQL 需求
  • 启下:执行成功后结果存入 state,步骤号递增,回到 PlanExecutor 继续下一个步骤

核心设计是三层重试 + 业务逻辑优先 + 结果双渠道传递

  • 三层重试:生成计数限制 + 语义校验不通过重试 + 执行失败重试,每层都回到 SQL 生成
  • 业务逻辑优先:先执行 SQL 存储结果,再做流式输出
  • 结果双渠道:历史结果集合(SQL_EXECUTE_NODE_OUTPUT)+ 最近结果内存(SQL_RESULT_LIST_MEMORY)

用「统计上月各部门销售额」的例子:

  • SQL生成:根据步骤1的 instruction 生成 SELECT 查询
  • 语义校验:校验 SQL 与 instruction 语义一致 → 通过
  • SQL执行:执行查询,结果存入 step_1,PLAN_CURRENT_STEP→2,回填 sql_query
  • 回到 PlanExecutor → 路由到报告生成步骤

这个模块的设计体现了 DataAgent 的核心理念:用 LLM 生成 SQL,用语义校验保证质量,用多层重试提高成功率,用业务逻辑优先保证结果可靠,用结构化状态传递实现多步骤协作

相关推荐
weixin_422329312 小时前
06-DataAgent 可行性评估模块
dataagent
weixin_422329313 小时前
07-DataAgent 计划生成模块
dataagent
weixin_422329313 小时前
03-DataAgent 查询增强模块
dataagent
weixin_422329314 小时前
01-DataAgent 意图识别模块
dataagent
递归尽头是星辰3 个月前
AI 访问数据仓库:从直连到微服务化
数据仓库·人工智能·微服务·dataagent·ai数据治理
Aloudata8 个月前
破局 AI 幻觉:构建以 NoETL 语义编织为核心的 AI 就绪数据架构
人工智能·架构·数据分析·dataagent
Aloudata8 个月前
企业落地 AI 数据分析,如何做好敏感数据安全防护?
人工智能·安全·数据挖掘·数据分析·chatbi·智能问数·dataagent
Aloudata9 个月前
大火的 ChatBI,是如何实现灵活的自然语言数据分析?
数据挖掘·数据分析·chatbi·dataagent·自然语言问数
数据库知识分享者小北1 年前
如何构建企业级数据分析助手:Data Agent 开发实践
数据库·阿里云·1024程序员节·dataagent