Skip to content

V1|可测量的问数基线 ​

实现状态:已完成。V1-S01~S05 全部 16 个任务通过(2026-09-09)。

这一期学会什么:基线的价值是发现失败分布,而不是在小数据集上追求漂亮演示。

打开本期 5 个故事 / 16 个任务 · 查看总进度

为什么有这一期 ​

限定 Sales 的 10~15 张表,跑通最小安全链路并发现真实失败。进入条件:V0 验收通过;选择并冻结 Sales 子集。

本期链路 ​

text
Web 提问 → API → 固定 Sales Schema → Prompt/LLM → SQLGlot AST 校验 → 只读执行 → 表格 → Trace

跟读顺序 ​

  1. 从前端 QueryRequest 跟到 API 校验,确认用户身份与 trace 如何传递。
  2. 阅读 Prompt Builder,明确固定 15 张表的上下文范围。
  3. 先用 fake Provider 理解流程,再用真实模型记录质量。
  4. 找到 AST 拒绝点、只读事务、timeout 和结果上限。
  5. 读逐题评测报告,选择一条选表错与一条指标错的样例。

必做学习实验 ​

用一个未知列问题、一个写 CTE 和一个超时 SQL 观察三种失败;把取消单加入销售数据,检查"能执行"与"业务正确"的差别。

记录输入、预测、实际输出、代码入口和失败原因;实验通过后再勾选相应任务。只有文档或 fake 运行不能替代真实集成与质量验收。

规模与验收门槛 ​

形成可复现 Baseline 与失败分类;不预设准确率,不将示意百分比当实测。

完整功能与错误场景以本期计划为准。基线指标已通过真实评测记录(见下方"实测指标"节)。

设计取舍 ​

限制范围让后续质量提升可归因。V1 不使用混合召回、语义注册表或 LangGraph,但安全基线不能为了演示而省略。

两分钟复述骨架 ​

"我的 V1 是固定 Sales 子集的基线,记录每道题的 SQL、版本与错误类型。通过 SQLGlot AST 白名单校验和 asyncpg 只读事务保证安全;管理端可人工标注 Wrong Table / Column / Join / Value / Metric / Time / Aggregation 等分类。V2 的优先级来自真实 wrong_metric + wrong_value(4/10)的高频分布,而不是预先堆功能。"


真实实现代码导读 ​

内容详情
已验收任务及证据V1-S01-T01~T03、V1-S02-T01~T03、V1-S03-T01~T03、V1-S04-T01~T03、V1-S05-T01~T04;plan/evidence/ 下 5 个验收记录文件
真实入口与关键函数server/api/query.py;QueryOrchestrator.submit;SafeQueryPipeline.run;SQLValidator.validate;AsyncpgReadOnlyExecutor.execute;ResultSummaryBuilder.build
上游/下游与数据契约HTTP → QueryRequest → Orchestrator → Prompt → LLMGateway → SQLValidator → ReadOnlyExecutor → QueryRepository;响应统一为 QueryResponse / QueryTrace
正常与失败场景的命令uv run pytest tests/test_sql_safety.py tests/test_result_and_evaluation.py tests/test_llm_generation.py tests/test_query.py -q
实测指标183 passed,1 warning;syntax_valid 61.54%,execution_correct 30.77%,first_success 47.37%(19 题,deepseek-v4-flash);10 条人工复核标签写入数据库
本期新决策与限制SQLGlot 默认拒绝未知函数;reader DSN + 只读事务双重隔离;Decimal 以字符串返回;摘要不调模型;V1 身份头仍是可信上游占位

V1-S01 跟读:请求怎样进入固定范围 ​

  1. server/api/query.py 校验请求并从 x-atlas-identity 取得 V1 临时身份。
  2. server/domain/query.py 定义所有状态共用的请求、响应、列、错误和 Trace 契约。
  3. server/domain/sales_scope.py 先拒绝库存等明确跨域请求;"收入"要求澄清;"销售额"绑定到 V0 指标草案 net_sales。
  4. server/orchestrator/query.py 把身份、数据/schema/模型版本和超时预算组成不可变上下文。
  5. server/query/repository.py 保存请求和状态;控制库表由迁移 9cb21c73e4a1 创建。

关键失败场景是 pipeline 抛出包含连接信息的异常:编排器只返回 internal_error 和 trace_id,不会把异常原文交给用户。这里选择显式 QueryPipeline 端口,是为了让 S02 替换 Provider 时不修改编排逻辑。

V1-S02 跟读:候选 SQL 怎样生成 ​

  1. server/generation/prompt.py 从固定 15 表白名单、指标草案和 train 示例构造版本化 Prompt。
  2. server/llm/gateway.py 隔离 DeepSeek Chat Completions,并提供可重放、可延迟的 Fake。
  3. server/generation/sql.py 在总超时预算内有限重试,再用 Pydantic 强校验 JSON 输出。
  4. server/query/repository.py 保存候选 SQL、Prompt hash、模型参数和 token 用量。
  5. 候选 SQL 到 S03 才经过 AST 校验与只读执行。

真实 DeepSeek 冒烟使用 deepseek-v4-flash,共消耗 4,517 tokens。Fake API 冒烟将相同追踪字段写入了控制库。格式错误、空 SQL、输出过长、限流和超时均有离线失败测试。

V1-S03 跟读:SQL 怎样被验证和执行 ​

text
SafeQueryPipeline.run
  ├── generation   → QueryOutcome(PROCESSING, candidate_sql)
  ├── validation   → SQLValidator.validate(candidate_sql)
  │     ├── parse(dialect="postgres")         拒绝非 PostgreSQL 语法
  │     ├── _reject_write_nodes               拒绝 DDL/DML/COPY/Transaction
  │     ├── _validate_tables                  拒绝跨 schema / 超范围表
  │     ├── _validate_columns                 拒绝超范围字段
  │     └── _validate_functions (23 个白名单) 拒绝 pg_sleep 等危险函数
  └── execution    → AsyncpgReadOnlyExecutor.execute(validated_sql)
        ├── asyncio.timeout(effective_ms)     asyncio 层超时
        ├── asyncio.Semaphore                 并发门槛
        ├── transaction(readonly=True)        只读事务
        ├── SET LOCAL statement_timeout       数据库层超时
        └── LIMIT max_rows+1                  截断检测

核心设计点:ValidatedSQL 是值类型(@dataclass(frozen=True)),执行器端口只接受它, 未经校验的字符串在接口层被物理隔离,不靠运行时判断。

实际通过的正反例(tests/test_sql_safety.py,18 passed):

  • 正例:SELECT COUNT(*) FROM fact_order、CTE 只读、EXISTS 子查询、多表 JOIN
  • 反例:DELETE、注释包裹的 UPDATE、多语句 + DROP、SELECT INTO、写 CTE、COPY、跨 schema 表、不存在字段、pg_sleep
  • 管道集成:test_invalid_sql_never_reaches_executor 用 Mock 断言校验失败时 executor.execute 调用次数为 0

V1-S04 跟读:用户端怎样展示结果 ​

  1. apps/web/app/page.tsx 是 "use client" 单页,覆盖 7 种状态(提问 / 处理中 / 成功表格 / 空结果 / 澄清 / 拒绝 / 失败)。
  2. formatCell(value, type) 处理类型映射:Decimal 不转 JS Number(避免精度损失);时间戳用 Asia/Shanghai 时区;NULL 显式标注。
  3. ResultSummaryBuilder.build(server/generation/answer.py)只读 columns/rows,不调模型,不可能输出与表格不一致的数字。
  4. 防重复提交:requestSequence 计数器,旧回调序列号不匹配时静默丢弃。
  5. XSS 防护:所有动态内容经 React JSX 渲染(无 dangerouslySetInnerHTML);SQL 在 <pre><code> 中展示。
  6. 空结果固定显示"没有符合当前条件的记录。这不等同于指标数值为零。"

V1-S05 跟读:Trace 与失败归因怎样工作 ​

  1. SafeQueryPipeline.run 向 query_record 写入 stages、execution_ms、referenced_tables、referenced_columns。
  2. server/api/trace.py 提供三个端点:列表(支持 status / failure_category 过滤)、详情、PATCH review(写 failure_category / review_note / reviewed_at)。
  3. server/observability/failure.py 的 suggest_failure_category 给出自动初始标签,管理员在 apps/admin/app/traces/page.tsx 可以覆盖。
  4. server/evaluation/baseline.py 的 select_v1_cases 用 SQLValidator 过滤分母,保证分母与白名单能力匹配;BaselineReport 同时记录样本数、分母和环境,不能省略分母宣称成绩。

实测指标与环境 ​

指标值环境
全仓测试183 passed,1 warningmacOS arm64,Python 3.12.12,pytest 8.4.2
ruff checkAll checks passed—
mypy65 source files,no issues—
apps/web TypeScript 构建✓(512 ms)Next.js 16.3.4,Turbopack
apps/admin TypeScript 构建✓(463 ms)Next.js 16.3.4,Turbopack
alembic currentc814be6d3a92(head)PostgreSQL 17,Docker
人工复核标签(数据库)10 条写入PATCH /api/v1/admin/traces/{id}/review
syntax_valid_rate61.54%(8 / 13)deepseek-v4-flash,v1-sql-generation-001,development
execution_correct_rate30.77%(4 / 13)同上
first_success_rate47.37%(9 / 19)同上,19 题(30 题中 11 题超出白名单被过滤)
latency p50 / p951189 ms / 2927 ms同上
total_tokens85,634(均 4,507 / 题)同上

基线报告全文:artifacts/evaluation/v1-baseline.json(2026-09-09)

失败分布:自动标注 vs 人工复核 ​

评测器自动标注与人工复核存在 3 处差异,体现了自动规则的局限:

题号自动标注人工复核说明
test-009wrong_aggregationwrong_join根因是 Join 扇出,不是聚合逻辑
test-010wrong_aggregationwrong_value模型触发澄清,根因是值映射缺失
test-012wrong_aggregationundetermined模型对明确题要求澄清,无 SQL 可定位

人工复核后最终分布(10 条失败,分母 19 题):

分类数量V2 行动
wrong_metric2注入语义模型注册表
wrong_value2补充字段值说明(refund_status、gross_amount)
undetermined2test-012 待复测;test-030 属 V4 行级权限
wrong_aggregation1few-shot 示例补充
wrong_join1few-shot 多表 Join 示例
wrong_time1注入业务时钟约束(business_clock=2026-06-30)
wrong_column1Prompt 示例强化字段存在性

延伸阅读 ​

需求 → 代码 → 验证 → 复盘