目标不是演示“LLM 会写 SQL”,而是把自然语言问数做成一条可运行、可观测、可评测、可迭代的工程链路。
项目围绕企业结构化数据分析场景展开,覆盖从前端请求、服务运行时、语义预检、元数据召回、图执行、SQL 安全校验、有界纠错、结果恢复、SSE 返回到评测闭环的完整流程。当前版本已经完成最后一轮 targeted hardening,进入项目封结状态。
很多 Text-to-SQL Demo 会把 schema 直接塞给模型,然后让模型一次性生成 SQL。这个项目更关注真实系统里的控制问题:
- 用户问题需要先被判断是否适合问数,恶意操作、越权探测、注入类输入不能进入 SQL 链路。
- 表、字段、指标、字段取值来自不同信息源,不能只依赖模型硬猜。
- SQL 生成后必须经过确定性安全校验、schema 校验、执行和有界修复。
- 结果异常、空结果、修复耗尽等场景需要有可追踪的 recovery 分支。
- 项目质量不能只靠主观演示,需要场景集、评分器、bad case 分析和回归套件。
一句话概括:这是一个面向企业结构化数据的可控 Text-to-SQL Agent 工程原型,不是一个已经具备完整 RBAC/ABAC 和生产 SLA 的企业上线系统。
flowchart LR
A["React 前端"] --> B["FastAPI /api/query"]
B --> C["QueryService"]
C --> D["QueryRuntime"]
D --> E["Preflight 语义预检"]
E --> SP["SemanticPlan assist"]
SP --> F["LangGraph 问数图"]
F --> G["Event Normalize"]
G --> H["SSE 流式返回"]
flowchart TD
Q["用户自然语言问题"] --> P["Preflight: 问数/澄清/拒绝"]
P --> SP["SemanticPlan assist"]
SP --> K["extract_keywords"]
K --> R1["recall_column"]
K --> R2["recall_value"]
K --> R3["filter_metric"]
R1 --> S["retrieval_selection 证据包筛选"]
R2 --> S
R3 --> S
S --> C["add_extra_context"]
C --> G["generate_sql"]
G --> V["check_sql_safety / validate_sql"]
V --> X["run_sql"]
V --> VE["可修复错误"]
VE --> Fix["correct_sql 有界纠错"]
Fix --> V
X --> QR["check_recovery"]
QR --> RN["正常"]
RN --> F["finalize_sql_result"]
QR --> RA["空结果/异常/修复耗尽"]
RA --> D["diagnose_recovery"]
D --> DR["retry_retrieval"]
DR --> K
D --> DG["regenerate_sql"]
DG --> G
D --> DS["停止"]
DS --> FR["finalize_recovery"]
React -> FastAPI -> QueryService -> QueryRuntime -> LangGraph -> SSE分层清晰。- 前端不直接绑定 LangGraph 内部节点事件,后端通过事件归一化层输出稳定 SSE 协议。
QueryRuntime统一管理一次查询生命周期,承接 preflight、graph 执行、事件封装和错误返回。
SemanticPlan assist先把用户问题整理成结构化语义计划,再增强extract_keywords和召回输入。- 多路召回:字段、指标、字段取值分别召回。
retrieval_selection输出结构化证据包,不是把所有候选文本直接堆给模型。- SQL 生成前会补充上下文,降低模型硬猜表字段和指标口径的概率。
- 已完成
SemanticPlan数据构建、SFT 微调、离线评估、远端 provider 接入和 targeted A/B 验证。 - 微调目标不是让模型直接生成 SQL,而是先输出可校验的业务语义计划,增强关键词抽取、字段/指标/过滤召回和风险信号识别。
SemanticPlan只作用于召回输入增强层,不绕过 SQL 安全校验、schema 校验、graph 路由和执行闭环。
- SQL 安全校验使用
sqlglotAST,而不是只靠字符串正则。 - 已覆盖只读约束、多语句、DDL/DML、注释注入、
SELECT *、未知表/列、LIMIT 注入/钳位等场景。 - SQL 纠错链具备失败分类、修复预算、重复无进展终止。
- 对破坏性意图和 SQL 注入类输入,纠错链会快速终止,避免把“应该拒绝的请求”硬修成查询。
- schema 校验已支持 CTE 和子查询别名,避免把虚拟表/别名误报为未知表。
- 已实现
check_recovery -> diagnose_recovery -> prepare_recovery_* -> finalize_recovery的最小闭环。 - 支持空结果、异常结果、SQL 修复耗尽等触发原因。
- 支持
retry_retrieval和regenerate_sql路由。 - 有
max_attempts硬预算和attempted_retrieval_queries记录,避免 recovery 无限重入。
- 运行链路带
request_id,便于从 route/service/runtime/graph 追踪一次请求。 - SSE 事件统一为
progress / result / error / completed。 - 已有 85 case 最终评测集、30 case targeted live E2E 样本、bad case 分析报告和已知问题文档。
最后一轮 targeted hardening 后,离线回归结果:
299 passed, 0 failed, 3 deselected
其中 deselected 是需要 live LLM / live graph 的用例,离线回归不会启动真实服务。
评测文件:app/eval/scenarios/final_eval_suite_v1.json
结果摘要见 docs/final_eval_summary_qwen3.md 和 docs/badcase_analysis_qwen3.md。
这轮评测的定位是压力测试和问题发现,不是为了给项目刷一个漂亮总分。它把系统在更大样本、更复杂标签下的真实问题打了出来:
| 指标 | 结果 | 说明 |
|---|---|---|
| 场景数 | 85 | core / multi / metric / sql_safety / recovery / bug_hunt 等 |
| final_success_rate | 69% | 原始 SQL 最终成功率 |
| safety_pass_rate | 91% | 安全评分通过率 |
| filter_binding_rate | 41% | 用户过滤条件绑定到正确字段/取值的比例 |
| recovery_action_hit_rate | 92% | recovery 预期动作命中率 |
这轮评测中有 26 个失败,其中 21 个是 DashScope 429 限流导致的基础设施噪音,不代表模型或链路能力。真正值得处理的问题主要是三类:
- 安全绕过:恶意输入被模型“善意改写”为安全 SELECT,没有在输入层拒绝。
- SQL 纠错死循环:破坏性意图、
SELECT *、子查询别名等问题进入无意义修复。 - filter binding 一致性仍有提升空间:部分样本中 SQL 虽然可执行,但用户过滤条件与目标字段/取值的对应关系还不够稳定。
因此,第一轮 85 case 的价值是明确问题边界和优化优先级,而不是直接作为项目最终展示口径。
评测文件:app/eval/scenarios/targeted_hardening_suite.json
第二轮不是一个独立的新总分测试,也不是替代 85 case。它是针对第一轮 bad case 的解释和验证集:把可低风险优化的问题抽出来,构造成 30 条真实 graph + 外部依赖的 targeted live E2E 样本,验证归因是否成立、修复是否收敛、正常主链路是否没有被破坏。
这 30 条样本包含两类问题:
- 第一轮暴露过的失败模式,例如破坏性意图、SQL 注入、敏感字段探测、
SELECT *、CTE/子查询别名、纠错快速终止。 - 新增的同类变体和正常问数回归,避免只针对原始 bad case 做硬编码补丁。
| 指标 | 结果 | 说明 |
|---|---|---|
| 场景数 | 30 | 安全、纠错终止、CTE/子查询、正常问数、管线集成 |
| safety_pass_rate | 97% | 29/30 安全评分通过 |
| normal core success | 10/11 | 正常问数主链路基本稳定 |
| safety interception | 12/12 | 破坏性、注入、敏感、越权、导出类请求均被拒绝 |
| quick termination | 2/2 | 不可执行意图不会进入无意义纠错 |
| table_hit_rate | 100% | 目标表命中 |
| metric_hit_rate | 100% | 目标指标命中 |
| recovery_action_hit_rate | 100% | recovery 动作命中 |
注意:这个 targeted suite 的原始 final_success_rate 是 47%,不能作为主结论。因为安全拒绝场景在 SQL 状态上通常是 failed,但行为上是正确的。它应该被解读为 bad case 收敛验证:安全通过率、正常问数成功率、quick termination 和目标 hardening 行为是否命中,才是这一轮的关键指标。
换句话说,项目最终评测口径不是“85 case 得 69%,30 case 得 47%”,而是:
85 case 用来暴露系统问题;30 case targeted live E2E 用来验证其中可低风险优化的问题已经收敛,同时保留过滤条件绑定一致性、权限治理等需要下一阶段架构升级的边界。
项目已经完成最后一轮 targeted hardening,但仍保留清晰边界:
filter_binding_rate在 85 case 中是一个值得关注的观测指标,它反映了部分场景下过滤条件与目标字段/取值之间的一致性仍不够稳定。但这个指标本身并不能脱离业务域、schema 复杂度和样本构成,被直接当作模型优劣的单一结论。后续如果继续做架构升级,更合适的方向仍然是通过 semantic plan、指标/维度 registry 和整体检索表达能力的增强去改善这类一致性问题,而不是继续堆 graph 节点。user_passwords这类不存在表名的敏感探测仍有一次安全漏判:模型可能把恶意探测“善意纠正”为已有表查询。- CTE 场景中曾出现中文别名导致的 SQL 生成失败,属于模型生成稳定性问题。
- 当前权限治理仍是轻量安全控制,不是完整 RBAC/ABAC。真实企业上线还需要表/字段/指标权限贯穿召回、生成、校验、执行四层。
- 85 case 全量评测受外部模型限流影响明显,正式展示时应开启 delay 和 429 retry。
在完成 targeted hardening 之后,项目继续补齐了 SemanticPlan SFT 微调链路,用结构化中间表示增强 extract_keywords,降低关键词抽取对通用分词和 prompt 临场发挥的依赖。
这部分工作不是让模型直接生成 SQL,而是让模型先输出可校验的业务语义计划:
- 用户问题中的指标、维度、过滤条件、时间范围、对比关系、风险信号被解析为
SemanticPlan。 SemanticPlan进入关键词和召回输入层,增强字段、指标和过滤语义的结构化表达。- SQL 安全校验、schema 校验、graph 路由、执行和 recovery 主链路保持不变。
微调数据分三步构建:
| 阶段 | 结果 |
|---|---|
| Pilot 数据 | 固定小规模人工 gold,用于验证数据格式、训练脚本和评估链路 |
| first_run_v1 | 构建 1000+ 训练样本,dev/test 使用 human gold 隔离 |
| first_run_v1_1 | 增加 260 条 targeted hardening 样本,强化 enum boundary、comparison、time/filter、risk_flags、clarification |
V1.1 最终训练包规模:
train: 1359
dev: 100
test: 100
云端 LoRA 微调基于 Qwen2.5-1.5B-Instruct + SemanticPlan V1.1 adapter 完成,训练结果如下:
| 指标 | V1 Dev | V1.1 Dev | V1 Test | V1.1 Test |
|---|---|---|---|---|
| parse valid | 0.84 | 0.95 | 0.82 | 0.94 |
| schema valid | 0.84 | 0.95 | 0.82 | 0.94 |
| overall | 0.6808 | 0.8478 | 0.6624 | 0.8280 |
其中 dev overall 从 0.6808 提升到 0.8478,test overall 从 0.6624 提升到 0.8280;schema 边界稳定性从 82%-84% 提升到 94%-95%。
模型接入采用远端 provider 方式:模型服务只负责加载 base model + LoRA adapter 并暴露 SemanticPlan HTTP 服务,业务主链路继续负责检索、SQL 生成、安全校验、执行和结果返回。这样可以把模型推理资源和业务运行时解耦。
最终 A/B 使用现有 30 条 targeted live E2E 样本,比较 legacy keyword extraction 与 assist + SemanticPlan V1.1:
| 指标 | Legacy | Assist |
|---|---|---|
| success count | 15/30 | 15/30 |
| semantic used | 0 | 13 |
| provider fallback | - | 2 |
| regressions | - | 0 |
| safety regressions | - | 0 |
| infrastructure errors | - | 0 |
| go/no-go | - | go |
SemanticPlan 实际介入的 13 条样本全部成功,A/B 没有引入主链路回归或安全回归。相比 legacy,assist 模式将 first_pass_success_rate 从 0.43 提升到 0.47,将 correction_needed_rate 从 0.07 降到 0.03,说明语义计划对关键词和召回输入有正向收敛效果;同时 provider 侧保留回退能力,不会把主链路稳定性押注在单一模型路径上。
| 模块 | 技术 |
|---|---|
| Agent 编排 | LangGraph |
| 后端接口 | FastAPI / SSE |
| SQL AST | sqlglot |
| 元数据与数仓 | MySQL / SQLAlchemy |
| 向量召回 | Qdrant |
| 全文检索 | Elasticsearch |
| Embedding | TEI / BAAI/bge-large-zh-v1.5 |
| 日志与上下文 | loguru / ContextVar |
| 前端 | React / Vite / Tailwind CSS |
| 环境管理 | uv / pnpm / Docker Compose |
app/
agent/ LangGraph 图、节点、状态、SQL 安全、recovery
api/ FastAPI 路由
clients/ MySQL / Qdrant / Elasticsearch / Embedding 客户端
eval/ 场景集、评分器、runner、报告生成
events/ SSE 事件协议与事件归一化
runtime/ QueryRuntime 与 preflight
services/ QueryService 与业务服务层
conf/ app_config.yaml / meta_config.yaml
docker/ 本地依赖服务配置
docs/ 设计、计划、评测总结、bad case 分析
frontend/ React 前端
logs/ 评测输出与运行日志
prompts/ SQL 生成、纠错、过滤 prompt
scripts/ 本地初始化与启动脚本
tests/ 单测、契约测试、eval 测试、targeted hardening 测试
uv sync
corepack enable
pnpm --dir frontend installCopy-Item .env.example .env在 .env 中写入模型 API Key 和本地依赖配置。
powershell -ExecutionPolicy Bypass -File scripts/init-dev.ps1该脚本会安装依赖、启动基础服务并构建元数据知识库。
powershell -ExecutionPolicy Bypass -File scripts/start-dev.ps1可选:
powershell -ExecutionPolicy Bypass -File scripts/start-dev.ps1 -BackendOnly
powershell -ExecutionPolicy Bypass -File scripts/start-dev.ps1 -FrontendOnly
powershell -ExecutionPolicy Bypass -File scripts/start-dev.ps1 -BackendPort 8010 -FrontendPort 5180uv run pytest -q需要本地外部依赖和模型配置可用:
uv run python -m app.scripts.run_final_eval `
--suite app/eval/scenarios/final_eval_suite_v1.json `
--output logs/final-eval-report.json `
--summary docs/final_eval_summary.md `
--issues docs/known_issues.md `
--delay-seconds 2 `
--retry-429-attempts 2 `
--retry-429-backoff-seconds 5用于最后一轮 targeted hardening 的真实链路验证:
uv run python -m app.scripts.run_final_eval `
--suite app/eval/scenarios/targeted_hardening_suite.json `
--output logs/targeted-hardening-report.json `
--summary docs/targeted_hardening_summary.md `
--issues docs/targeted_hardening_issues.md `
--delay-seconds 2 `
--retry-429-attempts 2 `
--retry-429-backoff-seconds 5项目已进入封结状态。SemanticPlan SFT 已完成数据构建、云端训练、离线评估、远端 provider、主链路 assist 接入、A/B 验证和一键回滚闭环。后续不再做无止境功能迭代,除非目标变成真实上线系统;如果继续推进,优先级应是:
- 完整 RBAC/ABAC 权限治理。
- 更大规模、更稳定的线上评测环境。