Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions app/image/fact_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,31 @@ def _numeric_fact_is_allowed(candidate: str, allowed: set[str]) -> bool:
return False


def _unverified_numeric_facts(
ocr_text: str,
allowed_numbers: set[str],
evidence_lines: list[str],
) -> tuple[str, ...]:
"""逐行判断数字,允许 OCR 仅在真实字母数字昵称中插入空格。"""

allowed_line_compacts = {
compact
for line in evidence_lines
if len(compact := _compact_text(line)) >= 4
}
unknown: set[str] = set()
for line in ocr_text.splitlines():
line_compact = _compact_text(line)
exact_evidence_line = line_compact in allowed_line_compacts
for candidate in _numeric_facts(line):
if _numeric_fact_is_allowed(candidate, allowed_numbers):
continue
if exact_evidence_line:
continue
unknown.add(candidate)
return tuple(sorted(unknown, key=lambda item: (len(item), item)))


def strip_unverified_prompt_numeric_units(prompt_file: Path) -> tuple[str, tuple[str, ...]]:
"""去掉摘要擅自附加、但原始证据没有的数字单位。

Expand Down Expand Up @@ -275,15 +300,10 @@ def review_image_facts(
allowed_numbers = _numeric_facts(numeric_evidence)
# 分镜序号属于版式,不是聊天事实。
allowed_numbers.update(str(number) for number in range(0, 11))
unknown_numeric = tuple(
sorted(
(
item
for item in _numeric_facts(text)
if not _numeric_fact_is_allowed(item, allowed_numbers)
),
key=lambda item: (len(item), item),
)
unknown_numeric = _unverified_numeric_facts(
text,
allowed_numbers,
evidence_lines,
)

normalized_lines = [line.strip() for line in text.splitlines() if line.strip()]
Expand Down
26 changes: 26 additions & 0 deletions tests/test_image_fact_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,32 @@ def test_allows_ocr_fragments_of_known_numbers_and_ignores_name_garble(tmp_path)
assert review.unknown_text == ()


def test_allows_ocr_space_inside_evidenced_alphanumeric_sender_name(tmp_path):
prompt, image = _evidence(tmp_path)
messages_path = tmp_path / "messages.json"
messages = json.loads(messages_path.read_text(encoding="utf-8"))
messages.append({"sender_name": "Gaosong925", "content": "推荐 Grok App"})
messages_path.write_text(json.dumps(messages, ensure_ascii=False), encoding="utf-8")

review = review_image_facts(prompt, image, ocr_text="Gaosong 925")

assert review.ok
assert review.unknown_numeric == ()


def test_rejects_changed_number_in_alphanumeric_sender_name(tmp_path):
prompt, image = _evidence(tmp_path)
messages_path = tmp_path / "messages.json"
messages = json.loads(messages_path.read_text(encoding="utf-8"))
messages.append({"sender_name": "Gaosong925", "content": "推荐 Grok App"})
messages_path.write_text(json.dumps(messages, ensure_ascii=False), encoding="utf-8")

review = review_image_facts(prompt, image, ocr_text="Gaosong 926")

assert not review.ok
assert review.unknown_numeric == ("926",)


def test_numeric_units_do_not_join_across_ocr_lines(tmp_path):
prompt, image = _evidence(tmp_path)

Expand Down