Skip to content

fix(nl2sql): send new SQL generation prompt as a user message - #633

Open
ydflow wants to merge 1 commit into
spring-ai-alibaba:mainfrom
ydflow:fix/nl2sql-new-sql-generation-user-message
Open

ydflow wants to merge 1 commit into
spring-ai-alibaba:mainfrom
ydflow:fix/nl2sql-new-sql-generation-user-message

Conversation

@ydflow

@ydflow ydflow commented Sep 22, 2026

Copy link
Copy Markdown

PR 描述(候选 1:#461 的第二处实例)

用法:确认候选 1 后,在 fork 上基于 upstream main(27e7baa)建分支,
应用下面「Patch」的改动,然后把 Description 段贴进 PR 正文。
本地已 commit:c0a9c29(工作区 D:\开源贡献\dataagent-scout\DataAgent-main)。

Fixes

Fixes #461(第二处实例)

关联既有 PR

#515 已修 PythonAnalyzeNode(llmService.callSystem(systemPrompt) → callUser(...)),
但同源的 Nl2SqlServiceImpl#generateSql 新 SQL 生成分支仍在使用 callSystem。
本 PR 补上这处,两者根因相同:请求只含 system message,缺 user message。

Root cause

StreamLlmService#callSystem:

@Override
public Flux<ChatResponse> callSystem(String system) {
    return registry.getChatClient().prompt().system(system).stream().chatResponse();
}

.system(system) 只设置 system message,没有 .user(...)。许多 OpenAI 兼容 provider
(DeepSeek、通义千问等)硬性要求 messages 数组中至少有一条 role: "user",否则直接返回
at least one message is required。

Nl2SqlServiceImpl#generateSql(第 79 行,新 SQL 生成分支):

newSqlFlux = llmService.toStringFlux(llmService.callSystem(prompt));

而同一方法第 71 行的 SQL 修复分支已经用 callUser:

newSqlFlux = llmService.toStringFlux(llmService.callUser(errorFixerPrompt));

同类两条路径,一条兼容一条不兼容——不一致本身即是佐证。

What this PR does

1 行主代码改动:callSystem(prompt) → callUser(prompt)。

Nl2SqlServiceImpl 中 5 处 LLM 调用(L56 / L71 / L79 / L120 / L160)现已统一为 callUser。

关于推翻既有测试断言

Nl2SqlServiceImplTest 原有断言锁定了错误行为:

// 修改前(第 120 / 125-126 行)
stubSystemSql("SELECT * FROM users");
verify(llmService).callSystem(prompt.capture());
verify(llmService, never()).callUser(anyString());

这里必须显式说明为何推翻它:callSystem 不是被刻意选择的调用方式,而是与 provider
兼容性要求相悖的疏漏。测试原本通过,是因为 mock 不会校验 messages 数组结构;真实
provider 会拒绝。改为:

// 修改后
stubUserSql("SELECT * FROM users");
verify(llmService).callUser(prompt.capture());
verify(llmService, never()).callSystem(anyString());

同时删除随之无用的 stubSystemSql() helper。

How to verify

RED(修复前)

./mvnw -pl data-agent-management -am -Dtest=Nl2SqlServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test

Tests run: 13, Failures: 0, Errors: 3, Skipped: 0
  generateSql_withoutMeaningfulExistingSql_usesNewGenerationPrompt  ×3  ? PotentialStubbingProblem
    this invocation of 'toStringFlux' method:
      llmService.toStringFlux(null);
      -> at Nl2SqlServiceImpl.generateSql(Nl2SqlServiceImpl.java:79)

栈精确指向第 79 行:callSystem 未被 stub 而返回 null。

GREEN(修复后)

同命令:

Tests run: 13, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

反证(瑶光)

把第 79 行回滚成 callSystem → 3 个参数化用例重新变红;恢复 → 转绿。
证明红灯来自被测层,不是 mock 假红。

全量 + checkstyle

mvn -pl data-agent-management -am -Dspotless.apply.skip=true test
  → Tests run: 1712, Failures: 6, Errors: 0

mvn -pl data-agent-management -am -Dspotless.apply.skip=true checkstyle:check
  → You have 0 Checkstyle violations. BUILD SUCCESS

关于那 6 个失败:它们是上游 main 基线既有失败,与本次改动无关。
已用基线隔离证明——把两个文件还原成 upstream main 原版后单独跑:

Tests run: 76, Failures: 6
  PromptHelperTest                  32 run, 4 failures
  NodeTracingLifecycleListenerTest  36 run, 1 failure
  PlannerNodeTest                    8 run, 1 failure

这三个套件均不在本次改动范围内。(PR #630 的 Notes 也记录了同样的失败。)

Patch

data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/nl2sql/Nl2SqlServiceImpl.java

@@ -79 @@
-			newSqlFlux = llmService.toStringFlux(llmService.callSystem(prompt));
+			newSqlFlux = llmService.toStringFlux(llmService.callUser(prompt));

data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/service/nl2sql/Nl2SqlServiceImplTest.java

@@ -120 @@
-		stubSystemSql("SELECT * FROM users");
+		stubUserSql("SELECT * FROM users");

@@ -125,126 @@
-		verify(llmService).callSystem(prompt.capture());
-		verify(llmService, never()).callUser(anyString());
+		verify(llmService).callUser(prompt.capture());
+		verify(llmService, never()).callSystem(anyString());

@@ -162,167 @@
-	}
-
-	private void stubSystemSql(String sql) {
-		Flux<ChatResponse> response = Flux.just(ChatResponseUtil.createPureResponse(sql));
-		when(llmService.callSystem(anyString())).thenReturn(response);
-		when(llmService.toStringFlux(response)).thenReturn(Flux.just(sql));

Special notes for review

Nl2SqlServiceImpl#generateSql used llmService.callSystem() on the
new-SQL path, which builds a request containing only a system message.
Many OpenAI-compatible providers (DeepSeek, Qwen, ...) require at least
one role:"user" message and fail with "at least one message is
required", so first-time SQL generation broke while the SQL repair path
in the same method already used callUser().

Switch the new-generation path to callUser() so both branches send a
user message. Update Nl2SqlServiceImplTest to stub and verify callUser()
on that path and assert callSystem() is never used, and drop the now
unused stubSystemSql() helper.

Second instance of spring-ai-alibaba#461; PR spring-ai-alibaba#515 only fixed PythonAnalyzeNode.
@ydflow

ydflow commented Sep 22, 2026

Copy link
Copy Markdown
Author

Note for reviewers on CI status: the three jobs in build-and-test.yml (format, check-style, test-and-build) are all guarded by if: (github.repository == 'spring-ai-alibaba/DataAgent'), so they do not run for fork PRs — the unstable combined status only means "no checks reported yet", not a failure.

Local verification on this branch (offline, spotless skipped since jgit cannot resolve the shallow origin/main ref here):

  • mvn -pl data-agent-management -am -Dtest=Nl2SqlServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test → Tests run: 13, Failures: 0, Errors: 0 (was Errors: 3 before the fix, stack pointing at Nl2SqlServiceImpl.java:79)
  • mvn -pl data-agent-management -am -Dspotless.apply.skip=true checkstyle:check → You have 0 Checkstyle violations. BUILD SUCCESS
  • Full module: Tests run: 1712, Failures: 6 — those 6 are pre-existing on upstream main; isolated by reverting these two files to the upstream versions and re-running the same three suites (PromptHelperTest 4, NodeTracingLifecycleListenerTest 1, PlannerNodeTest 1 → Tests run: 76, Failures: 6)

Happy to re-run anything maintainers want once CI is allowed to execute on the upstream side.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]PythonAnalyzeNode 调用 LLM 分析时报错 "at least one message is required"

1 participant