Credit risk
Building a Credit Risk Copilot a Regulator Would Accept
Most credit copilots stall at the compliance review. The fix is architectural: keep the scorecard conventional, ground every generated number in a validated fact, and never let the model recommend.
Every lending team has now built some version of a credit copilot. Most of them quietly stall in the same place: the model is genuinely useful in a demo, and then someone from risk or compliance asks what happens when it is wrong about a number, and there is no good answer. The fix is architectural, and it starts with being very strict about what the language model is allowed to decide.
The rule that makes everything else possible
The language model does not make the credit decision.
Not "the model makes the decision and a human reviews it." Not "the model makes the decision on thin-file applicants only." The scorecard stays a conventional, explainable model — a monotonic gradient-boosted tree or a regularised logistic regression over a governed feature set. That model is what produces the probability of default, and it is what you validate, monitor, document, and defend.
This is not conservatism for its own sake. Adverse action requirements in most jurisdictions mean that when you decline someone, you must be able to state the principal reasons — specifically, factually, and consistently. Fair-lending review needs you to demonstrate the decision boundary doesn't proxy for a protected class. Model risk governance needs a documented, reproducible artefact. A language model whose reasoning lives in its activations cannot give you any of that in a form a model validation function will sign off.
So what's the copilot for? Everything around the decision, which is where the analyst's time actually goes.
The shape of the system
documents ──► extraction (SLM) ──► structured facts ──► feature store
│
▼
scorecard (GBM / logistic)
│
┌────────────────────────┤
▼ ▼
PD + reason codes SHAP attributions
│ │
└──────────┬─────────────┘
▼
narrative layer (SLM)
│
┌──────────┴──────────┐
▼ ▼
numeric validator analyst Q&A
│
▼
memo / adverse action
The language model appears twice, and in both places it is working with facts that already exist somewhere auditable.
Stage 1: extraction
Bank statements, salary slips, GST filings, bureau reports. This is genuinely hard — not intellectually, but because the formats are endless and the failure modes are subtle. It is also the highest-volume step and therefore the clearest case for a small, fine-tuned, self-hosted model.
The design rule: extraction returns a value and a provenance pointer, or it returns nothing.
{
"field": "net_monthly_salary",
"value": 84500.00,
"currency": "INR",
"source": {
"document_id": "stmt_2026_07",
"page": 2,
"bbox": [412, 690, 508, 706],
"raw_text": "SALARY CREDIT ... 84,500.00"
},
"confidence": 0.94
}
Everything downstream depends on this. A number without a pointer back to the pixel it came from cannot be defended, cannot be spot-checked by an analyst, and cannot be debugged when it turns out the model has been reading the overdraft line as income for three weeks.
Stage 2: the scorecard (no language model)
Conventional features over the extracted facts — income stability, obligation ratios, bounce counts, bureau vintage and enquiry velocity, and so on. Monotonic constraints where the direction is known: more bounces should never reduce risk, and if your model says otherwise it has found a data artefact, not a signal.
Out of this you get the probability of default, the reason codes, and per-feature attributions. These are the raw material for everything the copilot says.
Stage 3: the narrative layer
Now the language model earns its place. Given the structured facts and the scorecard's attributions, write the credit memo an analyst would write — and answer their follow-up questions about the file.
The prompt contract is narrow on purpose:
SYSTEM
You write credit memos from structured underwriting data.
Rules:
- Use ONLY facts present in the FACTS block. Never infer a number.
- Every figure you state must appear verbatim in FACTS.
- Explain the score using the ATTRIBUTIONS block, in its ranked order.
- Never state or imply an approve/decline recommendation.
- If a fact needed for a section is missing, write "not available"
and continue.
FACTS
{structured_facts_json}
ATTRIBUTIONS
{shap_top_k_json}
"Never state a recommendation" surprises people. It exists because the moment the memo reads as advice, the human reviewer starts anchoring on it, and your human-in-the-loop control quietly stops being a control.
The validator is not optional
Grounding instructions reduce fabrication. They do not eliminate it, and in credit a single invented number is a compliance incident. So the generated text does not go anywhere until a deterministic check has passed:
import re
from decimal import Decimal
NUM = re.compile(r"[-+]?[\d,]*\.?\d+")
def ungrounded_numbers(memo: str, facts: dict, tol=Decimal("0.01")):
# Return every number in the memo that no fact supports.
allowed = {Decimal(str(v)) for v in _walk_numeric(facts)}
bad = []
for tok in NUM.findall(memo):
try:
val = Decimal(tok.replace(",", ""))
except Exception:
continue
if not any(abs(val - a) <= tol for a in allowed):
bad.append(tok)
return bad
# in the pipeline
issues = ungrounded_numbers(memo, facts)
if issues:
metrics.increment("memo.ungrounded", len(issues))
memo = regenerate_or_escalate(facts, attributions, issues)
It is a crude check and it catches a genuinely useful class of error — transposed digits, totals the model "helpfully" computed, percentages it estimated rather than read. Track the failure rate as a first-class metric: a sudden rise is usually the first sign that an upstream extraction template has drifted.
Evaluating it properly
Text-similarity scores against reference memos are close to worthless here — two memos can be worded completely differently and both be correct, or worded almost identically and differ on the one number that matters. Measure the things that can hurt you:
| Dimension | How to measure it | Bar |
|---|---|---|
| Numeric grounding | Automated validator over every generated memo | Zero tolerance; alert on any |
| Attribution fidelity | Do the drivers cited match the top SHAP features, in order? | Automated, high bar |
| Completeness | Required sections present and populated | Automated, high bar |
| Analyst usefulness | Blind A/B against human-written memos, rated by underwriters | Sampled weekly |
| Consistency | Near-identical files → near-identical memos | Paired-file regression suite |
| Latency | p50 / p95 / p99 end to end | Within the analyst's flow |
The consistency test is the one most teams miss and the one a fair-lending reviewer will find first. Construct pairs of applications that differ only in a protected or proxy attribute and confirm the memos don't diverge in tone or emphasis. Keep it in CI.
Fine-tuning: less data than you'd think
If you have historical analyst-written memos alongside the files they describe, you have a supervised dataset. In the low thousands of pairs, a LoRA fine-tune on a small open model is usually enough to pick up your house style, your section structure, and your vocabulary — which is most of what "sounds like us" means.
Two things worth doing that teams routinely skip:
- Strip the recommendation from every training memo. If your analysts wrote "recommend approval," and you train on it, your model will write it too, and your no-recommendation rule dies in training rather than in review.
- Include the ugly files. Missing documents, contradictory income evidence, thin bureau history. If the fine-tune only ever sees clean files, it learns to write confidently about incomplete ones — exactly the wrong failure mode.
What to log
Assume you will one day have to reconstruct a specific decision from eighteen months ago. That means, per application: model and prompt version hashes, the exact FACTS and ATTRIBUTIONS blocks passed in, the raw generation, validator results, any regeneration, what the analyst saw, what they changed, and what they decided.
Storage is cheap. Explaining to a regulator that you can't reproduce a memo is not.
What you actually get
Done this way, the copilot doesn't make credit decisions — it removes the document-wrangling and memo-drafting that consumes most of an underwriter's day, and it does so in a form where every claim traces back to a field, every field traces back to a page, and the decision itself remains the property of a model your risk function already knows how to defend.
That's a narrower promise than the demos make. It's also the version that survives contact with a model validation committee.
Want this built on your data?
Writing about this is our day job because building it is. We build and fine-tune custom AI models for fintech teams — fraud, credit risk, document extraction — and hand back models you own and can run yourself. Or build it yourself in a sciFi notebook.
Keep reading
Research
What the Latest Fintech AI Research Actually Says
Three 2026 papers converge on the same finding: the hard problem is no longer modelling, it's evidence — reproducibility you can prove, latency and cost you actually measured, and attributions that don't move between runs.
Fraud
Fraud Detection in the SLM Era: Where Language Models Actually Help
Gradient boosting still wins on transaction fraud. The real gains from small language models are in unstructured features, alert triage, and deduplication — everywhere except the authorisation path.