# 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`。 - 大模型禁止自由写 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. `Bounded Executor` 根据 `entity_type + query_type + filters` 选择白名单模板执行。 5. 若 `semantic_facts` 可用,则先走 facts-first;证据不足时再走受控 fallback 检索。 6. `Answer Verifier` 校验“问法合同、证据合同、来源合同”是否一致。 7. 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 不通过时,统一返回三类结果之一: - `retry_with_same_plan` - `retry_with_clarified_plan` - `explicit_insufficient_evidence` ## Failure Semantics - DSL 结构化失败:显式失败,不回退自由回答 - 执行器无模板支持:显式返回“当前内部查询模板未覆盖” - 命中为空但证据不足:按 `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/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`