everalgo-parser 0.2.0__tar.gz → 0.2.1__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.
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/.gitignore +4 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/CHANGELOG.md +14 -7
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/PKG-INFO +22 -14
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/README.md +19 -11
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/docs/architecture.md +1 -1
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/pyproject.toml +3 -3
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/__init__.py +8 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/_utils.py +3 -3
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/document.py +5 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/url.py +5 -5
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/LICENSE +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/audio.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/image.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/__init__.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/en/__init__.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/en/audio.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/en/document.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/en/image.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/zh/__init__.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/zh/audio.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/zh/document.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/prompts/zh/image.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/py.typed +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/src/everalgo/parser/video.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.bmp +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.docx +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.eml +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.htm +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.html +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.jpg +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.pdf +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.png +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.pptx +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.svg +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.tiff +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.txt +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.wav +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.webp +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/fixtures/sample.xlsx +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/__init__.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_audio.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_dispatch.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_document.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_e2e_all_formats.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_e2e_pdf_openrouter.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_image.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_public_api.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_url.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_utils.py +0 -0
- {everalgo_parser-0.2.0 → everalgo_parser-0.2.1}/tests/parser/test_video.py +0 -0
|
@@ -9,14 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
<!-- git-cliff-unreleased-end -->
|
|
11
11
|
|
|
12
|
+
## [0.2.1] - 2026-06-15
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **License relicensed from MIT to Apache-2.0** as part of the pre-open-source security audit.
|
|
17
|
+
|
|
12
18
|
## [0.2.0] - 2026-05-27
|
|
13
19
|
|
|
14
20
|
> First archived changelog. Entries below accumulated since the initial `0.1.0` PyPI release (published manually, without a git tag or per-package changelog), so this `0.2.0` section also consolidates the previously-unarchived `0.1.0` surface.
|
|
15
21
|
|
|
16
22
|
### Added
|
|
17
23
|
|
|
18
|
-
- **PDF parser** — single multimodal LLM call, mirrors
|
|
19
|
-
|
|
24
|
+
- **PDF parser** — single multimodal LLM call, mirrors the upstream
|
|
25
|
+
internal multimodal PDF parser reference implementation.
|
|
20
26
|
- **Image parser** — single LLM call for normal-ratio images; tall
|
|
21
27
|
screenshots (height/width > 10) are split into overlapping vertical
|
|
22
28
|
slices, OCR'd per slice, and merged with a second LLM call using
|
|
@@ -41,8 +47,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
41
47
|
unknown) → HTML handler with Open Graph / Twitter Card / `<meta>` tag
|
|
42
48
|
extraction into `ParsedContent.metadata`. `file://` and other local-
|
|
43
49
|
filesystem URIs are rejected (AGENTS.md §1). Inner modality recorded in
|
|
44
|
-
`metadata["inner_modality"]`. Metadata-extractor schema lifted from
|
|
45
|
-
|
|
50
|
+
`metadata["inner_modality"]`. Metadata-extractor schema lifted from the
|
|
51
|
+
upstream internal URL extractor reference implementation.
|
|
46
52
|
- **MIME-aware top-level dispatch** — `parser.aparse` now falls back to
|
|
47
53
|
`mime` when `extension` is missing or `UNKNOWN`, so the canonical
|
|
48
54
|
`RawFile(uri=..., mime="application/pdf")` example actually routes to
|
|
@@ -59,8 +65,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
59
65
|
|
|
60
66
|
### Notes
|
|
61
67
|
|
|
62
|
-
- `video` submodule remains a stub — upstream
|
|
63
|
-
video implementation to port; backend selection pending an ADR.
|
|
68
|
+
- `video` submodule remains a stub — the upstream internal multimodal parser
|
|
69
|
+
has no video implementation to port; backend selection pending an ADR.
|
|
64
70
|
- The parser layer deliberately omits retry / fallback / multi-key
|
|
65
71
|
rotation / Prometheus metrics. Those are deployment concerns
|
|
66
72
|
(ADR-012); algorithm-layer operators surface failures via `LLMError`
|
|
@@ -74,5 +80,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
74
80
|
- Optional `cairosvg >= 2.7.0` via `[svg]` extra.
|
|
75
81
|
- System: LibreOffice (`soffice` on PATH) for Office document parsing.
|
|
76
82
|
|
|
77
|
-
[Unreleased]: https://github.com/EverMind-AI/EverAlgo/compare/everalgo-parser/v0.2.
|
|
83
|
+
[Unreleased]: https://github.com/EverMind-AI/EverAlgo/compare/everalgo-parser/v0.2.1...HEAD
|
|
84
|
+
[0.2.1]: https://github.com/EverMind-AI/EverAlgo/compare/everalgo-parser/v0.2.0...everalgo-parser/v0.2.1
|
|
78
85
|
[0.2.0]: https://github.com/EverMind-AI/EverAlgo/releases/tag/everalgo-parser/v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: everalgo-parser
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: EverAlgo parser: multimodal raw-file parsing (image / audio / document / video / url) into ParsedContent.
|
|
5
5
|
Project-URL: Homepage, https://github.com/EverMind-AI/EverAlgo
|
|
6
6
|
Project-URL: Repository, https://github.com/EverMind-AI/EverAlgo
|
|
@@ -8,12 +8,12 @@ Project-URL: Issues, https://github.com/EverMind-AI/EverAlgo/issues
|
|
|
8
8
|
Project-URL: Documentation, https://github.com/EverMind-AI/EverAlgo/tree/main/packages/everalgo-parser
|
|
9
9
|
Project-URL: Changelog, https://github.com/EverMind-AI/EverAlgo/blob/main/packages/everalgo-parser/CHANGELOG.md
|
|
10
10
|
Author-email: EverMind <Evermind@shanda.com>
|
|
11
|
-
License-Expression:
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
12
|
License-File: LICENSE
|
|
13
13
|
Keywords: everalgo,llm,multimodal,parsing
|
|
14
14
|
Classifier: Development Status :: 3 - Alpha
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: License :: OSI Approved ::
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
@@ -29,33 +29,32 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
|
|
30
30
|
# everalgo-parser
|
|
31
31
|
|
|
32
|
-
Multimodal parsing — image / audio / document / video / url raw inputs into `ParsedContent`. Used by `everalgo-knowledge` for file ingestion and by
|
|
32
|
+
Multimodal parsing — image / audio / document / video / url raw inputs into `ParsedContent`. Used by `everalgo-knowledge` for file ingestion and by EverOS step 1 for inline parsing.
|
|
33
33
|
|
|
34
34
|
See the umbrella project: [EverAlgo monorepo](../../README.md) and the architecture document at [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md).
|
|
35
35
|
|
|
36
36
|
## Quick start
|
|
37
37
|
|
|
38
38
|
```python
|
|
39
|
-
import everalgo
|
|
40
39
|
from everalgo.llm.config import LLMConfig
|
|
41
40
|
from everalgo.llm.providers.openai_compat import OpenAICompatClient
|
|
42
41
|
from everalgo.parser import aparse, RawFile
|
|
43
42
|
|
|
44
|
-
#
|
|
45
|
-
# clients; OpenRouter is the reference deployment
|
|
46
|
-
# OpenRouter passthrough).
|
|
47
|
-
|
|
43
|
+
# Create an LLM client per call (or share one instance). The parser uses
|
|
44
|
+
# OpenAI-compatible clients; OpenRouter is the reference deployment
|
|
45
|
+
# (Gemini multimodal via OpenRouter passthrough).
|
|
46
|
+
client = OpenAICompatClient(LLMConfig(
|
|
48
47
|
model="google/gemini-3-flash-preview",
|
|
49
48
|
api_key="sk-or-v1-...",
|
|
50
49
|
base_url="https://openrouter.ai/api/v1",
|
|
51
|
-
))
|
|
50
|
+
))
|
|
52
51
|
|
|
53
52
|
# Bytes-in: caller already hydrated the file.
|
|
54
|
-
parsed = await aparse(RawFile(content=pdf_bytes, extension="pdf"))
|
|
53
|
+
parsed = await aparse(RawFile(content=pdf_bytes, extension="pdf"), llm=client)
|
|
55
54
|
print(parsed.text)
|
|
56
55
|
|
|
57
56
|
# URL-in: parser fetches over HTTP, then delegates to the HTML handler.
|
|
58
|
-
parsed = await aparse(RawFile(uri="https://example.com/article"))
|
|
57
|
+
parsed = await aparse(RawFile(uri="https://example.com/article"), llm=client)
|
|
59
58
|
print(parsed.metadata["title"], parsed.text[:500])
|
|
60
59
|
```
|
|
61
60
|
|
|
@@ -101,8 +100,17 @@ The parser detects `soffice` via `shutil.which("soffice")` and the canonical mac
|
|
|
101
100
|
- The library is **stateless**: it never reads the filesystem and never owns business state. HTTP I/O (LLM calls, URL fetching) is explicitly allowed.
|
|
102
101
|
- No retry / fallback / metrics inside operators — surface failures via `LLMError`, let the caller wrap.
|
|
103
102
|
|
|
103
|
+
## Security notes for callers
|
|
104
|
+
|
|
105
|
+
`everalgo-parser` is a stateless library; production safety properties belong to the calling service. The points below are easy to miss and worth wiring into your integration:
|
|
106
|
+
|
|
107
|
+
- **SSRF — `url.aparse` fetches arbitrary HTTP(S) URIs.** `fetch_uri` rejects non-http(s) schemes (no `file://`, no `gopher://`) but does **not** block private / link-local / loopback IPs (e.g. `127.0.0.1`, `10.0.0.0/8`, `169.254.169.254` cloud metadata, IPv6 ULA). If callers may pass attacker-controlled URLs, resolve the hostname first and reject private ranges before invoking `aparse`, or front the parser with an egress proxy that does the filtering.
|
|
108
|
+
- **Decompression-bomb cap is set at import time.** `everalgo/parser/__init__.py` pins `PIL.Image.MAX_IMAGE_PIXELS = 100_000_000` (10000×10000) — Pillow will raise `DecompressionBombError` above that. Re-assign after import if your workload genuinely needs larger images.
|
|
109
|
+
- **Office conversion runs LibreOffice as a subprocess.** `document._convert_to_pdf_via_soffice` validates the `extension` parameter (alphanumeric, ≤8 chars) before writing the temp file, but the LibreOffice attack surface itself is large. Run the host with a non-root user and treat untrusted office docs as you would any other complex binary input.
|
|
110
|
+
- **`cairosvg` (optional `[svg]` extra) is LGPL-3.0-or-later.** EverAlgo itself is Apache-2.0. LGPLv3 is compatible with Apache-2.0 when `cairosvg` is consumed as an unmodified library through its public Python API (which is what the SVG path does); if you statically vendor or modify `cairosvg`, LGPLv3 §4 / §5 obligations apply to that derivative. Not installing the `[svg]` extra removes the dependency entirely.
|
|
111
|
+
|
|
104
112
|
## Reference
|
|
105
113
|
|
|
106
114
|
- Architecture (definitive): [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md)
|
|
107
|
-
- Schema source for PDF / image / audio / document / html / email:
|
|
108
|
-
- Schema source for URL metadata extraction:
|
|
115
|
+
- Schema source for PDF / image / audio / document / html / email: upstream internal multimodal parser library (snapshot `prod-20260306-0331-v1`).
|
|
116
|
+
- Schema source for URL metadata extraction: upstream internal URL extractor reference implementation.
|
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
# everalgo-parser
|
|
2
2
|
|
|
3
|
-
Multimodal parsing — image / audio / document / video / url raw inputs into `ParsedContent`. Used by `everalgo-knowledge` for file ingestion and by
|
|
3
|
+
Multimodal parsing — image / audio / document / video / url raw inputs into `ParsedContent`. Used by `everalgo-knowledge` for file ingestion and by EverOS step 1 for inline parsing.
|
|
4
4
|
|
|
5
5
|
See the umbrella project: [EverAlgo monorepo](../../README.md) and the architecture document at [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md).
|
|
6
6
|
|
|
7
7
|
## Quick start
|
|
8
8
|
|
|
9
9
|
```python
|
|
10
|
-
import everalgo
|
|
11
10
|
from everalgo.llm.config import LLMConfig
|
|
12
11
|
from everalgo.llm.providers.openai_compat import OpenAICompatClient
|
|
13
12
|
from everalgo.parser import aparse, RawFile
|
|
14
13
|
|
|
15
|
-
#
|
|
16
|
-
# clients; OpenRouter is the reference deployment
|
|
17
|
-
# OpenRouter passthrough).
|
|
18
|
-
|
|
14
|
+
# Create an LLM client per call (or share one instance). The parser uses
|
|
15
|
+
# OpenAI-compatible clients; OpenRouter is the reference deployment
|
|
16
|
+
# (Gemini multimodal via OpenRouter passthrough).
|
|
17
|
+
client = OpenAICompatClient(LLMConfig(
|
|
19
18
|
model="google/gemini-3-flash-preview",
|
|
20
19
|
api_key="sk-or-v1-...",
|
|
21
20
|
base_url="https://openrouter.ai/api/v1",
|
|
22
|
-
))
|
|
21
|
+
))
|
|
23
22
|
|
|
24
23
|
# Bytes-in: caller already hydrated the file.
|
|
25
|
-
parsed = await aparse(RawFile(content=pdf_bytes, extension="pdf"))
|
|
24
|
+
parsed = await aparse(RawFile(content=pdf_bytes, extension="pdf"), llm=client)
|
|
26
25
|
print(parsed.text)
|
|
27
26
|
|
|
28
27
|
# URL-in: parser fetches over HTTP, then delegates to the HTML handler.
|
|
29
|
-
parsed = await aparse(RawFile(uri="https://example.com/article"))
|
|
28
|
+
parsed = await aparse(RawFile(uri="https://example.com/article"), llm=client)
|
|
30
29
|
print(parsed.metadata["title"], parsed.text[:500])
|
|
31
30
|
```
|
|
32
31
|
|
|
@@ -72,8 +71,17 @@ The parser detects `soffice` via `shutil.which("soffice")` and the canonical mac
|
|
|
72
71
|
- The library is **stateless**: it never reads the filesystem and never owns business state. HTTP I/O (LLM calls, URL fetching) is explicitly allowed.
|
|
73
72
|
- No retry / fallback / metrics inside operators — surface failures via `LLMError`, let the caller wrap.
|
|
74
73
|
|
|
74
|
+
## Security notes for callers
|
|
75
|
+
|
|
76
|
+
`everalgo-parser` is a stateless library; production safety properties belong to the calling service. The points below are easy to miss and worth wiring into your integration:
|
|
77
|
+
|
|
78
|
+
- **SSRF — `url.aparse` fetches arbitrary HTTP(S) URIs.** `fetch_uri` rejects non-http(s) schemes (no `file://`, no `gopher://`) but does **not** block private / link-local / loopback IPs (e.g. `127.0.0.1`, `10.0.0.0/8`, `169.254.169.254` cloud metadata, IPv6 ULA). If callers may pass attacker-controlled URLs, resolve the hostname first and reject private ranges before invoking `aparse`, or front the parser with an egress proxy that does the filtering.
|
|
79
|
+
- **Decompression-bomb cap is set at import time.** `everalgo/parser/__init__.py` pins `PIL.Image.MAX_IMAGE_PIXELS = 100_000_000` (10000×10000) — Pillow will raise `DecompressionBombError` above that. Re-assign after import if your workload genuinely needs larger images.
|
|
80
|
+
- **Office conversion runs LibreOffice as a subprocess.** `document._convert_to_pdf_via_soffice` validates the `extension` parameter (alphanumeric, ≤8 chars) before writing the temp file, but the LibreOffice attack surface itself is large. Run the host with a non-root user and treat untrusted office docs as you would any other complex binary input.
|
|
81
|
+
- **`cairosvg` (optional `[svg]` extra) is LGPL-3.0-or-later.** EverAlgo itself is Apache-2.0. LGPLv3 is compatible with Apache-2.0 when `cairosvg` is consumed as an unmodified library through its public Python API (which is what the SVG path does); if you statically vendor or modify `cairosvg`, LGPLv3 §4 / §5 obligations apply to that derivative. Not installing the `[svg]` extra removes the dependency entirely.
|
|
82
|
+
|
|
75
83
|
## Reference
|
|
76
84
|
|
|
77
85
|
- Architecture (definitive): [`docs/concepts/architecture.md`](../../docs/concepts/architecture.md)
|
|
78
|
-
- Schema source for PDF / image / audio / document / html / email:
|
|
79
|
-
- Schema source for URL metadata extraction:
|
|
86
|
+
- Schema source for PDF / image / audio / document / html / email: upstream internal multimodal parser library (snapshot `prod-20260306-0331-v1`).
|
|
87
|
+
- Schema source for URL metadata extraction: upstream internal URL extractor reference implementation.
|
|
@@ -301,7 +301,7 @@ flowchart LR
|
|
|
301
301
|
|
|
302
302
|
| 项 | 状态 | 备注 |
|
|
303
303
|
|---|---|---|
|
|
304
|
-
| `video.py` | stub |
|
|
304
|
+
| `video.py` | stub | 上游内部多模态解析库**也没有**实现,Gemini Video vs Whisper+帧抽取 选型待 ADR |
|
|
305
305
|
| `audio` BMP-like 转码 | 没做 | mp3/wav 测过;m4a/amr/aiff/aac/ogg/flac 未在 e2e 覆盖,Gemini 兼容度未全测 |
|
|
306
306
|
| `Office` 系统依赖动态检测降级 | 仅运行时报错 | 没有 import 时友好警告;`_find_soffice` 缺失就 `RuntimeError` |
|
|
307
307
|
| `T1` 主条目其他 schema | 待 RESOLVED | 我们只 RESOLVED 了 `T1.parser` 子集(`ParsedContent` / `Modality` / `RawFile`) |
|
|
@@ -4,17 +4,17 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "everalgo-parser"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "EverAlgo parser: multimodal raw-file parsing (image / audio / document / video / url) into ParsedContent."
|
|
9
9
|
requires-python = ">=3.12"
|
|
10
|
-
license = "
|
|
10
|
+
license = "Apache-2.0"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{ name = "EverMind", email = "Evermind@shanda.com" }]
|
|
13
13
|
keywords = ["llm", "multimodal", "parsing", "everalgo"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
|
-
"License :: OSI Approved ::
|
|
17
|
+
"License :: OSI Approved :: Apache Software License",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
19
19
|
"Programming Language :: Python :: 3.12",
|
|
20
20
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
@@ -14,6 +14,7 @@ from typing import TYPE_CHECKING
|
|
|
14
14
|
from urllib.parse import urlparse
|
|
15
15
|
|
|
16
16
|
from asgiref.sync import async_to_sync
|
|
17
|
+
from PIL import Image
|
|
17
18
|
|
|
18
19
|
from everalgo.parser import audio, document, image, url
|
|
19
20
|
from everalgo.types import (
|
|
@@ -39,6 +40,13 @@ __all__ = [
|
|
|
39
40
|
logger = logging.getLogger(__name__)
|
|
40
41
|
logger.addHandler(logging.NullHandler())
|
|
41
42
|
|
|
43
|
+
# Explicit decompression-bomb cap (Pillow default raises ``DecompressionBombError``
|
|
44
|
+
# only at ~2x this threshold). 100M pixels = 10000x10000, ample for any realistic
|
|
45
|
+
# screenshot / scan while keeping a hostile caller from exhausting memory via a
|
|
46
|
+
# crafted PNG/TIFF. Applications can override by re-assigning
|
|
47
|
+
# ``PIL.Image.MAX_IMAGE_PIXELS`` after importing the parser.
|
|
48
|
+
Image.MAX_IMAGE_PIXELS = 100_000_000
|
|
49
|
+
|
|
42
50
|
|
|
43
51
|
async def aparse(raw_file: RawFile, *, llm: LLMClient) -> ParsedContent:
|
|
44
52
|
"""Top-level dispatch: classify the input and route to a submodule.
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
- ``clean_html_content`` — strict text extraction (drops all tags).
|
|
7
7
|
- ``clean_html_for_llm`` — preserves structural tags inside ``<body>``,
|
|
8
8
|
strips known-noise tags and useless attributes, returns HTML.
|
|
9
|
-
- ``fetch_uri`` — HTTP(S) fetch for the URL parser. Adapted from
|
|
10
|
-
|
|
9
|
+
- ``fetch_uri`` — HTTP(S) fetch for the URL parser. Adapted from an
|
|
10
|
+
upstream internal URL extractor reference implementation.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
@@ -40,7 +40,7 @@ _DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; everalgo-parser/0.1; +https://ev
|
|
|
40
40
|
async def fetch_uri(uri: str, *, timeout: float = 30.0) -> tuple[bytes, str]:
|
|
41
41
|
"""Fetch ``http``/``https`` URI and return ``(content_bytes, content_type)``.
|
|
42
42
|
|
|
43
|
-
Adapted from
|
|
43
|
+
Adapted from an upstream internal URL extractor reference implementation;
|
|
44
44
|
rejects non-http(s) schemes per AGENTS.md §1 (no filesystem access).
|
|
45
45
|
|
|
46
46
|
Parameters
|
|
@@ -399,6 +399,11 @@ async def _convert_to_pdf_via_soffice(
|
|
|
399
399
|
content: bytes, extension: str, soffice: str
|
|
400
400
|
) -> bytes: # pragma: no cover (libreoffice binary, not in CI)
|
|
401
401
|
"""Run LibreOffice headless to convert bytes → PDF bytes."""
|
|
402
|
+
# Reject path-traversal / directory separators in `extension` so a hostile
|
|
403
|
+
# caller cannot escape the tempdir via e.g. ``extension="../../etc/passwd"``.
|
|
404
|
+
# `extension` is expected to be a short ASCII suffix (docx / xlsx / ...).
|
|
405
|
+
if not extension or not extension.isalnum() or len(extension) > 8:
|
|
406
|
+
raise ValueError(f"invalid extension for soffice conversion: {extension!r}")
|
|
402
407
|
with tempfile.TemporaryDirectory() as tmpdir: # pragma: no cover
|
|
403
408
|
in_path = Path(tmpdir) / f"input.{extension}" # pragma: no cover
|
|
404
409
|
in_path.write_bytes(content) # pragma: no cover
|
|
@@ -18,10 +18,10 @@ The returned ``ParsedContent.modality`` is always ``Modality.URL`` so
|
|
|
18
18
|
callers can distinguish "loaded from network" from "passed in as bytes";
|
|
19
19
|
the inner modality is recorded in ``metadata["inner_modality"]``.
|
|
20
20
|
|
|
21
|
-
Schema source for the metadata extractor:
|
|
22
|
-
``
|
|
23
|
-
|
|
24
|
-
``
|
|
21
|
+
Schema source for the metadata extractor: an upstream internal URL
|
|
22
|
+
extractor reference implementation (``_extract_og_tags`` /
|
|
23
|
+
``_extract_twitter_tags`` / ``_extract_meta_tags`` / ``_extract_title`` /
|
|
24
|
+
``_extract_favicon``).
|
|
25
25
|
"""
|
|
26
26
|
|
|
27
27
|
from __future__ import annotations
|
|
@@ -166,7 +166,7 @@ async def _dispatch_inner(
|
|
|
166
166
|
raise ValueError(f"url.aparse: cannot dispatch fetched body for modality={modality.value!r}")
|
|
167
167
|
|
|
168
168
|
|
|
169
|
-
# ---- metadata extraction (port from
|
|
169
|
+
# ---- metadata extraction (port from upstream internal URL extractor) ----
|
|
170
170
|
|
|
171
171
|
|
|
172
172
|
def _extract_metadata(html_text: str, *, base_url: str) -> dict[str, object]:
|
|
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
|