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

8.4 KiB
Raw Blame History

jaspersoft 五轮修正失败问题 — 现象文档

1. 问题概述

触发场景:用户上传一张车历卡(维修结算单)图片,系统通过 OCR 识别并生成 JRXML 报表模板。

现象:5 次自动修正循环全部失败,系统提示"[内容保真度不足] 得分 0.00/1.0",最终无法生成可用 JRXML。

测试会话sessions/6d39a91e11c54f02bb70a62d856ea2d4.json2026-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_jrxml24391 字符,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 6d39a91ecurrent_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 实际运行结果:

# 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):

score = round(field_coverage * 0.5 + element_coverage * 0.5, 3)

降级条件nodes.py:1294):

if fidelity["score"] < 0.5:
    state["status"] = "fail"

4. 已确认的根因

根因 1 — 评分逻辑设计错误(P0

问题field_coverage 将英文字段名和中文字段名做交集比对,在普通生成场景下永远为 0。

原因

  1. LLM 生成英文字段名(print_datevehicle_plate)是正确的设计选择
  2. OCR 提取器硬套发票模板,提取中文字段名(发票代码合计金额
  3. 两者来自完全不同的命名体系,不可能匹配
  4. field_coverage=0预期行为,而非错误

修复方向:评分公式改为只依赖 element_coveragefield_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_jrxmlecd592)仍有 <ns0:jasperReport>

修复方向

  • prompts/correction.md:改为无条件指令(检查 JRXML 是否包含 ns0:,而非依赖错误类型)
  • prompts/initial_generation.md + skeleton_generation.md:添加删除 ns0: 前缀的无条件指令

根因 4 — 正则 \w+ 不支持中文(低优先级)

位置nodes.py:1211

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_jrxml24391 字符)是最终状态(修正耗尽后最后一次保存的版本),而触发 5 次 fail 降级的是 jrxml_versions 中各版本的 JRXML——这些版本在修正循环中被逐步侵蚀,最终版本 jrxml_versions[-1] 只有 0 个文本元素(score=0.00)。

fidelity-check-audit 用 Python 分析的是最终保存的 current_jrxmlscore=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 问题参考