WIP: baseline on fix/retry-failure-root-causes

This commit is contained in:
2026-05-24 22:38:30 +08:00
parent 2d5183d2bd
commit f25a93b539
5 changed files with 438 additions and 35 deletions
+207
View File
@@ -0,0 +1,207 @@
# jaspersoft 五轮修正失败问题 — 现象文档
## 1. 问题概述
**触发场景**:用户上传一张车历卡(维修结算单)图片,系统通过 OCR 识别并生成 JRXML 报表模板。
**现象**:5 次自动修正循环全部失败,系统提示"[内容保真度不足] 得分 0.00/1.0",最终无法生成可用 JRXML。
**测试会话**`sessions/6d39a91e11c54f02bb70a62d856ea2d4.json`2026-05-24 15:14
---
## 2. 完整流程追踪
### 2.1 用户输入
- 上传文件:`e1113725c20fc4ec39bc9e4ab0caa6b2.jpg`(车历卡,1357×1920 RGB
- 用户输入:空(仅上传文件)
### 2.2 系统处理流程
```
上传图片
→ process_input 节点(OCR 字段提取 + 布局分析)
→ layout_analyzer34 行 × 1 列,A4 纵向)
→ ocr_extractor4 策略提取)
→ classify_intent= initial_generation
→ retrieve
→ route_after_retrieve(有 layout_schema,走 generate_skeleton
→ generate_skeleton(生成 ~34k 字符骨架 JRXML
→ refine_layoutBand 级窗口化精调)
→ map_fields(程序化字段替换 $F{field_N} → 真实字段名)
→ validatevalidate 节点)
→ XSD 验证:✅ 通过
→ OCR 保真度检查:❌ score=0.41 < 0.5 → 降级为 fail
→ error_msg = "[内容保真度不足] 得分 0.41/1.0。元素覆盖不足:JRXML 仅有 142 个文本元素,OCR 源有 173 个文本元素(覆盖率 82%)。JRXML 中未声明任何字段,但 OCR 提取了结构化字段数据"
→ 5 次 correct_jrxml 修正循环均失败
→ 状态:failMAX_RETRY=5 耗尽)
```
### 2.3 最终状态
```
status: fail
retry_count: 5
error_msg: "[内容保真度不足] 得分 0.00/1.0。JRXML 仅有 0 个文本元素,OCR 源有 173 个文本元素(覆盖率 0%)"
```
⚠️ **重要矛盾**`current_jrxml`24391 字符,142 个 textField)与 `error_msg`"0 个文本元素"score=0.00)存在矛盾。
验证服务审计(`validate-service-audit`)指出:"6d39a91e has 0 text elements causing score=0.00"——说明在**触发 fail 的那个时间点**,JRXML 确实只有 0 个文本元素。
但 session 文件最终保存的 `current_jrxml` 是 24391 字符版本。`jrxml_versions` 最后一条记录的 `jrxml` 才是触发失败的真实版本。
**结论**`current_jrxml` 在 5 次修正过程中被逐步侵蚀,最终版本是某个接近空壳的状态。`jrxml_versions[-1]` 中的 `jrxml` 才是真正的失败版本。
---
## 3. 关键数据对比
### 3.1 JRXML 实际内容
session `6d39a91e``current_jrxml`
- 长度:24391 字符
- `<textField` 标签数:284(含自闭合标签)
- 完整 textField 元素:142 个
- `<staticText>` 元素:0 个
- `<field>` 声明:63 个
- pageWidth/pageHeight595×842A4
- namespace:无 ns0: 前缀(✅ 已消除)
### 3.2 OCR 数据
layout_schema
- total_rows: 34
- total_columns: 1
- 总文本元素:173 个
ocr_extraction_result
- total_elements: 173
- fields 数组:18 个字段,全部是**发票模板字段**
- 不含税金额、价税合计、单价、发票代码、发票号码、合计金额、开票日期、总金额、数量、日期、校验码、税率、税额、规格型号、货物名称、购买方名称、金额、销售方名称
- 正确匹配率:3/1817%
### 3.3 评分数据
`_check_ocr_fidelity` 实际运行结果:
```python
# element_coverage 计算
textField_count = 142 # 完整元素(非标签数)
staticText_count = 0
total_jrxml_elements = 142
ocr_text_count = 173
element_coverage = min(142/173, 1.0) = 0.82
# field_coverage 计算
jrxml_fields = {"print_date", "repair_number", ..., "vehicle_plate", ...} # 63 个英文字段
ocr_field_names = {"发票代码", "发票号码", "合计金额", ...} # 18 个中文字段
matched = jrxml_fields ocr_field_names =
field_coverage = 0/18 = 0.0
# score 计算
score = 0.0 * 0.5 + 0.82 * 0.5 = 0.41
```
**评分公式**`nodes.py:1251`):
```python
score = round(field_coverage * 0.5 + element_coverage * 0.5, 3)
```
**降级条件**`nodes.py:1294`):
```python
if fidelity["score"] < 0.5:
state["status"] = "fail"
```
---
## 4. 已确认的根因
### 根因 1 — 评分逻辑设计错误(P0)
**问题**`field_coverage` 将英文字段名和中文字段名做交集比对,在普通生成场景下永远为 0。
**原因**
1. LLM 生成英文字段名(`print_date``vehicle_plate`)是正确的设计选择
2. OCR 提取器硬套发票模板,提取中文字段名(`发票代码``合计金额`
3. 两者来自完全不同的命名体系,不可能匹配
4. `field_coverage=0` 是**预期行为**,而非错误
**修复方向**:评分公式改为只依赖 `element_coverage``field_coverage` 作为信息提示而非降级条件。
### 根因 2 — OCR 字段提取器无文档类型区分(P0)
**问题**`backend/ocr_extractor.py` 对所有单据使用同一套发票字段模板(14 个字段)。
**现象**
- 车历卡被当作发票处理
- 手机号 `13516727312` 被 6 个字段复用(发票代码/校验码/价税合计/单价/税率/不含税金额)
- 字段名错配:发票号码→"服务套餐"、总金额→"钣金"、数量→"零件等级"
### 根因 3 — namespace 修复指令是条件触发(P1)
**位置**`prompts/correction.md` 第 11 行
**问题**:namespace 修复指令只在错误消息**包含 "namespace" 关键词**时才激活,是条件触发而非无条件指令。
**现象**
- ecd592 session 所有 5 次修正的实际错误类型是"字段未声明"(`字段 'u53d1_u7968...' 在表达式中使用但未声明`),**不包含 "namespace" 关键词**
- 因此 namespace 修复指令从未被激活
- ns0: 前缀在前两次修正中持续存在,直到第 3 次才被 LLM 自发消除
- 最终 `current_jrxml`ecd592)仍有 `<ns0:jasperReport>`
**修复方向**
- `prompts/correction.md`:改为无条件指令(检查 JRXML 是否包含 `ns0:`,而非依赖错误类型)
- `prompts/initial_generation.md` + `skeleton_generation.md`:添加删除 ns0: 前缀的无条件指令
### 根因 4 — 正则 `\w+` 不支持中文(低优先级)
**位置**`nodes.py:1211`
```python
jrxml_fields = set(re.findall(r'<field name="(\w+)"', jrxml))
```
**问题**`\w` 匹配 `[a-zA-Z0-9_]`,不匹配中文。如果 JRXML 使用中文字段名,正则返回 0 个匹配。
---
## 5. 验证服务的角色
**文件**`validation_service/main.py`
- XSD 验证:通过(✅)
- 结构检查:字段声明一致性、SQL SELECT 存在性、pageWidth/pageHeight
- **结论**XSD 验证通过,`correct_jrxml` 的 ns0: 消除也生效——真正导致 fail 的是 OCR 保真度评分
---
## 6. 现象描述的矛盾点(需进一步排查)
session `6d39a91e` 中存在数字矛盾:
| 指标 | session 中的值 | 矛盾说明 |
|---|---|---|
| 最终 `current_jrxml` | 24391 字符,142 个 textField | 这是最后一次修正后保存的最终版本 |
| `jrxml_versions[-1].jrxml` | 触发 fail 的真实版本 | 审计团队确认"6d39a91e has 0 text elements causing score=0.00" |
| `error_msg` score | "0.00/1.0" | 对应 `jrxml_versions[-1]`,而非 `current_jrxml` |
**核心矛盾已解决**`current_jrxml`(24391 字符)是**最终状态**(修正耗尽后最后一次保存的版本),而触发 5 次 fail 降级的是 `jrxml_versions` 中各版本的 JRXML——这些版本在修正循环中被逐步侵蚀,最终版本 `jrxml_versions[-1]` 只有 0 个文本元素(score=0.00)。
`fidelity-check-audit` 用 Python 分析的是最终保存的 `current_jrxml`score=0.5),而 `validate-service-audit` 分析的是 `jrxml_versions[-1]`score=0.00)。**两者分析的不是同一个时间点的 JRXML**。
---
## 7. 相关文件清单
| 文件 | 职责 | 备注 |
|---|---|---|
| `agent/nodes.py:1171-1257` | `_check_ocr_fidelity` | 评分逻辑(有 bug |
| `agent/nodes.py:1260-1350` | `validate` 节点 | 调用保真度检查 |
| `agent/nodes.py:1382-1461` | `correct_jrxml` | 修正循环 |
| `backend/ocr_extractor.py` | OCR 字段提取 | 无文档类型区分 |
| `prompts/correction.md` | 修正 prompt | namespace 触发受限 |
| `validation_service/main.py` | 验证服务 | XSD 通过 |
| `sessions/6d39a91e11c54f02bb70a62d856ea2d4.json` | 测试会话 | 主测试数据 |
| `sessions/ecd5921838004ab3bc4a1ef6ebd673d1.json` | 历史会话 | namespace 问题参考 |