PMFlow/docs/superpowers/specs/2026-04-15-semantic-facts-r...

192 lines
7.6 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.

# Semantic Facts Read Model Design
## Goal
为内部查询建立一层独立的“语义事实读模型”,把项目、信号、文档、配置等原始对象中的非结构化语义抽成可检索、可计数、可解释的事实条目。后续内部查询在 `Query DSL Plan` 之后优先查这层读模型,再决定是否回退到受控文本检索。
## Why This Is Needed
方案 1 能止住“问 existence 却答 total count”这类合同漂移,但它解决不了“数据库根本没有结构化字段”的问题。
像下面这些问法,本质上都需要一个语义层:
- 有没有 A 股项目
- 我们系统里有没有开票系统
- 哪些项目属于视频方向
- 这个项目是不是偏运营工具
如果底层只有 `name/title/description/status`,那执行器再受控,也只能做文本猜测。要真正提准,必须提前把“项目主题、行业标签、别名、用途、对象关系”这类语义抽出来,沉淀成 facts。
## Confirmed Baseline
- 方案 2 是内部查询的第二道必经步骤。
- 当语义事实数据可用时,内部查询必须优先走 `semantic_facts`。
- `semantic_facts` 是只读辅助模型,不是业务写入真源。
- facts 的生成必须异步完成,禁止在用户主链里同步抽取长耗时语义。
- facts 必须带 `confidence`、`evidence`、`extract_version` 和租户边界,避免“抽出来了但说不清依据”。
## Design Decision
这里不做 `project_semantic_facts` 这种单对象专用表,而是直接设计成跨对象通用表 `semantic_facts`。
原因很简单:
- 用户真实问法不只围绕项目
- `signal / doc / config / run` 也会进入内部问答
- 一开始就用通用读模型,后续不会因为对象扩大再重做一轮 schema
## Data Model
### Table 1: `semantic_facts`
建议字段如下:
| 字段 | 说明 |
| --- | --- |
| `id` | 主键 |
| `tenant_id` | 多租户边界 |
| `entity_type` | `project/signal/doc/config/run/...` |
| `entity_id` | 对应对象主键 |
| `fact_namespace` | 事实命名空间,如 `domain/theme/capability/alias/status` |
| `fact_key` | 事实键,如 `market_theme`、`industry`、`aliases` |
| `fact_value_text` | 原始值 |
| `fact_value_norm` | 归一化值,便于精确匹配 |
| `value_type` | `keyword/text/number/date/enum/json` |
| `confidence` | 抽取置信度 |
| `evidence_source_type` | 证据来源对象类型 |
| `evidence_source_id` | 证据来源对象 id |
| `evidence_excerpt` | 短证据摘要 |
| `extract_version` | 抽取器版本 |
| `freshness_at` | 事实对应的源数据时间 |
| `is_active` | 是否有效 |
| `created_at` | 创建时间 |
| `updated_at` | 更新时间 |
### Table 2: `semantic_fact_extraction_runs`
建议补一张轻量 run/audit 表:
| 字段 | 说明 |
| --- | --- |
| `id` | 主键 |
| `tenant_id` | 多租户边界 |
| `entity_type` | 被抽取对象类型 |
| `entity_id` | 被抽取对象 id |
| `trigger_type` | `create/update/backfill/manual_retry` |
| `status` | `queued/running/completed/failed` |
| `input_fingerprint` | 输入快照指纹,用于幂等 |
| `facts_written` | 本次写入事实数 |
| `failure_reason` | 失败原因 |
| `extract_version` | 抽取器版本 |
| `started_at` | 开始时间 |
| `finished_at` | 结束时间 |
## Fact Examples
示例一:项目描述里出现“面向证券投研团队,聚焦 A 股公告解析”。
可能抽出:
- `fact_namespace=theme`
- `fact_key=market_theme`
- `fact_value_text=A股`
- `fact_value_norm=a_share`
示例二:项目标题叫“开票助手”,描述里出现“面向财务运营,自动生成增值税发票草稿”。
可能抽出:
- `fact_key=business_domain`
- `fact_value_norm=invoicing`
- `fact_key=aliases`
- `fact_value_text=开票系统`
## Async Extraction Flow
语义事实抽取链建议统一为:
1. 项目、信号、文档、功能目录、配置快照等源对象完成事务提交。
2. 提交后只入队一个 `semantic fact extraction` 事件,不在主事务里直接跑 LLM。
3. worker 读取事件,加载最小源上下文与证据片段。
4. LLM 只负责结构化抽取事实,不负责最终回答。
5. 服务端做 normalize、去重、merge、失效旧 facts。
6. 抽取结果写入 `semantic_facts`,并把 run 状态写入 `semantic_fact_extraction_runs`。
7. 后续内部查询命中该对象时,优先从 `semantic_facts` 读取。
## Query Read Path
方案 2 与方案 1 的衔接方式是固定的:
1. `Query Route Gateway` 决定本轮走内部查询。
2. `Operation Contract` 先锁定回答合同与对象 mentions。
3. `Target Resolver` 在 project / feature / config / doc 等候选间做受控仲裁。
4. `Query DSL Plan` 再基于已解析目标产出 `query_type/entity_type/filters/answer_contract`。
5. `Bounded Executor` 先查 `semantic_facts`。
6. 若 facts 命中且置信度满足阈值,直接形成 existence/count/list/status 的证据基础。
7. 若 facts 不足,再回退到受控文本检索、图谱检索或配置快照读取。
8. `Answer Verifier` 再检查回答与证据是否一致。
这意味着后续内部查询的“真正默认路径”会变成:
`Operation Contract -> Target Resolver -> DSL Plan -> facts-first -> bounded fallback -> verifier`
## Confidence And Evidence Rules
- `confidence` 只用于排序和阈值判断,不允许把低置信猜测包装成确定性事实
- facts 用于回答时,必须能回指 `evidence_source_type/evidence_source_id/evidence_excerpt`
- 同一个 `entity_type/entity_id/fact_key/fact_value_norm` 要支持幂等 upsert
- 源对象删除、重写或语义失效后,facts 必须支持失效标记,不允许永久悬挂
## Guardrails
- `semantic_facts` 只用于内部只读查询和解释,不作为写操作真源
- 不允许让 LLM 在 facts 缺失时自由脑补
- 不允许把 facts 命中为空解释成“对象不存在”,除非计划明确允许且 fallback 也已执行
- 不允许绕过 `Query DSL Plan` 直接裸查 facts 表
## Backfill Strategy
首轮建议:
1. 对现有 `projects` 做一次租户内 backfill
2. 对与项目强绑定的 `signals / docs / feature catalog` 做增量补抽
3. 在运行稳定后,再扩到 `config / run / integration snapshot`
## 计划改动文件清单(基线确认后)
### 方案 2 数据模型与迁移
- `backend/alembic/versions/<new>_add_semantic_facts.py`
- `backend/app/models/semantic_fact.py`
- `backend/app/models/semantic_fact_extraction_run.py`
- `backend/app/models/__init__.py`
### 方案 2 服务与异步链路
- `backend/app/services/semantic_fact_service/service.py`
- `backend/app/services/semantic_fact_service/mixin_extract.py`
- `backend/app/services/semantic_fact_service/mixin_query.py`
- `backend/app/services/project_knowledge_graph_service/mixin_event_queue.py`
- `backend/app/services/project_knowledge_graph_service/service.py`
- `backend/app/services/adaptive_rag_service/mixin_answer_retrieval.py`
- `backend/app/services/adaptive_rag_service/mixin_retrieval_router_db.py`
- `backend/app/services/adaptive_rag_service/mixin_fast_answer_project_main.py`
### 方案 2 回归测试
- `backend/tests/test_project_knowledge_graph_event_queue_contract.py`
- `backend/tests/test_adaptive_rag_mainline_contract.py`
- `backend/tests/test_ai_pmo_ingress_mainline.py`
- `backend/tests/test_runtime_structured_semantics_contract.py`
- `backend/tests/test_semantic_fact_service.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`