Skip to main content

valjson

Diagnostics for structured JSON output from LLMs: extract, validate, fix, score per-field accuracy, and gate on confidence. Most commands need no model.

Fine-tuning your LLM for JSON? Your aggregate metrics might be hiding per-field regressions.

pip install valjson

What it does

valjson is the observability layer for structured JSON output. It meets you wherever you are:

You have Command Needs model?
Messy text with JSON valjson --extract --data output.txt No
JSON + schema valjson --validate --schema s.json --data output.jsonl No
Broken JSON + schema valjson --fix --schema s.json --data output.jsonl No
JSON + schema valjson --anatomy --schema s.json --data output.jsonl No
JSON + schema + gold valjson --compare --schema s.json --data output.jsonl --gold truth.jsonl No
Two output sets valjson --diff --schema s.json --data a.jsonl --data2 b.jsonl No
Per-field probabilities valjson --gate --data probs.jsonl No
Model + checkpoint valjson --checkpoint lora/ --schema s.json --data test.jsonl Yes

The problem

Standard fine-tuning + grammar-constrained decoding produces valid JSON. Aggregate loss improves. But:

STRUCTURAL         5.33 -> 0.00     -100%   OK
KEY                0.47 -> 0.00     -100%   OK
BOOLEAN            0.46 -> 1.05     +130%   !! REGRESSION
TOTAL              0.55 -> 0.17      -69%

Aggregate loss improved 69%. Boolean prediction got 130% worse. valjson catches this.

Per-field accuracy vs gold

When you have human-labeled gold JSONs, --compare gives you per-field accuracy without needing a model — role-aware, so a blown free-text field does not drown the signal on the constrained fields you actually care about:

valjson --compare \
    --schema schema.json \
    --data generated.jsonl \
    --gold gold.jsonl \
    --ignore-role STRING,ARRAY
  • --match-by <key> — pair records by ID (wrapper-first lookup), not line order.
  • --ignore-role STRING,ARRAY — focus on BOOLEAN / ENUM / NUMBER fields where exact equality is meaningful. Unmatched IDs are reported separately.

Evidential gating: abstain when the model is unsure

When the model returns a probability distribution over allowed values for each constrained field, --gate decides per record whether to commit, abstain, or reject based on the margin between the top two values:

valjson --gate \
    --data probs.jsonl \
    --margin-threshold 0.30

Input format (one record per line):

{"id": "rec-001",
 "probs": {
   "refundable": {"True": 0.553, "False": 0.447},
   "status":     {"submitted": 0.9, "pending": 0.1, "draft": 0.0}
 }}

A field's margin = top_prob − second_prob. If margin ≥ threshold, the field is committed; otherwise it abstains. Per-field abstention rates are reported — fields with persistently high abstention are diagnostic of underspecified training targets (the regression pattern this paper documents).

Quick start

See QUICK_START.md for a hands-on walkthrough from messy output to full analysis.

Python API

from valjson import analyze

report = analyze(
    model_name="Qwen/Qwen2.5-0.5B-Instruct",
    checkpoint="my_lora/",
    schema="schema.json",
    data="test.jsonl",
)
print(report)

if report.regressions:
    print(f"REGRESSIONS: {[r.role for r in report.regressions]}")

Exit code is 1 if regressions are detected. Use in CI/CD.

Grammar Roles

Role Description Examples
STRUCTURAL JSON syntax { } [ ] : ,
QUOTE String delimiters "
KEY Object key characters city, cuisine
ENUM_VALUE Categorical values Italian, Economy
BOOLEAN Boolean strings True, False
NUMBER Numeric characters 42, 3.14
FREE_TEXT Non-categorical content names, addresses
WHITESPACE Formatting spaces, newlines

Links

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

valjson-2.0.1.tar.gz (56.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

valjson-2.0.1-py3-none-any.whl (64.9 kB view details)

Uploaded Python 3

File details

Details for the file valjson-2.0.1.tar.gz.

File metadata

  • Download URL: valjson-2.0.1.tar.gz
  • Upload date:
  • Size: 56.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for valjson-2.0.1.tar.gz
Algorithm Hash digest
SHA256 dfea541992447c142a69e2811fc622fd58a5e70df3892a9314d372b6e266468d
MD5 b90c8f9b32cc50243697f300d2edbdd0
BLAKE2b-256 2ce6e146f6a1c2b6c0e99a8f80378dd3b3a8073c6a7d6da6a12f9027eb3cfb55

See more details on using hashes here.

File details

Details for the file valjson-2.0.1-py3-none-any.whl.

File metadata

  • Download URL: valjson-2.0.1-py3-none-any.whl
  • Upload date:
  • Size: 64.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for valjson-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c7909930d645917bda375c576c4103baa3e2f5850c602521d204b25483158c34
MD5 14621ec175bc8334803b17aabe14c3b6
BLAKE2b-256 ce445c578c212449b8bfa23ef6ed097afb539cb5a4cce3e486756906abce52b9

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

2.0.1 This release

2 files

2.0.0

2 files

1.0.0

2 files

0.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page