Skip to main content

llmparse

PyPI Python versions License: MIT

llmparse

Robustly extract, repair, and coerce structured data (primarily JSON) out of messy LLM text output.

Part of the ragkit suite. Install with pip install ragkit-llmparse, then import llmparse.

LLMs love to wrap JSON in markdown fences, add a friendly sentence before and after it, sprinkle in trailing commas, use single quotes, emit Python literals (True/False/None), and forget to quote object keys. llmparse cleans all of that up and hands you a real Python object — optionally coerced to a schema you expect.

Pure standard library (json, re, ast). No dependencies. Python 3.8+.

Note: this is a best-effort, heuristic library. It is designed to recover data from almost-JSON. It is not a strict validator and it can be fooled by sufficiently pathological input.

Install

pip install ragkit-llmparse

Local development (from llmparse/):

pip install -e .

Quick Start

Parse a messy LLM reply — fences plus prose — straight into a dict:

import llmparse

reply = """
Sure! Here is the data you asked for:

```json
{
    "name": "Ada Lovelace",
    "born": 1815,
    "fields": ["math", "computing"]
}

Hope this helps! """

data = llmparse.loads(reply) print(data["name"]) # Ada Lovelace print(data["fields"]) # ['math', 'computing']


## Handling each kind of mess

`loads` runs a pipeline: try raw `json.loads`, then extract the first balanced JSON region, then repair it, then fall back to `ast.literal_eval`.

### Trailing commas

```python
llmparse.loads('{"a": 1, "b": 2,}')      # {'a': 1, 'b': 2}
llmparse.loads('[1, 2, 3,]')             # [1, 2, 3]

Single quotes

llmparse.loads("{'name': 'Alice', 'age': 30}")
# {'name': 'Alice', 'age': 30}

Apostrophes inside double-quoted strings are left alone:

llmparse.loads('{"msg": "it\'s fine"}')  # {'msg': "it's fine"}

Python literals

llmparse.loads('{"a": True, "b": False, "c": None}')
# {'a': True, 'b': False, 'c': None}

Unquoted keys

llmparse.loads('{name: "Bob", age: 25}')
# {'name': 'Bob', 'age': 25}

Python-dict-style output (ast fallback)

When the text is valid Python but not valid JSON (tuples, etc.), loads falls back to ast.literal_eval:

llmparse.loads("{'a': (1, 2), 'b': {'nested': True}}")
# {'a': (1, 2), 'b': {'nested': True}}

Extracting without parsing

extract_json returns the raw JSON substring(s). The brace scanner respects string literals and escapes, so a } inside a string will not cut the object short:

llmparse.extract_json('{"a": "text with } brace"}')
# '{"a": "text with } brace"}'

# All top-level objects/arrays:
llmparse.extract_json('First {"a": 1} then {"b": 2} and [3, 4].', first=False)
# ['{"a": 1}', '{"b": 2}', '[3, 4]']

repair_json gives you the fixed-up string if you want to inspect it:

llmparse.repair_json("{name: 'Al', active: True, tags: ['x', 'y',],}")
# '{"name": "Al", "active": true, "tags": ["x", "y"]}'

Repairing already-valid JSON returns an equivalent, still-parseable string.

Schema coercion

Describe the shape you expect and let llmparse cast values into it. A schema maps each field to either a bare type or a spec dict.

schema = {
    "name":     str,
    "price":    float,
    "in_stock": bool,
    "qty":      int,
    # spec dict form:
    "discount": {"type": float, "required": False, "default": 0.0},
}

obj = {"name": "Widget", "price": "19.99", "in_stock": "true", "qty": "5"}
clean = llmparse.coerce(obj, schema)
# {'name': 'Widget', 'price': 19.99, 'in_stock': True, 'qty': 5, 'discount': 0.0}

Coercion rules (when coerce is on, which is the default per field):

  • "3" -> int 3
  • "3.5" -> float 3.5
  • "true", "false", 1, 0 -> bool
  • numbers/bools -> str

Spec dict options

key meaning default
type target type (int, float, str, bool, list, dict)
required whether the field must be present True
default value to fill if the field is missing
coerce cast the value, or require an exact type match True

Aggregated errors

coerce collects every problem and raises a single SchemaError whose .errors list holds them all — so you see all missing/invalid fields at once:

schema = {"a": int, "b": str, "c": float}
try:
    llmparse.coerce({"a": "oops"}, schema)
except llmparse.SchemaError as e:
    for problem in e.errors:
        print(problem)
    # field 'a': cannot coerce 'oops' to int
    # missing required field 'b'
    # missing required field 'c'

Extra fields

Extra fields not mentioned in the schema are kept by default. Pass strict=True to drop them:

llmparse.coerce({"a": 1, "extra": 2}, {"a": int})                 # {'a': 1, 'extra': 2}
llmparse.coerce({"a": 1, "extra": 2}, {"a": int}, strict=True)    # {'a': 1}

One-shot: parse + coerce

parse is loads followed by coerce when a schema is supplied. Extra kwargs (repair, fallback_ast) pass through to loads.

reply = """Here you go:
```json
{name: 'Widget', 'price': '19.99', in_stock: True, qty: '5',}

"""

schema = {"name": str, "price": float, "in_stock": bool, "qty": int} llmparse.parse(reply, schema)

{'name': 'Widget', 'price': 19.99, 'in_stock': True, 'qty': 5}


## Multiple objects in one blob

`extract_all_json` parses every balanced JSON object/array it can find, skipping the ones that do not parse:

```python
text = 'First user {"id": 1} and second {"id": 2}. Also a list [10, 20].'
llmparse.extract_all_json(text)
# [{'id': 1}, {'id': 2}, [10, 20]]

API summary

  • loads(text, repair=True, fallback_ast=True) — main entrypoint; returns a Python object or raises ParseError.
  • parse(text, schema=None, strict=False, **loads_kwargs)loads then optional coerce.
  • extract_json(text, first=True) — raw JSON substring(s).
  • extract_all_json(text) — list of parsed objects.
  • repair_json(s) — best-effort near-JSON -> JSON string.
  • coerce(obj, schema, strict=False) — type coercion/validation.
  • ParseError — has .snippet.
  • SchemaError — has .errors (list).

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

ragkit_llmparse-0.1.0.tar.gz (14.9 kB view details)

Uploaded Source

Built Distribution

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

ragkit_llmparse-0.1.0-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file ragkit_llmparse-0.1.0.tar.gz.

File metadata

  • Download URL: ragkit_llmparse-0.1.0.tar.gz
  • Upload date:
  • Size: 14.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for ragkit_llmparse-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e65a37d1c4d37388ad92fec54ff2084adcca177d8fb75d4f3dbd7a669dc3ac7f
MD5 789f7849ac45cc99d63fb431c21bb77f
BLAKE2b-256 c66f9a8dc790f6c580fe65c94c6dfe937ee53a3695f91f53ac968fb9446ab0ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for ragkit_llmparse-0.1.0.tar.gz:

Publisher: publish.yml on Meet2147/pythonLibraries

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ragkit_llmparse-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: ragkit_llmparse-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 10.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for ragkit_llmparse-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 893fb6f89045a2b81876dbddd448839edce5407acdd44dd8611aa41e30f52f7f
MD5 13adf00c5f53baaf48f0cb453f5283ae
BLAKE2b-256 87a7a9f516d77e8e1d4046292e60bfe1ed5e91de86fa0e623289e75cf7ae4e05

See more details on using hashes here.

Provenance

The following attestation bundles were made for ragkit_llmparse-0.1.0-py3-none-any.whl:

Publisher: publish.yml on Meet2147/pythonLibraries

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page