| """InformationExtractor + BackReferenceValidator 测试(任务 6)。 |
| |
| 覆盖 Property 1(无捏造)/ 需求 4.1、4.2、4.5、9.5。 |
| """ |
|
|
| from __future__ import annotations |
|
|
| from kernel.task_sheet import DocumentProfile, Intent |
| from kernel.understanding import BackReferenceValidator, InformationExtractor |
|
|
|
|
| SOURCE = "有关物质检测结果 总杂% 0.00 含量 99.19 限度 总杂≤2.0%" |
|
|
|
|
| def test_extractor_skips_header_label_as_value(): |
| llm_out = { |
| "items": [ |
| {"field": "value", "value": "样品名称", "source_ref": "样品名称"}, |
| {"field": "value", "value": "0.00", "source_ref": "总杂% 0.00", |
| "group": {"attribute": "总杂"}}, |
| ] |
| } |
| items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out) |
| values = [it.value for it in items] |
| assert "样品名称" not in values |
| assert "0.00" in values |
|
|
|
|
| def test_extractor_empty_without_llm_and_no_cache(): |
| items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY) |
| assert items == [] |
|
|
|
|
| def test_validator_drops_unlocatable_items(): |
| llm_out = { |
| "items": [ |
| {"field": "value", "value": "0.00", "source_ref": "总杂% 0.00"}, |
| {"field": "value", "value": "-10", "source_ref": "降解 -10%"}, |
| ] |
| } |
| items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out) |
| kept, missing = BackReferenceValidator.validate(items, SOURCE) |
| kept_vals = [it.value for it in kept] |
| assert "0.00" in kept_vals |
| assert "-10" not in kept_vals |
| assert "value" in missing |
|
|
|
|
| def test_validator_whitespace_insensitive_match(): |
| llm_out = {"items": [{"field": "spec_limit", "value": "总杂≤2.0%", "source_ref": "总杂 ≤ 2.0%"}]} |
| items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out) |
| kept, missing = BackReferenceValidator.validate(items, SOURCE) |
| assert len(kept) == 1 |
| assert missing == [] |
|
|
|
|
| def test_validator_no_fabrication_property(): |
| |
| import re |
| llm_out = { |
| "items": [ |
| {"field": "value", "value": "0.00", "source_ref": "总杂% 0.00"}, |
| {"field": "value", "value": "fake", "source_ref": "不存在的内容xyz"}, |
| ] |
| } |
| items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out) |
| kept, _ = BackReferenceValidator.validate(items, SOURCE) |
| norm_hay = re.sub(r"\s+", "", SOURCE) |
| assert all(it.value != "fake" for it in kept) |
| for it in kept: |
| nums = re.findall(r"-?\d+(?:\.\d+)?", it.value) |
| grounded = (all(n in SOURCE or any(float(n) == float(m) for m in |
| re.findall(r"-?\d+(?:\.\d+)?", SOURCE)) for n in nums) |
| if nums else re.sub(r"\s+", "", it.source_ref) in norm_hay) |
| assert grounded |
|
|
|
|
| def test_validator_value_centric_keeps_when_number_present(): |
| """逐属性提示词下,source_ref 非连续子串但数值在原文 → 应保留(回归修复)。""" |
| source = "涂布厚度/mm 膜厚/mm 溶化时限/s 0.5 0.05 60" |
| llm_out = { |
| "items": [ |
| {"field": "膜厚", "value": "0.05", "source_ref": "膜厚 0.05", |
| "group": {"attribute": "膜厚"}}, |
| ] |
| } |
| items = InformationExtractor(llm=None).extract(source, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out) |
| kept, missing = BackReferenceValidator.validate(items, source) |
| assert len(kept) == 1 |
| assert kept[0].value == "0.05" |
| assert missing == [] |
|
|
|
|
| def test_validator_drops_fabricated_number_not_in_source(): |
| source = "涂布厚度/mm 膜厚/mm 0.5 0.05 60" |
| llm_out = { |
| "items": [ |
| {"field": "膜厚", "value": "99.99", "source_ref": "膜厚 99.99", |
| "group": {"attribute": "膜厚"}}, |
| ] |
| } |
| items = InformationExtractor(llm=None).extract(source, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out) |
| kept, missing = BackReferenceValidator.validate(items, source) |
| assert kept == [] |
| assert "膜厚" in missing |
|
|
|
|
| def test_validator_tolerates_float_format_difference(): |
| source = "含量 99.50 %" |
| llm_out = {"items": [{"field": "含量", "value": "99.5", "source_ref": "含量 99.5"}]} |
| items = InformationExtractor(llm=None).extract(source, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out) |
| kept, _ = BackReferenceValidator.validate(items, source) |
| assert len(kept) == 1 |
|
|