py-mre 1.1.0__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.
- py_mre-1.1.0/LICENSE +22 -0
- py_mre-1.1.0/PKG-INFO +292 -0
- py_mre-1.1.0/README.md +230 -0
- py_mre-1.1.0/mre/__init__.py +82 -0
- py_mre-1.1.0/mre/agent/__init__.py +41 -0
- py_mre-1.1.0/mre/agent/llm.py +85 -0
- py_mre-1.1.0/mre/agent/loop.py +429 -0
- py_mre-1.1.0/mre/agent/prompts.py +110 -0
- py_mre-1.1.0/mre/agent/schema.py +129 -0
- py_mre-1.1.0/mre/agent/views.py +24 -0
- py_mre-1.1.0/mre/appendix.py +199 -0
- py_mre-1.1.0/mre/format_detect.py +131 -0
- py_mre-1.1.0/mre/generate.py +157 -0
- py_mre-1.1.0/mre/generation.py +242 -0
- py_mre-1.1.0/mre/html_site_adapter.py +587 -0
- py_mre-1.1.0/mre/hwp_adapter.py +175 -0
- py_mre-1.1.0/mre/hwp_convert.py +138 -0
- py_mre-1.1.0/mre/llm_util.py +226 -0
- py_mre-1.1.0/mre/nodes.py +48 -0
- py_mre-1.1.0/mre/opc_adapter.py +357 -0
- py_mre-1.1.0/mre/pdf_adapter.py +219 -0
- py_mre-1.1.0/mre/reader.py +36 -0
- py_mre-1.1.0/mre/repair.py +332 -0
- py_mre-1.1.0/mre/xml_builder.py +107 -0
- py_mre-1.1.0/py_mre.egg-info/PKG-INFO +292 -0
- py_mre-1.1.0/py_mre.egg-info/SOURCES.txt +29 -0
- py_mre-1.1.0/py_mre.egg-info/dependency_links.txt +1 -0
- py_mre-1.1.0/py_mre.egg-info/requires.txt +20 -0
- py_mre-1.1.0/py_mre.egg-info/top_level.txt +1 -0
- py_mre-1.1.0/pyproject.toml +96 -0
- py_mre-1.1.0/setup.cfg +4 -0
py_mre-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Youngwoo Cho, Serin Chang
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
py_mre-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-mre
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Machine-Readable Extension (MRE): generate LLM-navigable structured headers for HTML/HWPX/DOCX documents, for agentic RAG
|
|
5
|
+
Author-email: selyn-a <126850773+selyn-a@users.noreply.github.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Youngwoo Cho, Serin Chang
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://github.com/Machine-Readable-Extension/mre
|
|
30
|
+
Project-URL: Repository, https://github.com/Machine-Readable-Extension/mre
|
|
31
|
+
Keywords: rag,retrieval-augmented-generation,llm-agent,mre,document-structure
|
|
32
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: Intended Audience :: Science/Research
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
41
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
42
|
+
Requires-Python: >=3.10
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Requires-Dist: openai>=1.0
|
|
46
|
+
Requires-Dist: beautifulsoup4>=4.9
|
|
47
|
+
Requires-Dist: lxml>=4.6
|
|
48
|
+
Requires-Dist: olefile>=0.46
|
|
49
|
+
Requires-Dist: pypdf>=4.0
|
|
50
|
+
Provides-Extra: fuzzy
|
|
51
|
+
Requires-Dist: thefuzz>=0.19; extra == "fuzzy"
|
|
52
|
+
Provides-Extra: test
|
|
53
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
54
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "test"
|
|
55
|
+
Requires-Dist: pytest-cov>=5.0; extra == "test"
|
|
56
|
+
Provides-Extra: docs
|
|
57
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
58
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
59
|
+
Provides-Extra: typecheck
|
|
60
|
+
Requires-Dist: mypy>=1.8; extra == "typecheck"
|
|
61
|
+
Dynamic: license-file
|
|
62
|
+
|
|
63
|
+
# Machine-Readable-Extension(MRE)
|
|
64
|
+
|
|
65
|
+
[](https://github.com/Machine-Readable-Extension/mre/actions/workflows/tests.yml)
|
|
66
|
+
[](https://sonarcloud.io/summary/new_code?id=Machine-Readable-Extension_mre)
|
|
67
|
+
[](https://sonarcloud.io/summary/new_code?id=Machine-Readable-Extension_mre)
|
|
68
|
+
[](https://sonarcloud.io/summary/new_code?id=Machine-Readable-Extension_mre)
|
|
69
|
+
[](https://machine-readable-extension.github.io/mre/)
|
|
70
|
+
|
|
71
|
+
**Machine-Readable Extension (MRE)** — a producer-side document standard and
|
|
72
|
+
navigation structure that lets LLM agents read a document precisely, instead
|
|
73
|
+
of consuming its raw, markup-heavy source.
|
|
74
|
+
|
|
75
|
+
Full docs, including the auto-generated API reference: **[machine-readable-extension.github.io/mre](https://machine-readable-extension.github.io/mre/)**
|
|
76
|
+
|
|
77
|
+
## Why
|
|
78
|
+
|
|
79
|
+
The web is built for human eyes: HTML pages are dominated by markup tags,
|
|
80
|
+
scripts, styling, and boilerplate that a browser renders away but an LLM
|
|
81
|
+
agent has to read in full. The same overhead shows up in XML-based formats
|
|
82
|
+
like DOCX, HWPX, and EPUB. That noise raises inference cost and increases the
|
|
83
|
+
risk of an agent missing the passage it actually needed, buried somewhere in
|
|
84
|
+
a long context.
|
|
85
|
+
|
|
86
|
+
MRE takes a different approach from *cleaning documents up after the fact*
|
|
87
|
+
(heuristic scrapers, LLM-written parsers per site). It puts a small,
|
|
88
|
+
standardized XML header into the document **once, at publication time** —
|
|
89
|
+
much like `robots.txt` or `sitemap.xml` let producers tell crawlers where to
|
|
90
|
+
go. The header doesn't just say what a document is about; it tells an agent
|
|
91
|
+
exactly which paragraph to look at, by a stable ID. An agent reads the
|
|
92
|
+
header, requests only the paragraphs it needs, and never has to touch the
|
|
93
|
+
raw markup.
|
|
94
|
+
|
|
95
|
+
This package covers both ends of that story for a registered site: it
|
|
96
|
+
generates and embeds MRE headers into a document (`generate_mre()`), and it
|
|
97
|
+
retrieves a specific paragraph's full text back out of an MRE-embedded
|
|
98
|
+
document by ID (`fetch_block()`) — the same per-site adapter backs both, so
|
|
99
|
+
a site's owner writes their parsing logic once and it stays correct on both
|
|
100
|
+
sides. Everything *above* that — the agent's turn-by-turn reasoning loop
|
|
101
|
+
deciding which IDs to request — is a separate concern, kept out of this
|
|
102
|
+
core package. An opt-in `mre.agent` subpackage implements one such loop for
|
|
103
|
+
anyone who wants it too, not just the primitives — see
|
|
104
|
+
[Agentic RAG](#agentic-rag), below.
|
|
105
|
+
|
|
106
|
+
## What it looks like
|
|
107
|
+
|
|
108
|
+
```xml
|
|
109
|
+
<html>
|
|
110
|
+
<head>
|
|
111
|
+
...
|
|
112
|
+
<script type="application/mre+xml">
|
|
113
|
+
<mre version="1.0">
|
|
114
|
+
<metadata>
|
|
115
|
+
<title>Pleasure Cove</title>
|
|
116
|
+
<summary>Pleasure Cove is ...</summary>
|
|
117
|
+
</metadata>
|
|
118
|
+
<tree>
|
|
119
|
+
<node id="p1">
|
|
120
|
+
<desc>Released in 1979 on the ABC network.</desc>
|
|
121
|
+
<keys>Pleasure Cove, 1979, ABC</keys>
|
|
122
|
+
</node>
|
|
123
|
+
<node id="p2">
|
|
124
|
+
<desc>Protagonist is a conman at the resort.</desc>
|
|
125
|
+
<keys>Raymond Gordon, conman, resort</keys>
|
|
126
|
+
</node>
|
|
127
|
+
<node id="p3">
|
|
128
|
+
<desc>Tom Jones stars as Raymond Gordon.</desc>
|
|
129
|
+
<keys>Tom Jones, Raymond Gordon</keys>
|
|
130
|
+
</node>
|
|
131
|
+
</tree>
|
|
132
|
+
</mre>
|
|
133
|
+
</script>
|
|
134
|
+
</head>
|
|
135
|
+
<body>
|
|
136
|
+
...
|
|
137
|
+
</body>
|
|
138
|
+
</html>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`<metadata>` carries the document's title and a short summary — it lets an
|
|
142
|
+
agent judge whether the *whole document* is worth exploring before reading
|
|
143
|
+
anything else. `<tree>` maps the document's paragraphs: each `<node>` has a
|
|
144
|
+
stable ID, a `<desc>` naming what the paragraph asserts, and `<keys>` —
|
|
145
|
+
distinctive entity names that let an agent bridge across documents when
|
|
146
|
+
chasing a multi-hop query. Paragraph text itself is *not* in the header —
|
|
147
|
+
it's fetched on demand by ID from the source document, by a separate
|
|
148
|
+
retrieval-side parser. Full schema: [Specification](https://machine-readable-extension.github.io/mre/spec/).
|
|
149
|
+
|
|
150
|
+
Early experiments on MRE-based agentic RAG show up to a 48.4% relative F1
|
|
151
|
+
improvement over baselines on multi-hop QA benchmarks, by letting the agent
|
|
152
|
+
target the right paragraph instead of reading (or missing) it inside a
|
|
153
|
+
long, noisy context. Write-up is in progress (not yet on arXiv); benchmark
|
|
154
|
+
and evaluation code lives at
|
|
155
|
+
[Lactobacillus/machine-readable-extension](https://github.com/Lactobacillus/machine-readable-extension).
|
|
156
|
+
|
|
157
|
+
## Install
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pip install py-mre
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The PyPI distribution is named `py-mre` (`mre` itself was already taken by an
|
|
164
|
+
unrelated project) — `import mre` either way.
|
|
165
|
+
|
|
166
|
+
The keyword-grounding [repair pass](https://machine-readable-extension.github.io/mre/quickstart/#repair)
|
|
167
|
+
has an optional fuzzy-matching fallback:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pip install "py-mre[fuzzy]"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Quick start
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
import asyncio
|
|
177
|
+
import openai
|
|
178
|
+
from mre import generate_mre
|
|
179
|
+
|
|
180
|
+
async def main():
|
|
181
|
+
# Any OpenAI-compatible async client works — the official SDK, or a
|
|
182
|
+
# vLLM / other OpenAI-compatible server (just point base_url at it).
|
|
183
|
+
client = openai.AsyncOpenAI()
|
|
184
|
+
|
|
185
|
+
with open("pleasure_cove.html", encoding="utf-8") as f:
|
|
186
|
+
html = f.read()
|
|
187
|
+
|
|
188
|
+
result = await generate_mre(
|
|
189
|
+
html,
|
|
190
|
+
client=client,
|
|
191
|
+
model="gpt-4o-mini",
|
|
192
|
+
title="Pleasure Cove",
|
|
193
|
+
url="https://en.wikipedia.org/wiki/Pleasure_Cove", # picks the site adapter
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
print(result.mre_xml) # the generated <mre> block
|
|
197
|
+
print(result.embedded_html) # original HTML with MRE injected into <head>
|
|
198
|
+
|
|
199
|
+
asyncio.run(main())
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`generate_mre()` auto-detects the document format from `fmt`/`url`/magic
|
|
203
|
+
bytes, dispatches to the right adapter, calls the LLM to fill in per-paragraph
|
|
204
|
+
`<desc>`/`<keys>` and a document `<summary>`, repairs any paragraphs the LLM
|
|
205
|
+
skipped, assembles the `<mre>` XML, and (by default) embeds it back into the
|
|
206
|
+
document. Model choice isn't hardcoded — you always pass your own
|
|
207
|
+
`(client, model)`.
|
|
208
|
+
|
|
209
|
+
### Fetching a paragraph back out
|
|
210
|
+
|
|
211
|
+
An agent reading the header decides which `id`s it needs, then calls
|
|
212
|
+
`fetch_block()` (HTML only, for now) to get that paragraph's full text —
|
|
213
|
+
untruncated, unlike the short preview `<desc>` the LLM saw while generating:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from mre import fetch_block
|
|
217
|
+
|
|
218
|
+
text = fetch_block(
|
|
219
|
+
"https://en.wikipedia.org/wiki/Pleasure_Cove", # same url, to pick the adapter
|
|
220
|
+
result.embedded_html,
|
|
221
|
+
"p2",
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Pass `"full"` as the id to get the whole document's text at once, for a
|
|
226
|
+
workflow where the agent decides a single paragraph isn't enough context.
|
|
227
|
+
This goes through the *same* site adapter as generation — for a document
|
|
228
|
+
whose adapter doesn't implement `fetch` (a generation-only adapter),
|
|
229
|
+
`fetch_block()` raises `FetchNotSupportedError` rather than guessing.
|
|
230
|
+
|
|
231
|
+
`generate_mre()`/`fetch_block()` also guard against a document outliving the
|
|
232
|
+
adapter that generated it (a `generator-fingerprint` mismatch) — see
|
|
233
|
+
[Detecting a stale adapter](https://machine-readable-extension.github.io/mre/quickstart/#detecting-a-stale-adapter)
|
|
234
|
+
in the docs.
|
|
235
|
+
|
|
236
|
+
## Agentic RAG
|
|
237
|
+
|
|
238
|
+
`mre` core stops at generating and fetching — deciding which `id`s to
|
|
239
|
+
request, turn by turn, is a separate concern kept out of the base package.
|
|
240
|
+
The opt-in `mre.agent` subpackage implements that loop, using **progressive
|
|
241
|
+
disclosure**: every candidate document starts out showing only its
|
|
242
|
+
`<metadata>`, and the agent expands specific documents' full `<tree>` (or
|
|
243
|
+
fetches them whole) before drilling into individual paragraphs. Candidate
|
|
244
|
+
documents can be HTML, HWPX, DOCX, or PDF, freely mixed in the same run.
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
from mre.agent import run_agent
|
|
248
|
+
|
|
249
|
+
result = await run_agent(
|
|
250
|
+
"Who starred in Pleasure Cove?",
|
|
251
|
+
{"Pleasure Cove": {"html": embedded_html, "url": "https://en.wikipedia.org/wiki/Pleasure_Cove"}},
|
|
252
|
+
client=client,
|
|
253
|
+
model="gpt-4o-mini",
|
|
254
|
+
)
|
|
255
|
+
print(result.answer, result.success, result.stats)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Every piece `run_agent()` is built from — the system prompt, the guided-
|
|
259
|
+
decoding schemas, the metadata view — is independently importable too, for
|
|
260
|
+
wiring MRE into a different agent loop instead. Full walkthrough:
|
|
261
|
+
[Agentic RAG](https://machine-readable-extension.github.io/mre/agentic-rag/).
|
|
262
|
+
|
|
263
|
+
## Supported document formats
|
|
264
|
+
|
|
265
|
+
| Format | Parsing | Embedding | Fetch |
|
|
266
|
+
|---|---|---|---|
|
|
267
|
+
| HTML (Wikipedia) | built-in site adapter | `<script type="application/mre+xml">` inside `<head>` | `fetch_block()` |
|
|
268
|
+
| HWPX | built-in | extra `mre.xml` entry in the zip archive | `fetch_opc()` |
|
|
269
|
+
| DOCX | built-in (body paragraphs only — table cells are out of scope) | extra `mre.xml` entry in the zip archive | `fetch_opc()` |
|
|
270
|
+
| HWP (legacy, OLE2) | built-in, parsing-only — `mre.hwp_adapter.parse_hwp()` | not implemented | not yet |
|
|
271
|
+
| PDF | built-in, paragraph text only — see [Legacy HWP / PDF](https://machine-readable-extension.github.io/mre/formats/) | `mre.xml` as a PDF file attachment | `fetch_pdf()` |
|
|
272
|
+
|
|
273
|
+
> ⚠️ **HWP has no embed path, and the one workaround has measured content loss.**
|
|
274
|
+
> `mre.convert_hwp(path, target=DocFormat.DOCX)` shells out to an externally-installed
|
|
275
|
+
> LibreOffice + [H2Orestart](https://github.com/ebandal/H2Orestart) (a community
|
|
276
|
+
> reverse-engineered filter, not Hancom's own converter) so you can embed into the
|
|
277
|
+
> converted docx/pdf instead. A revision-history entry vanished entirely in testing
|
|
278
|
+
> against a real government document — verify important documents' output before
|
|
279
|
+
> trusting it. See [Legacy HWP](https://machine-readable-extension.github.io/mre/formats/#legacy-hwp-parsing-only)
|
|
280
|
+
> for the full fidelity numbers and usage.
|
|
281
|
+
|
|
282
|
+
HTML support is a **site-adapter registry**, not a generic scraper — only
|
|
283
|
+
`wikipedia.org` ships out of the box, but a new site can be registered
|
|
284
|
+
in-process or shipped as an installable plugin package (an `mre.site_adapters`
|
|
285
|
+
entry point). See [`examples/mre-example-adapter/`](examples/mre-example-adapter)
|
|
286
|
+
for a working reference, and
|
|
287
|
+
[Document formats](https://machine-readable-extension.github.io/mre/formats/)
|
|
288
|
+
for the full guide to adding a site.
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT — see [LICENSE](LICENSE).
|
py_mre-1.1.0/README.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Machine-Readable-Extension(MRE)
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Machine-Readable-Extension/mre/actions/workflows/tests.yml)
|
|
4
|
+
[](https://sonarcloud.io/summary/new_code?id=Machine-Readable-Extension_mre)
|
|
5
|
+
[](https://sonarcloud.io/summary/new_code?id=Machine-Readable-Extension_mre)
|
|
6
|
+
[](https://sonarcloud.io/summary/new_code?id=Machine-Readable-Extension_mre)
|
|
7
|
+
[](https://machine-readable-extension.github.io/mre/)
|
|
8
|
+
|
|
9
|
+
**Machine-Readable Extension (MRE)** — a producer-side document standard and
|
|
10
|
+
navigation structure that lets LLM agents read a document precisely, instead
|
|
11
|
+
of consuming its raw, markup-heavy source.
|
|
12
|
+
|
|
13
|
+
Full docs, including the auto-generated API reference: **[machine-readable-extension.github.io/mre](https://machine-readable-extension.github.io/mre/)**
|
|
14
|
+
|
|
15
|
+
## Why
|
|
16
|
+
|
|
17
|
+
The web is built for human eyes: HTML pages are dominated by markup tags,
|
|
18
|
+
scripts, styling, and boilerplate that a browser renders away but an LLM
|
|
19
|
+
agent has to read in full. The same overhead shows up in XML-based formats
|
|
20
|
+
like DOCX, HWPX, and EPUB. That noise raises inference cost and increases the
|
|
21
|
+
risk of an agent missing the passage it actually needed, buried somewhere in
|
|
22
|
+
a long context.
|
|
23
|
+
|
|
24
|
+
MRE takes a different approach from *cleaning documents up after the fact*
|
|
25
|
+
(heuristic scrapers, LLM-written parsers per site). It puts a small,
|
|
26
|
+
standardized XML header into the document **once, at publication time** —
|
|
27
|
+
much like `robots.txt` or `sitemap.xml` let producers tell crawlers where to
|
|
28
|
+
go. The header doesn't just say what a document is about; it tells an agent
|
|
29
|
+
exactly which paragraph to look at, by a stable ID. An agent reads the
|
|
30
|
+
header, requests only the paragraphs it needs, and never has to touch the
|
|
31
|
+
raw markup.
|
|
32
|
+
|
|
33
|
+
This package covers both ends of that story for a registered site: it
|
|
34
|
+
generates and embeds MRE headers into a document (`generate_mre()`), and it
|
|
35
|
+
retrieves a specific paragraph's full text back out of an MRE-embedded
|
|
36
|
+
document by ID (`fetch_block()`) — the same per-site adapter backs both, so
|
|
37
|
+
a site's owner writes their parsing logic once and it stays correct on both
|
|
38
|
+
sides. Everything *above* that — the agent's turn-by-turn reasoning loop
|
|
39
|
+
deciding which IDs to request — is a separate concern, kept out of this
|
|
40
|
+
core package. An opt-in `mre.agent` subpackage implements one such loop for
|
|
41
|
+
anyone who wants it too, not just the primitives — see
|
|
42
|
+
[Agentic RAG](#agentic-rag), below.
|
|
43
|
+
|
|
44
|
+
## What it looks like
|
|
45
|
+
|
|
46
|
+
```xml
|
|
47
|
+
<html>
|
|
48
|
+
<head>
|
|
49
|
+
...
|
|
50
|
+
<script type="application/mre+xml">
|
|
51
|
+
<mre version="1.0">
|
|
52
|
+
<metadata>
|
|
53
|
+
<title>Pleasure Cove</title>
|
|
54
|
+
<summary>Pleasure Cove is ...</summary>
|
|
55
|
+
</metadata>
|
|
56
|
+
<tree>
|
|
57
|
+
<node id="p1">
|
|
58
|
+
<desc>Released in 1979 on the ABC network.</desc>
|
|
59
|
+
<keys>Pleasure Cove, 1979, ABC</keys>
|
|
60
|
+
</node>
|
|
61
|
+
<node id="p2">
|
|
62
|
+
<desc>Protagonist is a conman at the resort.</desc>
|
|
63
|
+
<keys>Raymond Gordon, conman, resort</keys>
|
|
64
|
+
</node>
|
|
65
|
+
<node id="p3">
|
|
66
|
+
<desc>Tom Jones stars as Raymond Gordon.</desc>
|
|
67
|
+
<keys>Tom Jones, Raymond Gordon</keys>
|
|
68
|
+
</node>
|
|
69
|
+
</tree>
|
|
70
|
+
</mre>
|
|
71
|
+
</script>
|
|
72
|
+
</head>
|
|
73
|
+
<body>
|
|
74
|
+
...
|
|
75
|
+
</body>
|
|
76
|
+
</html>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`<metadata>` carries the document's title and a short summary — it lets an
|
|
80
|
+
agent judge whether the *whole document* is worth exploring before reading
|
|
81
|
+
anything else. `<tree>` maps the document's paragraphs: each `<node>` has a
|
|
82
|
+
stable ID, a `<desc>` naming what the paragraph asserts, and `<keys>` —
|
|
83
|
+
distinctive entity names that let an agent bridge across documents when
|
|
84
|
+
chasing a multi-hop query. Paragraph text itself is *not* in the header —
|
|
85
|
+
it's fetched on demand by ID from the source document, by a separate
|
|
86
|
+
retrieval-side parser. Full schema: [Specification](https://machine-readable-extension.github.io/mre/spec/).
|
|
87
|
+
|
|
88
|
+
Early experiments on MRE-based agentic RAG show up to a 48.4% relative F1
|
|
89
|
+
improvement over baselines on multi-hop QA benchmarks, by letting the agent
|
|
90
|
+
target the right paragraph instead of reading (or missing) it inside a
|
|
91
|
+
long, noisy context. Write-up is in progress (not yet on arXiv); benchmark
|
|
92
|
+
and evaluation code lives at
|
|
93
|
+
[Lactobacillus/machine-readable-extension](https://github.com/Lactobacillus/machine-readable-extension).
|
|
94
|
+
|
|
95
|
+
## Install
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install py-mre
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The PyPI distribution is named `py-mre` (`mre` itself was already taken by an
|
|
102
|
+
unrelated project) — `import mre` either way.
|
|
103
|
+
|
|
104
|
+
The keyword-grounding [repair pass](https://machine-readable-extension.github.io/mre/quickstart/#repair)
|
|
105
|
+
has an optional fuzzy-matching fallback:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install "py-mre[fuzzy]"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Quick start
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import asyncio
|
|
115
|
+
import openai
|
|
116
|
+
from mre import generate_mre
|
|
117
|
+
|
|
118
|
+
async def main():
|
|
119
|
+
# Any OpenAI-compatible async client works — the official SDK, or a
|
|
120
|
+
# vLLM / other OpenAI-compatible server (just point base_url at it).
|
|
121
|
+
client = openai.AsyncOpenAI()
|
|
122
|
+
|
|
123
|
+
with open("pleasure_cove.html", encoding="utf-8") as f:
|
|
124
|
+
html = f.read()
|
|
125
|
+
|
|
126
|
+
result = await generate_mre(
|
|
127
|
+
html,
|
|
128
|
+
client=client,
|
|
129
|
+
model="gpt-4o-mini",
|
|
130
|
+
title="Pleasure Cove",
|
|
131
|
+
url="https://en.wikipedia.org/wiki/Pleasure_Cove", # picks the site adapter
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
print(result.mre_xml) # the generated <mre> block
|
|
135
|
+
print(result.embedded_html) # original HTML with MRE injected into <head>
|
|
136
|
+
|
|
137
|
+
asyncio.run(main())
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`generate_mre()` auto-detects the document format from `fmt`/`url`/magic
|
|
141
|
+
bytes, dispatches to the right adapter, calls the LLM to fill in per-paragraph
|
|
142
|
+
`<desc>`/`<keys>` and a document `<summary>`, repairs any paragraphs the LLM
|
|
143
|
+
skipped, assembles the `<mre>` XML, and (by default) embeds it back into the
|
|
144
|
+
document. Model choice isn't hardcoded — you always pass your own
|
|
145
|
+
`(client, model)`.
|
|
146
|
+
|
|
147
|
+
### Fetching a paragraph back out
|
|
148
|
+
|
|
149
|
+
An agent reading the header decides which `id`s it needs, then calls
|
|
150
|
+
`fetch_block()` (HTML only, for now) to get that paragraph's full text —
|
|
151
|
+
untruncated, unlike the short preview `<desc>` the LLM saw while generating:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from mre import fetch_block
|
|
155
|
+
|
|
156
|
+
text = fetch_block(
|
|
157
|
+
"https://en.wikipedia.org/wiki/Pleasure_Cove", # same url, to pick the adapter
|
|
158
|
+
result.embedded_html,
|
|
159
|
+
"p2",
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Pass `"full"` as the id to get the whole document's text at once, for a
|
|
164
|
+
workflow where the agent decides a single paragraph isn't enough context.
|
|
165
|
+
This goes through the *same* site adapter as generation — for a document
|
|
166
|
+
whose adapter doesn't implement `fetch` (a generation-only adapter),
|
|
167
|
+
`fetch_block()` raises `FetchNotSupportedError` rather than guessing.
|
|
168
|
+
|
|
169
|
+
`generate_mre()`/`fetch_block()` also guard against a document outliving the
|
|
170
|
+
adapter that generated it (a `generator-fingerprint` mismatch) — see
|
|
171
|
+
[Detecting a stale adapter](https://machine-readable-extension.github.io/mre/quickstart/#detecting-a-stale-adapter)
|
|
172
|
+
in the docs.
|
|
173
|
+
|
|
174
|
+
## Agentic RAG
|
|
175
|
+
|
|
176
|
+
`mre` core stops at generating and fetching — deciding which `id`s to
|
|
177
|
+
request, turn by turn, is a separate concern kept out of the base package.
|
|
178
|
+
The opt-in `mre.agent` subpackage implements that loop, using **progressive
|
|
179
|
+
disclosure**: every candidate document starts out showing only its
|
|
180
|
+
`<metadata>`, and the agent expands specific documents' full `<tree>` (or
|
|
181
|
+
fetches them whole) before drilling into individual paragraphs. Candidate
|
|
182
|
+
documents can be HTML, HWPX, DOCX, or PDF, freely mixed in the same run.
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from mre.agent import run_agent
|
|
186
|
+
|
|
187
|
+
result = await run_agent(
|
|
188
|
+
"Who starred in Pleasure Cove?",
|
|
189
|
+
{"Pleasure Cove": {"html": embedded_html, "url": "https://en.wikipedia.org/wiki/Pleasure_Cove"}},
|
|
190
|
+
client=client,
|
|
191
|
+
model="gpt-4o-mini",
|
|
192
|
+
)
|
|
193
|
+
print(result.answer, result.success, result.stats)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Every piece `run_agent()` is built from — the system prompt, the guided-
|
|
197
|
+
decoding schemas, the metadata view — is independently importable too, for
|
|
198
|
+
wiring MRE into a different agent loop instead. Full walkthrough:
|
|
199
|
+
[Agentic RAG](https://machine-readable-extension.github.io/mre/agentic-rag/).
|
|
200
|
+
|
|
201
|
+
## Supported document formats
|
|
202
|
+
|
|
203
|
+
| Format | Parsing | Embedding | Fetch |
|
|
204
|
+
|---|---|---|---|
|
|
205
|
+
| HTML (Wikipedia) | built-in site adapter | `<script type="application/mre+xml">` inside `<head>` | `fetch_block()` |
|
|
206
|
+
| HWPX | built-in | extra `mre.xml` entry in the zip archive | `fetch_opc()` |
|
|
207
|
+
| DOCX | built-in (body paragraphs only — table cells are out of scope) | extra `mre.xml` entry in the zip archive | `fetch_opc()` |
|
|
208
|
+
| HWP (legacy, OLE2) | built-in, parsing-only — `mre.hwp_adapter.parse_hwp()` | not implemented | not yet |
|
|
209
|
+
| PDF | built-in, paragraph text only — see [Legacy HWP / PDF](https://machine-readable-extension.github.io/mre/formats/) | `mre.xml` as a PDF file attachment | `fetch_pdf()` |
|
|
210
|
+
|
|
211
|
+
> ⚠️ **HWP has no embed path, and the one workaround has measured content loss.**
|
|
212
|
+
> `mre.convert_hwp(path, target=DocFormat.DOCX)` shells out to an externally-installed
|
|
213
|
+
> LibreOffice + [H2Orestart](https://github.com/ebandal/H2Orestart) (a community
|
|
214
|
+
> reverse-engineered filter, not Hancom's own converter) so you can embed into the
|
|
215
|
+
> converted docx/pdf instead. A revision-history entry vanished entirely in testing
|
|
216
|
+
> against a real government document — verify important documents' output before
|
|
217
|
+
> trusting it. See [Legacy HWP](https://machine-readable-extension.github.io/mre/formats/#legacy-hwp-parsing-only)
|
|
218
|
+
> for the full fidelity numbers and usage.
|
|
219
|
+
|
|
220
|
+
HTML support is a **site-adapter registry**, not a generic scraper — only
|
|
221
|
+
`wikipedia.org` ships out of the box, but a new site can be registered
|
|
222
|
+
in-process or shipped as an installable plugin package (an `mre.site_adapters`
|
|
223
|
+
entry point). See [`examples/mre-example-adapter/`](examples/mre-example-adapter)
|
|
224
|
+
for a working reference, and
|
|
225
|
+
[Document formats](https://machine-readable-extension.github.io/mre/formats/)
|
|
226
|
+
for the full guide to adding a site.
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
__version__ = "1.1.0"
|
|
2
|
+
|
|
3
|
+
from mre.format_detect import DocFormat, FormatDetectionError, detect_format
|
|
4
|
+
from mre.html_site_adapter import (
|
|
5
|
+
FetchNotSupportedError,
|
|
6
|
+
GeneratorFingerprintMismatch,
|
|
7
|
+
HTMLSiteAdapter,
|
|
8
|
+
UnknownSiteError,
|
|
9
|
+
compute_adapter_fingerprint,
|
|
10
|
+
detect_site,
|
|
11
|
+
discover_plugin_adapters,
|
|
12
|
+
fetch_block,
|
|
13
|
+
get_site_adapter,
|
|
14
|
+
parse_html,
|
|
15
|
+
register_site,
|
|
16
|
+
registered_sites,
|
|
17
|
+
)
|
|
18
|
+
from mre.opc_adapter import (
|
|
19
|
+
OPCAdapter,
|
|
20
|
+
embed_mre_opc,
|
|
21
|
+
extract_mre_xml_opc,
|
|
22
|
+
fetch_opc,
|
|
23
|
+
get_opc_adapter,
|
|
24
|
+
parse_opc,
|
|
25
|
+
)
|
|
26
|
+
from mre.generate import MREGenerationResult, generate_mre
|
|
27
|
+
from mre.hwp_adapter import parse_hwp
|
|
28
|
+
from mre.hwp_convert import (
|
|
29
|
+
HwpConversionError,
|
|
30
|
+
LibreOfficeNotAvailableError,
|
|
31
|
+
convert_hwp,
|
|
32
|
+
)
|
|
33
|
+
from mre.pdf_adapter import (
|
|
34
|
+
embed_mre_pdf,
|
|
35
|
+
extract_mre_xml_pdf,
|
|
36
|
+
fetch_pdf,
|
|
37
|
+
mre_xml_exists_pdf,
|
|
38
|
+
parse_pdf,
|
|
39
|
+
)
|
|
40
|
+
from mre.reader import extract_mre_xml
|
|
41
|
+
|
|
42
|
+
# 내장 어댑터(Wikipedia 등)는 mre.html_site_adapter 모듈 로드 시 이미 등록됐다. 플러그인
|
|
43
|
+
# 발견은 여기, mre 패키지의 다른 모든 export 가 이미 바인딩된 뒤에 실행해야 한다 — 플러그인이
|
|
44
|
+
# 관례대로 `from mre import HTMLSiteAdapter` 로 임포트할 때 순환 임포트가 나지 않도록.
|
|
45
|
+
discover_plugin_adapters()
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"__version__",
|
|
49
|
+
"DocFormat",
|
|
50
|
+
"FormatDetectionError",
|
|
51
|
+
"detect_format",
|
|
52
|
+
"FetchNotSupportedError",
|
|
53
|
+
"GeneratorFingerprintMismatch",
|
|
54
|
+
"HTMLSiteAdapter",
|
|
55
|
+
"UnknownSiteError",
|
|
56
|
+
"compute_adapter_fingerprint",
|
|
57
|
+
"detect_site",
|
|
58
|
+
"discover_plugin_adapters",
|
|
59
|
+
"fetch_block",
|
|
60
|
+
"get_site_adapter",
|
|
61
|
+
"parse_html",
|
|
62
|
+
"register_site",
|
|
63
|
+
"registered_sites",
|
|
64
|
+
"OPCAdapter",
|
|
65
|
+
"embed_mre_opc",
|
|
66
|
+
"extract_mre_xml_opc",
|
|
67
|
+
"fetch_opc",
|
|
68
|
+
"get_opc_adapter",
|
|
69
|
+
"parse_opc",
|
|
70
|
+
"MREGenerationResult",
|
|
71
|
+
"generate_mre",
|
|
72
|
+
"parse_hwp",
|
|
73
|
+
"HwpConversionError",
|
|
74
|
+
"LibreOfficeNotAvailableError",
|
|
75
|
+
"convert_hwp",
|
|
76
|
+
"embed_mre_pdf",
|
|
77
|
+
"extract_mre_xml_pdf",
|
|
78
|
+
"fetch_pdf",
|
|
79
|
+
"mre_xml_exists_pdf",
|
|
80
|
+
"parse_pdf",
|
|
81
|
+
"extract_mre_xml",
|
|
82
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mre.agent — MRE 기반 agentic RAG 루프, 옵트인 서브패키지.
|
|
3
|
+
|
|
4
|
+
`mre` 코어 패키지는 문서 표준(생성 + fetch)만 다루는 게 의도적인 경계다 —
|
|
5
|
+
"에이전트의 턴별 추론 루프는 이 패키지의 관심사가 아니다"(mre/README.md). 이 서브패키지는
|
|
6
|
+
그 경계 바깥, 실제로 MRE 헤더를 읽고 도구를 호출하며 답을 도출하는 루프를 원하는 사용자를
|
|
7
|
+
위한 것 — 코어를 건드리지 않고 독립적으로 버저닝/실험한다.
|
|
8
|
+
|
|
9
|
+
지금은 progressive(metadata-only 2단계 공개) 방식 하나만 구현한다. 두 가지 방식으로
|
|
10
|
+
쓸 수 있다:
|
|
11
|
+
1. run_agent() 하나로 바로 — 완성형 진입점.
|
|
12
|
+
2. 개별 조각(build_progressive_action_schema/metadata_view/SYSTEM_PROMPT 등)을 가져다
|
|
13
|
+
직접 다른 루프(LangChain 등)에 도구로 꽂아 쓰기 — mre 코어가 generate_mre() 와
|
|
14
|
+
HTMLSiteAdapter/fetch_block/build_mre_xml 을 둘 다 공개하는 것과 동일한 패턴.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from mre.agent.loop import AgentResult, BlockFetchError, MRENotFoundError, run_agent
|
|
18
|
+
from mre.agent.prompts import ANSWER_FORMAT, SYSTEM_PROMPT
|
|
19
|
+
from mre.agent.schema import (
|
|
20
|
+
CHECK_SUFFICIENCY_SCHEMA,
|
|
21
|
+
MAX_DOCS_PER_TURN,
|
|
22
|
+
MAX_PIDS_PER_DOC,
|
|
23
|
+
MAX_TURNS,
|
|
24
|
+
build_progressive_action_schema,
|
|
25
|
+
)
|
|
26
|
+
from mre.agent.views import metadata_view
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AgentResult",
|
|
30
|
+
"BlockFetchError",
|
|
31
|
+
"MRENotFoundError",
|
|
32
|
+
"run_agent",
|
|
33
|
+
"ANSWER_FORMAT",
|
|
34
|
+
"SYSTEM_PROMPT",
|
|
35
|
+
"CHECK_SUFFICIENCY_SCHEMA",
|
|
36
|
+
"MAX_DOCS_PER_TURN",
|
|
37
|
+
"MAX_PIDS_PER_DOC",
|
|
38
|
+
"MAX_TURNS",
|
|
39
|
+
"build_progressive_action_schema",
|
|
40
|
+
"metadata_view",
|
|
41
|
+
]
|