Files
agent_jrxml/docs/bug-report-5-retry-failure.md
T

207 lines
8.4 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.
# 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 问题参考 |