# Phase 2A+2B（条款图）稳定性优先修订意见

> 文档性质：独立评审与修订意见文档。
>
> 目标：在不破坏 Phase 0 / Phase 1 既有能力、不拖慢基础查询链路的前提下，分阶段、安全地引入 Article 条款图能力。

---

## 1. 结论摘要

不建议直接按原始 Phase 2A+2B 实施清单落地。

原清单的大方向基本正确，但如果不先修正运行时边界、降级策略和查询隔离，Phase 2 的引入很容易带来以下问题：

- 污染现有 Phase 0 / 1 主抽取 prompt，导致已有抽取结果变脏
- 打开条款节点后，通用图谱查询返回量激增，影响 explorer 可用性和性能
- 条款语义失败时拖垮整篇文档图构建，影响基础入库稳定性
- Article 搜索和文档实体查询在数据规模放大后出现明显性能回退

建议采用“稳定性优先”的修订方案：

- 代码先合入，默认不开启 `phase_2a` / `phase_2b`
- `phase_2a` 与 `phase_2b` 分两次发布，不同发布窗口启用
- 条款结构切分、条款语义标注、条款查询接口、通用图谱查询隔离四件事必须分开治理
- 任何 Article 相关失败都不得影响现有 Document / Matter 基础链路

---

## 2. 可直接保留的设计

以下设计与当前仓库实现兼容，可以保留：

- `Article.key_property = article_id` 的主键策略可以保留
- `article_id = {doc_id}__art_{order}` 可以保留
- 规则切分 `article_splitter.py` 作为独立模块可以保留
- `article_annotation.py` 作为独立 prompt 模块可以保留
- `HAS_ARTICLE` 单独建边可以保留
- 在 Neo4j 中增加 `article_fulltext` 索引是合理的
- `phase_2b` 依赖 `phase_2a` 的配置约束可以保留

保留原因：当前 Neo4j 写入层已经按 schema 中的 `key_property` 自动匹配节点，不是写死 `name`，因此 Article 走 `article_id` 不会与现有 merge 机制冲突。

---

## 3. 当前实现约束

在修订实施清单前，需要先承认当前代码有以下真实约束：

### 3.1 Schema 开关不是纯配置问题

- `get_schema()` 是有缓存的
- 仅修改 `graph_schema.yaml` 不会自动让正在运行的进程即时切换 phase
- `active_phases` 调整后，必须重启服务或显式 reload schema 后才会生效

结论：启用 `phase_2a` / `phase_2b` 必须作为单独发布动作，不应和功能代码开发混在同一个验证窗口里。

### 3.2 主抽取 prompt 当前会自动读取所有活跃类型

- `graph_builder.py` 的主抽取 system prompt 来自当前活跃 schema
- 一旦直接启用 `phase_2a` / `phase_2b`，主 LLM 抽取会看到 `Article`、`HAS_ARTICLE`、`SETS_CONDITION` 等 Phase 2 类型

结论：如果不加过滤，原有 Phase 0 / 1 抽取链路会被条款图污染，原清单中“Phase 2 独立追加，不修改现有主抽取流程”的目标实际上达不到。

### 3.3 主抽取正文存在 12K 截断

- 当前主抽取只发送正文前 12000 字符
- 因此长文后半段的条件、材料、时限本来就可能漏抽

结论：条款图如果完全依赖“关联已有实体”，会丢掉长文后半段条款语义覆盖率；但如果在首版就允许条款阶段自动补建大量新实体，又会显著增加不稳定性。这个取舍必须在实施清单里明确，而不能模糊处理。

### 3.4 通用图谱查询会自动把 Article 当普通实体处理

- 文档实体接口会返回所有非 Document 邻居
- graph overview 也会展开所有直接邻居
- 如果一篇文档下面挂几十个 Article，通用图谱页面会明显膨胀

结论：Article 不能直接无差别并入通用 explorer 返回面，必须做默认隔离。

---

## 4. 必须修订项

### 4.1 Phase 2A 与 Phase 2B 必须真正拆开，而不是只拆配置名

原清单最大的结构性问题，是把 `phase_2a` 与 `phase_2b` 的运行时行为仍然耦合在一起。

修订要求：

- `phase_2a` 单独启用时，必须能独立产出：
  - Article 节点
  - `HAS_ARTICLE`
  - `norm_type`
  - Article → Matter 的 `GOVERNS`
  - Article → Organization 的 `ASSIGNS_TO`
- `phase_2b` 单独职责仅限新增：
  - `SETS_CONDITION`
  - `SETS_TIME_LIMIT`
  - Article → Material 的 `REQUIRES_MATERIAL`
- 不允许出现“启用 2A 后只有 Article 结构，没有 2A 语义”的半成品状态

建议实现方式：

- 将条款语义补标拆成两个 gate：
  - `phase_2a` gate：输出 `norm_type`、`GOVERNS`、`ASSIGNS_TO`
  - `phase_2b` gate：输出 `SETS_CONDITION`、`SETS_TIME_LIMIT`、`REQUIRES_MATERIAL`
- 可以是两个私有方法，也可以是一个方法内部按 phase 拆分，但发布行为必须可分离

### 4.2 主抽取与条款抽取必须隔离

这是稳定性最关键的一条。

修订要求：

- 主抽取 prompt 继续服务 Phase 0 / 1，不直接承担 Article 抽取职责
- `Article` 相关类型不能因为 phase 激活就自动进入主抽取 prompt
- 条款图统一走独立的 splitter + annotation 链路
- 不修改当前主抽取 JSON 契约

建议实现方式：

- 在 `graph_builder.py` 中，为主抽取 prompt 增加显式过滤逻辑
- 过滤掉 `extraction_mode = hybrid` 的 `Article`
- 过滤掉以 `Article` 为 source 或 target 的 Phase 2 关系
- Article 能力只在 `_annotate_articles_*()` 中出现，不进入主抽取 `chat_json()` 的上下文

这样做的收益：

- Phase 0 / 1 抽取结果不会因启用条款图而回归
- 现有单元测试和集成测试的基线更稳定
- Phase 2 可以在不动主抽取契约的前提下逐步演进

### 4.3 Phase 2 必须是增强链路，不能反向影响基础入库

修订要求：

- `split_articles()` 失败时：跳过 Article，不影响 Document / Matter 写入
- `phase_2a` 标注失败时：至少保留 Article 结构节点和 `HAS_ARTICLE`
- `phase_2b` 标注失败时：不得回滚 `phase_2a` 已成功的数据
- 不允许因为条款阶段异常导致整篇 `build_graph()` 失败

建议实现方式：

- GraphBuilder 中把 2A / 2B 视为“附加步骤”
- 每一步都独立 try / except，并打结构化日志
- `build_graph()` 的主成功条件仍以 Document + 基础实体关系成功写入为准

最低要求：

- 条款图失败时，返回结果中要有明确指标，便于监控
- 但不能让已有文档查询、事项查询、治理链查询因此不可用

### 4.4 查询面必须隔离，不能默认把 Article 暴露给通用 explorer

修订要求：

- 现有 `/graph/overview` 默认不展开 Article
- 现有 `/graph/document/{doc_id}/entities` 默认不返回 Article，或增加显式参数后才返回
- 条款浏览必须走专门接口，而不是依赖通用实体接口硬拉

建议新增和调整：

- 新增：`GET /api/v1/graph/document/{doc_id}/articles`
- 新增：`GET /api/v1/graph/articles/{article_id}`
- 新增：`GET /api/v1/graph/articles?query=...`
- 扩展 `get_matter_card()` 时增加 `governing_articles`，但数量要设上限，且默认按 `doc_id + order` 稳定排序

不建议：

- 在首版就让 Article 自动出现在 graph explorer 概览节点中
- 在文档详情实体列表里无上限返回全部条款

### 4.5 性能护栏必须写进实施清单，而不是靠后续观察

条款图对在线查询和离线构建的影响不一样，必须分别约束。

#### 在线查询链路

要求：

- 现有治理链、事项卡、图谱概览、实体搜索接口的 P95 不应因 Article 引入发生明显回退
- 对用户可见的现有接口，不允许因为 Article 节点暴增而显著增大返回体

建议：

- 通用接口默认排除 Article
- 条款专属接口只返回摘要字段，不在列表接口中返回 `content` 全文
- Article 搜索优先考虑全文索引路径，而不是首版直接用 `CONTAINS title/content`

说明：

- Matter 数量通常远小于 Article 数量
- Matter 搜索可暂时忍受 `CONTAINS`
- Article 搜索如果也直接走 `CONTAINS`，后续规模增长后很容易成为瓶颈

#### 离线构建链路

要求：

- 允许图构建耗时增加，但必须可观测、可限流、可降级
- 不能因为 2B 新增一次 LLM 调用就失去控制

建议：

- 条款语义标注按批处理，不要把整份超长条款列表一次塞进模型
- 每条只传：`article_number`、`title`、`content` 前 200 字符
- 单批建议 20 到 30 条 Article
- 对超大文档设置保护阈值

建议的首版保护策略：

- 若文档条款数超过阈值，只执行 2A 结构写入，跳过 2B 细粒度语义
- 记录 `article_annotation_skipped_too_many_articles` 日志与指标
- 后续再针对超大文档做专项优化

### 4.6 首版 2B 建议范围收缩，优先稳定而不是一次做满

从 PRD 完整能力看，Phase 2B 理想状态是条款级条件、材料、时限都能完整回链。

但从稳定性角度，首版 2B 不建议一步到位做“条款补建新实体 + 复杂归一化 + 全量回链”。

建议首版 2B 稳定范围：

- 优先连接已存在的 Condition / Material / TimeLimit
- 只在高置信度、结构明确时建立新关系
- 暂不在首版自动补建大量新的 Condition / Material / TimeLimit 节点
- 若后续要补建新实体，应放在独立增强版本，并加独立 feature flag

这样做的代价：

- 覆盖率会低于 PRD 的终局目标

这样做的收益：

- 不会立刻放大“长文后半段漏抽 + 条款阶段误建实体 + 全局 name merge 污染”三类风险

建议在文档中明确写明：

- 这是“稳定性优先版 2B”
- 不是 PRD 终局能力的最终上限

### 4.7 证据回链字段必须进入数据设计和查询设计

修订要求：

- 所有由条款阶段产生的新关系，至少要支持以下属性中的一部分：
  - `source_doc_id`
  - `source_article_id`
  - `evidence_text`
  - `confidence`
- 如果当前拿不到 chunk 级映射，可以先不强制 `source_chunk_id`
- 但至少不能让条款语义关系变成“只有边，没有证据”

原因：

- PRD 的核心价值之一就是“条款证据可回链”
- 现有 Neo4j 关系写入层已经支持关系属性，不应浪费这个能力

首版建议：

- `source_doc_id` 必填
- `source_article_id` 必填
- `evidence_text` 取条款摘要或命中的短证据片段
- `confidence` 允许为空，但字段预留

### 4.8 运维发布必须拆成“代码发布”和“phase 开启”两个动作

修订要求：

- 不要把代码变更和 phase 开启放在同一次上线里
- phase 开启必须伴随全量重建和单独验证窗口

建议发布顺序：

1. 发布代码，保持 `phase_2a` / `phase_2b` 关闭
2. 在测试环境开启 `phase_2a`，全量重建，验证
3. 单独观察一轮构建耗时、节点数、API 回归
4. 再开启 `phase_2b`，重复验证
5. 生产环境也按同样顺序分两次启用

---

## 5. 修订后的实施顺序

建议把原计划拆成以下稳定性优先顺序：

### Release A：代码预埋，不启用条款图

- 新增 `article_splitter.py`
- 新增 `article_annotation.py`
- GraphBuilder 增加 Article 分支代码
- GraphQueryService 增加 Article 查询方法
- Graph API 增加 Article 专属接口
- 现有通用接口增加 Article 默认隔离
- 保持 `graph_schema.yaml` 中 `phase_2a` / `phase_2b` 仍为关闭状态

验收目标：

- 现有测试全绿
- 现有接口行为不变
- 不引入基础链路性能回退

### Release B：仅启用 Phase 2A

- 打开 `phase_2a`
- 执行全量重建
- 只验证 Article 结构图 + 2A 语义

验收目标：

- Article 切分正确
- `HAS_ARTICLE` 正确
- `norm_type` 有合理准确率
- `GOVERNS` / `ASSIGNS_TO` 不污染现有 Matter / Organization 查询
- 通用 graph explorer 不因 Article 膨胀

### Release C：启用稳定性优先版 Phase 2B

- 打开 `phase_2b`
- 仅增加细粒度关系
- 仍保持降级优先

验收目标：

- 2B 失败不影响 2A 和基础图
- Article → Condition / Material / TimeLimit 关系可查询
- 在线接口无显著性能回退

### Release D：增强版 2B（可后置）

- 如果业务确认需要更高覆盖率，再评估补建新 Condition / Material / TimeLimit 节点
- 该能力建议独立评审、独立开关、独立验收

---

## 6. 文件级修订建议

### backend/app/config/graph_schema.yaml

- 可增加 `phase_2a` / `phase_2b`
- 但启用动作必须后置到独立发布窗口
- 不建议在开发阶段默认打开

### backend/app/core/graph_builder.py

- 必须增加主抽取 prompt 过滤逻辑，隔离 Article
- 必须把 2A / 2B 分成独立 gate
- 必须保证条款失败不影响主图构建成功
- 必须增加条款阶段的批处理与超量保护

### backend/app/prompts/entity_extraction.py

- 不应因为 schema 激活而自动把 Article 暴露给主抽取
- 需要支持“主抽取只看 Phase 0 / 1”的稳定模式

### backend/app/prompts/article_annotation.py

- 新建，负责条款独立语义标注
- 输入必须小而稳定，避免拖慢构建

### backend/app/core/article_splitter.py

- 新建，纯规则、纯确定性
- 不依赖 LLM，不抛出影响主链路的异常

### backend/app/infrastructure/neo4j_client.py

- 增加 `article_fulltext` 索引
- 保留关系属性写入能力
- 不需要为 Article 重写底层 merge 主键机制

### backend/app/core/graph_query_service.py

- 新增 Article 查询方法
- 修改 `get_matter_card()`，追加 `governing_articles`
- 修改通用 explorer 查询，默认隔离 Article

### backend/app/api/v1/graph.py

- 新增 Article 专属接口
- MatterCardResponse 增加 `governing_articles`
- 不建议在通用接口里默认暴露 Article 全量数据

### backend/tests/

- 新增 `test_article_splitter_unit.py`
- 新增 `test_phase2_unit.py`
- 更新 `test_api_graph.py`
- 必须补充“已有接口不受 Article 影响”的回归测试

---

## 7. 必须新增的回归与性能验收

### 7.1 功能回归

以下现有能力必须在引入 Phase 2 后保持稳定：

- Phase 0 治理链查询
- Phase 1 事项搜索与事项知识卡
- 文档实体查询
- graph overview
- entity search
- related docs

最低要求：

- 现有测试基线全部通过
- 现有接口返回结构不因 Article 默认暴露而变化

### 7.2 构建稳定性

必须验证：

- 无“第X条”的文档不生成错误 Article
- 条款切分失败时，文档仍能完成基础图构建
- 2A 标注失败时，Article 结构仍能落库
- 2B 标注失败时，2A 数据仍可查询

### 7.3 查询性能

必须观察：

- `/graph/overview`
- `/graph/document/{doc_id}/entities`
- `/graph/matters`
- `/graph/matters/{matter_id}`
- 新增 `/graph/articles`

建议验收标准：

- 现有在线接口 P95 不出现明显回退
- 新增 Article 接口在合理 limit 下响应稳定
- 通用图谱接口返回体大小可控

### 7.4 数据规模监控

必须新增监控项：

- 每篇文档平均 Article 数
- 2A 标注成功率
- 2B 标注成功率
- 因超量保护而跳过 2B 的文档数
- Article 相关新关系数
- 条款阶段平均耗时

---

## 8. 明确不建议的做法

- 不建议在同一个 MR 或同一次上线里同时开启 `phase_2a` 和 `phase_2b`
- 不建议让主抽取 prompt 自动感知 Article 类型
- 不建议让 Article 失败导致整篇 `build_graph()` 失败
- 不建议在首版就把 Article 直接暴露到通用 graph explorer
- 不建议首版 Article 搜索直接全量走 `CONTAINS title/content`
- 不建议首版 2B 同时做“细粒度关系 + 大量新实体补建 + 复杂回链”

---

## 9. 推荐采纳口径

如果目标是“功能稳定、可用、不影响基础功能、尽量不拖慢性能”，建议以本修订意见作为 Phase 2 的正式实施约束。

可以接受的范围是：

- 首版功能略保守
- 但必须保证现有主链路稳定

不建议接受的范围是：

- 条款图一上来做满全部能力
- 但没有隔离、降级、性能护栏与回归验证

本质上，Phase 2 应被视为“增强能力”，不是“允许牺牲现有稳定性来换取新能力”的改造项。
