xtremeparse 0.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.
- xtremeparse-0.1.0/.gitignore +15 -0
- xtremeparse-0.1.0/.python-version +1 -0
- xtremeparse-0.1.0/LICENSE +21 -0
- xtremeparse-0.1.0/PKG-INFO +119 -0
- xtremeparse-0.1.0/README.md +90 -0
- xtremeparse-0.1.0/docs/dsl.md +101 -0
- xtremeparse-0.1.0/docs/prompting.md +90 -0
- xtremeparse-0.1.0/pyproject.toml +55 -0
- xtremeparse-0.1.0/pytest.ini +2 -0
- xtremeparse-0.1.0/tests/__init__.py +1 -0
- xtremeparse-0.1.0/tests/helpers.py +5 -0
- xtremeparse-0.1.0/tests/test_chunking.py +90 -0
- xtremeparse-0.1.0/tests/test_contracts.py +27 -0
- xtremeparse-0.1.0/tests/test_corrections.py +180 -0
- xtremeparse-0.1.0/tests/test_evalkit.py +44 -0
- xtremeparse-0.1.0/tests/test_evals.py +129 -0
- xtremeparse-0.1.0/tests/test_executor.py +289 -0
- xtremeparse-0.1.0/tests/test_extractor.py +336 -0
- xtremeparse-0.1.0/tests/test_judging.py +37 -0
- xtremeparse-0.1.0/tests/test_paths.py +27 -0
- xtremeparse-0.1.0/tests/test_router.py +561 -0
- xtremeparse-0.1.0/tests/test_scheduling.py +55 -0
- xtremeparse-0.1.0/tests/test_units.py +133 -0
- xtremeparse-0.1.0/xtremeparse/__init__.py +34 -0
- xtremeparse-0.1.0/xtremeparse/chunking.py +83 -0
- xtremeparse-0.1.0/xtremeparse/contracts.py +117 -0
- xtremeparse-0.1.0/xtremeparse/corrections.py +166 -0
- xtremeparse-0.1.0/xtremeparse/evalkit.py +43 -0
- xtremeparse-0.1.0/xtremeparse/evals.py +97 -0
- xtremeparse-0.1.0/xtremeparse/executor.py +208 -0
- xtremeparse-0.1.0/xtremeparse/extractor.py +114 -0
- xtremeparse-0.1.0/xtremeparse/judging.py +58 -0
- xtremeparse-0.1.0/xtremeparse/merge.py +28 -0
- xtremeparse-0.1.0/xtremeparse/paths.py +38 -0
- xtremeparse-0.1.0/xtremeparse/prompting.py +60 -0
- xtremeparse-0.1.0/xtremeparse/router.py +710 -0
- xtremeparse-0.1.0/xtremeparse/scheduling.py +57 -0
- xtremeparse-0.1.0/xtremeparse/testing.py +34 -0
- xtremeparse-0.1.0/xtremeparse/units.py +90 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Flow Jiang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: xtremeparse
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: XtremeParse: extreme-concurrency structured extraction — chunk, route, fan out, self-correct
|
|
5
|
+
Project-URL: Homepage, https://github.com/flowjzh/xtremeparse
|
|
6
|
+
Project-URL: Repository, https://github.com/flowjzh/xtremeparse.git
|
|
7
|
+
Project-URL: Issues, https://github.com/flowjzh/xtremeparse/issues
|
|
8
|
+
Author-email: Flow Jiang <flowjzh@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: concurrency,extraction,json-schema,llm,parsing,structured-data
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: xtremeflow>=0.1.0
|
|
26
|
+
Provides-Extra: evals
|
|
27
|
+
Requires-Dist: pydantic-evals; extra == 'evals'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# XtremeParse
|
|
31
|
+
|
|
32
|
+
> **"Feed it text and a schema. Get structure back — fast."**
|
|
33
|
+
|
|
34
|
+
XtremeParse is an extreme-concurrency structured-extraction engine for LLM
|
|
35
|
+
pipelines. Given plain text and a JSON Schema, it:
|
|
36
|
+
|
|
37
|
+
1. **Chunks** the text deterministically (markdown structure → sentence
|
|
38
|
+
punctuation → character windows — single-line inputs included).
|
|
39
|
+
2. **Routes** chunks to extraction units via one light agent call that
|
|
40
|
+
outputs only an index map (minimal output tokens, maximal speed) and
|
|
41
|
+
itemizes repeated content for fan-out.
|
|
42
|
+
3. **Fans out** one specialist per unit — per item for long repeated
|
|
43
|
+
sections, whole-array for short ones — under xtremeflow's scheduler.
|
|
44
|
+
4. **Self-corrects**: validates the merged result with your validator,
|
|
45
|
+
routes issues back to the failing specialists, and re-runs only them
|
|
46
|
+
with the errors appended to their conversation history.
|
|
47
|
+
|
|
48
|
+
The full-text prefix is shared across all calls to maximize provider-side
|
|
49
|
+
KV-cache hits; the router runs first and warms the cache for the fleet
|
|
50
|
+
behind it.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from xtremeparse import Extractor
|
|
56
|
+
|
|
57
|
+
class MyRunner: # adapt your agent framework (PydanticAI, ...)
|
|
58
|
+
async def run(self, *, instructions, result_schema, content,
|
|
59
|
+
scope=None, tools=None, history=None, feedback=None): ...
|
|
60
|
+
|
|
61
|
+
extractor = Extractor(MyRunner())
|
|
62
|
+
|
|
63
|
+
result = await extractor.extract(text, json_schema, validator)
|
|
64
|
+
result.data # best-effort schema-shaped dict
|
|
65
|
+
result.issues # unresolved error-level issues (lenient, never raises)
|
|
66
|
+
result.trace # chunks, router index map, specialist calls, correction rounds
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Neutral by design
|
|
70
|
+
|
|
71
|
+
The library has zero vendor dependencies beyond `xtremeflow`. It never
|
|
72
|
+
imports your schema tool, your agent framework, or your validator:
|
|
73
|
+
|
|
74
|
+
- **Structure contract** — JSON Schema dict in, schema-conforming dict out.
|
|
75
|
+
- **Validation contract** — you inject a `validator` callable returning
|
|
76
|
+
error-level `Issue` objects (anything with `path`/`code`/`message`/
|
|
77
|
+
`expected`/`got` conforms, zero adapter code).
|
|
78
|
+
- **Agent contract** — you adapt an `AgentRunner` to your framework
|
|
79
|
+
(PydanticAI, or whatever comes next).
|
|
80
|
+
|
|
81
|
+
Extraction never raises on imperfect data: `ExtractionResult.data` is the
|
|
82
|
+
best effort and `.issues` tells the truth. Strictness is your policy.
|
|
83
|
+
|
|
84
|
+
Tuning knobs (constructor): `unit_strategy={'path': 'per-item' | 'whole'}`
|
|
85
|
+
prior knowledge for array units, `output_budgets={'path': n | [n, ...]}`
|
|
86
|
+
to override the router's own `@<n>` arrangements, `max_rounds` for the
|
|
87
|
+
correction budget, `max_chars` for chunk size, `max_concurrency` for the
|
|
88
|
+
scheduler. The three pipeline prompts are replaceable too —
|
|
89
|
+
`router_instructions` / `recount_instructions` / `specialist_instructions`
|
|
90
|
+
(see [docs/prompting.md](docs/prompting.md); the routing DSL they elicit
|
|
91
|
+
is a public contract, [docs/dsl.md](docs/dsl.md)).
|
|
92
|
+
|
|
93
|
+
## Trace contract
|
|
94
|
+
|
|
95
|
+
`result.trace` is eval-facing API — stable keys, populated by the
|
|
96
|
+
pipeline stages:
|
|
97
|
+
|
|
98
|
+
| key | shape |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `chunks` | the chunk list the router saw |
|
|
101
|
+
| `router` | `{'counts': {path: n}, 'assignments': [{'unit', 'item', 'chunks'}], 'budgets': {path: n \| [n, ...]}}` |
|
|
102
|
+
| `groups` | one per specialist call: `{'unit', 'kind', 'item', 'strategy', 'chunk_ids', 'budget', 'batch'}` |
|
|
103
|
+
| `corrections` | one per re-run: `{'unit_path', 'item', 'issue_paths'}` |
|
|
104
|
+
| `prompts` | per prompt slot: `'default'` or `#<sha1-8>` of a host override |
|
|
105
|
+
|
|
106
|
+
Eval support: `xtremeparse.evalkit` (zero-dep) reads budgets and router
|
|
107
|
+
invariants back out of a trace; `xtremeparse.judging` runs one rubric
|
|
108
|
+
call through the same `AgentRunner` neutrality (bring your own judge —
|
|
109
|
+
a model other than the extraction fleet's and temperature 0 both read
|
|
110
|
+
better, measured); `pip install xtremeparse[evals]` adds pydantic-evals
|
|
111
|
+
adapters (`RouterOverlap`, `BudgetFit`, `RubricJudge`) over those
|
|
112
|
+
mechanisms for hosts building regression suites.
|
|
113
|
+
|
|
114
|
+
## Status
|
|
115
|
+
|
|
116
|
+
Pipeline complete (chunk → route → fan out → merge → correct), 160+
|
|
117
|
+
deterministic tests, zero LLM required for the suite. See
|
|
118
|
+
`apps/cvspectra/docs/` in the cohirerx monorepo for the governing design
|
|
119
|
+
and implementation plan.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# XtremeParse
|
|
2
|
+
|
|
3
|
+
> **"Feed it text and a schema. Get structure back — fast."**
|
|
4
|
+
|
|
5
|
+
XtremeParse is an extreme-concurrency structured-extraction engine for LLM
|
|
6
|
+
pipelines. Given plain text and a JSON Schema, it:
|
|
7
|
+
|
|
8
|
+
1. **Chunks** the text deterministically (markdown structure → sentence
|
|
9
|
+
punctuation → character windows — single-line inputs included).
|
|
10
|
+
2. **Routes** chunks to extraction units via one light agent call that
|
|
11
|
+
outputs only an index map (minimal output tokens, maximal speed) and
|
|
12
|
+
itemizes repeated content for fan-out.
|
|
13
|
+
3. **Fans out** one specialist per unit — per item for long repeated
|
|
14
|
+
sections, whole-array for short ones — under xtremeflow's scheduler.
|
|
15
|
+
4. **Self-corrects**: validates the merged result with your validator,
|
|
16
|
+
routes issues back to the failing specialists, and re-runs only them
|
|
17
|
+
with the errors appended to their conversation history.
|
|
18
|
+
|
|
19
|
+
The full-text prefix is shared across all calls to maximize provider-side
|
|
20
|
+
KV-cache hits; the router runs first and warms the cache for the fleet
|
|
21
|
+
behind it.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from xtremeparse import Extractor
|
|
27
|
+
|
|
28
|
+
class MyRunner: # adapt your agent framework (PydanticAI, ...)
|
|
29
|
+
async def run(self, *, instructions, result_schema, content,
|
|
30
|
+
scope=None, tools=None, history=None, feedback=None): ...
|
|
31
|
+
|
|
32
|
+
extractor = Extractor(MyRunner())
|
|
33
|
+
|
|
34
|
+
result = await extractor.extract(text, json_schema, validator)
|
|
35
|
+
result.data # best-effort schema-shaped dict
|
|
36
|
+
result.issues # unresolved error-level issues (lenient, never raises)
|
|
37
|
+
result.trace # chunks, router index map, specialist calls, correction rounds
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Neutral by design
|
|
41
|
+
|
|
42
|
+
The library has zero vendor dependencies beyond `xtremeflow`. It never
|
|
43
|
+
imports your schema tool, your agent framework, or your validator:
|
|
44
|
+
|
|
45
|
+
- **Structure contract** — JSON Schema dict in, schema-conforming dict out.
|
|
46
|
+
- **Validation contract** — you inject a `validator` callable returning
|
|
47
|
+
error-level `Issue` objects (anything with `path`/`code`/`message`/
|
|
48
|
+
`expected`/`got` conforms, zero adapter code).
|
|
49
|
+
- **Agent contract** — you adapt an `AgentRunner` to your framework
|
|
50
|
+
(PydanticAI, or whatever comes next).
|
|
51
|
+
|
|
52
|
+
Extraction never raises on imperfect data: `ExtractionResult.data` is the
|
|
53
|
+
best effort and `.issues` tells the truth. Strictness is your policy.
|
|
54
|
+
|
|
55
|
+
Tuning knobs (constructor): `unit_strategy={'path': 'per-item' | 'whole'}`
|
|
56
|
+
prior knowledge for array units, `output_budgets={'path': n | [n, ...]}`
|
|
57
|
+
to override the router's own `@<n>` arrangements, `max_rounds` for the
|
|
58
|
+
correction budget, `max_chars` for chunk size, `max_concurrency` for the
|
|
59
|
+
scheduler. The three pipeline prompts are replaceable too —
|
|
60
|
+
`router_instructions` / `recount_instructions` / `specialist_instructions`
|
|
61
|
+
(see [docs/prompting.md](docs/prompting.md); the routing DSL they elicit
|
|
62
|
+
is a public contract, [docs/dsl.md](docs/dsl.md)).
|
|
63
|
+
|
|
64
|
+
## Trace contract
|
|
65
|
+
|
|
66
|
+
`result.trace` is eval-facing API — stable keys, populated by the
|
|
67
|
+
pipeline stages:
|
|
68
|
+
|
|
69
|
+
| key | shape |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `chunks` | the chunk list the router saw |
|
|
72
|
+
| `router` | `{'counts': {path: n}, 'assignments': [{'unit', 'item', 'chunks'}], 'budgets': {path: n \| [n, ...]}}` |
|
|
73
|
+
| `groups` | one per specialist call: `{'unit', 'kind', 'item', 'strategy', 'chunk_ids', 'budget', 'batch'}` |
|
|
74
|
+
| `corrections` | one per re-run: `{'unit_path', 'item', 'issue_paths'}` |
|
|
75
|
+
| `prompts` | per prompt slot: `'default'` or `#<sha1-8>` of a host override |
|
|
76
|
+
|
|
77
|
+
Eval support: `xtremeparse.evalkit` (zero-dep) reads budgets and router
|
|
78
|
+
invariants back out of a trace; `xtremeparse.judging` runs one rubric
|
|
79
|
+
call through the same `AgentRunner` neutrality (bring your own judge —
|
|
80
|
+
a model other than the extraction fleet's and temperature 0 both read
|
|
81
|
+
better, measured); `pip install xtremeparse[evals]` adds pydantic-evals
|
|
82
|
+
adapters (`RouterOverlap`, `BudgetFit`, `RubricJudge`) over those
|
|
83
|
+
mechanisms for hosts building regression suites.
|
|
84
|
+
|
|
85
|
+
## Status
|
|
86
|
+
|
|
87
|
+
Pipeline complete (chunk → route → fan out → merge → correct), 160+
|
|
88
|
+
deterministic tests, zero LLM required for the suite. See
|
|
89
|
+
`apps/cvspectra/docs/` in the cohirerx monorepo for the governing design
|
|
90
|
+
and implementation plan.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# The routing DSL
|
|
2
|
+
|
|
3
|
+
The router's entire output is a segment map plus count declarations in a
|
|
4
|
+
compact line DSL. The library parses it, validates it, and repairs it
|
|
5
|
+
with precise feedback; a host that overrides the router prompt
|
|
6
|
+
([prompting.md](prompting.md)) must elicit exactly this language — the
|
|
7
|
+
grammar below is a stable public contract. Breaking it is a breaking
|
|
8
|
+
change.
|
|
9
|
+
|
|
10
|
+
## Shape
|
|
11
|
+
|
|
12
|
+
Output is map lines first, then exactly one declaration line per
|
|
13
|
+
repeating (array) unit.
|
|
14
|
+
|
|
15
|
+
### Map lines
|
|
16
|
+
|
|
17
|
+
<start>-<end> <dest>[,<dest>...]
|
|
18
|
+
<start> <dest> # a single chunk may omit "-<end>"
|
|
19
|
+
|
|
20
|
+
- `start`/`end` are chunk ids. Lines ascend, never overlap, and together
|
|
21
|
+
cover every chunk id in `0..top` exactly once.
|
|
22
|
+
- A destination is `<code>` or `<code>.<item>`:
|
|
23
|
+
- `code` is a letter code from the legend the prompt carries —
|
|
24
|
+
assigned by the library in unit order; `-` is reserved.
|
|
25
|
+
- `.item` on a repeating unit is the instance's index, numbered across
|
|
26
|
+
the WHOLE document in the order the map meets the instances.
|
|
27
|
+
- A bare repeating code (`4-9 x`) means several instances share the run
|
|
28
|
+
unsplit — that material is extracted once, whole.
|
|
29
|
+
- A run may feed several DIFFERENT units at once, comma-joined
|
|
30
|
+
(`5 x.0,y.0`) — a summary or cross-cutting unit rides the lines of the
|
|
31
|
+
unit whose text it shares, item by item.
|
|
32
|
+
- Several items of the SAME unit may share one line (`3 x.0,x.1,x.2`)
|
|
33
|
+
when chunk boundaries cannot separate the instances.
|
|
34
|
+
- `-` on its own (never comma-joined) marks chunks irrelevant to every
|
|
35
|
+
unit.
|
|
36
|
+
|
|
37
|
+
### Declaration lines
|
|
38
|
+
|
|
39
|
+
After the map, every repeating unit gets exactly one of:
|
|
40
|
+
|
|
41
|
+
x: <count> [@<budget>[,<budget>...]]
|
|
42
|
+
x = <source> [@<budget>]
|
|
43
|
+
|
|
44
|
+
- `x: <count>` — the document holds `<count>` instances of the unit.
|
|
45
|
+
Item indexes used in the map must run exactly `0..count-1` with no
|
|
46
|
+
gaps, and every declared item must receive at least one chunk.
|
|
47
|
+
- `x = <source>` — the unit has no text of its own; its items mirror
|
|
48
|
+
`<source>`'s, a directly mapped repeating unit. Such a unit takes NO
|
|
49
|
+
map lines.
|
|
50
|
+
- The budget suffix is tolerated, never validated: one entry per item
|
|
51
|
+
in item-index order (`@500,300,150`), or one entry covering every item.
|
|
52
|
+
Three forms: `@<n>` an absolute character cap, `@<n>%` a ratio of the
|
|
53
|
+
item's mapped material (a verbatim copy is `@100%`), and
|
|
54
|
+
`@<avg>x<count>` an average keyword length times a keyword count.
|
|
55
|
+
Malformed entries are dropped silently — the count still parses, no
|
|
56
|
+
error.
|
|
57
|
+
|
|
58
|
+
## What validation enforces
|
|
59
|
+
|
|
60
|
+
Violations are fed back as repair errors (bounded rounds, then
|
|
61
|
+
`RouterError`):
|
|
62
|
+
|
|
63
|
+
- map lines parse, ascend, do not overlap, and cover `0..top` exactly
|
|
64
|
+
- the item set each unit uses in the map equals `0..declared-1`
|
|
65
|
+
- every repeating unit is declared (a missing declaration reads as 0,
|
|
66
|
+
which triggers a separate fresh-conversation recount before it is
|
|
67
|
+
trusted)
|
|
68
|
+
- a derivation's `source` is itself a directly mapped repeating unit
|
|
69
|
+
|
|
70
|
+
Tolerated noise: counts on non-array units are ignored; an exact
|
|
71
|
+
duplicate map line collapses; a dotted tail on an item (`2.0`) keeps its
|
|
72
|
+
leading index.
|
|
73
|
+
|
|
74
|
+
## Budgets
|
|
75
|
+
|
|
76
|
+
The suffix is the router's arrangement — its estimate of the characters
|
|
77
|
+
each item's output JSON will run to (keys and punctuation included).
|
|
78
|
+
It declares the estimate in the form that matches the extraction's
|
|
79
|
+
shape:
|
|
80
|
+
|
|
81
|
+
- `@<n>` — an absolute cap, for fixed-length summaries and anything
|
|
82
|
+
else.
|
|
83
|
+
- `@<n>%` — a ratio of the item's mapped material, for verbatim or
|
|
84
|
+
unbounded-refinement extraction (`@100%` is a verbatim copy; one
|
|
85
|
+
shared ratio scales per item against each item's own material). The
|
|
86
|
+
resolved estimate adds the item schema's skeleton (key names and
|
|
87
|
+
punctuation, computed from the schema) on top — the ratio covers the
|
|
88
|
+
content, the structure is code-known.
|
|
89
|
+
- `@<avg>x<count>` — an average keyword length times the document's
|
|
90
|
+
keyword count, for lists of short same-shaped entries. One shared
|
|
91
|
+
entry is the document-level total: the resolver splits it across the
|
|
92
|
+
items it covers.
|
|
93
|
+
|
|
94
|
+
The library resolves every form to absolute characters against the
|
|
95
|
+
routed material before use; the declared forms ride in the trace's
|
|
96
|
+
router budgets verbatim. Budgets shape output through batch scheduling
|
|
97
|
+
(consecutive small items coalesce into one shared call while their
|
|
98
|
+
accumulated budget fits the per-call capacity) and are never enforced
|
|
99
|
+
on the output. Overruns are accepted — a retry would cost a full
|
|
100
|
+
extra decode. A host's `output_budgets={'path': n | [n, ...]}`
|
|
101
|
+
overrides the router's declarations per path.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Prompt overrides
|
|
2
|
+
|
|
3
|
+
xtremeparse ships measured default prompts. They are replaceable — real
|
|
4
|
+
effectiveness ultimately depends on the host's schema and eval — but an
|
|
5
|
+
override is an escape hatch for domain/model/language adaptation, not
|
|
6
|
+
the primary tuning path.
|
|
7
|
+
|
|
8
|
+
## Layering order (measured)
|
|
9
|
+
|
|
10
|
+
1. **Schema `description`s first.** Field-level semantics live in the
|
|
11
|
+
schema the host passes in: unit cards and legends are composed from
|
|
12
|
+
them, and field fidelity follows the description's wording (a
|
|
13
|
+
fidelity clause in a field description moves output where generic
|
|
14
|
+
prompt rules never did).
|
|
15
|
+
2. **Prompt overrides second.** For adapting the generic extraction
|
|
16
|
+
discipline to a new document domain, a weaker model, or another
|
|
17
|
+
language.
|
|
18
|
+
3. **Never** the DSL ([dsl.md](dsl.md)) or the validation / recount /
|
|
19
|
+
correction loops — that machinery is the library's core contract.
|
|
20
|
+
|
|
21
|
+
## The three slots
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
Extractor(runner,
|
|
25
|
+
router_instructions=..., # the segment-map prompt
|
|
26
|
+
recount_instructions=..., # the zero-unit recount prompt
|
|
27
|
+
specialist_instructions=...) # the extraction prompt
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| kwarg | placeholders |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `router_instructions` | `top`, `none`, `legend`, `chunks` |
|
|
33
|
+
| `recount_instructions` | `legend`, `chunks` |
|
|
34
|
+
| `specialist_instructions` | `card` |
|
|
35
|
+
|
|
36
|
+
- Templates are `str.format` strings. Required placeholders are
|
|
37
|
+
validated at construction: a template missing one raises `ValueError`
|
|
38
|
+
instead of silently rendering a prompt without its chunk listing.
|
|
39
|
+
- `top` is the highest chunk id, `none` the NONE marker, `chunks` the
|
|
40
|
+
numbered chunk listing, `card` the unit's semantic card — all built by
|
|
41
|
+
code. `legend` is one `code = <unit card header>` line per unit; its
|
|
42
|
+
format is fixed by code (the card header is the cue the default
|
|
43
|
+
prompts rely on — an override legend interpretation is the override's
|
|
44
|
+
to keep consistent).
|
|
45
|
+
- Correction rounds re-dispatch with the same override automatically —
|
|
46
|
+
a correction round continues the original call's conversation history.
|
|
47
|
+
- The judge prompt (`xtremeparse.judging.judge(instructions=...)`) is
|
|
48
|
+
overridable the same way — one placeholder, `rubric`.
|
|
49
|
+
|
|
50
|
+
## Judging (measured guidance)
|
|
51
|
+
|
|
52
|
+
Judge-side settings live at the adapter, as recommendations:
|
|
53
|
+
|
|
54
|
+
- A model other than the extraction fleet's tends to read kinder to
|
|
55
|
+
self-judging bias.
|
|
56
|
+
- Temperature 0 steadies verdicts (a judge that flips run to run
|
|
57
|
+
measures noise, not quality).
|
|
58
|
+
|
|
59
|
+
Shape the judged material before trusting exclusion clauses — a judge
|
|
60
|
+
shown field structure keeps demanding a field-for-field mirror of the
|
|
61
|
+
source even against explicit exclusions, measured across models and
|
|
62
|
+
reasoning modes. For containment questions (is every fact anywhere?),
|
|
63
|
+
judge the host's flattened-digest projection and carry the schema's
|
|
64
|
+
field inventory in the rubric. The open-diff judge measures
|
|
65
|
+
~zero sensitivity to seeded deletions — presence checking belongs to
|
|
66
|
+
deterministic anchors beside it, not to the judge alone.
|
|
67
|
+
|
|
68
|
+
## Provenance
|
|
69
|
+
|
|
70
|
+
`result.trace.prompts` marks every slot `'default'` or `#<sha1-8>` of
|
|
71
|
+
the host's template, so eval regressions stay attributable to whose
|
|
72
|
+
prompt produced them.
|
|
73
|
+
|
|
74
|
+
## Not overridable
|
|
75
|
+
|
|
76
|
+
- The budget suffix handling — the router's arrangement forms are
|
|
77
|
+
resolved and consumed by code, not by prompt wording.
|
|
78
|
+
- Repair and recount feedback messages — generated from validation
|
|
79
|
+
errors, not prose.
|
|
80
|
+
- The shared payload prefix (`content`) — byte-identical across every
|
|
81
|
+
call of one extraction; the provider KV cache depends on it.
|
|
82
|
+
|
|
83
|
+
## Stability caveat
|
|
84
|
+
|
|
85
|
+
The defaults carry measured calibration: map-before-count (a count-first
|
|
86
|
+
declaration made models drop a section's tail entry), card-header
|
|
87
|
+
legends (bare paths cost recognition), form-matched budget declarations
|
|
88
|
+
(ratio by default, keyword and absolute for their shapes). An override
|
|
89
|
+
discards all of it. A/B against the default on a frozen corpus
|
|
90
|
+
before shipping one.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = 'xtremeparse'
|
|
3
|
+
description = 'XtremeParse: extreme-concurrency structured extraction — chunk, route, fan out, self-correct'
|
|
4
|
+
readme = 'README.md'
|
|
5
|
+
requires-python = '>=3.9'
|
|
6
|
+
license = 'MIT'
|
|
7
|
+
authors = [
|
|
8
|
+
{name = 'Flow Jiang', email = 'flowjzh@gmail.com'},
|
|
9
|
+
]
|
|
10
|
+
keywords = ['llm', 'extraction', 'json-schema', 'concurrency', 'structured-data', 'parsing']
|
|
11
|
+
classifiers = [
|
|
12
|
+
'Development Status :: 3 - Alpha',
|
|
13
|
+
'Intended Audience :: Developers',
|
|
14
|
+
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
15
|
+
'Programming Language :: Python :: 3',
|
|
16
|
+
'Programming Language :: Python :: 3.9',
|
|
17
|
+
'Programming Language :: Python :: 3.10',
|
|
18
|
+
'Programming Language :: Python :: 3.11',
|
|
19
|
+
'Programming Language :: Python :: 3.12',
|
|
20
|
+
'Programming Language :: Python :: 3.13',
|
|
21
|
+
'Programming Language :: Python :: 3 :: Only',
|
|
22
|
+
'Operating System :: OS Independent',
|
|
23
|
+
'Typing :: Typed',
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
'xtremeflow>=0.1.0',
|
|
27
|
+
]
|
|
28
|
+
dynamic = ["version"]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
evals = [
|
|
32
|
+
'pydantic-evals',
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
'pytest>=8.4.2',
|
|
38
|
+
'pytest-asyncio>=0.24.0',
|
|
39
|
+
'pydantic-evals',
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = 'https://github.com/flowjzh/xtremeparse'
|
|
44
|
+
Repository = 'https://github.com/flowjzh/xtremeparse.git'
|
|
45
|
+
Issues = 'https://github.com/flowjzh/xtremeparse/issues'
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["hatchling", "uv-dynamic-versioning"]
|
|
49
|
+
build-backend = "hatchling.build"
|
|
50
|
+
|
|
51
|
+
[tool.hatch.version]
|
|
52
|
+
source = "uv-dynamic-versioning"
|
|
53
|
+
|
|
54
|
+
[tool.uv.sources]
|
|
55
|
+
xtremeflow = { workspace = true }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Boundary-probe test suite."""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Boundary probes for the deterministic chunker."""
|
|
2
|
+
|
|
3
|
+
from xtremeparse.chunking import chunk_text
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_empty_and_whitespace_only():
|
|
7
|
+
assert chunk_text('') == []
|
|
8
|
+
assert chunk_text(' \n\t \n\n') == []
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_tiny_input_is_one_chunk():
|
|
12
|
+
assert chunk_text('姓名') == ['姓名']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_plain_hard_wrapped_lines_form_one_paragraph_chunk():
|
|
16
|
+
text = '姓名:张三\n年龄:30\n电话:13800000000'
|
|
17
|
+
assert chunk_text(text) == [text]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_blank_lines_separate_paragraphs():
|
|
21
|
+
assert chunk_text('第一段\n\n第二段\n \n第三段') == ['第一段', '第二段', '第三段']
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_heading_opens_block_absorbing_following_plain_lines():
|
|
25
|
+
text = '## 工作经历\n2020-2023 腾讯 后端'
|
|
26
|
+
assert chunk_text(text) == [text]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_list_items_and_numbered_items_are_individual_chunks():
|
|
30
|
+
text = '经历:\n- 腾讯 后端\n• 阿里 高级专家\n1. 早期创业\n2、外企'
|
|
31
|
+
assert chunk_text(text) == ['经历:', '- 腾讯 后端', '• 阿里 高级专家', '1. 早期创业', '2、外企']
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_negative_numbers_are_not_list_items():
|
|
35
|
+
assert chunk_text('-10分\n正常行') == ['-10分\n正常行']
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_line_leading_dates_are_not_list_items():
|
|
39
|
+
text = '2015.3 joined 腾讯\n2018.5 left'
|
|
40
|
+
assert chunk_text(text) == [text]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_table_rows_are_individual_chunks():
|
|
44
|
+
text = '|公司|职位|\n|---|---|\n|腾讯|后端|'
|
|
45
|
+
assert chunk_text(text) == ['|公司|职位|', '|---|---|', '|腾讯|后端|']
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_sentences_split_and_pack_to_ceiling():
|
|
49
|
+
text = '第一句。' * 300 # 300 four-char sentences, 1200 chars
|
|
50
|
+
chunks = chunk_text(text, max_chars=200)
|
|
51
|
+
assert all(len(c) <= 200 for c in chunks)
|
|
52
|
+
assert len(chunks) == -(-len(text) // 200) # minimal packing, no slack
|
|
53
|
+
assert ''.join(chunks) == text
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_fullwidth_full_stop_is_a_sentence_ender():
|
|
57
|
+
chunks = chunk_text('第一段.第二段.', max_chars=6)
|
|
58
|
+
assert chunks == ['第一段.', '第二段.']
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_digit_dots_never_split_but_sentence_dots_do():
|
|
62
|
+
chunks = chunk_text('Joined 2015.3. Left 2018.5.', max_chars=15)
|
|
63
|
+
assert chunks == ['Joined 2015.3.', ' Left 2018.5.']
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_unpunctuated_line_falls_to_character_windows():
|
|
67
|
+
assert chunk_text('密' * 1300, max_chars=600) == ['密' * 600, '密' * 600, '密' * 100]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_dense_block_inside_markdown_uses_windows_but_keeps_heading():
|
|
71
|
+
text = f'## 经历\n{"密" * 700}\n\n## 其他\n简短'
|
|
72
|
+
chunks = chunk_text(text, max_chars=600)
|
|
73
|
+
assert ''.join(chunks[:2]) == f'## 经历\n{"密" * 700}' # dense block windowed whole
|
|
74
|
+
assert chunks[0].startswith('## 经历\n')
|
|
75
|
+
assert chunks[2] == '## 其他\n简短'
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_crlf_is_normalized():
|
|
79
|
+
assert chunk_text('第一段\r\n\r\n第二段') == ['第一段', '第二段']
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_deterministic_and_ordered_substrings():
|
|
83
|
+
text = '# 简历\n张三,30岁\n\n## 经历\n- 腾讯 后端\n\n自我介绍。很长。'
|
|
84
|
+
first, second = chunk_text(text), chunk_text(text)
|
|
85
|
+
assert first == second
|
|
86
|
+
pos = 0
|
|
87
|
+
for chunk in first:
|
|
88
|
+
i = text.find(chunk, pos)
|
|
89
|
+
assert i >= 0, 'chunks must be ordered substrings'
|
|
90
|
+
pos = i + len(chunk)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Contracts stay vendor-neutral and structurally compatible."""
|
|
2
|
+
|
|
3
|
+
from xtremeparse.contracts import AgentResult, AgentRunner, ExtractionResult, Issue, Trace
|
|
4
|
+
from tests.helpers import FakeIssue
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FakeRunner:
|
|
8
|
+
async def run(self, **kwargs):
|
|
9
|
+
return AgentResult(data={}, history=[])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_host_issue_conforms_without_adaptation():
|
|
13
|
+
issue = FakeIssue(path='basic_info.name', message='required', code='missing_required',
|
|
14
|
+
expected=['a', 'b'])
|
|
15
|
+
assert isinstance(issue, Issue)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_runner_protocol_is_structural():
|
|
19
|
+
assert isinstance(FakeRunner(), AgentRunner)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_result_holds_opaque_parts():
|
|
23
|
+
issues = [FakeIssue(path='career', message='empty', code='missing_required')]
|
|
24
|
+
result = ExtractionResult(data={'basic_info': {}}, issues=issues, trace=Trace())
|
|
25
|
+
assert result.data == {'basic_info': {}}
|
|
26
|
+
assert result.issues[0].path == 'career'
|
|
27
|
+
assert result.trace.chunks == []
|