deepdoc-lib 0.2.0__py3-none-any.whl
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.
- deepdoc/README.md +122 -0
- deepdoc/README_zh.md +116 -0
- deepdoc/__init__.py +43 -0
- deepdoc/_version.py +34 -0
- deepdoc/common/__init__.py +52 -0
- deepdoc/common/config_utils.py +63 -0
- deepdoc/common/connection_utils.py +73 -0
- deepdoc/common/file_utils.py +19 -0
- deepdoc/common/misc_utils.py +44 -0
- deepdoc/common/model_store.py +369 -0
- deepdoc/common/settings.py +42 -0
- deepdoc/common/tiktoken_cache.py +84 -0
- deepdoc/common/token_utils.py +96 -0
- deepdoc/config.py +149 -0
- deepdoc/depend/find_codec.py +42 -0
- deepdoc/depend/nltk_manager.py +114 -0
- deepdoc/depend/prompts/vision_llm_describe_prompt.md +23 -0
- deepdoc/depend/prompts/vision_llm_figure_describe_prompt.md +24 -0
- deepdoc/depend/prompts.py +35 -0
- deepdoc/depend/rag_tokenizer.py +578 -0
- deepdoc/depend/simple_cv_model.py +469 -0
- deepdoc/depend/surname.py +91 -0
- deepdoc/depend/timeout.py +73 -0
- deepdoc/depend/vision_llm_chunk.py +35 -0
- deepdoc/dict/README.md +19 -0
- deepdoc/dict/huqie.txt +555629 -0
- deepdoc/download_models.py +169 -0
- deepdoc/llm_adapter/__init__.py +15 -0
- deepdoc/llm_adapter/adapter.py +223 -0
- deepdoc/llm_adapter/utils.py +104 -0
- deepdoc/llm_adapter/vision.py +163 -0
- deepdoc/parser/__init__.py +42 -0
- deepdoc/parser/docling_parser.py +889 -0
- deepdoc/parser/docx_parser.py +150 -0
- deepdoc/parser/excel_parser.py +270 -0
- deepdoc/parser/figure_parser.py +182 -0
- deepdoc/parser/html_parser.py +221 -0
- deepdoc/parser/json_parser.py +179 -0
- deepdoc/parser/markdown_parser.py +321 -0
- deepdoc/parser/mineru_parser.py +646 -0
- deepdoc/parser/pdf_parser.py +1591 -0
- deepdoc/parser/ppt_parser.py +96 -0
- deepdoc/parser/resume/__init__.py +109 -0
- deepdoc/parser/resume/entities/__init__.py +15 -0
- deepdoc/parser/resume/entities/corporations.py +128 -0
- deepdoc/parser/resume/entities/degrees.py +44 -0
- deepdoc/parser/resume/entities/industries.py +712 -0
- deepdoc/parser/resume/entities/regions.py +789 -0
- deepdoc/parser/resume/entities/res/corp.tks.freq.json +65 -0
- deepdoc/parser/resume/entities/res/corp_baike_len.csv +31480 -0
- deepdoc/parser/resume/entities/res/corp_tag.json +14939 -0
- deepdoc/parser/resume/entities/res/good_corp.json +911 -0
- deepdoc/parser/resume/entities/res/good_sch.json +595 -0
- deepdoc/parser/resume/entities/res/school.rank.csv +1627 -0
- deepdoc/parser/resume/entities/res/schools.csv +5713 -0
- deepdoc/parser/resume/entities/schools.py +91 -0
- deepdoc/parser/resume/step_one.py +189 -0
- deepdoc/parser/resume/step_two.py +692 -0
- deepdoc/parser/tcadp_parser.py +538 -0
- deepdoc/parser/txt_parser.py +64 -0
- deepdoc/parser/utils.py +33 -0
- deepdoc/vision/__init__.py +90 -0
- deepdoc/vision/layout_recognizer.py +481 -0
- deepdoc/vision/ocr.py +757 -0
- deepdoc/vision/operators.py +733 -0
- deepdoc/vision/postprocess.py +370 -0
- deepdoc/vision/recognizer.py +451 -0
- deepdoc/vision/seeit.py +87 -0
- deepdoc/vision/t_ocr.py +101 -0
- deepdoc/vision/t_recognizer.py +186 -0
- deepdoc/vision/table_structure_recognizer.py +617 -0
- deepdoc_lib-0.2.0.dist-info/METADATA +246 -0
- deepdoc_lib-0.2.0.dist-info/RECORD +78 -0
- deepdoc_lib-0.2.0.dist-info/WHEEL +5 -0
- deepdoc_lib-0.2.0.dist-info/entry_points.txt +2 -0
- deepdoc_lib-0.2.0.dist-info/licenses/LICENSE +201 -0
- deepdoc_lib-0.2.0.dist-info/top_level.txt +2 -0
- scripts/download_models.py +10 -0
deepdoc/config.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from importlib import resources
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
from .common.model_store import (
|
|
10
|
+
resolve_vision_model_dir,
|
|
11
|
+
resolve_xgb_model_dir,
|
|
12
|
+
validate_bundle_dir,
|
|
13
|
+
)
|
|
14
|
+
from .common.misc_utils import offline_mode_or_from_env
|
|
15
|
+
|
|
16
|
+
ProviderType = Literal["local", "modelscope", "auto"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _normalize_provider(provider: str) -> ProviderType:
|
|
20
|
+
normalized = provider.strip().lower()
|
|
21
|
+
aliases = {
|
|
22
|
+
"ms": "modelscope",
|
|
23
|
+
"remote": "modelscope",
|
|
24
|
+
"filesystem": "local",
|
|
25
|
+
"user": "local",
|
|
26
|
+
}
|
|
27
|
+
normalized = aliases.get(normalized, normalized)
|
|
28
|
+
if normalized not in {"local", "modelscope", "auto"}:
|
|
29
|
+
raise ValueError("Unsupported model provider '{}'. Use one of: local, modelscope, auto.".format(provider))
|
|
30
|
+
return normalized # type: ignore[return-value]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _require_file(path: Path, message: str) -> None:
|
|
34
|
+
if not path.exists():
|
|
35
|
+
raise FileNotFoundError(message)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _default_packaged_dict_path() -> str:
|
|
39
|
+
packaged_dict = resources.files("deepdoc").joinpath("dict", "huqie.txt")
|
|
40
|
+
dict_path = Path(str(packaged_dict))
|
|
41
|
+
_require_file(
|
|
42
|
+
dict_path,
|
|
43
|
+
"Packaged tokenizer dictionary not found: {}".format(dict_path),
|
|
44
|
+
)
|
|
45
|
+
return str(dict_path)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class TokenizerConfig:
|
|
50
|
+
dict_path: str | None = None
|
|
51
|
+
offline: bool = False
|
|
52
|
+
nltk_data_dir: str | None = None
|
|
53
|
+
|
|
54
|
+
def resolve_dict_path(self) -> str:
|
|
55
|
+
if self.dict_path:
|
|
56
|
+
dictionary = Path(self.dict_path).expanduser().resolve()
|
|
57
|
+
if dictionary.is_dir():
|
|
58
|
+
dictionary = dictionary.joinpath("huqie.txt")
|
|
59
|
+
if dictionary.suffix != ".txt":
|
|
60
|
+
raise ValueError("TokenizerConfig.dict_path must point to a '.txt' dictionary file, got: {}".format(dictionary))
|
|
61
|
+
_require_file(
|
|
62
|
+
dictionary,
|
|
63
|
+
"Tokenizer dictionary not found: {}. Provide a valid TokenizerConfig.dict_path.".format(dictionary),
|
|
64
|
+
)
|
|
65
|
+
return str(dictionary)
|
|
66
|
+
|
|
67
|
+
return _default_packaged_dict_path()
|
|
68
|
+
|
|
69
|
+
def resolve_dict_prefix(self) -> str:
|
|
70
|
+
return str(Path(self.resolve_dict_path()).with_suffix(""))
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_env(cls) -> "TokenizerConfig":
|
|
74
|
+
tokenizer_dir = os.getenv("DEEPDOC_TOKENIZER_MODEL_DIR")
|
|
75
|
+
dict_path = str(Path(tokenizer_dir).expanduser().resolve().joinpath("huqie.txt")) if tokenizer_dir else None
|
|
76
|
+
|
|
77
|
+
return cls(
|
|
78
|
+
dict_path=dict_path,
|
|
79
|
+
offline=offline_mode_or_from_env(None),
|
|
80
|
+
nltk_data_dir=os.getenv("DEEPDOC_NLTK_DATA_DIR"),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass(frozen=True)
|
|
85
|
+
class PdfModelConfig:
|
|
86
|
+
vision_model_dir: str | None = None
|
|
87
|
+
xgb_model_dir: str | None = None
|
|
88
|
+
ascend_model_dir: str | None = None
|
|
89
|
+
model_home: str | None = None
|
|
90
|
+
model_provider: ProviderType = "auto"
|
|
91
|
+
|
|
92
|
+
def normalized_provider(self) -> ProviderType:
|
|
93
|
+
return _normalize_provider(self.model_provider)
|
|
94
|
+
|
|
95
|
+
def _resolve_bundle_dir(self, bundle: str, explicit_dir: str | None) -> str:
|
|
96
|
+
if explicit_dir:
|
|
97
|
+
candidate = Path(explicit_dir).expanduser().resolve()
|
|
98
|
+
exists, missing = validate_bundle_dir(bundle, candidate)
|
|
99
|
+
if not exists:
|
|
100
|
+
raise FileNotFoundError("Missing required files for '{}' bundle in {}: {}".format(bundle, candidate, ", ".join(missing)))
|
|
101
|
+
return str(candidate)
|
|
102
|
+
|
|
103
|
+
model_provider = self.normalized_provider()
|
|
104
|
+
model_offline = model_provider == "local"
|
|
105
|
+
|
|
106
|
+
if bundle == "vision":
|
|
107
|
+
return resolve_vision_model_dir(
|
|
108
|
+
model_home=self.model_home,
|
|
109
|
+
provider=model_provider,
|
|
110
|
+
offline=model_offline,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if bundle == "xgb":
|
|
114
|
+
return resolve_xgb_model_dir(
|
|
115
|
+
model_home=self.model_home,
|
|
116
|
+
provider=model_provider,
|
|
117
|
+
offline=model_offline,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
raise ValueError(f"Unsupported PDF model bundle '{bundle}'")
|
|
121
|
+
|
|
122
|
+
def resolve_vision_model_dir(self) -> str:
|
|
123
|
+
return self._resolve_bundle_dir("vision", self.vision_model_dir)
|
|
124
|
+
|
|
125
|
+
def resolve_xgb_model_dir(self) -> str:
|
|
126
|
+
return self._resolve_bundle_dir("xgb", self.xgb_model_dir)
|
|
127
|
+
|
|
128
|
+
def resolve_ascend_model_dir(self) -> str | None:
|
|
129
|
+
if not self.ascend_model_dir:
|
|
130
|
+
return None
|
|
131
|
+
candidate = Path(self.ascend_model_dir).expanduser().resolve()
|
|
132
|
+
if not candidate.exists() or not candidate.is_dir():
|
|
133
|
+
raise FileNotFoundError(f"Ascend model directory does not exist: {candidate}")
|
|
134
|
+
return str(candidate)
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def from_env(cls) -> "PdfModelConfig":
|
|
138
|
+
return cls(
|
|
139
|
+
vision_model_dir=os.getenv("DEEPDOC_VISION_MODEL_DIR"),
|
|
140
|
+
xgb_model_dir=os.getenv("DEEPDOC_XGB_MODEL_DIR"),
|
|
141
|
+
ascend_model_dir=os.getenv("DEEPDOC_ASCEND_MODEL_DIR"),
|
|
142
|
+
model_provider=_normalize_provider(os.getenv("DEEPDOC_MODEL_PROVIDER", "auto")),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass(frozen=True)
|
|
147
|
+
class ParserRuntimeConfig:
|
|
148
|
+
tokenizer: TokenizerConfig
|
|
149
|
+
pdf_models: PdfModelConfig
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chardet
|
|
2
|
+
|
|
3
|
+
all_codecs = [
|
|
4
|
+
'utf-8', 'gb2312', 'gbk', 'utf_16', 'ascii', 'big5', 'big5hkscs',
|
|
5
|
+
'cp037', 'cp273', 'cp424', 'cp437',
|
|
6
|
+
'cp500', 'cp720', 'cp737', 'cp775', 'cp850', 'cp852', 'cp855', 'cp856', 'cp857',
|
|
7
|
+
'cp858', 'cp860', 'cp861', 'cp862', 'cp863', 'cp864', 'cp865', 'cp866', 'cp869',
|
|
8
|
+
'cp874', 'cp875', 'cp932', 'cp949', 'cp950', 'cp1006', 'cp1026', 'cp1125',
|
|
9
|
+
'cp1140', 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255', 'cp1256',
|
|
10
|
+
'cp1257', 'cp1258', 'euc_jp', 'euc_jis_2004', 'euc_jisx0213', 'euc_kr',
|
|
11
|
+
'gb18030', 'hz', 'iso2022_jp', 'iso2022_jp_1', 'iso2022_jp_2',
|
|
12
|
+
'iso2022_jp_2004', 'iso2022_jp_3', 'iso2022_jp_ext', 'iso2022_kr', 'latin_1',
|
|
13
|
+
'iso8859_2', 'iso8859_3', 'iso8859_4', 'iso8859_5', 'iso8859_6', 'iso8859_7',
|
|
14
|
+
'iso8859_8', 'iso8859_9', 'iso8859_10', 'iso8859_11', 'iso8859_13',
|
|
15
|
+
'iso8859_14', 'iso8859_15', 'iso8859_16', 'johab', 'koi8_r', 'koi8_t', 'koi8_u',
|
|
16
|
+
'kz1048', 'mac_cyrillic', 'mac_greek', 'mac_iceland', 'mac_latin2', 'mac_roman',
|
|
17
|
+
'mac_turkish', 'ptcp154', 'shift_jis', 'shift_jis_2004', 'shift_jisx0213',
|
|
18
|
+
'utf_32', 'utf_32_be', 'utf_32_le', 'utf_16_be', 'utf_16_le', 'utf_7', 'windows-1250', 'windows-1251',
|
|
19
|
+
'windows-1252', 'windows-1253', 'windows-1254', 'windows-1255', 'windows-1256',
|
|
20
|
+
'windows-1257', 'windows-1258', 'latin-2'
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def find_codec(blob):
|
|
25
|
+
detected = chardet.detect(blob[:1024])
|
|
26
|
+
if detected['confidence'] > 0.5:
|
|
27
|
+
if detected['encoding'] == "ascii":
|
|
28
|
+
return "utf-8"
|
|
29
|
+
|
|
30
|
+
for c in all_codecs:
|
|
31
|
+
try:
|
|
32
|
+
blob[:1024].decode(c)
|
|
33
|
+
return c
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
try:
|
|
37
|
+
blob.decode(c)
|
|
38
|
+
return c
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
return "utf-8"
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import threading
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from ..common.misc_utils import offline_mode_or_from_env
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
_RESOURCE_SPECS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
|
10
|
+
("punkt", ("tokenizers/punkt", "tokenizers/punkt.zip")),
|
|
11
|
+
("punkt_tab", ("tokenizers/punkt_tab", "tokenizers/punkt_tab.zip")),
|
|
12
|
+
("wordnet", ("corpora/wordnet", "corpora/wordnet.zip")),
|
|
13
|
+
(
|
|
14
|
+
"averaged_perceptron_tagger",
|
|
15
|
+
(
|
|
16
|
+
"taggers/averaged_perceptron_tagger",
|
|
17
|
+
"taggers/averaged_perceptron_tagger.zip",
|
|
18
|
+
"taggers/averaged_perceptron_tagger_eng",
|
|
19
|
+
"taggers/averaged_perceptron_tagger_eng.zip",
|
|
20
|
+
),
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
_lock = threading.Lock()
|
|
25
|
+
_ensured_keys: set[tuple[str, bool]] = set()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _resolve_nltk_data_dir(data_dir: str | None) -> Path | None:
|
|
29
|
+
# Resolution precedence:
|
|
30
|
+
# 1) explicit arg
|
|
31
|
+
# 2) DEEPDOC_NLTK_DATA_DIR
|
|
32
|
+
# 3) NLTK_DATA (common NLTK env var)
|
|
33
|
+
# 4) fall back to a stable Deepdoc cache location so parsers can pick it up automatically
|
|
34
|
+
configured = data_dir or os.getenv("DEEPDOC_NLTK_DATA_DIR") or os.getenv("NLTK_DATA")
|
|
35
|
+
if configured and configured.strip():
|
|
36
|
+
return Path(configured).expanduser().resolve()
|
|
37
|
+
|
|
38
|
+
model_home = os.getenv("DEEPDOC_MODEL_HOME")
|
|
39
|
+
if model_home and model_home.strip():
|
|
40
|
+
base = Path(model_home).expanduser().resolve()
|
|
41
|
+
else:
|
|
42
|
+
base = Path.home().joinpath(".cache", "deepdoc")
|
|
43
|
+
return base.joinpath("nltk_data")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _ensure_search_path(nltk_module, data_path: Path | None):
|
|
47
|
+
if not data_path:
|
|
48
|
+
return
|
|
49
|
+
data_path.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
text_path = str(data_path)
|
|
51
|
+
if text_path not in nltk_module.data.path:
|
|
52
|
+
nltk_module.data.path.insert(0, text_path)
|
|
53
|
+
os.environ["NLTK_DATA"] = text_path
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _resource_exists(nltk_module, candidates: tuple[str, ...]) -> bool:
|
|
57
|
+
for resource_path in candidates:
|
|
58
|
+
try:
|
|
59
|
+
nltk_module.data.find(resource_path)
|
|
60
|
+
return True
|
|
61
|
+
except LookupError:
|
|
62
|
+
continue
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
logger.warning("NLTK resource check failed for %s: %s", resource_path, exc)
|
|
65
|
+
continue
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def ensure_nltk_data(
|
|
70
|
+
*,
|
|
71
|
+
data_dir: str | None = None,
|
|
72
|
+
offline: bool | None = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Ensure required NLTK resources are available for tokenizer usage."""
|
|
75
|
+
|
|
76
|
+
import nltk
|
|
77
|
+
|
|
78
|
+
resolved_dir = _resolve_nltk_data_dir(data_dir)
|
|
79
|
+
offline_mode = offline_mode_or_from_env(offline)
|
|
80
|
+
auto_download_mode = not offline_mode
|
|
81
|
+
|
|
82
|
+
_ensure_search_path(nltk, resolved_dir)
|
|
83
|
+
cache_key = (str(resolved_dir) if resolved_dir else "", offline_mode)
|
|
84
|
+
|
|
85
|
+
with _lock:
|
|
86
|
+
if cache_key in _ensured_keys:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
missing_packages: list[str] = []
|
|
90
|
+
for package, candidates in _RESOURCE_SPECS:
|
|
91
|
+
if not _resource_exists(nltk, candidates):
|
|
92
|
+
missing_packages.append(package)
|
|
93
|
+
|
|
94
|
+
if missing_packages and auto_download_mode:
|
|
95
|
+
download_dir = str(resolved_dir) if resolved_dir else None
|
|
96
|
+
for package in list(missing_packages):
|
|
97
|
+
try:
|
|
98
|
+
success = nltk.download(package, quiet=True, download_dir=download_dir)
|
|
99
|
+
except Exception as exc:
|
|
100
|
+
logger.warning("Failed to download NLTK package %s: %s", package, exc)
|
|
101
|
+
success = False
|
|
102
|
+
if success and _resource_exists(nltk, dict(_RESOURCE_SPECS)[package]):
|
|
103
|
+
missing_packages.remove(package)
|
|
104
|
+
|
|
105
|
+
if missing_packages:
|
|
106
|
+
searched_paths = ", ".join(nltk.data.path)
|
|
107
|
+
raise RuntimeError(
|
|
108
|
+
"Missing required NLTK packages: {}. Searched paths: {}. Set DEEPDOC_NLTK_DATA_DIR to a local NLTK data path, or disable offline mode by setting DEEPDOC_OFFLINE=0.".format(
|
|
109
|
+
", ".join(missing_packages),
|
|
110
|
+
searched_paths,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
_ensured_keys.add(cache_key)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## INSTRUCTION
|
|
2
|
+
Transcribe the content from the provided PDF page image into clean Markdown format.
|
|
3
|
+
|
|
4
|
+
- Only output the content transcribed from the image.
|
|
5
|
+
- Do NOT output this instruction or any other explanation.
|
|
6
|
+
- If the content is missing or you do not understand the input, return an empty string.
|
|
7
|
+
|
|
8
|
+
## RULES
|
|
9
|
+
1. Do NOT generate examples, demonstrations, or templates.
|
|
10
|
+
2. Do NOT output any extra text such as 'Example', 'Example Output', or similar.
|
|
11
|
+
3. Do NOT generate any tables, headings, or content that is not explicitly present in the image.
|
|
12
|
+
4. Transcribe content word-for-word. Do NOT modify, translate, or omit any content.
|
|
13
|
+
5. Do NOT explain Markdown or mention that you are using Markdown.
|
|
14
|
+
6. Do NOT wrap the output in ```markdown or ``` blocks.
|
|
15
|
+
7. Only apply Markdown structure to headings, paragraphs, lists, and tables, strictly based on the layout of the image. Do NOT create tables unless an actual table exists in the image.
|
|
16
|
+
8. Preserve the original language, information, and order exactly as shown in the image.
|
|
17
|
+
|
|
18
|
+
{% if page %}
|
|
19
|
+
At the end of the transcription, add the page divider: `--- Page {{ page }} ---`.
|
|
20
|
+
{% endif %}
|
|
21
|
+
|
|
22
|
+
> If you do not detect valid content in the image, return an empty string.
|
|
23
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## ROLE
|
|
2
|
+
You are an expert visual data analyst.
|
|
3
|
+
|
|
4
|
+
## GOAL
|
|
5
|
+
Analyze the image and provide a comprehensive description of its content. Focus on identifying the type of visual data representation (e.g., bar chart, pie chart, line graph, table, flowchart), its structure, and any text captions or labels included in the image.
|
|
6
|
+
|
|
7
|
+
## TASKS
|
|
8
|
+
1. Describe the overall structure of the visual representation. Specify if it is a chart, graph, table, or diagram.
|
|
9
|
+
2. Identify and extract any axes, legends, titles, or labels present in the image. Provide the exact text where available.
|
|
10
|
+
3. Extract the data points from the visual elements (e.g., bar heights, line graph coordinates, pie chart segments, table rows and columns).
|
|
11
|
+
4. Analyze and explain any trends, comparisons, or patterns shown in the data.
|
|
12
|
+
5. Capture any annotations, captions, or footnotes, and explain their relevance to the image.
|
|
13
|
+
6. Only include details that are explicitly present in the image. If an element (e.g., axis, legend, or caption) does not exist or is not visible, do not mention it.
|
|
14
|
+
|
|
15
|
+
## OUTPUT FORMAT (Include only sections relevant to the image content)
|
|
16
|
+
- Visual Type: [Type]
|
|
17
|
+
- Title: [Title text, if available]
|
|
18
|
+
- Axes / Legends / Labels: [Details, if available]
|
|
19
|
+
- Data Points: [Extracted data]
|
|
20
|
+
- Trends / Insights: [Analysis and interpretation]
|
|
21
|
+
- Captions / Annotations: [Text and relevance, if available]
|
|
22
|
+
|
|
23
|
+
> Ensure high accuracy, clarity, and completeness in your analysis, and include only the information present in the image. Avoid unnecessary statements about missing elements.
|
|
24
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import jinja2
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
BASE_DIR = os.path.dirname(__file__)
|
|
5
|
+
|
|
6
|
+
PROMPT_DIR = os.path.join(BASE_DIR, "prompts")
|
|
7
|
+
|
|
8
|
+
_loaded_prompts = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_prompt(name: str) -> str:
|
|
12
|
+
if name in _loaded_prompts:
|
|
13
|
+
return _loaded_prompts[name]
|
|
14
|
+
|
|
15
|
+
path = os.path.join(PROMPT_DIR, f"{name}.md")
|
|
16
|
+
if not os.path.isfile(path):
|
|
17
|
+
raise FileNotFoundError(f"Prompt file '{name}.md' not found in prompts/ directory at {PROMPT_DIR}.")
|
|
18
|
+
|
|
19
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
20
|
+
content = f.read().strip()
|
|
21
|
+
_loaded_prompts[name] = content
|
|
22
|
+
return content
|
|
23
|
+
|
|
24
|
+
VISION_LLM_DESCRIBE_PROMPT = load_prompt("vision_llm_describe_prompt")
|
|
25
|
+
VISION_LLM_FIGURE_DESCRIBE_PROMPT = load_prompt("vision_llm_figure_describe_prompt")
|
|
26
|
+
PROMPT_JINJA_ENV = jinja2.Environment(autoescape=False, trim_blocks=True, lstrip_blocks=True)
|
|
27
|
+
|
|
28
|
+
def vision_llm_describe_prompt(page=None) -> str:
|
|
29
|
+
template = PROMPT_JINJA_ENV.from_string(VISION_LLM_DESCRIBE_PROMPT)
|
|
30
|
+
|
|
31
|
+
return template.render(page=page)
|
|
32
|
+
|
|
33
|
+
def vision_llm_figure_describe_prompt() -> str:
|
|
34
|
+
template = PROMPT_JINJA_ENV.from_string(VISION_LLM_FIGURE_DESCRIBE_PROMPT)
|
|
35
|
+
return template.render()
|