Validation & Retry Loops
A validation-retry loop turns extraction failures into self-correcting workflows by feeding Claude the original document, its failed output, and the specific validation error.
Even with tool use, production extraction produces semantic errors that schemas cannot catch. A validation-retry loop wraps each call: validate the output, and if it fails, send Claude a corrective retry. Done well, this converts one-shot failures into a resilient, self-correcting pipeline.
The retry-with-error-feedback pattern
An effective retry returns three things to the model: the original document (source to re-examine), the failed extraction (what it produced), and the specific validation error (exactly what was wrong). The specific error is the critical piece — without it, the model has no guidance and usually reproduces the same mistake.
A naive retry that just says 'try again' reproduces the mistake. Include the precise, per-field validation error so the model knows what to fix.
Retrying with no diagnosis gives the model nothing new, so it usually reproduces the same wrong output.
messages.append({"role": "user",
"content": "That was wrong. Try again."})Return the specific validation error (plus the document and failed output) so the model corrects exactly the offending fields.
messages.append({"role": "user", "content":
f"Error: {err}\nRe-examine the document and "
"fix only the fields in this error."})from pydantic import BaseModel, model_validator
class Invoice(BaseModel):
line_items_total: float
stated_total: float
@model_validator(mode="after")
def totals_match(self):
if round(self.line_items_total, 2) != round(self.stated_total, 2):
raise ValueError(
f"line_items_total {self.line_items_total} != "
f"stated_total {self.stated_total}"
)
return self
def extract_with_retry(document, max_attempts=3):
messages = [{"role": "user", "content": document}]
for attempt in range(max_attempts):
raw = call_claude_tool(messages) # returns tool_use input dict
try:
return Invoice(**raw)
except ValueError as err:
messages.append({"role": "assistant", "content": str(raw)})
messages.append({
"role": "user",
"content": (
"Your extraction failed validation.\n"
f"Error: {err}\n"
"Re-examine the original document and correct only the "
"fields involved in this error."
),
})
raise RuntimeError("Extraction failed after retries; route to human review")When retries help and when they don't
The exam heavily tests distinguishing fixable from unfixable failures. Retries work when the information exists but was processed incorrectly; they cannot conjure information that is not there.
| Failure type | Retry outcome |
|---|---|
| Format mismatch (date/currency) | Fixable — retry with error |
| Value in wrong field / bad nesting | Fixable — retry with error |
| Missed line item skews the total | Fixable — retry with error |
| Field genuinely absent from source | Unfixable — flag for human review |
| Data only in an external document | Unfixable — flag for human review |
Not every retry succeeds. If the source truly lacks the information, retrying just burns tokens — flag it for human review instead. Document A (line items sum to £450 but stated total is £500) is a retry; Document B (department field absent) is a human-review flag.
Build validation into the schema
- Calculated vs. stated totals — extract both the model's computed sum and the document's stated total; any discrepancy auto-flags without extra logic.
- Conflict-detection booleans — add a flag for contradictory source data (e.g., '30 days' in one section, 'net 60' in another) so the model surfaces the conflict instead of silently picking one.
Syntax, semantics, and Pydantic's dual role
Schema syntax errors (malformed JSON, wrong types) are eliminated by tool use. Semantic validation — cross-field arithmetic, date ordering, business rules — cannot be expressed by a JSON schema and needs external logic. This is where Pydantic earns its place: a Pydantic model enforces structure *and* semantics in one object, and its ValidationError produces machine-readable, per-field messages that drop straight into the retry prompt.
Pydantic is not redundant after tool use. Tool use covers structure; Pydantic expresses the cross-field rules schemas can't, and its errors are exactly the feedback your retry loop needs.
How the exam will try to trick you
The distractors below look right under time pressure — learn the tell.
- The trap
Retry any validation failure — retries always eventually succeed.
Correct answerRetry only when the information exists but was processed wrong; flag genuinely absent data for human review.
Why: A retry cannot conjure information that is not in the source — Document A (totals mismatch) retries; Document B (field absent) is a human flag.
- The trap
Retry with a generic message like that was wrong, try again.
Correct answerReturn the original document, the failed extraction, and the specific validation error.
Why: Without the precise error the model has no guidance and reproduces the same mistake.
- The trap
Treat
tool_useJSON-schema validation as the complete solution.Correct answerAdd semantic validators (e.g. Pydantic) for cross-field business rules on top of the schema.
Why: Schemas catch syntax errors but cannot express rules like matching sums or ordered dates.
Key takeaways
- A retry must include the original document, the failed output, and the specific validation error.
- Without the specific error, retries usually reproduce the same mistake.
- Retries fix format, structural, and arithmetic errors — not genuinely absent information.
- Flag unfixable cases (missing source data) for human review instead of looping.
- Extract calculated and stated totals, plus conflict-detection flags, to self-validate.
- Pydantic enforces structure and semantics and emits per-field errors ideal for retry prompts.
Frequently asked questions
How do you make Claude self-correct extraction errors?+
Wrap the call in a validation-retry loop: validate the output, and on failure send Claude the original document, its failed extraction, and the specific validation error so it can correct exactly the fields that were wrong.
When should a retry loop give up and flag for human review?+
When the failure is unfixable — the required information is genuinely absent from the source or lives only in an external document the model never saw. Retrying those cases just reproduces the gap; route them to a human instead.