Validating and Retrying

Validate, Then Retry with the Error

Even with schemas (and always with providers or open models that do not enforce them), validate the result. If it fails, call again and tell the model what was wrong. Most failures are fixed in one retry. After a few failed attempts, fall back (send to a human, use a default).

A JSON Extractor with Validation and Retries

Example

The scripted stand-in model makes two typical mistakes before getting it right; the loop feeds each error back:

import json, re

SCRIPTED_REPLIES = iter([
    'Here you go: {"product": "Boat Airdopes 141", "price": 1299}',          # extra text + missing field
    '{"product": "Boat Airdopes 141", "price": "1,299", "in_stock": true}',  # price is a string
    '{"product": "Boat Airdopes 141", "price": 1299, "in_stock": true}',     # correct
])
def fake_model(messages):
    """Stand-in for the model: returns the next scripted reply."""
    return next(SCRIPTED_REPLIES)

def check(data):
    errs = [f"missing {k}" for k in ("product", "price", "in_stock") if k not in data]
    if "price" in data and not isinstance(data["price"], (int, float)):
        errs.append("price must be a number, not a string")
    return errs

def extract_json(text, max_attempts=3):
    messages = [{"role": "user", "content": f"Extract product, price (number), in_stock (bool) as JSON:\n{text}"}]
    for attempt in range(1, max_attempts + 1):
        reply = fake_model(messages)
        match = re.search(r"\{.*\}", reply, re.S)          # tolerate text around the JSON
        try:
            data = json.loads(match.group(0)) if match else {}
            errors = check(data)
        except json.JSONDecodeError as e:
            errors = [f"invalid JSON: {e.msg}"]
        if not errors:
            print(f"attempt {attempt}: OK")
            return data
        print(f"attempt {attempt}: {errors}")
        messages += [{"role": "assistant", "content": reply},
                     {"role": "user", "content": f"Fix these problems and reply with JSON only: {errors}"}]
    raise ValueError("could not get valid JSON")

print(extract_json("Boat Airdopes 141 - Rs 1,299 - available"))

Output

attempt 1: ['missing in_stock']
attempt 2: ['price must be a number, not a string']
attempt 3: OK
{'product': 'Boat Airdopes 141', 'price': 1299, 'in_stock': True}

Exercise

Exercise: Add a rule to check(): price must be between 1 and 10,00,000 (10 lakh). Which attempt succeeds if the model replies with "price": 0?

Show answer
def check(data):
    errs = [f"missing {k}" for k in ("product", "price", "in_stock") if k not in data]
    price = data.get("price")
    if price is not None:
        if not isinstance(price, (int, float)):
            errs.append("price must be a number, not a string")
        elif not 1 <= price <= 1_000_000:
            errs.append("price must be between 1 and 1000000")
    return errs
# A reply with "price": 0 now fails check() and triggers a retry with the error message.