orbitkit 0.9.22__tar.gz → 0.9.24__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {orbitkit-0.9.22/orbitkit.egg-info → orbitkit-0.9.24}/PKG-INFO +2 -3
- orbitkit-0.9.24/orbitkit/VERSION +1 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_quality/parse_record.py +9 -3
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/__init__.py +2 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/filters/drop_vlm_artifacts.py +53 -16
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/filters/repeated_text.py +105 -6
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/pipeline.py +26 -1
- {orbitkit-0.9.22 → orbitkit-0.9.24/orbitkit.egg-info}/PKG-INFO +2 -3
- orbitkit-0.9.22/orbitkit/VERSION +0 -1
- {orbitkit-0.9.22 → orbitkit-0.9.24}/LICENSE +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/MANIFEST.in +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/README.md +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/airflow_handler/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/airflow_handler/data_preprocessing.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/airflow_handler/file_flow_entry.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/airflow_handler/file_flow_entry_process.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/airflow_handler/file_flow_exit_process.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/airflow_handler/file_handler.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/airflow_handler/file_handler_v2.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/audio_transcoder/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/audio_transcoder/netmind_extract_v1.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/constant/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/constant/report_schema.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/id_srv/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/id_srv/id_gen.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/id_srv/id_perm_like.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/lark_send/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/lark_send/lark.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/llm_tools/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/llm_tools/quick_rag_chat.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/orbit_type/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/orbit_type/doc_4_compile_rule.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/orbit_type/orbit_type_simple.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/orbit_type/tools.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_chunking/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_chunking/chunk_fallback.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_chunking/chunk_plan.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_chunking/page_limit.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_chunking/pdf_metadata.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_embedding/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_embedding/pdf_txt_embedding.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_embedding/pdf_txt_embedding_v2.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/a_stock_extractor_v1.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/exceptions.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/mineru_demo.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_block_extractor_base.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_block_extractor_v1.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_block_extractor_v2.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_extractor_azure.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_extractor_minerU_v1.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_extractor_netmind_v1.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_extractor_netmind_v2.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_extractor_netmind_v3.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor/pdf_extractor_orbit.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor_simple/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor_simple/base.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor_simple/cloud_provider.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor_simple/core.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor_simple/exceptions.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor_simple/extractors.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_extractor_simple/utils.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_pipeline_io/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_pipeline_io/cdn.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_pipeline_io/pg_task_store.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_quality/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_quality/failure_codes.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_quality/ocr_lang.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_quality/verdict.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_transformer/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_transformer/mineru/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_transformer/mineru/backends.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_transformer/mineru/bbox_rotation.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_transformer/mineru/result_transformer.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/filters/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/filters/drop_invalid_bbox.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/filters/drop_non_text.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/registry.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_validator/validate.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_writer/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/pdf_writer/pdf_writer_simple.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/__init__.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/cache_asset_downloader.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/common.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/customize_regix_manager.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/is_xbrl_structure.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/secret_manager.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/universal_extractor.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_aliyun.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_aliyun_oss_simple.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_aws.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_aws_s3_wrapper.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_date.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_html.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_kafka.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_md5.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_selenium.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_simple_timer.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_str.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_type_mapping.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit/util/util_url.py +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit.egg-info/SOURCES.txt +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit.egg-info/dependency_links.txt +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit.egg-info/not-zip-safe +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit.egg-info/requires.txt +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/orbitkit.egg-info/top_level.txt +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/setup.cfg +0 -0
- {orbitkit-0.9.22 → orbitkit-0.9.24}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: orbitkit
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.24
|
|
4
4
|
Summary: This project is only for Orbit Tech internal use.
|
|
5
5
|
Home-page: https://github.com/clown-0726/orbitkit
|
|
6
6
|
Author: Lilu Cao
|
|
@@ -45,7 +45,6 @@ Dynamic: description
|
|
|
45
45
|
Dynamic: description-content-type
|
|
46
46
|
Dynamic: home-page
|
|
47
47
|
Dynamic: license
|
|
48
|
-
Dynamic: license-file
|
|
49
48
|
Dynamic: maintainer
|
|
50
49
|
Dynamic: maintainer-email
|
|
51
50
|
Dynamic: platform
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.9.24
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
parsing_tool_ext 工具专属:mineru 用
|
|
16
16
|
{"mode", "vlm_rd_rate", "vlm_rd_threshold",
|
|
17
17
|
"bbox_error_rate", "bbox_error_threshold",
|
|
18
|
-
"vlm_rd_samples", "
|
|
19
|
-
"fallback"}
|
|
18
|
+
"vlm_rd_samples", "vlm_artifact_samples",
|
|
19
|
+
"fallback_lang", "fallback_method", "fallback"}
|
|
20
20
|
|
|
21
21
|
质量闸门(三道,任一超标即未过;失败码按下面的优先级取第一个超标的):
|
|
22
22
|
1. ``self_heal_error_rate > self_heal_threshold``(坏块率)→ 30007,可兜底重投
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
文本片段):判废只给一个率时,「预定 0.05 是否合理」无法复核,因为无法区分
|
|
36
36
|
真退化和页眉/表头那种正常重复。没命中时不写这个键。
|
|
37
37
|
|
|
38
|
+
``vlm_artifact_samples`` 同理,是几条被 ``drop_vlm_artifacts`` 丢弃的块(页码 /
|
|
39
|
+
命中短语 / 截断片段):坏块率超阀时能核「是真幻觉还是误伤正文」,也是提炼新
|
|
40
|
+
anchor 的原料。没丢过不写这个键。
|
|
41
|
+
|
|
38
42
|
``fallback`` 是兜底结果(:class:`FallbackTrace`,一个对象):没兜过不写这个键。
|
|
39
43
|
其它字段只描述**最终采用**的那份产出 —— 兜底重投了但没救回、保留首轮结论
|
|
40
44
|
时,``mode`` 仍是首轮引擎,只有这里能看出「试过、没成」及原因。
|
|
@@ -252,6 +256,8 @@ def build_parse_record(
|
|
|
252
256
|
}
|
|
253
257
|
if samples:
|
|
254
258
|
ext["vlm_rd_samples"] = [dict(s) for s in samples]
|
|
259
|
+
if stats.artifact_samples:
|
|
260
|
+
ext["vlm_artifact_samples"] = [dict(s) for s in stats.artifact_samples]
|
|
255
261
|
if fallback_lang:
|
|
256
262
|
ext["fallback_lang"] = fallback_lang
|
|
257
263
|
if fallback_method:
|
|
@@ -269,4 +275,4 @@ def build_parse_record(
|
|
|
269
275
|
bbox_error_threshold=bbox_error_threshold,
|
|
270
276
|
degenerate_samples=samples,
|
|
271
277
|
parsing_tool_ext=ext,
|
|
272
|
-
)
|
|
278
|
+
)
|
|
@@ -34,6 +34,7 @@ from .filters.repeated_text import (
|
|
|
34
34
|
trim_repeated_text,
|
|
35
35
|
)
|
|
36
36
|
from .pipeline import (
|
|
37
|
+
ARTIFACT_SAMPLE_LIMIT,
|
|
37
38
|
DEGENERATE_SAMPLE_LIMIT,
|
|
38
39
|
BlockFilter,
|
|
39
40
|
DropHook,
|
|
@@ -66,6 +67,7 @@ __all__ = [
|
|
|
66
67
|
"SequenceFilter",
|
|
67
68
|
"DropHook",
|
|
68
69
|
"FilterStats",
|
|
70
|
+
"ARTIFACT_SAMPLE_LIMIT",
|
|
69
71
|
"DEGENERATE_SAMPLE_LIMIT",
|
|
70
72
|
"Sample",
|
|
71
73
|
"apply_block_filters",
|
|
@@ -25,45 +25,82 @@ mineru VLM 偶发把训练时的 chain-of-thought / 评估文本当成"识别结
|
|
|
25
25
|
**anchor 短语** —— 单次命中即丢弃。anchor 必须是"真实文档绝不
|
|
26
26
|
出现"的措辞,零误报。
|
|
27
27
|
|
|
28
|
-
判定流程:先查 anchor(单次命中即 drop);再查 soft list(≥2
|
|
28
|
+
判定流程:先查 anchor(单次命中即 drop);再查 soft list(≥2 个**不同短语**
|
|
29
|
+
命中、且其中至少 1 个是**强短语**即 drop)。
|
|
30
|
+
|
|
31
|
+
软短语分强 / 弱:
|
|
32
|
+
|
|
33
|
+
- 强 = 自指 / 评估措辞,真实文档几乎不出现("Ground Truth"、"hallucinated"、
|
|
34
|
+
"OCR output"…);
|
|
35
|
+
- 弱 = 真实文档里常见的措辞("Rule 13.25A"、"inconsistent with"——港交所公告、
|
|
36
|
+
合同、法律条款里成对出现是常态)。弱短语只能给强短语"补票",两个弱短语同现
|
|
37
|
+
不判死。
|
|
38
|
+
|
|
39
|
+
"不同短语"按**短语(正则)**计数,不按匹配到的字符串:``rule 13`` 与 ``rule 17``
|
|
40
|
+
是同一条短语命中两次,只算 1。
|
|
29
41
|
"""
|
|
30
42
|
|
|
31
43
|
import re
|
|
32
|
-
from typing import Optional
|
|
44
|
+
from typing import List, Optional
|
|
33
45
|
|
|
34
46
|
# Anchor 短语:真实文档不会出现的 VLM-only 自述句式。单次命中即丢弃。
|
|
35
47
|
# 添加新 anchor 前必须 grep 历史归档样本(>10k PDFs)确认零命中。
|
|
36
48
|
_VLM_ANCHOR_PHRASES = [
|
|
37
49
|
r"\bThe image contains? no text\b",
|
|
50
|
+
r"\bThe OCR has hallucinated\b",
|
|
51
|
+
r"\binconsistent with the Ground Truth\b",
|
|
52
|
+
r"\bThe provided OCR (?:content|result|output)\b",
|
|
53
|
+
r"\bUNDERSCORE & LINE RULES\b",
|
|
38
54
|
]
|
|
39
55
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
_VLM_PHRASES = [
|
|
56
|
+
# 强软短语:自指 / 评估措辞。
|
|
57
|
+
_VLM_STRONG_PHRASES = [
|
|
43
58
|
r"\bGround Truth\b",
|
|
44
|
-
r"\bOCR (?:result|content|output
|
|
59
|
+
r"\bOCR (?:result|content|output)\b",
|
|
60
|
+
r"\bOCR (?:has|must)\b",
|
|
45
61
|
r"\bviolating the rule\b",
|
|
46
62
|
r"\bhallucinat(?:ed|ing|ion)\b",
|
|
47
|
-
r"\bRule \d+",
|
|
48
|
-
r"\binconsistent with\b",
|
|
49
63
|
r"\bplaceholder underscore\b",
|
|
50
64
|
r"\bprovided OCR\b",
|
|
51
65
|
r"\bstylistic line\b",
|
|
52
66
|
]
|
|
53
67
|
|
|
68
|
+
# 弱软短语:真实文档常见,只能配合强短语凑数。
|
|
69
|
+
_VLM_WEAK_PHRASES = [
|
|
70
|
+
r"\bRule \d+",
|
|
71
|
+
r"\binconsistent with\b",
|
|
72
|
+
]
|
|
73
|
+
|
|
54
74
|
_VLM_ANCHOR_RE = re.compile("|".join(_VLM_ANCHOR_PHRASES), re.IGNORECASE)
|
|
55
|
-
|
|
75
|
+
_VLM_STRONG_RES = [re.compile(p, re.IGNORECASE) for p in _VLM_STRONG_PHRASES]
|
|
76
|
+
_VLM_WEAK_RES = [re.compile(p, re.IGNORECASE) for p in _VLM_WEAK_PHRASES]
|
|
56
77
|
_MIN_HITS = 2 # 软阈值:不同短语命中数
|
|
57
78
|
_MIN_TEXT_LEN = 50 # 短文本不可能塞下 2 个不同短语
|
|
58
79
|
|
|
59
80
|
|
|
60
|
-
def
|
|
61
|
-
|
|
81
|
+
def vlm_artifact_hits(text: str) -> List[str]:
|
|
82
|
+
"""判为 VLM 幻觉时命中的短语(每条短语取第 1 次匹配的原文),未判中返回空列表。
|
|
83
|
+
|
|
84
|
+
供 filter 本身与丢弃样本记录(``x_parse_record.vlm_artifact_samples``)共用,
|
|
85
|
+
保证"为什么被丢"与"是否被丢"是同一套判定。
|
|
86
|
+
"""
|
|
62
87
|
if len(text) < _MIN_TEXT_LEN:
|
|
63
|
-
return
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
88
|
+
return []
|
|
89
|
+
anchor = _VLM_ANCHOR_RE.search(text)
|
|
90
|
+
if anchor:
|
|
91
|
+
return [anchor.group(0)]
|
|
92
|
+
strong = [m.group(0) for m in (r.search(text) for r in _VLM_STRONG_RES) if m]
|
|
93
|
+
if not strong:
|
|
94
|
+
return []
|
|
95
|
+
weak = [m.group(0) for m in (r.search(text) for r in _VLM_WEAK_RES) if m]
|
|
96
|
+
hits = strong + weak
|
|
67
97
|
if len(hits) >= _MIN_HITS:
|
|
98
|
+
return hits
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def drop_vlm_artifacts(block: dict) -> Optional[dict]:
|
|
103
|
+
text = block.get("sentence") or ""
|
|
104
|
+
if vlm_artifact_hits(text):
|
|
68
105
|
return None
|
|
69
|
-
return block
|
|
106
|
+
return block
|
|
@@ -32,6 +32,19 @@ VLM/OCR 偶发在一个 block 里把某个短语连续复读很多次,例如::
|
|
|
32
32
|
filter**,因此质量闸门的复读退化率始终反映真实情况。它同时给出每个退化块的
|
|
33
33
|
**命中详情**(复读短语 / 重复次数 / 文本片段),进 ``x_parse_record`` 后
|
|
34
34
|
「退化率 0.054 该不该判死」才成为可复核的判断,而不是只有一个数字。
|
|
35
|
+
|
|
36
|
+
**不算复读的重复**(表格 / 图表 markdown 里成行出现是常态,不是模型退化):
|
|
37
|
+
|
|
38
|
+
1. markdown 表格分隔行 ``| --- | --- | --- |``——语法而非内容,整行不参与判定;
|
|
39
|
+
2. 占位符 / 空值 token(``-`` ``—`` ``na`` ``N/A`` ``none`` ``|`` 纯标点等)
|
|
40
|
+
对检测**透明**:既不计入短语,也不打断短语;
|
|
41
|
+
3. 复读短语必须含至少一个**实词**(≥2 个拉丁字母,或 ≥1 个汉字 / 假名 / 谚文),
|
|
42
|
+
纯数字、纯符号的重复(``0 0 0 0``、``... ... ...``)不算。CJK 按**单字**算实词:
|
|
43
|
+
韩文按音节、VLM 退化时按单字吐(``的 的 的``、``의 이 의 이``),都是真复读。
|
|
44
|
+
|
|
45
|
+
**token 内复读**:同一字符在一个 token 里连续 ≥ ``_MIN_CHAR_RUN`` 次(``5000000…``、
|
|
46
|
+
``————…``)没有空白,按词切不出重复;切词时把这种连续段拆成逐字 token,走同一套
|
|
47
|
+
结构检测(不受实词 / 占位符约束),trim 后只剩首次出现的那个字符。
|
|
35
48
|
"""
|
|
36
49
|
|
|
37
50
|
from __future__ import annotations
|
|
@@ -57,6 +70,29 @@ _MIN_BLOCK_WORDS = 4
|
|
|
57
70
|
|
|
58
71
|
_WORD_RE = re.compile(r"\S+")
|
|
59
72
|
|
|
73
|
+
# markdown 表格分隔行:| --- | :---: | ---: |(也允许无首尾竖线)
|
|
74
|
+
_MD_SEPARATOR_LINE_RE = re.compile(r"^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$")
|
|
75
|
+
|
|
76
|
+
# 占位符 / 空值 token(小写比较):表格里成行出现是常态,对复读检测透明
|
|
77
|
+
_PLACEHOLDER_TOKENS = frozenset(
|
|
78
|
+
{
|
|
79
|
+
"na", "n/a", "n.a.", "n.a", "nan", "none", "non", "null", "nil",
|
|
80
|
+
"nm", "n.m.", "tbd", "tba", "n/d", "nd", "无", "暂无", "不适用",
|
|
81
|
+
"0", "0.0", "0.00", "0%", "0.0%", "0.00%",
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# 实词:≥2 个连续字母(拉丁 / 西里尔 / 带重音等),或 ≥1 个 CJK 字符(汉字 / 假名 /
|
|
86
|
+
# 谚文音节)——CJK 单字就是词,不能要求 ≥2。
|
|
87
|
+
_REAL_WORD_RE = re.compile(
|
|
88
|
+
r"[^\W\d_]{2,}"
|
|
89
|
+
r"|[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af\uf900-\ufaff\U00020000-\U0002ffff]"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# token 内同一字符连续这么多次算退化(一个 "5000000000" 的真实数字也就 10 个 0)
|
|
93
|
+
_MIN_CHAR_RUN = 20
|
|
94
|
+
_CHAR_RUN_RE = re.compile(r"(.)\1{%d,}" % (_MIN_CHAR_RUN - 1))
|
|
95
|
+
|
|
60
96
|
# 样本片段截断长度:只为让人判断「是真复读还是页眉/表头」,不做内容留存
|
|
61
97
|
_SAMPLE_CHARS = 160
|
|
62
98
|
|
|
@@ -78,6 +114,69 @@ def _tokenize(text: str) -> Tuple[List[str], List[Tuple[int, int]]]:
|
|
|
78
114
|
return words, spans
|
|
79
115
|
|
|
80
116
|
|
|
117
|
+
def _is_placeholder(word: str) -> bool:
|
|
118
|
+
"""占位符 / 空值 / 纯标点 token:对复读检测透明。"""
|
|
119
|
+
if not any(ch.isalnum() for ch in word):
|
|
120
|
+
return True # 纯符号:| - — … / * ○ √ × ...
|
|
121
|
+
return word.lower() in _PLACEHOLDER_TOKENS
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _content_tokens(text: str) -> Tuple[List[str], List[Tuple[int, int]]]:
|
|
125
|
+
"""复读检测用的词序列:剔除 markdown 分隔行与占位符 token。
|
|
126
|
+
|
|
127
|
+
返回的 spans 仍是**原串**字符区间,trim 据此在原串上挖段;被剔除的 token
|
|
128
|
+
不出现在序列里,因此既不计入短语也不打断短语。
|
|
129
|
+
"""
|
|
130
|
+
words: List[str] = []
|
|
131
|
+
spans: List[Tuple[int, int]] = []
|
|
132
|
+
offset = 0
|
|
133
|
+
for line in text.splitlines(keepends=True):
|
|
134
|
+
if not _MD_SEPARATOR_LINE_RE.match(line):
|
|
135
|
+
for m in _WORD_RE.finditer(line):
|
|
136
|
+
word = m.group(0)
|
|
137
|
+
if _CHAR_RUN_RE.search(word):
|
|
138
|
+
_split_char_runs(word, offset + m.start(), words, spans)
|
|
139
|
+
continue
|
|
140
|
+
if _is_placeholder(word):
|
|
141
|
+
continue
|
|
142
|
+
words.append(word)
|
|
143
|
+
spans.append((offset + m.start(), offset + m.end()))
|
|
144
|
+
offset += len(line)
|
|
145
|
+
return words, spans
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _split_char_runs(
|
|
149
|
+
word: str, base: int, words: List[str], spans: List[Tuple[int, int]]
|
|
150
|
+
) -> None:
|
|
151
|
+
"""把含同字符连续段的 token 拆成:前缀 / 逐字的连续段 / 后缀(可多段)。
|
|
152
|
+
|
|
153
|
+
连续段的字符不走占位符过滤:``0``、``-`` 在普通位置是占位符,连写二十个就是退化。
|
|
154
|
+
"""
|
|
155
|
+
pos = 0
|
|
156
|
+
for m in _CHAR_RUN_RE.finditer(word):
|
|
157
|
+
if m.start() > pos:
|
|
158
|
+
words.append(word[pos : m.start()])
|
|
159
|
+
spans.append((base + pos, base + m.start()))
|
|
160
|
+
for k in range(m.start(), m.end()):
|
|
161
|
+
words.append(word[k])
|
|
162
|
+
spans.append((base + k, base + k + 1))
|
|
163
|
+
pos = m.end()
|
|
164
|
+
if pos < len(word):
|
|
165
|
+
words.append(word[pos:])
|
|
166
|
+
spans.append((base + pos, base + len(word)))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _has_real_word(phrase: Sequence[str]) -> bool:
|
|
170
|
+
return any(_REAL_WORD_RE.search(w) for w in phrase)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _counts_as_repeat(phrase: Sequence[str], reps: int) -> bool:
|
|
174
|
+
"""短语含实词,或是单字符连续 ≥ ``_MIN_CHAR_RUN`` 次(token 内复读拆出来的形态)。"""
|
|
175
|
+
if _has_real_word(phrase):
|
|
176
|
+
return True
|
|
177
|
+
return len(phrase) == 1 and len(phrase[0]) == 1 and reps >= _MIN_CHAR_RUN
|
|
178
|
+
|
|
179
|
+
|
|
81
180
|
def _find_runaway_repeats(
|
|
82
181
|
words: List[str], *, max_n: int, min_reps: int
|
|
83
182
|
) -> List[Tuple[int, int, int]]:
|
|
@@ -99,7 +198,7 @@ def _find_runaway_repeats(
|
|
|
99
198
|
while j + n <= n_words and words[j : j + n] == words[i : i + n]:
|
|
100
199
|
reps += 1
|
|
101
200
|
j += n
|
|
102
|
-
if reps >= min_reps:
|
|
201
|
+
if reps >= min_reps and _counts_as_repeat(words[i : i + n], reps):
|
|
103
202
|
candidates.append((reps * n, i, n))
|
|
104
203
|
reps_of[(i, n)] = reps
|
|
105
204
|
i = j # 跳过整段,避免重复检测子段
|
|
@@ -139,7 +238,7 @@ def has_runaway_repetition(
|
|
|
139
238
|
只判定、不改文本,供统计「退化块占比」用。短于 ``min_words`` 的文本一律
|
|
140
239
|
返回 ``False``(真实短句可能有正常重复)。
|
|
141
240
|
"""
|
|
142
|
-
words, _ =
|
|
241
|
+
words, _ = _content_tokens(text or "")
|
|
143
242
|
if len(words) < min_words:
|
|
144
243
|
return False
|
|
145
244
|
return bool(_find_runaway_repeats(words, max_n=max_n, min_reps=min_reps))
|
|
@@ -215,7 +314,7 @@ def find_degenerate_blocks(blocks: Sequence[dict]) -> Dict[int, Sample]:
|
|
|
215
314
|
found: Dict[int, Sample] = {}
|
|
216
315
|
for i, b in enumerate(blocks):
|
|
217
316
|
text = b.get("sentence") or ""
|
|
218
|
-
words, spans =
|
|
317
|
+
words, spans = _content_tokens(text)
|
|
219
318
|
if len(words) < _MIN_WORDS:
|
|
220
319
|
continue
|
|
221
320
|
run = _find_runaway_repeat(words, max_n=_MAX_N, min_reps=_MIN_REPS)
|
|
@@ -297,7 +396,7 @@ def trim_repeated_text(block: dict) -> Optional[dict]:
|
|
|
297
396
|
(可能是合法用词),删掉其余重复。
|
|
298
397
|
"""
|
|
299
398
|
text = block.get("sentence") or ""
|
|
300
|
-
words, spans =
|
|
399
|
+
words, spans = _content_tokens(text)
|
|
301
400
|
if len(words) < _MIN_WORDS:
|
|
302
401
|
return block
|
|
303
402
|
|
|
@@ -321,7 +420,7 @@ def trim_repeated_text(block: dict) -> Optional[dict]:
|
|
|
321
420
|
def drop_repeated_text(block: dict) -> Optional[dict]:
|
|
322
421
|
"""复读段主导整块(覆盖词数占比 ≥ 阈值)时整块丢弃,否则保留。"""
|
|
323
422
|
text = block.get("sentence") or ""
|
|
324
|
-
words, _spans =
|
|
423
|
+
words, _spans = _content_tokens(text)
|
|
325
424
|
if len(words) < _MIN_WORDS:
|
|
326
425
|
return block
|
|
327
426
|
|
|
@@ -332,4 +431,4 @@ def drop_repeated_text(block: dict) -> Optional[dict]:
|
|
|
332
431
|
covered = sum(n * reps for _start, n, reps in runs)
|
|
333
432
|
if covered / len(words) >= _DROP_COVER_FRACTION:
|
|
334
433
|
return None
|
|
335
|
-
return block
|
|
434
|
+
return block
|
|
@@ -28,10 +28,15 @@ from dataclasses import dataclass, field
|
|
|
28
28
|
from typing import Callable, Dict, List, Optional, Tuple
|
|
29
29
|
|
|
30
30
|
from .filters.drop_invalid_bbox import has_bbox_error
|
|
31
|
+
from .filters.drop_vlm_artifacts import drop_vlm_artifacts, vlm_artifact_hits
|
|
31
32
|
from .filters.repeated_text import Sample, find_degenerate_blocks
|
|
32
33
|
|
|
33
34
|
# x_parse_record 里最多带几条退化样本(够人工判断形态,不把 JSONB 当内容库)
|
|
34
35
|
DEGENERATE_SAMPLE_LIMIT = 3
|
|
36
|
+
# 同上:最多带几条被 drop_vlm_artifacts 丢弃的块样本(供复核假阳 / 提炼新 anchor)
|
|
37
|
+
ARTIFACT_SAMPLE_LIMIT = 3
|
|
38
|
+
_ARTIFACT_SNIPPET_CHARS = 200
|
|
39
|
+
_VLM_ARTIFACT_FILTER = drop_vlm_artifacts.__name__
|
|
35
40
|
|
|
36
41
|
log = logging.getLogger(__name__)
|
|
37
42
|
|
|
@@ -61,6 +66,9 @@ class FilterStats:
|
|
|
61
66
|
(超阀 → 30005)
|
|
62
67
|
- ``degenerate_samples``:最多 ``DEGENERATE_SAMPLE_LIMIT`` 条命中详情(复读
|
|
63
68
|
短语 / 重复次数 / 截断的文本片段),让 ``vlm_rd_rate`` 可人工复核。
|
|
69
|
+
- ``artifact_samples``:最多 ``ARTIFACT_SAMPLE_LIMIT`` 条被 ``drop_vlm_artifacts``
|
|
70
|
+
丢弃的块(页码 / 命中短语 / 截断的文本片段),让坏块率可人工复核:是真
|
|
71
|
+
幻觉还是被误伤的正文。
|
|
64
72
|
"""
|
|
65
73
|
|
|
66
74
|
total_in: int = 0
|
|
@@ -68,6 +76,7 @@ class FilterStats:
|
|
|
68
76
|
bbox_error: int = 0
|
|
69
77
|
degenerate: int = 0
|
|
70
78
|
degenerate_samples: List[Sample] = field(default_factory=list)
|
|
79
|
+
artifact_samples: List[Sample] = field(default_factory=list)
|
|
71
80
|
|
|
72
81
|
@property
|
|
73
82
|
def dropped_total(self) -> int:
|
|
@@ -94,6 +103,20 @@ def collect_stats(blocks: List[Block], stats: FilterStats) -> FilterStats:
|
|
|
94
103
|
return stats
|
|
95
104
|
|
|
96
105
|
|
|
106
|
+
def _record_artifact_sample(block: Block, stats: FilterStats) -> None:
|
|
107
|
+
if len(stats.artifact_samples) >= ARTIFACT_SAMPLE_LIMIT:
|
|
108
|
+
return
|
|
109
|
+
text = block.get("sentence") or ""
|
|
110
|
+
page = block.get("page")
|
|
111
|
+
stats.artifact_samples.append(
|
|
112
|
+
{
|
|
113
|
+
"page": page if isinstance(page, int) else None,
|
|
114
|
+
"hits": " | ".join(vlm_artifact_hits(text)),
|
|
115
|
+
"snippet": text[:_ARTIFACT_SNIPPET_CHARS],
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
97
120
|
def apply_block_filters(
|
|
98
121
|
blocks: List[Block],
|
|
99
122
|
pages: List[Page],
|
|
@@ -152,6 +175,8 @@ def apply_block_filters(
|
|
|
152
175
|
name = dropped_by or "anonymous"
|
|
153
176
|
if stats is not None:
|
|
154
177
|
stats.dropped[name] = stats.dropped.get(name, 0) + 1
|
|
178
|
+
if name == _VLM_ARTIFACT_FILTER:
|
|
179
|
+
_record_artifact_sample(b, stats)
|
|
155
180
|
if on_drop is not None:
|
|
156
181
|
try:
|
|
157
182
|
on_drop(name, str(b.get("type") or ""))
|
|
@@ -191,4 +216,4 @@ def apply_block_filters(
|
|
|
191
216
|
)
|
|
192
217
|
rebuilt_pages.append(new_p)
|
|
193
218
|
|
|
194
|
-
return surviving, rebuilt_pages
|
|
219
|
+
return surviving, rebuilt_pages
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: orbitkit
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.24
|
|
4
4
|
Summary: This project is only for Orbit Tech internal use.
|
|
5
5
|
Home-page: https://github.com/clown-0726/orbitkit
|
|
6
6
|
Author: Lilu Cao
|
|
@@ -45,7 +45,6 @@ Dynamic: description
|
|
|
45
45
|
Dynamic: description-content-type
|
|
46
46
|
Dynamic: home-page
|
|
47
47
|
Dynamic: license
|
|
48
|
-
Dynamic: license-file
|
|
49
48
|
Dynamic: maintainer
|
|
50
49
|
Dynamic: maintainer-email
|
|
51
50
|
Dynamic: platform
|
orbitkit-0.9.22/orbitkit/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.9.22
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|