PMFlow/docs/superpowers/specs/2026-04-15-internal-query-d...

221 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Internal Query DSL Plan Design
## Goal
为 `query.answer` 的内部只读查询引入一条受控、可审计、可校验的查询计划链,让大模型只负责“理解问题并产出结构化计划”,数据库与执行器只负责“按白名单模板取证”,最后再由 verifier 检查“回答是否真的对上了用户的问题”。
## Why Now
当前链路已经能先判 `internal / external / uncertain`,但一旦进入内部问答,`rag_query_semantics` 对问句类型的表达仍然过粗,且执行层缺少“问法合同”和“结果校验”这两道边界。
直接表现就是:
- “有没有 A 股项目”会被压扁成 `count + project`
- 执行层随后直接进入“项目总数”分支
- 最终返回一个真实但答非所问的数字
这不是单点 existence 漏洞,而是内部查询缺少统一查询计划与回答合同的系统性问题。
## Confirmed Baseline
- 方案 1 是内部查询的第一道必经步骤。
- `route_mode=internal` 的 non-plan `query.answer` 在真正查库前,必须先生成 `Query DSL Plan`,并在计划定稿后组装 `InternalContextPacket` 作为 internal-only 运行时单一真源。
- 大模型禁止自由写 SQL;只能输出受控 DSL。
- 受控执行器只能命中白名单实体、白名单字段映射、白名单查询模板。
- 协议短回复、模式控制、执行动作请求不得进入 DSL 规划;必须在 ingress / followup gate 提前拦住。
- 执行结果必须经过 answer verifier;如果 verifier 发现“问的是 existence,答的是 total count”这类合同漂移,必须显式失败或重试,不得直接出站。
## Scope
### In Scope
- `query.answer` 的内部只读查询路径
- `rag_query_semantics` 的问句类型收口
- `Query DSL Plan` 结构定义
- 受控执行器与回答合同
- verifier 校验与审计字段
- 与 `Query Route Gateway`、后续 `semantic_facts` 读模型的衔接
### Out Of Scope
- `plan mode`、requirement-plan、draft confirm 主链
- 外部实时检索链
- 自由 SQL agent、多轮自反思 text-to-SQL
- 内部写操作或自动入库
## Query Taxonomy Baseline
内部只读查询的 `query_type` 基线统一为:
| query_type | 用户真实意图 | 典型问法 | 默认回答合同 |
| --- | --- | --- | --- |
| `existence` | 某对象/语义是否存在 | 有没有 A 股项目 | `yes_no_with_evidence` |
| `count` | 数量、总数、人数 | 现在有多少个项目 | `count_scalar` |
| `list` | 枚举、有哪些、分别是什么 | 系统里有哪些项目 | `entity_list` |
| `status` | 状态、属性、配置值、快照 | 项目 64 现在什么状态 | `entity_snapshot` |
| `explain` | 原理、用途、调用链、定义 | 当前系统架构是什么 | `explanation_with_citations` |
| `diagnose` | 异常原因、根因、证据链 | 为什么刚才答错了 | `diagnosis_with_evidence` |
| `compare` | 差异、对比、谁更适合 | 现在接的和最新模型差别 | `comparison_with_basis` |
| `unknown` | 无法可靠归类 | 低信息量或语义不足 | `clarify_or_fail` |
下面这些不属于 `query_type`,必须在进入 `rag_query_semantics` 之前处理掉:
- 协议回复:`A/B/C`、`确认`、`继续`
- 模式控制:`退出需求模式`
- 执行动作:`接入外部来源继续核实`
- 草稿/需求收敛动作:`再出一版草稿`
## Query DSL Plan Contract
`Query DSL Plan` 建议统一为单一结构化对象:
| 字段 | 说明 |
| --- | --- |
| `plan_version` | DSL 版本号,便于平滑演进 |
| `query_type` | 上表中的标准问句类型 |
| `entity_type` | `project/task/signal/user/doc/config/run/...` |
| `answer_contract` | 最终回答必须满足的合同 |
| `filters[]` | 结构化过滤条件,支持 `field/operator/value/value_source` |
| `scope` | `tenant_id/project_id/thread_anchor/referenced_objects` 等上下文限定 |
| `projection` | 回答所需字段或摘要目标 |
| `sort` | 列表/比较类排序意图 |
| `limit` | 列表上限 |
| `retrieval_policy` | `facts_first / allow_fallback_text / require_fresh_fact` 等 |
| `verifier_checks[]` | 出站前必须通过的校验项 |
| `clarify_on_empty` | 命中为空时是澄清还是直接回“未找到” |
示例:用户问“帮我看看系统里面有没有 A 股项目。”
```json
{
"plan_version": "v1",
"query_type": "existence",
"entity_type": "project",
"answer_contract": "yes_no_with_evidence",
"filters": [
{
"field": "market_theme",
"operator": "equals_or_alias",
"value": "A股",
"value_source": "user_text"
}
],
"scope": {
"tenant_bound": true
},
"projection": ["project_id", "title", "matched_evidence"],
"retrieval_policy": {
"facts_first": true,
"allow_fallback_text": true
},
"verifier_checks": [
"contract_matches_query_type",
"evidence_backed",
"no_total_count_substitution"
],
"clarify_on_empty": false
}
```
## Mandatory Execution Flow
`route_mode=internal` 后的内部问答链路统一为:
1. `Query Route Gateway` 决定本轮必须走内部查询。
2. `rag_query_semantics` 只产出标准 `query_type`、`entity_type` 与 DSL 所需槽位,不再直接决定最终回答。
3. `Query DSL Normalizer` 做 schema 校验、枚举收口、上下文补足与黑名单清洗。
4. `InternalContextPacket` 固化 `question / scope / route_decision / operation_contract / target_resolution / query_plan`,并作为 internal executor / verifier / runtime summary 的单一真源;`query_semantics` 只保留上游 scratchpad 与兼容镜像。
5. mainline 必须先对 packet 做预检;若 packet 缺失、`operation_contract.query_type` 缺失、`target_resolution.resolution_mode=clarify` 或 `query_plan.answer_contract` 缺失,直接返回 `clarify / explicit_insufficient_evidence`,不允许继续执行。
6. `Bounded Executor` 根据 packet 内的 `entity_type + query_type + filters` 选择白名单模板执行。
7. 若 `semantic_facts` 可用,则先走 facts-first;证据不足时再走受控 fallback 检索。
8. `Answer Verifier` 校验“问法合同、证据合同、来源合同”是否一致。
9. verifier 通过后再进入回答渲染;不通过则显式失败或重试,不允许静默出错。
## Bounded Executor Rules
执行器必须遵守以下硬边界:
- 不接收自由 SQL 字符串
- 不允许 LLM 决定 join 路径、表名、排序字段白名单之外的内容
- 只允许已声明的实体模板,如 `project existence/count/list/status`
- 所有执行都必须自动绑定 `tenant_id`
- facts-first 命中时,回答证据必须能回指到具体 fact 或原始证据对象
- fallback 文本检索只能在 DSL 明确允许时触发
## Answer Verifier Contract
verifier 至少检查:
- `query_type` 与 `answer_contract` 是否一致
- 回答正文是否覆盖了用户真正问的对象和条件
- 是否误把 existence / status / compare 问题答成 total count
- 回答所引用的证据是否真实命中本次执行结果
- `reply_contract` 是否与 `route_mode=internal` 一致
- verifier 的合同真源必须来自 `InternalContextPacket.operation_contract.query_type` 与 `InternalContextPacket.query_plan.answer_contract`,禁止根据 answer 文本或散落的 `query_semantics` 字段倒推
verifier 不通过时,统一返回三类结果之一:
- `retry_with_same_plan`
- `retry_with_clarified_plan`
- `explicit_insufficient_evidence`
## Failure Semantics
- DSL 结构化失败:显式失败,不回退自由回答
- `InternalContextPacket` 缺失:直接 `clarify / explicit_insufficient_evidence`
- `operation_contract.query_type` 缺失:直接 `clarify / explicit_insufficient_evidence`
- `target_resolution.resolution_mode=clarify`:直接澄清,不回退自由回答
- `query_plan.answer_contract` 缺失:直接 `explicit_insufficient_evidence`
- 执行器无模板支持:显式返回“当前内部查询模板未覆盖”
- 命中为空但证据不足:按 `clarify_on_empty` 澄清或返回“当前未确认到”
- verifier 判定合同漂移:拒绝出站并写审计日志
## Rollout
### Phase A
先把本设计写入架构基线,不动运行时代码。
### Phase B
实现 `Query DSL Plan + Bounded Executor + Answer Verifier`,先止住“答非所问”的问题。
### Phase C
接入 `semantic_facts` 读模型后,内部查询默认走 facts-first,再做受控 fallback。
## 计划改动文件清单(基线确认后)
### 方案 1 运行时主链
- `backend/app/config/ai_prompts.py`
- `backend/app/services/adaptive_rag_service/shared_part_02.py`
- `backend/app/services/adaptive_rag_service/mixin_core_intent.py`
- `backend/app/services/adaptive_rag_service/mixin_query_route_gateway.py`
- `backend/app/services/adaptive_rag_service/mixin_answer_mainline.py`
- `backend/app/services/adaptive_rag_service/internal_query_context.py`
- `backend/app/services/adaptive_rag_service/mixin_answer_payload.py`
- `backend/app/services/adaptive_rag_service/mixin_deterministic_query_core.py`
- `backend/app/services/adaptive_rag_service/mixin_deterministic_query_count.py`
- `backend/app/services/adaptive_rag_service/mixin_deterministic_query_list.py`
- `backend/app/services/adaptive_rag_service/mixin_fast_answer_project_main.py`
- `backend/app/services/adaptive_rag_service/mixin_retrieval_router_db.py`
- `backend/app/services/adaptive_rag_service/service.py`
### 方案 1 回归测试
- `backend/tests/test_adaptive_rag_mainline_contract.py`
- `backend/tests/test_ai_pmo_ingress_mainline.py`
- `backend/tests/test_rag_route_contract.py`
- `backend/tests/test_runtime_structured_semantics_contract.py`
### 本轮文档基线
- `AGENTS.md`
- `CLAUDE.md`
- `docs/architecture/RUNTIME_GUARDRAILS.md`
- `docs/architecture/DATAFLOW.md`
- `docs/architecture/ARCHITECTURE_INVENTORY.md`
- `docs/superpowers/specs/2026-04-15-internal-query-dsl-plan-design.md`
- `docs/superpowers/specs/2026-04-15-semantic-facts-read-model-design.md`