graph-knowledge-doc-parser 0.1.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.
- graph_knowledge_doc_parser-0.1.0.dist-info/METADATA +326 -0
- graph_knowledge_doc_parser-0.1.0.dist-info/RECORD +38 -0
- graph_knowledge_doc_parser-0.1.0.dist-info/WHEEL +4 -0
- graph_knowledge_doc_parser-0.1.0.dist-info/entry_points.txt +3 -0
- kg_doc_parser/__init__.py +9 -0
- kg_doc_parser/cast_hinting.py +19 -0
- kg_doc_parser/document_ingester_logger.py +766 -0
- kg_doc_parser/models.py +277 -0
- kg_doc_parser/ocr.py +752 -0
- kg_doc_parser/pdf2png.py +286 -0
- kg_doc_parser/semantic_document_splitting_layerwise_edits.py +3302 -0
- kg_doc_parser/text_processing_utils.py +30 -0
- kg_doc_parser/utils/__init__.py +0 -0
- kg_doc_parser/utils/bounded_threadpool_executor.py +37 -0
- kg_doc_parser/utils/file_loaders.py +405 -0
- kg_doc_parser/utils/langchain.py +220 -0
- kg_doc_parser/utils/log.py +135 -0
- kg_doc_parser/utils/version_chaining.py +1278 -0
- kg_doc_parser/workflow_ingest/__init__.py +187 -0
- kg_doc_parser/workflow_ingest/_kogwistar.py +13 -0
- kg_doc_parser/workflow_ingest/adapters.py +212 -0
- kg_doc_parser/workflow_ingest/cache.py +63 -0
- kg_doc_parser/workflow_ingest/cli.py +324 -0
- kg_doc_parser/workflow_ingest/clients.py +444 -0
- kg_doc_parser/workflow_ingest/demo_harness.py +427 -0
- kg_doc_parser/workflow_ingest/design.py +208 -0
- kg_doc_parser/workflow_ingest/handlers.py +617 -0
- kg_doc_parser/workflow_ingest/models.py +575 -0
- kg_doc_parser/workflow_ingest/ocr_pipeline.py +1581 -0
- kg_doc_parser/workflow_ingest/page_index.py +473 -0
- kg_doc_parser/workflow_ingest/parser_core.py +862 -0
- kg_doc_parser/workflow_ingest/parsing.py +249 -0
- kg_doc_parser/workflow_ingest/probe.py +164 -0
- kg_doc_parser/workflow_ingest/providers.py +412 -0
- kg_doc_parser/workflow_ingest/runners.py +546 -0
- kg_doc_parser/workflow_ingest/semantics.py +231 -0
- kg_doc_parser/workflow_ingest/service.py +112 -0
- kg_doc_parser/workflow_ingest/smoke_assets.py +62 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Command line entrypoints for reusable workflow ingest runners.
|
|
4
|
+
|
|
5
|
+
The CLI is intentionally thin: it only parses arguments, builds provider
|
|
6
|
+
settings, and calls the reusable runner helpers in `kg_doc_parser.workflow_ingest.runners`.
|
|
7
|
+
That keeps the API-first surface available to tests and orchestration code
|
|
8
|
+
while still giving humans a simple command family to run locally.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Iterable
|
|
15
|
+
|
|
16
|
+
from .demo_harness import DemoHarnessConfig
|
|
17
|
+
from .providers import WorkflowProviderSettings
|
|
18
|
+
from .runners import (
|
|
19
|
+
run_demo_harness_workflow,
|
|
20
|
+
run_layerwise_batch_workflow,
|
|
21
|
+
run_layerwise_source_workflow,
|
|
22
|
+
build_legacy_parse_semantic_fn,
|
|
23
|
+
run_ocr_batch_workflow,
|
|
24
|
+
run_ocr_source_workflow,
|
|
25
|
+
run_page_index_batch_workflow,
|
|
26
|
+
run_page_index_source_workflow,
|
|
27
|
+
)
|
|
28
|
+
from .runners import _fallback_parse_semantic_fn
|
|
29
|
+
from .probe import WorkflowProbe
|
|
30
|
+
from .smoke_assets import generate_ocr_smoke_assets
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _provider_settings_from_args(args: argparse.Namespace) -> WorkflowProviderSettings | None:
|
|
34
|
+
ocr_values = {
|
|
35
|
+
"provider": args.ocr_provider,
|
|
36
|
+
"model": args.ocr_model,
|
|
37
|
+
"base_url": args.ocr_base_url,
|
|
38
|
+
"api_key_env": args.ocr_api_key_env,
|
|
39
|
+
}
|
|
40
|
+
parser_values = {
|
|
41
|
+
"provider": args.parser_provider,
|
|
42
|
+
"model": args.parser_model,
|
|
43
|
+
"base_url": args.parser_base_url,
|
|
44
|
+
"api_key_env": args.parser_api_key_env,
|
|
45
|
+
}
|
|
46
|
+
ocr_override = any(value is not None for value in ocr_values.values())
|
|
47
|
+
parser_override = any(value is not None for value in parser_values.values())
|
|
48
|
+
if not ocr_override and not parser_override:
|
|
49
|
+
return None
|
|
50
|
+
settings = WorkflowProviderSettings.from_env()
|
|
51
|
+
if ocr_override:
|
|
52
|
+
settings = settings.model_copy(
|
|
53
|
+
update={
|
|
54
|
+
"ocr": settings.ocr.model_copy(
|
|
55
|
+
update={key: value for key, value in ocr_values.items() if value is not None}
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
if parser_override:
|
|
60
|
+
settings = settings.model_copy(
|
|
61
|
+
update={
|
|
62
|
+
"parser": settings.parser.model_copy(
|
|
63
|
+
update={key: value for key, value in parser_values.items() if value is not None}
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
return settings
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _add_provider_args(parser: argparse.ArgumentParser) -> None:
|
|
71
|
+
group = parser.add_argument_group("provider overrides")
|
|
72
|
+
group.add_argument("--ocr-provider", default=None)
|
|
73
|
+
group.add_argument("--ocr-model", default=None)
|
|
74
|
+
group.add_argument("--ocr-base-url", default=None)
|
|
75
|
+
group.add_argument("--ocr-api-key-env", default=None)
|
|
76
|
+
group.add_argument("--parser-provider", default=None)
|
|
77
|
+
group.add_argument("--parser-model", default=None)
|
|
78
|
+
group.add_argument("--parser-base-url", default=None)
|
|
79
|
+
group.add_argument("--parser-api-key-env", default=None)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _emit_result(result) -> None:
|
|
83
|
+
payload = {
|
|
84
|
+
"kind": result.kind,
|
|
85
|
+
"input_path": str(result.input_path),
|
|
86
|
+
"output_dir": str(result.output_dir),
|
|
87
|
+
"status": result.status,
|
|
88
|
+
"probe_path": str(result.probe_path) if result.probe_path else None,
|
|
89
|
+
"summary_path": str(result.summary_path) if result.summary_path else None,
|
|
90
|
+
"extra": result.extra,
|
|
91
|
+
}
|
|
92
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _emit_result_list(results) -> None:
|
|
96
|
+
print(
|
|
97
|
+
json.dumps(
|
|
98
|
+
[
|
|
99
|
+
{
|
|
100
|
+
"kind": result.kind,
|
|
101
|
+
"input_path": str(result.input_path),
|
|
102
|
+
"output_dir": str(result.output_dir),
|
|
103
|
+
"status": result.status,
|
|
104
|
+
"probe_path": str(result.probe_path) if result.probe_path else None,
|
|
105
|
+
"summary_path": str(result.summary_path) if result.summary_path else None,
|
|
106
|
+
"extra": result.extra,
|
|
107
|
+
}
|
|
108
|
+
for result in results
|
|
109
|
+
],
|
|
110
|
+
indent=2,
|
|
111
|
+
sort_keys=True,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _build_probe(output_dir: Path, *, command: str) -> WorkflowProbe:
|
|
117
|
+
probe = WorkflowProbe(output_dir / "workflow-events.jsonl")
|
|
118
|
+
probe.emit("cli.command_started", command=command, output_dir=str(output_dir))
|
|
119
|
+
return probe
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _add_common_output_args(parser: argparse.ArgumentParser) -> None:
|
|
123
|
+
parser.add_argument("--output-dir", required=True)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _ocr_command(args: argparse.Namespace) -> int:
|
|
127
|
+
provider_settings = _provider_settings_from_args(args)
|
|
128
|
+
output_dir = Path(args.output_dir)
|
|
129
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
130
|
+
probe = _build_probe(output_dir, command="ocr")
|
|
131
|
+
deps: dict[str, object] = {"parse_semantic_fn": _fallback_parse_semantic_fn}
|
|
132
|
+
if any(
|
|
133
|
+
value is not None
|
|
134
|
+
for value in (
|
|
135
|
+
args.parser_provider,
|
|
136
|
+
args.parser_model,
|
|
137
|
+
args.parser_base_url,
|
|
138
|
+
args.parser_api_key_env,
|
|
139
|
+
)
|
|
140
|
+
):
|
|
141
|
+
deps = {
|
|
142
|
+
"parse_semantic_fn": build_legacy_parse_semantic_fn(
|
|
143
|
+
provider_settings=provider_settings,
|
|
144
|
+
model_names=[args.parser_model] if args.parser_model else None,
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
try:
|
|
148
|
+
inputs = [Path(item) for item in args.inputs]
|
|
149
|
+
if len(inputs) == 1 and inputs[0].is_file():
|
|
150
|
+
result = run_ocr_source_workflow(
|
|
151
|
+
inputs[0],
|
|
152
|
+
output_dir=output_dir,
|
|
153
|
+
provider_settings=provider_settings,
|
|
154
|
+
ocr_candidate_models=args.ocr_candidate_model or None,
|
|
155
|
+
probe=probe,
|
|
156
|
+
deps=deps,
|
|
157
|
+
)
|
|
158
|
+
_emit_result(result)
|
|
159
|
+
else:
|
|
160
|
+
result = run_ocr_batch_workflow(
|
|
161
|
+
inputs,
|
|
162
|
+
output_dir=output_dir,
|
|
163
|
+
provider_settings=provider_settings,
|
|
164
|
+
ocr_candidate_models=args.ocr_candidate_model or None,
|
|
165
|
+
probe=probe,
|
|
166
|
+
deps=deps,
|
|
167
|
+
)
|
|
168
|
+
_emit_result_list(result)
|
|
169
|
+
probe.emit("cli.command_finished", status="ok")
|
|
170
|
+
return 0
|
|
171
|
+
finally:
|
|
172
|
+
probe.close()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _page_index_command(args: argparse.Namespace) -> int:
|
|
176
|
+
provider_settings = _provider_settings_from_args(args)
|
|
177
|
+
output_dir = Path(args.output_dir)
|
|
178
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
179
|
+
probe = _build_probe(output_dir, command="page-index")
|
|
180
|
+
try:
|
|
181
|
+
inputs = [Path(item) for item in args.inputs]
|
|
182
|
+
if len(inputs) == 1 and inputs[0].is_file():
|
|
183
|
+
result = run_page_index_source_workflow(
|
|
184
|
+
inputs[0],
|
|
185
|
+
output_dir=output_dir,
|
|
186
|
+
mode=args.mode,
|
|
187
|
+
source_format=args.source_format,
|
|
188
|
+
provider_settings=provider_settings,
|
|
189
|
+
probe=probe,
|
|
190
|
+
)
|
|
191
|
+
_emit_result(result)
|
|
192
|
+
else:
|
|
193
|
+
results = run_page_index_batch_workflow(
|
|
194
|
+
inputs,
|
|
195
|
+
output_dir=output_dir,
|
|
196
|
+
mode=args.mode,
|
|
197
|
+
source_format=args.source_format,
|
|
198
|
+
provider_settings=provider_settings,
|
|
199
|
+
probe=probe,
|
|
200
|
+
)
|
|
201
|
+
_emit_result_list(results)
|
|
202
|
+
probe.emit("cli.command_finished", status="ok")
|
|
203
|
+
return 0
|
|
204
|
+
finally:
|
|
205
|
+
probe.close()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _layerwise_command(args: argparse.Namespace) -> int:
|
|
209
|
+
output_dir = Path(args.output_dir)
|
|
210
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
211
|
+
probe = _build_probe(output_dir, command="layerwise")
|
|
212
|
+
try:
|
|
213
|
+
inputs = [Path(item) for item in args.inputs]
|
|
214
|
+
if len(inputs) == 1 and inputs[0].is_dir():
|
|
215
|
+
result = run_layerwise_source_workflow(
|
|
216
|
+
inputs[0],
|
|
217
|
+
output_dir=output_dir,
|
|
218
|
+
parsing_mode=args.parsing_mode,
|
|
219
|
+
max_depth=args.max_depth,
|
|
220
|
+
probe=probe,
|
|
221
|
+
)
|
|
222
|
+
_emit_result(result)
|
|
223
|
+
else:
|
|
224
|
+
results = run_layerwise_batch_workflow(
|
|
225
|
+
inputs,
|
|
226
|
+
output_dir=output_dir,
|
|
227
|
+
parsing_mode=args.parsing_mode,
|
|
228
|
+
max_depth=args.max_depth,
|
|
229
|
+
probe=probe,
|
|
230
|
+
)
|
|
231
|
+
_emit_result_list(results)
|
|
232
|
+
probe.emit("cli.command_finished", status="ok")
|
|
233
|
+
return 0
|
|
234
|
+
finally:
|
|
235
|
+
probe.close()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _demo_command(args: argparse.Namespace) -> int:
|
|
239
|
+
output_dir = Path(args.output_dir)
|
|
240
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
241
|
+
probe = _build_probe(output_dir, command="demo")
|
|
242
|
+
try:
|
|
243
|
+
config = DemoHarnessConfig(
|
|
244
|
+
output_dir=output_dir,
|
|
245
|
+
document_id=args.document_id,
|
|
246
|
+
title=args.title,
|
|
247
|
+
text=args.text,
|
|
248
|
+
parser_mode=args.parser_mode,
|
|
249
|
+
server_mode=args.server_mode,
|
|
250
|
+
external_base_url=args.external_base_url,
|
|
251
|
+
enable_sys_monitoring=not args.disable_sys_monitoring,
|
|
252
|
+
)
|
|
253
|
+
artifacts = run_demo_harness_workflow(config)
|
|
254
|
+
print(json.dumps(artifacts.__dict__, indent=2, sort_keys=True, default=str))
|
|
255
|
+
probe.emit("cli.command_finished", status="ok")
|
|
256
|
+
return 0
|
|
257
|
+
finally:
|
|
258
|
+
probe.close()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _ocr_smoke_assets_command(args: argparse.Namespace) -> int:
|
|
262
|
+
output_dir = Path(args.output_dir)
|
|
263
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
264
|
+
outputs = generate_ocr_smoke_assets(output_dir)
|
|
265
|
+
print(json.dumps({"output_dir": str(output_dir), **outputs}, indent=2, sort_keys=True))
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
270
|
+
parser = argparse.ArgumentParser(prog="workflow-ingest", description="Reusable workflow ingest commands")
|
|
271
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
272
|
+
|
|
273
|
+
ocr = subparsers.add_parser("ocr", help="Run OCR workflow for a file or folder")
|
|
274
|
+
ocr.add_argument("inputs", nargs="+", help="One image/PDF file or a folder of files")
|
|
275
|
+
_add_common_output_args(ocr)
|
|
276
|
+
_add_provider_args(ocr)
|
|
277
|
+
ocr.add_argument("--ocr-candidate-model", action="append", default=[], help="Candidate OCR model names")
|
|
278
|
+
ocr.set_defaults(func=_ocr_command)
|
|
279
|
+
|
|
280
|
+
page_index = subparsers.add_parser("page-index", help="Parse text/markdown into a page-index tree")
|
|
281
|
+
page_index.add_argument("inputs", nargs="+", help="One file or a folder of text/markdown files")
|
|
282
|
+
_add_common_output_args(page_index)
|
|
283
|
+
_add_provider_args(page_index)
|
|
284
|
+
page_index.add_argument("--mode", choices=["heuristic", "ollama"], default="heuristic")
|
|
285
|
+
page_index.add_argument("--source-format", choices=["auto", "text", "markdown"], default="auto")
|
|
286
|
+
page_index.set_defaults(func=_page_index_command)
|
|
287
|
+
|
|
288
|
+
layerwise = subparsers.add_parser("layerwise", help="Run the legacy recursive layerwise parser")
|
|
289
|
+
layerwise.add_argument("inputs", nargs="+", help="One legacy OCR folder or a folder batch")
|
|
290
|
+
_add_common_output_args(layerwise)
|
|
291
|
+
layerwise.add_argument("--parsing-mode", default="snippet")
|
|
292
|
+
layerwise.add_argument("--max-depth", type=int, default=10)
|
|
293
|
+
layerwise.set_defaults(func=_layerwise_command)
|
|
294
|
+
|
|
295
|
+
demo = subparsers.add_parser("demo", help="Run the workflow ingest demo harness")
|
|
296
|
+
_add_common_output_args(demo)
|
|
297
|
+
demo.add_argument("--document-id", default="demo-doc")
|
|
298
|
+
demo.add_argument("--title", default="Workflow Ingest Demo")
|
|
299
|
+
demo.add_argument("--text", default="Alpha clause\nBeta clause\nGamma clause")
|
|
300
|
+
demo.add_argument("--parser-mode", choices=["fake_layered", "legacy_cached"], default="fake_layered")
|
|
301
|
+
demo.add_argument(
|
|
302
|
+
"--server-mode",
|
|
303
|
+
choices=["testclient", "subprocess_http", "external_http"],
|
|
304
|
+
default="testclient",
|
|
305
|
+
)
|
|
306
|
+
demo.add_argument("--external-base-url", default=None)
|
|
307
|
+
demo.add_argument("--disable-sys-monitoring", action="store_true")
|
|
308
|
+
demo.set_defaults(func=_demo_command)
|
|
309
|
+
|
|
310
|
+
smoke = subparsers.add_parser("ocr-smoke-assets", help="Generate OCR smoke assets")
|
|
311
|
+
smoke.add_argument("--output-dir", required=True)
|
|
312
|
+
smoke.set_defaults(func=_ocr_smoke_assets_command)
|
|
313
|
+
|
|
314
|
+
return parser
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def main(argv: Iterable[str] | None = None) -> int:
|
|
318
|
+
parser = build_parser()
|
|
319
|
+
args = parser.parse_args(list(argv) if argv is not None else None)
|
|
320
|
+
return int(args.func(args))
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if __name__ == "__main__": # pragma: no cover
|
|
324
|
+
raise SystemExit(main())
|