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.
Files changed (39) hide show
  1. xtremeparse-0.1.0/.gitignore +15 -0
  2. xtremeparse-0.1.0/.python-version +1 -0
  3. xtremeparse-0.1.0/LICENSE +21 -0
  4. xtremeparse-0.1.0/PKG-INFO +119 -0
  5. xtremeparse-0.1.0/README.md +90 -0
  6. xtremeparse-0.1.0/docs/dsl.md +101 -0
  7. xtremeparse-0.1.0/docs/prompting.md +90 -0
  8. xtremeparse-0.1.0/pyproject.toml +55 -0
  9. xtremeparse-0.1.0/pytest.ini +2 -0
  10. xtremeparse-0.1.0/tests/__init__.py +1 -0
  11. xtremeparse-0.1.0/tests/helpers.py +5 -0
  12. xtremeparse-0.1.0/tests/test_chunking.py +90 -0
  13. xtremeparse-0.1.0/tests/test_contracts.py +27 -0
  14. xtremeparse-0.1.0/tests/test_corrections.py +180 -0
  15. xtremeparse-0.1.0/tests/test_evalkit.py +44 -0
  16. xtremeparse-0.1.0/tests/test_evals.py +129 -0
  17. xtremeparse-0.1.0/tests/test_executor.py +289 -0
  18. xtremeparse-0.1.0/tests/test_extractor.py +336 -0
  19. xtremeparse-0.1.0/tests/test_judging.py +37 -0
  20. xtremeparse-0.1.0/tests/test_paths.py +27 -0
  21. xtremeparse-0.1.0/tests/test_router.py +561 -0
  22. xtremeparse-0.1.0/tests/test_scheduling.py +55 -0
  23. xtremeparse-0.1.0/tests/test_units.py +133 -0
  24. xtremeparse-0.1.0/xtremeparse/__init__.py +34 -0
  25. xtremeparse-0.1.0/xtremeparse/chunking.py +83 -0
  26. xtremeparse-0.1.0/xtremeparse/contracts.py +117 -0
  27. xtremeparse-0.1.0/xtremeparse/corrections.py +166 -0
  28. xtremeparse-0.1.0/xtremeparse/evalkit.py +43 -0
  29. xtremeparse-0.1.0/xtremeparse/evals.py +97 -0
  30. xtremeparse-0.1.0/xtremeparse/executor.py +208 -0
  31. xtremeparse-0.1.0/xtremeparse/extractor.py +114 -0
  32. xtremeparse-0.1.0/xtremeparse/judging.py +58 -0
  33. xtremeparse-0.1.0/xtremeparse/merge.py +28 -0
  34. xtremeparse-0.1.0/xtremeparse/paths.py +38 -0
  35. xtremeparse-0.1.0/xtremeparse/prompting.py +60 -0
  36. xtremeparse-0.1.0/xtremeparse/router.py +710 -0
  37. xtremeparse-0.1.0/xtremeparse/scheduling.py +57 -0
  38. xtremeparse-0.1.0/xtremeparse/testing.py +34 -0
  39. xtremeparse-0.1.0/xtremeparse/units.py +90 -0
@@ -0,0 +1,15 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Workspace Local
13
+ .env
14
+ .claude
15
+ .DS_Store
@@ -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,2 @@
1
+ [pytest]
2
+ asyncio_mode = auto
@@ -0,0 +1 @@
1
+ """Boundary-probe test suite."""
@@ -0,0 +1,5 @@
1
+ """Shared test doubles: owned by the protocol owner."""
2
+
3
+ from xtremeparse.testing import FakeIssue, ScriptedRunner, agent_result
4
+
5
+ __all__ = ['FakeIssue', 'ScriptedRunner', 'agent_result']
@@ -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 == []