221 lines
9.8 KiB
Markdown
221 lines
9.8 KiB
Markdown
# 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`
|