boothpy 0.4.2__tar.gz → 0.4.4__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.
- {boothpy-0.4.2 → boothpy-0.4.4}/PKG-INFO +71 -16
- {boothpy-0.4.2 → boothpy-0.4.4}/README.md +69 -14
- {boothpy-0.4.2 → boothpy-0.4.4}/pyproject.toml +2 -2
- {boothpy-0.4.2 → boothpy-0.4.4}/src/booth/__init__.py +3 -1
- {boothpy-0.4.2 → boothpy-0.4.4}/src/booth/core.py +114 -152
- {boothpy-0.4.2 → boothpy-0.4.4}/src/boothpy.egg-info/PKG-INFO +71 -16
- {boothpy-0.4.2 → boothpy-0.4.4}/src/boothpy.egg-info/SOURCES.txt +2 -0
- boothpy-0.4.4/tests/test_bugfixes_0_4_4.py +346 -0
- boothpy-0.4.4/tests/test_parsed.py +189 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/LICENSE +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/setup.cfg +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/src/boothpy.egg-info/dependency_links.txt +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/src/boothpy.egg-info/requires.txt +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/src/boothpy.egg-info/top_level.txt +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/tests/test_acheck.py +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/tests/test_core.py +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/tests/test_evidence.py +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/tests/test_method.py +0 -0
- {boothpy-0.4.2 → boothpy-0.4.4}/tests/test_validator.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boothpy
|
|
3
|
-
Version: 0.4.
|
|
4
|
-
Summary: A lightweight checkpoint
|
|
3
|
+
Version: 0.4.4
|
|
4
|
+
Summary: A lightweight checkpoint library for LLM outputs.
|
|
5
5
|
Author: Vedant Brahmbhatt
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
@@ -57,7 +57,7 @@ The name comes from the idea of a **ticket booth, toll booth, or parking/payment
|
|
|
57
57
|
|
|
58
58
|
## Current Status
|
|
59
59
|
|
|
60
|
-
**v0.4.
|
|
60
|
+
**v0.4.4**
|
|
61
61
|
|
|
62
62
|
BOOTH currently provides:
|
|
63
63
|
|
|
@@ -69,7 +69,7 @@ BOOTH currently provides:
|
|
|
69
69
|
* synchronous and asynchronous LLM checkpoint functions
|
|
70
70
|
* evidence-agreement checking against evidence supplied by the caller
|
|
71
71
|
* configurable confidence and evidence thresholds
|
|
72
|
-
* structured result objects, including `result.method` to identify which mechanism produced a result
|
|
72
|
+
* structured result objects, including `result.method` to identify which mechanism produced a result, and `result.parsed` to expose the model's raw JSON response
|
|
73
73
|
* attempt history
|
|
74
74
|
* explicit `VERIFIED`, `REPAIRED`, `AMBIGUOUS`, `UNCERTAIN`, and `BLOCKED` statuses
|
|
75
75
|
|
|
@@ -125,6 +125,8 @@ If BOOTH cannot obtain an acceptable result, it returns `UNCERTAIN`.
|
|
|
125
125
|
|
|
126
126
|
BOOTH also provides `check_with_evidence()` for applications that already have evidence from their own RAG, search, database, or tool pipeline.
|
|
127
127
|
|
|
128
|
+
**On robustness to malformed model output:** every field BOOTH extracts from the model's JSON response is validated against its expected type before being trusted, not just parsed and used as-is. A model returning the JSON string `"false"` instead of the boolean `false` for `ambiguous`, or the boolean `true` instead of a number for `confidence`, is rejected and treated as an unparseable attempt rather than silently coerced into the wrong value — the same discipline BOOTH has always applied to an out-of-range confidence score, now applied consistently across every coerced field. See the [Changelog](CHANGELOG.md)'s `v0.4.4` entry for the specific cases this covers.
|
|
129
|
+
|
|
128
130
|
---
|
|
129
131
|
|
|
130
132
|
## Features
|
|
@@ -253,7 +255,7 @@ result = booth.check(
|
|
|
253
255
|
)
|
|
254
256
|
```
|
|
255
257
|
|
|
256
|
-
`validator` receives the attempt's answer and returns
|
|
258
|
+
`validator` receives the attempt's answer and returns one of:
|
|
257
259
|
|
|
258
260
|
* `True` / `False` — a plain pass/fail. `False` produces a generic failure message.
|
|
259
261
|
* `(bool, str)` — pass/fail plus a specific reason, shown to the model verbatim on the retry prompt:
|
|
@@ -267,14 +269,19 @@ def validate_amount(answer: str):
|
|
|
267
269
|
result = booth.check(call_llm, prompt, validator=validate_amount)
|
|
268
270
|
```
|
|
269
271
|
|
|
272
|
+
* `(bool, None)`, or the equivalent as a **list** rather than a tuple (`[bool, str]` / `[bool, None]`) — accepted with the identical contract as the tuple form above (0.4.4+). `return True, None` is a natural way to write "passed, no message needed," and `[passed, message]` is an easy habit to fall into; both previously triggered an "invalid return type" rejection even though the intent was clear. A `False` with no message gets a generic fallback message, same as bare `False`.
|
|
273
|
+
* `numpy.bool_` (and other duck-typed booleans) are accepted anywhere a plain `bool` is (0.4.4+), recognized by type identity rather than by importing `numpy` — BOOTH stays zero-dependency.
|
|
274
|
+
|
|
270
275
|
**Ordering:** an attempt is only run through `validator` if it parsed successfully **and** was not flagged ambiguous — a question that's ambiguous as asked isn't something a validator should be judging, and there is nothing to validate if the response never parsed. A validation failure is checked *before* the confidence gate: an answer that fails your validator does not get a chance to pass purely on high self-reported confidence.
|
|
271
276
|
|
|
272
|
-
An exception raised inside `validator`, or a return value that isn't
|
|
277
|
+
An exception raised inside `validator`, or a return value that isn't one of the accepted shapes above, is treated as a failed validation — it never propagates out of `check()`/`acheck()`.
|
|
273
278
|
|
|
274
279
|
`validator=None` (the default) is a true no-op: every code path this parameter introduces is unreachable if you never pass it, so existing calls are unaffected.
|
|
275
280
|
|
|
276
281
|
`validator` must be **synchronous**, for both `check()` and `acheck()`. If your validation logic needs to await something (an API call, a DB lookup), resolve it yourself first and pass a plain sync closure in.
|
|
277
282
|
|
|
283
|
+
The type alias for a validator function, `ValidatorFn`, is importable from the top-level package (`from booth import ValidatorFn`) for type-hinting your own validator functions.
|
|
284
|
+
|
|
278
285
|
---
|
|
279
286
|
|
|
280
287
|
### `result.method`
|
|
@@ -293,7 +300,7 @@ result.method
|
|
|
293
300
|
# attempt that did parse and did pass validation
|
|
294
301
|
```
|
|
295
302
|
|
|
296
|
-
This is most useful for `UNCERTAIN` results, where it distinguishes
|
|
303
|
+
This is most useful for `UNCERTAIN` results, where it distinguishes genuinely different problems that call for different fixes:
|
|
297
304
|
|
|
298
305
|
```python
|
|
299
306
|
if result.status == booth.UNCERTAIN:
|
|
@@ -309,6 +316,40 @@ if result.status == booth.UNCERTAIN:
|
|
|
309
316
|
|
|
310
317
|
---
|
|
311
318
|
|
|
319
|
+
### `result.parsed`
|
|
320
|
+
|
|
321
|
+
Exposes the model's **raw JSON response object**, exactly as returned — before any of BOOTH's own coercion (`str(answer)`, `float(confidence)`, forcing `interpretations` into a list of strings, and so on).
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
result = booth.check(call_llm, "What is the order ID for this request?")
|
|
325
|
+
|
|
326
|
+
result.answer # BOOTH's normalized answer, e.g. "ORD-4471"
|
|
327
|
+
result.parsed # the raw dict the model returned, unmodified
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
This is a **transparency layer, not a second validation layer** — BOOTH already decided `answer`/`confidence`/`ambiguous` from this same object; `parsed` exists so you can see exactly what the model said, including fields BOOTH itself doesn't use:
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
def call_llm(prompt: str) -> str:
|
|
334
|
+
# imagine the model, prompted to also include a source field,
|
|
335
|
+
# returns:
|
|
336
|
+
# {"answer": "30 days", "confidence": "0.95", "ambiguous": false,
|
|
337
|
+
# "source_document": "policy.pdf"}
|
|
338
|
+
...
|
|
339
|
+
|
|
340
|
+
result = booth.check(call_llm, prompt)
|
|
341
|
+
|
|
342
|
+
result.confidence # 0.95 (float — BOOTH coerced it)
|
|
343
|
+
result.parsed["confidence"] # "0.95" (str — the raw value, untouched)
|
|
344
|
+
result.parsed["source_document"] # "policy.pdf" — a field BOOTH never asked for or uses
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
That type divergence between `result.confidence` and `result.parsed["confidence"]` is deliberate, not a bug — `parsed` is meant to show you precisely what came back, even where BOOTH normalized it for its own use.
|
|
348
|
+
|
|
349
|
+
`parsed` reflects the attempt that determined the result: the winning attempt for `VERIFIED`/`REPAIRED`/`AMBIGUOUS`, the last *successfully parsed* attempt for `UNCERTAIN`, or `None` if no attempt ever parsed. `check_with_evidence()` results always have `parsed=None` — there is no LLM JSON parse involved on that path at all.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
312
353
|
### Synchronous and asynchronous APIs
|
|
313
354
|
|
|
314
355
|
BOOTH provides both:
|
|
@@ -360,7 +401,7 @@ async def main():
|
|
|
360
401
|
asyncio.run(main())
|
|
361
402
|
```
|
|
362
403
|
|
|
363
|
-
Both APIs use the same decision logic, including `validator`. The difference is how the supplied LLM function is called.
|
|
404
|
+
Both APIs use the same decision logic, including `validator` and `parsed`. The difference is how the supplied LLM function is called.
|
|
364
405
|
|
|
365
406
|
---
|
|
366
407
|
|
|
@@ -416,7 +457,7 @@ A score of `0.87` passes. A score of `0.62` does not.
|
|
|
416
457
|
|
|
417
458
|
Boolean comparison results are treated as strict pass/fail values. `evidence_threshold` is not applied to boolean results.
|
|
418
459
|
|
|
419
|
-
`check_with_evidence()` has no `validator` concept — it is a standalone, single-purpose comparison gate, untouched by the `validator`
|
|
460
|
+
`check_with_evidence()` has no `validator` concept, and its results always have `parsed=None` — it is a standalone, single-purpose comparison gate, untouched by either the `validator` or `parsed` additions.
|
|
420
461
|
|
|
421
462
|
---
|
|
422
463
|
|
|
@@ -484,7 +525,7 @@ Optional callback invoked after each attempt.
|
|
|
484
525
|
|
|
485
526
|
#### `validator` (keyword-only, 0.4.2+)
|
|
486
527
|
|
|
487
|
-
Optional `Callable[[str], bool | tuple[bool, str]]`. Runs on an attempt's answer only if that attempt parsed successfully and was not ambiguous. See [Custom validation with `validator`](#custom-validation-with-validator) above for the full contract. Must be synchronous. Default `None` — a true no-op.
|
|
528
|
+
Optional `ValidatorFn` — `Callable[[str], bool | tuple[bool, str]]`. Runs on an attempt's answer only if that attempt parsed successfully and was not ambiguous. See [Custom validation with `validator`](#custom-validation-with-validator) above for the full accepted-shape contract (widened in 0.4.4 to also accept `(bool, None)`, list forms, and `numpy.bool_`). Must be synchronous. Default `None` — a true no-op.
|
|
488
529
|
|
|
489
530
|
---
|
|
490
531
|
|
|
@@ -530,6 +571,7 @@ Checks an answer against caller-supplied evidence. It:
|
|
|
530
571
|
* performs no retries
|
|
531
572
|
* does not modify a previous `BoothResult`
|
|
532
573
|
* has no `validator` parameter — it is a standalone comparison gate
|
|
574
|
+
* always returns `parsed=None` — there is no LLM JSON response involved
|
|
533
575
|
* uses the caller's `compare_fn`
|
|
534
576
|
|
|
535
577
|
#### `answer`
|
|
@@ -568,6 +610,7 @@ result.ambiguous
|
|
|
568
610
|
result.interpretations
|
|
569
611
|
result.all_parse_failed
|
|
570
612
|
result.method
|
|
613
|
+
result.parsed
|
|
571
614
|
```
|
|
572
615
|
|
|
573
616
|
### `answer`
|
|
@@ -588,7 +631,7 @@ The comparison score produced by `check_with_evidence()`. `None` for normal `che
|
|
|
588
631
|
|
|
589
632
|
### `attempts`
|
|
590
633
|
|
|
591
|
-
The full history of LLM attempts made by `check()` or `acheck()`, each including per-attempt `passed_validation` / `validation_error` (always `True` / `None` if no `validator` was supplied). Evidence checks do not make attempts, so their attempt list is empty.
|
|
634
|
+
The full history of LLM attempts made by `check()` or `acheck()`, each including per-attempt `passed_validation` / `validation_error` (always `True` / `None` if no `validator` was supplied) and `parsed` (the raw JSON object for that specific attempt, `None` if it failed to parse). Evidence checks do not make attempts, so their attempt list is empty.
|
|
592
635
|
|
|
593
636
|
### `n_attempts`
|
|
594
637
|
|
|
@@ -614,6 +657,10 @@ The interpretations reported when the model marks a question as ambiguous.
|
|
|
614
657
|
|
|
615
658
|
Which mechanism produced the result — `"ambiguity"`, `"evidence"`, `"parse_failure"`, `"validation"`, or `"confidence"`. See [`result.method`](#resultmethod) above.
|
|
616
659
|
|
|
660
|
+
### `parsed` (0.4.3+)
|
|
661
|
+
|
|
662
|
+
The model's raw, uncoerced JSON response object. See [`result.parsed`](#resultparsed) above.
|
|
663
|
+
|
|
617
664
|
---
|
|
618
665
|
|
|
619
666
|
## Result Statuses
|
|
@@ -666,6 +713,7 @@ BOOTH currently does **not**:
|
|
|
666
713
|
* provide calibrated confidence probabilities
|
|
667
714
|
* guarantee that retrieved evidence is correct, complete, relevant, or current
|
|
668
715
|
* guarantee that a custom `validator` is itself correct — a validator can pass a wrong answer or reject a correct one, same as any other application-supplied rule
|
|
716
|
+
* validate or enforce a schema on `result.parsed` — it is exposed as-is, entirely unvalidated
|
|
669
717
|
* replace application-specific validation or safety systems (though `validator` gives you a documented hook to plug your own logic into BOOTH's retry loop rather than reimplementing that loop yourself)
|
|
670
718
|
|
|
671
719
|
BOOTH is a **checkpoint library**, not an LLM framework, search engine, RAG framework, or autonomous verification system.
|
|
@@ -686,6 +734,10 @@ Ambiguity detection depends on the model recognizing the ambiguity. BOOTH can de
|
|
|
686
734
|
|
|
687
735
|
`validator` is exactly as reliable as the logic you give it. BOOTH enforces that a validator's decision is respected consistently in the retry loop — it does not, and cannot, check whether the validator's own logic is actually correct for your use case.
|
|
688
736
|
|
|
737
|
+
### `result.parsed` is unvalidated
|
|
738
|
+
|
|
739
|
+
`result.parsed` is the model's raw JSON object, exposed as-is. BOOTH does not validate its shape, enforce a schema on it, or guarantee any field beyond the five it extracts for itself (`answer`, `confidence`, `ambiguous`, `interpretations`, `chosen_interpretation`) is present or well-typed. Reading extra fields from `parsed` is entirely the caller's responsibility, including handling missing keys or unexpected types.
|
|
740
|
+
|
|
689
741
|
### Evidence quality
|
|
690
742
|
|
|
691
743
|
Evidence checking is only as useful as the evidence and comparison function supplied by the application. If the evidence is wrong, incomplete, outdated, or unrelated, BOOTH does not independently detect that. Likewise, a weak `compare_fn` can produce a misleading result. This includes the case where retrieved evidence is baked into the model's own prompt as RAG context — a wrong document can make the model's answer both more *confident* and more evidence-*consistent*, without becoming more correct.
|
|
@@ -742,6 +794,7 @@ Future BOOTH development may explore:
|
|
|
742
794
|
* richer composition of multiple checkpoint results
|
|
743
795
|
* better evaluation and calibration tooling
|
|
744
796
|
* additional integrations with retrieval and tool systems
|
|
797
|
+
* an optional, lightweight structured-schema gate on top of `result.parsed` — deferred deliberately until there's a clear, minimal shape for it, rather than reaching for a general schema/validation dependency
|
|
745
798
|
|
|
746
799
|
These are future directions, not capabilities currently guaranteed by the library.
|
|
747
800
|
|
|
@@ -775,9 +828,9 @@ Run the test suite:
|
|
|
775
828
|
pytest
|
|
776
829
|
```
|
|
777
830
|
|
|
778
|
-
The test suite covers the core checkpoint behavior, asynchronous API, parsing behavior, ambiguity handling, reconsideration, custom validation,
|
|
831
|
+
The test suite covers the core checkpoint behavior, asynchronous API, parsing behavior, ambiguity handling, reconsideration, custom validation, evidence checking, raw-response exposure via `result.parsed`, and a dedicated regression suite for the `v0.4.4` bugfix batch. CI runs the full suite on push/PR across Python 3.9–3.12.
|
|
779
832
|
|
|
780
|
-
The
|
|
833
|
+
The `v0.4.4` regression tests specifically verify: a JSON string `"false"` for `ambiguous` is not misread as `True`; a JSON boolean for `confidence` is rejected rather than silently accepted as `1.0`/`0.0`; `ValidatorFn` is importable from the top-level package; a falsy-but-present `chosen_interpretation` (`0`, `""`) is preserved rather than discarded to `None`; `numpy.bool_`-shaped validator returns are accepted; `(bool, None)` and list-shaped validator returns are accepted while genuinely malformed shapes are still rejected; confidence exactly equal to `threshold` passes on both `check()` and `acheck()`; list-wrapped JSON (`[{...}]`) still recovers via the parsing fallback; and the previously-unverified nested-brace edge case's actual behavior is now locked in by a test rather than assumed.
|
|
781
834
|
|
|
782
835
|
---
|
|
783
836
|
|
|
@@ -787,9 +840,11 @@ The validator tests include cases for: `validator=None` full-regression parity,
|
|
|
787
840
|
2. **Make uncertainty explicit.** When an output does not meet the configured acceptance condition, return a structured status instead of silently passing it through.
|
|
788
841
|
3. **Treat ambiguity, validation, and confidence as separate, ordered checks.** A confident, validator-passing answer can still be ambiguous; a confident answer can still fail a caller's own validation rule before confidence is ever consulted.
|
|
789
842
|
4. **Reconsider instead of blindly resampling.** Retries give the model an opportunity to examine its previous response — and the reason it failed (parse failure, validation failure, or low confidence) determines what the model is actually shown.
|
|
790
|
-
5. **
|
|
791
|
-
6. **
|
|
792
|
-
7. **
|
|
843
|
+
5. **Expose, don't reinterpret.** `result.parsed` is a transparency layer over data BOOTH already has, not a second parser or validator — it shows the model's raw response rather than deciding what it should mean.
|
|
844
|
+
6. **Reject invalid data, don't silently coerce it.** An out-of-range confidence, a boolean masquerading as a confidence score, or an unrecognized value for `ambiguous` are all treated as reasons to reject the attempt — never guessed at or silently reinterpreted into something that happens not to crash. Applied consistently across every coerced field as of `v0.4.4`.
|
|
845
|
+
7. **Keep evidence retrieval outside BOOTH.** Applications remain free to use their own RAG, search, database, or tool infrastructure.
|
|
846
|
+
8. **Do not pretend agreement is truth.** Agreement with an answer, confidence value, custom validator, or retrieved evidence is not the same as independently proving the claim.
|
|
847
|
+
9. **Stay provider-agnostic.** BOOTH works with different LLM providers because the application supplies the model-calling function.
|
|
793
848
|
|
|
794
849
|
---
|
|
795
850
|
|
|
@@ -12,7 +12,7 @@ The name comes from the idea of a **ticket booth, toll booth, or parking/payment
|
|
|
12
12
|
|
|
13
13
|
## Current Status
|
|
14
14
|
|
|
15
|
-
**v0.4.
|
|
15
|
+
**v0.4.4**
|
|
16
16
|
|
|
17
17
|
BOOTH currently provides:
|
|
18
18
|
|
|
@@ -24,7 +24,7 @@ BOOTH currently provides:
|
|
|
24
24
|
* synchronous and asynchronous LLM checkpoint functions
|
|
25
25
|
* evidence-agreement checking against evidence supplied by the caller
|
|
26
26
|
* configurable confidence and evidence thresholds
|
|
27
|
-
* structured result objects, including `result.method` to identify which mechanism produced a result
|
|
27
|
+
* structured result objects, including `result.method` to identify which mechanism produced a result, and `result.parsed` to expose the model's raw JSON response
|
|
28
28
|
* attempt history
|
|
29
29
|
* explicit `VERIFIED`, `REPAIRED`, `AMBIGUOUS`, `UNCERTAIN`, and `BLOCKED` statuses
|
|
30
30
|
|
|
@@ -80,6 +80,8 @@ If BOOTH cannot obtain an acceptable result, it returns `UNCERTAIN`.
|
|
|
80
80
|
|
|
81
81
|
BOOTH also provides `check_with_evidence()` for applications that already have evidence from their own RAG, search, database, or tool pipeline.
|
|
82
82
|
|
|
83
|
+
**On robustness to malformed model output:** every field BOOTH extracts from the model's JSON response is validated against its expected type before being trusted, not just parsed and used as-is. A model returning the JSON string `"false"` instead of the boolean `false` for `ambiguous`, or the boolean `true` instead of a number for `confidence`, is rejected and treated as an unparseable attempt rather than silently coerced into the wrong value — the same discipline BOOTH has always applied to an out-of-range confidence score, now applied consistently across every coerced field. See the [Changelog](CHANGELOG.md)'s `v0.4.4` entry for the specific cases this covers.
|
|
84
|
+
|
|
83
85
|
---
|
|
84
86
|
|
|
85
87
|
## Features
|
|
@@ -208,7 +210,7 @@ result = booth.check(
|
|
|
208
210
|
)
|
|
209
211
|
```
|
|
210
212
|
|
|
211
|
-
`validator` receives the attempt's answer and returns
|
|
213
|
+
`validator` receives the attempt's answer and returns one of:
|
|
212
214
|
|
|
213
215
|
* `True` / `False` — a plain pass/fail. `False` produces a generic failure message.
|
|
214
216
|
* `(bool, str)` — pass/fail plus a specific reason, shown to the model verbatim on the retry prompt:
|
|
@@ -222,14 +224,19 @@ def validate_amount(answer: str):
|
|
|
222
224
|
result = booth.check(call_llm, prompt, validator=validate_amount)
|
|
223
225
|
```
|
|
224
226
|
|
|
227
|
+
* `(bool, None)`, or the equivalent as a **list** rather than a tuple (`[bool, str]` / `[bool, None]`) — accepted with the identical contract as the tuple form above (0.4.4+). `return True, None` is a natural way to write "passed, no message needed," and `[passed, message]` is an easy habit to fall into; both previously triggered an "invalid return type" rejection even though the intent was clear. A `False` with no message gets a generic fallback message, same as bare `False`.
|
|
228
|
+
* `numpy.bool_` (and other duck-typed booleans) are accepted anywhere a plain `bool` is (0.4.4+), recognized by type identity rather than by importing `numpy` — BOOTH stays zero-dependency.
|
|
229
|
+
|
|
225
230
|
**Ordering:** an attempt is only run through `validator` if it parsed successfully **and** was not flagged ambiguous — a question that's ambiguous as asked isn't something a validator should be judging, and there is nothing to validate if the response never parsed. A validation failure is checked *before* the confidence gate: an answer that fails your validator does not get a chance to pass purely on high self-reported confidence.
|
|
226
231
|
|
|
227
|
-
An exception raised inside `validator`, or a return value that isn't
|
|
232
|
+
An exception raised inside `validator`, or a return value that isn't one of the accepted shapes above, is treated as a failed validation — it never propagates out of `check()`/`acheck()`.
|
|
228
233
|
|
|
229
234
|
`validator=None` (the default) is a true no-op: every code path this parameter introduces is unreachable if you never pass it, so existing calls are unaffected.
|
|
230
235
|
|
|
231
236
|
`validator` must be **synchronous**, for both `check()` and `acheck()`. If your validation logic needs to await something (an API call, a DB lookup), resolve it yourself first and pass a plain sync closure in.
|
|
232
237
|
|
|
238
|
+
The type alias for a validator function, `ValidatorFn`, is importable from the top-level package (`from booth import ValidatorFn`) for type-hinting your own validator functions.
|
|
239
|
+
|
|
233
240
|
---
|
|
234
241
|
|
|
235
242
|
### `result.method`
|
|
@@ -248,7 +255,7 @@ result.method
|
|
|
248
255
|
# attempt that did parse and did pass validation
|
|
249
256
|
```
|
|
250
257
|
|
|
251
|
-
This is most useful for `UNCERTAIN` results, where it distinguishes
|
|
258
|
+
This is most useful for `UNCERTAIN` results, where it distinguishes genuinely different problems that call for different fixes:
|
|
252
259
|
|
|
253
260
|
```python
|
|
254
261
|
if result.status == booth.UNCERTAIN:
|
|
@@ -264,6 +271,40 @@ if result.status == booth.UNCERTAIN:
|
|
|
264
271
|
|
|
265
272
|
---
|
|
266
273
|
|
|
274
|
+
### `result.parsed`
|
|
275
|
+
|
|
276
|
+
Exposes the model's **raw JSON response object**, exactly as returned — before any of BOOTH's own coercion (`str(answer)`, `float(confidence)`, forcing `interpretations` into a list of strings, and so on).
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
result = booth.check(call_llm, "What is the order ID for this request?")
|
|
280
|
+
|
|
281
|
+
result.answer # BOOTH's normalized answer, e.g. "ORD-4471"
|
|
282
|
+
result.parsed # the raw dict the model returned, unmodified
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
This is a **transparency layer, not a second validation layer** — BOOTH already decided `answer`/`confidence`/`ambiguous` from this same object; `parsed` exists so you can see exactly what the model said, including fields BOOTH itself doesn't use:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
def call_llm(prompt: str) -> str:
|
|
289
|
+
# imagine the model, prompted to also include a source field,
|
|
290
|
+
# returns:
|
|
291
|
+
# {"answer": "30 days", "confidence": "0.95", "ambiguous": false,
|
|
292
|
+
# "source_document": "policy.pdf"}
|
|
293
|
+
...
|
|
294
|
+
|
|
295
|
+
result = booth.check(call_llm, prompt)
|
|
296
|
+
|
|
297
|
+
result.confidence # 0.95 (float — BOOTH coerced it)
|
|
298
|
+
result.parsed["confidence"] # "0.95" (str — the raw value, untouched)
|
|
299
|
+
result.parsed["source_document"] # "policy.pdf" — a field BOOTH never asked for or uses
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
That type divergence between `result.confidence` and `result.parsed["confidence"]` is deliberate, not a bug — `parsed` is meant to show you precisely what came back, even where BOOTH normalized it for its own use.
|
|
303
|
+
|
|
304
|
+
`parsed` reflects the attempt that determined the result: the winning attempt for `VERIFIED`/`REPAIRED`/`AMBIGUOUS`, the last *successfully parsed* attempt for `UNCERTAIN`, or `None` if no attempt ever parsed. `check_with_evidence()` results always have `parsed=None` — there is no LLM JSON parse involved on that path at all.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
267
308
|
### Synchronous and asynchronous APIs
|
|
268
309
|
|
|
269
310
|
BOOTH provides both:
|
|
@@ -315,7 +356,7 @@ async def main():
|
|
|
315
356
|
asyncio.run(main())
|
|
316
357
|
```
|
|
317
358
|
|
|
318
|
-
Both APIs use the same decision logic, including `validator`. The difference is how the supplied LLM function is called.
|
|
359
|
+
Both APIs use the same decision logic, including `validator` and `parsed`. The difference is how the supplied LLM function is called.
|
|
319
360
|
|
|
320
361
|
---
|
|
321
362
|
|
|
@@ -371,7 +412,7 @@ A score of `0.87` passes. A score of `0.62` does not.
|
|
|
371
412
|
|
|
372
413
|
Boolean comparison results are treated as strict pass/fail values. `evidence_threshold` is not applied to boolean results.
|
|
373
414
|
|
|
374
|
-
`check_with_evidence()` has no `validator` concept — it is a standalone, single-purpose comparison gate, untouched by the `validator`
|
|
415
|
+
`check_with_evidence()` has no `validator` concept, and its results always have `parsed=None` — it is a standalone, single-purpose comparison gate, untouched by either the `validator` or `parsed` additions.
|
|
375
416
|
|
|
376
417
|
---
|
|
377
418
|
|
|
@@ -439,7 +480,7 @@ Optional callback invoked after each attempt.
|
|
|
439
480
|
|
|
440
481
|
#### `validator` (keyword-only, 0.4.2+)
|
|
441
482
|
|
|
442
|
-
Optional `Callable[[str], bool | tuple[bool, str]]`. Runs on an attempt's answer only if that attempt parsed successfully and was not ambiguous. See [Custom validation with `validator`](#custom-validation-with-validator) above for the full contract. Must be synchronous. Default `None` — a true no-op.
|
|
483
|
+
Optional `ValidatorFn` — `Callable[[str], bool | tuple[bool, str]]`. Runs on an attempt's answer only if that attempt parsed successfully and was not ambiguous. See [Custom validation with `validator`](#custom-validation-with-validator) above for the full accepted-shape contract (widened in 0.4.4 to also accept `(bool, None)`, list forms, and `numpy.bool_`). Must be synchronous. Default `None` — a true no-op.
|
|
443
484
|
|
|
444
485
|
---
|
|
445
486
|
|
|
@@ -485,6 +526,7 @@ Checks an answer against caller-supplied evidence. It:
|
|
|
485
526
|
* performs no retries
|
|
486
527
|
* does not modify a previous `BoothResult`
|
|
487
528
|
* has no `validator` parameter — it is a standalone comparison gate
|
|
529
|
+
* always returns `parsed=None` — there is no LLM JSON response involved
|
|
488
530
|
* uses the caller's `compare_fn`
|
|
489
531
|
|
|
490
532
|
#### `answer`
|
|
@@ -523,6 +565,7 @@ result.ambiguous
|
|
|
523
565
|
result.interpretations
|
|
524
566
|
result.all_parse_failed
|
|
525
567
|
result.method
|
|
568
|
+
result.parsed
|
|
526
569
|
```
|
|
527
570
|
|
|
528
571
|
### `answer`
|
|
@@ -543,7 +586,7 @@ The comparison score produced by `check_with_evidence()`. `None` for normal `che
|
|
|
543
586
|
|
|
544
587
|
### `attempts`
|
|
545
588
|
|
|
546
|
-
The full history of LLM attempts made by `check()` or `acheck()`, each including per-attempt `passed_validation` / `validation_error` (always `True` / `None` if no `validator` was supplied). Evidence checks do not make attempts, so their attempt list is empty.
|
|
589
|
+
The full history of LLM attempts made by `check()` or `acheck()`, each including per-attempt `passed_validation` / `validation_error` (always `True` / `None` if no `validator` was supplied) and `parsed` (the raw JSON object for that specific attempt, `None` if it failed to parse). Evidence checks do not make attempts, so their attempt list is empty.
|
|
547
590
|
|
|
548
591
|
### `n_attempts`
|
|
549
592
|
|
|
@@ -569,6 +612,10 @@ The interpretations reported when the model marks a question as ambiguous.
|
|
|
569
612
|
|
|
570
613
|
Which mechanism produced the result — `"ambiguity"`, `"evidence"`, `"parse_failure"`, `"validation"`, or `"confidence"`. See [`result.method`](#resultmethod) above.
|
|
571
614
|
|
|
615
|
+
### `parsed` (0.4.3+)
|
|
616
|
+
|
|
617
|
+
The model's raw, uncoerced JSON response object. See [`result.parsed`](#resultparsed) above.
|
|
618
|
+
|
|
572
619
|
---
|
|
573
620
|
|
|
574
621
|
## Result Statuses
|
|
@@ -621,6 +668,7 @@ BOOTH currently does **not**:
|
|
|
621
668
|
* provide calibrated confidence probabilities
|
|
622
669
|
* guarantee that retrieved evidence is correct, complete, relevant, or current
|
|
623
670
|
* guarantee that a custom `validator` is itself correct — a validator can pass a wrong answer or reject a correct one, same as any other application-supplied rule
|
|
671
|
+
* validate or enforce a schema on `result.parsed` — it is exposed as-is, entirely unvalidated
|
|
624
672
|
* replace application-specific validation or safety systems (though `validator` gives you a documented hook to plug your own logic into BOOTH's retry loop rather than reimplementing that loop yourself)
|
|
625
673
|
|
|
626
674
|
BOOTH is a **checkpoint library**, not an LLM framework, search engine, RAG framework, or autonomous verification system.
|
|
@@ -641,6 +689,10 @@ Ambiguity detection depends on the model recognizing the ambiguity. BOOTH can de
|
|
|
641
689
|
|
|
642
690
|
`validator` is exactly as reliable as the logic you give it. BOOTH enforces that a validator's decision is respected consistently in the retry loop — it does not, and cannot, check whether the validator's own logic is actually correct for your use case.
|
|
643
691
|
|
|
692
|
+
### `result.parsed` is unvalidated
|
|
693
|
+
|
|
694
|
+
`result.parsed` is the model's raw JSON object, exposed as-is. BOOTH does not validate its shape, enforce a schema on it, or guarantee any field beyond the five it extracts for itself (`answer`, `confidence`, `ambiguous`, `interpretations`, `chosen_interpretation`) is present or well-typed. Reading extra fields from `parsed` is entirely the caller's responsibility, including handling missing keys or unexpected types.
|
|
695
|
+
|
|
644
696
|
### Evidence quality
|
|
645
697
|
|
|
646
698
|
Evidence checking is only as useful as the evidence and comparison function supplied by the application. If the evidence is wrong, incomplete, outdated, or unrelated, BOOTH does not independently detect that. Likewise, a weak `compare_fn` can produce a misleading result. This includes the case where retrieved evidence is baked into the model's own prompt as RAG context — a wrong document can make the model's answer both more *confident* and more evidence-*consistent*, without becoming more correct.
|
|
@@ -697,6 +749,7 @@ Future BOOTH development may explore:
|
|
|
697
749
|
* richer composition of multiple checkpoint results
|
|
698
750
|
* better evaluation and calibration tooling
|
|
699
751
|
* additional integrations with retrieval and tool systems
|
|
752
|
+
* an optional, lightweight structured-schema gate on top of `result.parsed` — deferred deliberately until there's a clear, minimal shape for it, rather than reaching for a general schema/validation dependency
|
|
700
753
|
|
|
701
754
|
These are future directions, not capabilities currently guaranteed by the library.
|
|
702
755
|
|
|
@@ -730,9 +783,9 @@ Run the test suite:
|
|
|
730
783
|
pytest
|
|
731
784
|
```
|
|
732
785
|
|
|
733
|
-
The test suite covers the core checkpoint behavior, asynchronous API, parsing behavior, ambiguity handling, reconsideration, custom validation,
|
|
786
|
+
The test suite covers the core checkpoint behavior, asynchronous API, parsing behavior, ambiguity handling, reconsideration, custom validation, evidence checking, raw-response exposure via `result.parsed`, and a dedicated regression suite for the `v0.4.4` bugfix batch. CI runs the full suite on push/PR across Python 3.9–3.12.
|
|
734
787
|
|
|
735
|
-
The
|
|
788
|
+
The `v0.4.4` regression tests specifically verify: a JSON string `"false"` for `ambiguous` is not misread as `True`; a JSON boolean for `confidence` is rejected rather than silently accepted as `1.0`/`0.0`; `ValidatorFn` is importable from the top-level package; a falsy-but-present `chosen_interpretation` (`0`, `""`) is preserved rather than discarded to `None`; `numpy.bool_`-shaped validator returns are accepted; `(bool, None)` and list-shaped validator returns are accepted while genuinely malformed shapes are still rejected; confidence exactly equal to `threshold` passes on both `check()` and `acheck()`; list-wrapped JSON (`[{...}]`) still recovers via the parsing fallback; and the previously-unverified nested-brace edge case's actual behavior is now locked in by a test rather than assumed.
|
|
736
789
|
|
|
737
790
|
---
|
|
738
791
|
|
|
@@ -742,9 +795,11 @@ The validator tests include cases for: `validator=None` full-regression parity,
|
|
|
742
795
|
2. **Make uncertainty explicit.** When an output does not meet the configured acceptance condition, return a structured status instead of silently passing it through.
|
|
743
796
|
3. **Treat ambiguity, validation, and confidence as separate, ordered checks.** A confident, validator-passing answer can still be ambiguous; a confident answer can still fail a caller's own validation rule before confidence is ever consulted.
|
|
744
797
|
4. **Reconsider instead of blindly resampling.** Retries give the model an opportunity to examine its previous response — and the reason it failed (parse failure, validation failure, or low confidence) determines what the model is actually shown.
|
|
745
|
-
5. **
|
|
746
|
-
6. **
|
|
747
|
-
7. **
|
|
798
|
+
5. **Expose, don't reinterpret.** `result.parsed` is a transparency layer over data BOOTH already has, not a second parser or validator — it shows the model's raw response rather than deciding what it should mean.
|
|
799
|
+
6. **Reject invalid data, don't silently coerce it.** An out-of-range confidence, a boolean masquerading as a confidence score, or an unrecognized value for `ambiguous` are all treated as reasons to reject the attempt — never guessed at or silently reinterpreted into something that happens not to crash. Applied consistently across every coerced field as of `v0.4.4`.
|
|
800
|
+
7. **Keep evidence retrieval outside BOOTH.** Applications remain free to use their own RAG, search, database, or tool infrastructure.
|
|
801
|
+
8. **Do not pretend agreement is truth.** Agreement with an answer, confidence value, custom validator, or retrieved evidence is not the same as independently proving the claim.
|
|
802
|
+
9. **Stay provider-agnostic.** BOOTH works with different LLM providers because the application supplies the model-calling function.
|
|
748
803
|
|
|
749
804
|
---
|
|
750
805
|
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "boothpy"
|
|
7
|
-
version = "0.4.
|
|
8
|
-
description = "A lightweight checkpoint
|
|
7
|
+
version = "0.4.4"
|
|
8
|
+
description = "A lightweight checkpoint library for LLM outputs."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
@@ -5,6 +5,7 @@ from booth.core import (
|
|
|
5
5
|
acheck,
|
|
6
6
|
check_with_evidence,
|
|
7
7
|
CompareFn,
|
|
8
|
+
ValidatorFn,
|
|
8
9
|
VERIFIED,
|
|
9
10
|
REPAIRED,
|
|
10
11
|
AMBIGUOUS,
|
|
@@ -14,7 +15,7 @@ from booth.core import (
|
|
|
14
15
|
DEFAULT_MAX_RETRIES,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
|
-
__version__ = "0.4.
|
|
18
|
+
__version__ = "0.4.4"
|
|
18
19
|
|
|
19
20
|
__all__ = [
|
|
20
21
|
"Attempt",
|
|
@@ -23,6 +24,7 @@ __all__ = [
|
|
|
23
24
|
"acheck",
|
|
24
25
|
"check_with_evidence",
|
|
25
26
|
"CompareFn",
|
|
27
|
+
"ValidatorFn",
|
|
26
28
|
"VERIFIED",
|
|
27
29
|
"REPAIRED",
|
|
28
30
|
"AMBIGUOUS",
|