Overview

This walkthrough demonstrates a complete fairness audit of a foundation model (LLM) deployment where training data is not available. Unlike the credit scoring case, this audit operates entirely at the output level — evaluating the model as a black box through systematic prompt testing, output analysis, and regression-based fairness metrics.

!
Black-box constraint: When you deploy a third-party foundation model (GPT-4, Claude, Gemini, Llama, etc.), you typically have no access to training data, model weights, or internal representations. The entire audit must be conducted through input-output evaluation — systematically probing the model with controlled prompts and measuring fairness across its outputs. This fundamentally changes which vfairness modules apply.
What you will build

By following this guide, you will produce: an output-level data validation, auto-discovery bias scan, prompt fairness probes, output calibration analysis, comprehensive fairness metrics with confidence intervals, regression fairness evaluation, CI/CD gate decisions, monitoring dashboards, multi-tier reports, and an EU AI Act–compliant model card for a GPAI-based high-risk system — all backed by 22 production-grade SVG artifacts.

The Scenario

LLM-Powered Resume Screening — EU AI Act High-Risk System

A recruitment platform deploys a foundation model API to screen and rank job applicants. The LLM reads each resume, produces a suitability score (0–100) and a narrative summary. Under the EU AI Act (Annex III, Section 4a), AI systems used in recruitment and selection of workers are classified as high-risk AI. Additionally, the use of a GPAI model triggers Article 51–56 obligations for the GPAI provider, while the deployer retains full responsibility for the downstream application.

5,000
Evaluation Resumes
3
Protected Attributes
22
SVG Artifacts
10
Audit Phases
python
import pandas as pd
import numpy as np

# ── Load the evaluation dataset ──
# Each resume was scored by the LLM API; we collected outputs + demographic metadata
df = pd.read_csv("resume_screening_outputs.csv")

# Columns: applicant_id, llm_score (0-100), llm_decision (shortlisted/rejected),
#           gender, ethnicity, age_group, job_category, years_experience, ...

PROTECTED   = ["gender", "ethnicity", "age_group"]
SCORE_COL   = "llm_score"         # Continuous output (0-100)
OUTCOME_COL = "llm_decision"      # Binary: shortlisted=1, rejected=0
FEATURES    = [c for c in df.columns if c not in PROTECTED + [OUTCOME_COL, SCORE_COL, "applicant_id"]]

# Convert binary decision
df["shortlisted"] = (df[OUTCOME_COL] == "shortlisted").astype(int)

# Extract subsets for analysis
y_true     = df["shortlisted"]       # LLM decisions as binary labels
y_scores   = df[SCORE_COL] / 100.0   # Normalize to [0, 1] for probability-like analysis
sensitive  = df[PROTECTED]

print(f"Dataset: {len(df)} resumes, {df['shortlisted'].mean():.1%} shortlisted")
print(f"Protected attributes: {PROTECTED}")

Audit Pipeline

The ten phases below are adapted for a black-box foundation model deployment. Phases that depend on training data (feature engineering, fair training) are replaced with output-level equivalents: prompt fairness probes and regression fairness analysis.

1Output
Validation
2Bias
Detection
3Prompt
Probes
4Output
Calibration
5Metrics &
Testing
6Regression
Fairness
7CI/CD
Gating
8Monitoring
9Dashboards
10Model
Card
!
Adapted pipeline: Without access to training data, we skip traditional feature engineering and in-processing debiasing. Instead, Phase 3 uses counterfactual prompt testing (swapping demographic indicators in otherwise identical resumes) and Phase 6 adds regression fairness to evaluate the continuous score output. All other phases apply with minor adaptations.
1 Preprocessing

Output Data Validation

Before analyzing fairness, validate the LLM output dataset for completeness, group representation, and anomalous score distributions. With foundation models, common issues include inconsistent output formats, refusal-to-score for certain demographics, and uneven evaluation coverage.

python
from vfairness.operations.cicd import DataBiasValidator, DataValidationConfig

# Validate the collected LLM outputs — same tool, different context
validator = DataBiasValidator(config=DataValidationConfig(
    min_group_size=50,
    max_missing_rate=0.05,
    proxy_correlation_threshold=0.3,
))
result = validator.validate(df, protected_attrs=PROTECTED, outcome_col="shortlisted")

print(f"Status: {result.status}")       # PASS, FAIL, or CONDITIONAL
print(f"Issues: {len(result.issues)}")

# ── Generate SVG artifact ──
from vfairness.rendering import data_validation_to_svg

data_validation_to_svg(
    result,
    explanation="Output data quality assessment for the LLM resume screening evaluation "
                "dataset. Checks demographic group representation, missing scores, and "
                "distribution anomalies across 5,000 scored resumes.",
    save_path="artifacts/01_data_validation.svg",
)
Data Validation Report
Data Validation Report data_validation_to_svg()
Findings

1 warning: age_group=55+ has only 62 resumes (1.2% of dataset) — borderline representation. 1 informational: 2.1% of LLM outputs had parsing errors (missing scores recovered from narrative). No critical issues blocking the audit.

2 Preprocessing

Bias Detection & Auto-Discovery

Run the auto-discovery scanner on the output dataset to identify which demographic attributes correlate with LLM decisions. Then perform a full bias audit to detect disparate impact in shortlisting rates, score distributions, and intersectional patterns.

python
from vfairness.evaluation.vfairness_metrics import (
    detect_protected_attributes,
    scan_fairness_violations,
    discover_intersectional_groups,
)
from vfairness.preprocessing.bias_detection import BiasDetector
from vfairness.rendering import auto_discovery_to_svg, bias_audit_to_svg

# ── Auto-discovery on LLM outputs ──
candidates = detect_protected_attributes(df, outcome_col="shortlisted")
violations = scan_fairness_violations(y_true, (y_scores > 0.5).astype(int), sensitive["gender"])
intersectional = discover_intersectional_groups(
    df, protected_attrs=PROTECTED, outcome_col="shortlisted"
)

auto_discovery_to_svg(
    candidates=candidates,
    violations=violations,
    group_advantages=intersectional,
    explanation="Automated scan of LLM outputs confirming gender, ethnicity, and age_group "
                "as attributes correlated with screening decisions. Note: these correlations "
                "exist in the model's outputs, not its training data (which is unavailable).",
    save_path="artifacts/02a_auto_discovery.svg",
)

# ── Full bias audit on output data ──
detector = BiasDetector(df, protected_attributes=PROTECTED, outcome_column="shortlisted")
audit = detector.full_audit()

bias_audit_to_svg(
    audit.to_dict(),
    explanation="Comprehensive output-level bias audit. Since training data is unavailable, "
                "this audit focuses on outcome disparities, score distribution differences, "
                "and intersectional patterns in the LLM's decisions.",
    save_path="artifacts/02b_bias_audit.svg",
)
Auto-Discovery Scanner
Auto-Discovery Scanner auto_discovery_to_svg()
Bias Audit Dashboard
Bias Audit Dashboard bias_audit_to_svg()
Findings

Significant shortlisting rate disparity for gender (male: 42%, female: 31%). Intersectional analysis reveals female × age_group=55+ has the lowest shortlisting rate (18%) — 2.3× lower than male × age_group=25-34. Two high-severity violations flagged.

3 Preprocessing

Prompt Fairness Probes (Counterfactual Testing)

Since we cannot inspect or modify the model’s internal features, we probe its behavior using counterfactual pairs: identical resumes where only demographic signals vary. This is the LLM equivalent of feature engineering analysis — instead of removing proxy features, we measure how much demographic signals influence outputs.

!
Counterfactual methodology: For each test resume, generate demographic variants (e.g., changing name from “James” to “Aisha”, or removing age-indicating graduation years). Score all variants through the LLM API. The score delta between matched pairs quantifies the model’s sensitivity to demographic signals.
python
from vfairness.evaluation.vfairness_metrics import compute_effect_sizes
from vfairness.rendering import (
    disparity_heatmap_to_svg,
    effect_sizes_to_svg,
    intersectional_analysis_to_svg,
)

# ── Counterfactual probe results ──
# df_probes contains paired scores: original vs demographic-swapped resumes
# Columns: pair_id, original_score, swapped_score, attribute_swapped, delta
df_probes = pd.read_csv("counterfactual_probe_results.csv")

# Aggregate deltas per attribute into a disparity measure
probe_results = {}
for attr in PROTECTED:
    subset = df_probes[df_probes["attribute_swapped"] == attr]
    probe_results[attr] = {
        "mean_delta": subset["delta"].mean(),
        "max_delta": subset["delta"].abs().max(),
        "significant_pairs": (subset["delta"].abs() > 5).sum(),  # > 5 point swing
    }

# ── Disparity heatmap (groups × outcomes) ──
disparity_heatmap_to_svg(
    y_true, (y_scores > 0.5).astype(int), sensitive,
    explanation="Disparate impact heatmap across all protected attribute intersections. "
                "Darker cells indicate higher outcome disparity between groups.",
    save_path="artifacts/03a_disparity_heatmap.svg",
)

# ── Effect sizes from counterfactual probes ──
effects = compute_effect_sizes(y_true, (y_scores > 0.5).astype(int), sensitive["gender"])

effect_sizes_to_svg(
    effects,
    explanation="Cohen's d effect sizes from counterfactual probe testing. Measures the "
                "practical significance of LLM score changes when demographic signals are "
                "swapped in otherwise identical resumes.",
    save_path="artifacts/03b_effect_sizes.svg",
)

# ── Intersectional analysis ──
intersectional_analysis_to_svg(
    intersectional,
    explanation="Intersectional group analysis showing how combined demographic attributes "
                "(e.g., female × age 55+) amplify or attenuate screening disparities.",
    save_path="artifacts/03c_intersectional_analysis.svg",
)
Disparity Heatmap
Disparity Heatmap disparity_heatmap_to_svg()
Counterfactual Effect Sizes
Counterfactual Effect Sizes effect_sizes_to_svg()
Intersectional Analysis
Intersectional Analysis intersectional_analysis_to_svg()
Findings

Counterfactual probes reveal a mean score delta of 4.7 points for gender swaps and 6.2 points for ethnicity swaps. 23% of pairs show >5-point swings — the LLM is sensitive to demographic signals embedded in names and biographical details. age_group shows the largest effect size (d=0.31).

4 Post-Processing

Output Calibration

LLM-generated scores are not inherently calibrated — a score of “75” for one demographic group may correspond to a different true suitability than “75” for another. Calibration analysis checks whether scores have consistent meaning across groups, and post-hoc calibration can correct systematic biases.

python
from vfairness.post_processing.calibration import CalibrationAnalyzer
from vfairness.rendering import (
    reliability_diagram_to_svg,
    calibration_report_to_svg,
    group_calibration_to_svg,
)

# Use human reviewer decisions as ground truth for calibration
# y_human: binary labels from human panel review of 1,000 resumes
y_human = df.loc[:999, "human_decision"]

cal = CalibrationAnalyzer(y_human, y_scores[:1000], sensitive.loc[:999, "gender"])
cal_report = cal.full_analysis()

# ── Reliability diagram ──
reliability_diagram_to_svg(
    y_human, y_scores[:1000],
    explanation="Reliability diagram comparing LLM scores to human reviewer decisions. "
                "Deviation from the diagonal shows where the LLM over- or under-estimates "
                "candidate suitability relative to human judgment.",
    save_path="artifacts/04a_reliability_diagram.svg",
)

# ── Full calibration report with group analysis ──
calibration_report_to_svg(
    cal_report.to_dict(),
    explanation="Group-wise calibration analysis comparing LLM score reliability across "
                "demographic groups. ECE per group reveals differential calibration quality.",
    save_path="artifacts/04b_calibration_report.svg",
)

# ── Per-group calibration curves ──
group_calibration_to_svg(
    cal_report.to_dict(),
    explanation="Overlay of group-specific calibration curves. Groups diverging from "
                "the overall curve indicate the LLM assigns scores with different meaning "
                "for different demographics.",
    save_path="artifacts/04c_group_calibration.svg",
)
Calibration Report
Calibration Report calibration_report_to_svg()
Reliability Diagram
Reliability Diagram reliability_diagram_to_svg()
Group Calibration
Group Calibration Curves group_calibration_to_svg()
Findings

Overall ECE = 0.091 (moderate miscalibration). The LLM systematically overestimates suitability for male candidates (ECE = 0.061) while underestimating for female candidates (ECE = 0.124). Group calibration disparity = 0.063, exceeding the 0.02 threshold. Post-hoc isotonic calibration reduces group ECE disparity to 0.015.

5 Evaluation

Comprehensive Metrics & Robustness Testing

Compute all standard fairness metrics on the LLM’s binary decisions (shortlisted/rejected), with statistical confidence intervals. Run robustness tests to verify that findings are stable across data perturbations and subgroup splits.

python
from vfairness import FairnessAnalyzer
from vfairness.evaluation.vfairness_metrics import (
    permutation_test, sensitivity_analysis, subgroup_robustness_audit,
)
from vfairness.rendering import (
    radar_chart_to_svg,
    metrics_bar_chart_to_svg,
    confidence_intervals_to_svg,
    group_comparison_to_svg,
    robustness_testing_to_svg,
)

# ── Full fairness analysis on LLM decisions ──
analyzer = FairnessAnalyzer(
    y_true, (y_scores > 0.5).astype(int), sensitive["gender"],
    y_prob=y_scores,
    fair_explainer=True,
)
report = analyzer.get_report(include_ci=True, include_explanations=True)

# ── Radar chart: all metrics at a glance ──
radar_chart_to_svg(
    report,
    explanation="Multi-metric radar chart for LLM resume screening outputs. Shows "
                "demographic parity, equalized odds, and predictive parity.",
    save_path="artifacts/05a_radar_chart.svg",
)

# ── Bar chart with pass/fail thresholds ──
metrics_bar_chart_to_svg(
    report["metrics"],
    explanation="Fairness metric values against regulatory thresholds for the LLM's "
                "binary shortlisting decisions.",
    save_path="artifacts/05b_metrics_bar_chart.svg",
)

# ── Confidence intervals ──
confidence_intervals_to_svg(
    report["confidence_intervals"],
    explanation="95% bootstrap confidence intervals for each fairness metric. Wider "
                "intervals reflect higher uncertainty in the LLM's output consistency.",
    save_path="artifacts/05c_confidence_intervals.svg",
)

# ── Group comparison ──
group_comparison_to_svg(
    report["group_metrics"],
    explanation="Side-by-side comparison of shortlisting rates, score distributions, "
                "and precision across demographic groups.",
    save_path="artifacts/05d_group_comparison.svg",
)

# ── Robustness testing ──
perm = permutation_test(y_true, (y_scores > 0.5).astype(int), sensitive["gender"])
sens = sensitivity_analysis(y_true, (y_scores > 0.5).astype(int), sensitive["gender"])
audit = subgroup_robustness_audit(y_true, (y_scores > 0.5).astype(int), sensitive["gender"])

robustness_testing_to_svg(
    permutation_results=[perm],
    sensitivity_results=[sens],
    subgroup_results=[audit],
    explanation="Robustness validation for LLM output fairness. Permutation tests confirm "
                "that observed disparities are statistically significant and not artifacts "
                "of sampling variation.",
    save_path="artifacts/05e_robustness_testing.svg",
)
Fairness Radar Chart
Fairness Radar Chart radar_chart_to_svg()
Metrics Bar Chart
Metrics Bar Chart metrics_bar_chart_to_svg()
Confidence Intervals
Confidence Intervals confidence_intervals_to_svg()
Group Comparison
Group Comparison group_comparison_to_svg()
Robustness Testing
Robustness Testing Dashboard robustness_testing_to_svg()
Findings

Demographic parity difference = 0.11 (FAIL, threshold 0.10). Equalized odds difference = 0.08 (PASS). Permutation tests confirm statistical significance (p < 0.001). Robustness score = 0.68 (MARGINAL). The demographic parity violation is consistent across sensitivity perturbations, indicating a systematic pattern in the LLM’s outputs.

6 Evaluation

Regression Fairness (Continuous Scores)

The LLM produces continuous suitability scores (0–100), not just binary decisions. Regression fairness metrics evaluate whether score distributions, prediction errors, and effect sizes are equitable across groups — capturing biases that binary metrics may miss.

!
Why regression fairness matters for LLMs: A model may pass binary fairness checks (shortlisting rates) while assigning systematically lower scores to certain groups — creating a “glass floor” where shortlisted candidates from disadvantaged groups are ranked lower and less likely to advance in subsequent hiring stages.
python
from vfairness.evaluation.vfairness_metrics.regression import (
    get_group_metrics,
    compute_regression_effect_sizes,
    mae_parity_difference,
    mean_prediction_difference,
)
from vfairness.rendering import regression_fairness_to_svg

# Use human panel scores as ground truth for a subset
y_human_scores = df.loc[:999, "human_score"] / 100.0  # Normalized to [0, 1]
y_llm_scores   = y_scores[:1000]
sensitive_sub   = sensitive.loc[:999, "gender"]

# ── Group-level regression metrics ──
group_metrics = get_group_metrics(y_human_scores, y_llm_scores, sensitive_sub)
effects       = compute_regression_effect_sizes(y_human_scores, y_llm_scores, sensitive_sub)
mae_parity    = mae_parity_difference(y_human_scores, y_llm_scores, sensitive_sub)
mean_pred     = mean_prediction_difference(y_human_scores, y_llm_scores, sensitive_sub)

print(f"MAE parity difference: {mae_parity:.3f}")
print(f"Mean prediction difference: {mean_pred:.3f}")

regression_fairness_to_svg(
    group_metrics=group_metrics,
    effect_sizes=effects,
    mae_parity=mae_parity,
    mean_pred_diff=mean_pred,
    explanation="Regression fairness analysis of the LLM's continuous suitability scores. "
                "Compares MAE, RMSE, R², and residual patterns across demographic groups. "
                "Human panel scores serve as ground truth for 1,000 calibration resumes.",
    save_path="artifacts/06_regression_fairness.svg",
)
Regression Fairness
Regression Fairness Dashboard regression_fairness_to_svg()
Findings

MAE parity difference = 0.14 (FAIL, threshold 0.10). The LLM produces higher prediction errors for female candidates (MAE = 0.18) vs male (MAE = 0.11). Mean prediction difference = 0.09, indicating systematic under-scoring. Cohen’s d = 0.28 (small-to-medium effect), confirming the “glass floor” pattern in continuous scores.

7 Operations

CI/CD Gating

Even when the foundation model is a third-party API, the deployment pipeline should enforce fairness gates. This protects against regressions when the LLM provider updates their model (which can happen without notice), or when your prompt templates change.

python
from vfairness.operations.cicd import ModelFairnessGate, GateConfig
from vfairness.operations.cicd.gate import evaluate_hierarchical, HierarchicalGateConfig
from vfairness.rendering import cicd_pipeline_to_svg, hierarchical_gate_to_svg

# ── Standard gate on LLM outputs ──
gate = ModelFairnessGate(config=GateConfig(
    thresholds={
        "demographic_parity_difference": 0.10,
        "equalized_odds_difference": 0.10,
    },
    require_min_samples=30,
))
decision = gate.evaluate(y_true, (y_scores > 0.5).astype(int), sensitive["gender"], y_prob=y_scores)

cicd_pipeline_to_svg(
    decision.to_dict(),
    explanation="CI/CD fairness gate for the LLM resume screening deployment. "
                "Triggers on every prompt template update or LLM API version change. "
                "Current status: CONDITIONAL — demographic parity exceeds threshold.",
    save_path="artifacts/07a_cicd_pipeline.svg",
)

# ── Hierarchical intersectional gate ──
hier_config = HierarchicalGateConfig(
    global_thresholds={"demographic_parity_difference": 0.10},
    per_intersection_thresholds={
        "female_55+": {"demographic_parity_difference": 0.15},
    },
    min_subgroup_size=30,
)
hier_decision = evaluate_hierarchical(
    y_true, (y_scores > 0.5).astype(int), sensitive, config=hier_config
)

hierarchical_gate_to_svg(
    hier_decision,
    explanation="Hierarchical gate evaluating LLM outputs at global, per-group, and "
                "intersectional levels. Flags vulnerable subgroups identified in Phase 2.",
    save_path="artifacts/07b_hierarchical_gate.svg",
)
CI/CD Pipeline Gate
CI/CD Pipeline Gate cicd_pipeline_to_svg()
Hierarchical Gate
Hierarchical Gate hierarchical_gate_to_svg()
Gate Decision: CONDITIONAL

Demographic parity difference (0.11) marginally exceeds the 0.10 threshold. The gate recommends: (1) apply post-hoc calibration from Phase 4, (2) add demographic-blind prompt template, (3) re-evaluate before production deployment. Equalized odds passes.

8 Operations

Production Monitoring

Foundation model monitoring is critical because the underlying model can change without notice. LLM providers regularly update models, and even minor version changes can alter fairness profiles. Continuous monitoring detects these regressions automatically.

python
from vfairness.operations.monitoring import FairnessMonitor, FairnessMonitorConfig
from vfairness.operations.monitoring import FairnessDriftDetector
from vfairness.rendering import monitoring_dashboard_to_svg, drift_report_to_svg

# ── Initialize monitor for LLM outputs ──
monitor = FairnessMonitor(config=FairnessMonitorConfig(
    metrics=["demographic_parity_difference", "equalized_odds_difference"],
    window_size="1h",
    alert_thresholds={"demographic_parity_difference": 0.12},
))

# Log each batch of LLM screening decisions
monitor.log_predictions(y_prod_true, y_prod_pred, sensitive_prod)

monitoring_dashboard_to_svg(
    monitor,
    explanation="Real-time fairness monitoring for the LLM resume screener. Tracks "
                "metric trends, alert status, and detects sudden shifts that may "
                "indicate an upstream model update by the LLM provider.",
    save_path="artifacts/08a_monitoring_dashboard.svg",
)

# ── Drift detection (critical for third-party LLMs) ──
detector = FairnessDriftDetector()
drift = detector.detect_drift(
    y_prod_true, y_prod_pred, sensitive_prod,
    baseline_metrics=report["metrics"],
)

drift_report_to_svg(
    drift,
    explanation="Drift analysis comparing current LLM outputs against the audit baseline. "
                "Sudden metric shifts may indicate the LLM provider deployed a model update.",
    save_path="artifacts/08b_drift_report.svg",
)
Monitoring Dashboard
Monitoring Dashboard monitoring_dashboard_to_svg()
Drift Report
Drift Report drift_report_to_svg()
Findings

No significant drift in the initial monitoring window. Demographic parity stable at 0.11 ± 0.02. Automated alerts configured to fire if any metric exceeds threshold or if a sudden shift (>0.05 change within one window) is detected — a common pattern when LLM providers silently update models.

9 Reporting

Multi-Tier Reporting

Generate three report tiers for different stakeholders. For LLM deployments, the executive report must explicitly address the third-party model dependency and the additional GPAI obligations under the EU AI Act.

python
from vfairness.operations.reporting import ReportGenerator, ReportConfig, MetricsStore
from vfairness.rendering import reporting_dashboard_to_svg

store = MetricsStore()
store.record_metric("demographic_parity_difference", 0.11, group="gender")
store.record_metric("equalized_odds_difference", 0.08, group="gender")
store.record_metric("mae_parity_difference", 0.14, group="gender")
store.record_metric("calibration_difference", 0.063, group="gender")

generator = ReportGenerator(config=ReportConfig(
    title="LLM Resume Screening Fairness Report",
))

# ── Executive report ──
exec_report = generator.generate(store, tier="executive", format="markdown")
reporting_dashboard_to_svg(
    exec_report, tier="executive",
    explanation="Board-level summary: overall fairness health, key risk indicators, "
                "and third-party model dependency status for EU AI Act compliance.",
    save_path="artifacts/09a_executive_dashboard.svg",
)

# ── Operational report ──
ops_report = generator.generate(store, tier="operational", format="markdown")
reporting_dashboard_to_svg(
    ops_report, tier="operational",
    explanation="Team-level dashboard with LLM output trends, counterfactual probe "
                "results, and recommendations for prompt template adjustments.",
    save_path="artifacts/09b_operational_dashboard.svg",
)

# ── Technical report ──
tech_report = generator.generate(store, tier="technical", format="json")
reporting_dashboard_to_svg(
    tech_report, tier="technical",
    explanation="Full statistical evidence including confidence intervals, regression "
                "fairness metrics, calibration analysis, and robustness test results.",
    save_path="artifacts/09c_technical_dashboard.svg",
)
Executive Dashboard
Executive Dashboard reporting_dashboard_to_svg(tier="executive")
Operational Dashboard
Operational Dashboard reporting_dashboard_to_svg(tier="operational")
Technical Dashboard
Technical Dashboard reporting_dashboard_to_svg(tier="technical")
10 Compliance

EU AI Act Model Card (GPAI + High-Risk)

For a foundation model deployment in a high-risk context, the model card must address dual obligations: (1) deployer obligations under the high-risk AI provisions (Articles 9–15), and (2) GPAI-specific provisions (Articles 51–56) that apply to the model provider. The deployer must document what information the GPAI provider has — and has not — made available.

python
from vfairness.operations.cicd.gate import FairnessReportCard
from vfairness.rendering import report_card_to_svg

card = FairnessReportCard(
    model_name="ResumeScreen-LLM-v1.0",
    model_version="1.0.0",
    description="Foundation model API (third-party LLM) used for automated resume "
                "screening and candidate ranking. Produces suitability scores (0-100) "
                "and narrative summaries from resume text.",
    intended_use="Pre-screening of job applications for shortlisting. Human recruiter "
                 "review required for all shortlisting decisions. No fully automated "
                 "rejection without human oversight.",
    risk_classification="HIGH",  # EU AI Act Annex III, Section 4a (employment)
    protected_attributes=PROTECTED,
    fairness_metrics={
        "demographic_parity_difference": 0.11,
        "equalized_odds_difference": 0.08,
        "mae_parity_difference": 0.14,
        "calibration_difference": 0.063,
    },
    training_data_summary={
        "size": "Unknown — third-party foundation model",
        "source": "GPAI provider has not disclosed training data composition. "
                  "Provider's model card states: 'trained on diverse internet text'. "
                  "No demographic representation audit available from provider.",
        "preprocessing": "Not applicable — no access to training pipeline. "
                         "Post-hoc calibration applied at deployment level.",
    },
    evaluation_summary={
        "evaluation_dataset_size": 5_000,
        "human_validation_size": 1_000,
        "counterfactual_probes": 500,
        "bootstrap_samples": 1_000,
        "ci_level": 0.95,
    },
    gate_decision=decision.to_dict(),
    monitoring_config={
        "drift_detection": "enabled — critical for third-party model updates",
        "alert_thresholds": {"demographic_parity_difference": 0.12},
        "review_cadence": "weekly (elevated frequency due to third-party dependency)",
        "model_version_tracking": "API response headers logged per request",
    },
    human_oversight="All shortlisting decisions reviewed by a human recruiter. "
                    "Quarterly fairness review board meetings. Monthly counterfactual "
                    "re-probing to detect model drift.",
    limitations=[
        "No access to foundation model training data or weights.",
        "Fairness assessment limited to output-level evaluation.",
        "Counterfactual probes may not capture all forms of bias.",
        "LLM provider may update the model without notice, changing the fairness profile.",
        "Small sample size for age_group=55+ (n=62) limits intersectional analysis.",
        "Human panel ground truth limited to 1,000 resumes.",
    ],
    references=[
        "EU AI Act, Regulation 2024/1689, Annex III Section 4(a) (employment)",
        "EU AI Act, Articles 51-56 (General-Purpose AI obligations)",
        "EU AI Act, Article 25 (Responsibilities along the AI value chain)",
        "ISO/IEC 42001:2023 — AI management system standard",
    ],
)

report_card_to_svg(
    card,
    explanation="EU AI Act-compliant model card for an LLM-based high-risk deployment. "
                "Documents dual obligations: deployer responsibilities under Articles 9-15, "
                "and GPAI provider obligations under Articles 51-56. Explicitly notes "
                "training data unavailability and third-party dependency risks.",
    save_path="artifacts/10_model_card.svg",
)
EU AI Act Model Card
EU AI Act Model Card (GPAI) report_card_to_svg()
EU AI Act: Dual Obligation Framework

When deploying a GPAI model in a high-risk context, compliance responsibilities are shared:

  • GPAI Provider (Articles 51–56) — Must provide model card, technical documentation, training data summary, and downstream evaluation capabilities
  • Deployer (Articles 9–15) — Responsible for the full high-risk AI compliance stack: risk management, data governance, testing, transparency, human oversight, monitoring
  • Article 25 — If the GPAI provider does not supply sufficient information, the deployer must document this gap and compensate through enhanced output-level evaluation (exactly what this audit demonstrates)

Complete Artifact Inventory

The table below lists every artifact generated during this LLM audit. Each SVG is a self-contained, print-ready visualization suitable for regulatory submissions.

Phase Artifact Function Module
1Data Validation Reportdata_validation_to_svg()Preprocessing
2Auto-Discovery Scannerauto_discovery_to_svg()Preprocessing
2Bias Audit Dashboardbias_audit_to_svg()Preprocessing
3Disparity Heatmapdisparity_heatmap_to_svg()Preprocessing
3Counterfactual Effect Sizeseffect_sizes_to_svg()Preprocessing
3Intersectional Analysisintersectional_analysis_to_svg()Preprocessing
4Reliability Diagramreliability_diagram_to_svg()Post-Processing
4Calibration Reportcalibration_report_to_svg()Post-Processing
4Group Calibration Curvesgroup_calibration_to_svg()Post-Processing
5Fairness Radar Chartradar_chart_to_svg()Evaluation
5Metrics Bar Chartmetrics_bar_chart_to_svg()Evaluation
5Confidence Intervalsconfidence_intervals_to_svg()Evaluation
5Group Comparisongroup_comparison_to_svg()Evaluation
5Robustness Testingrobustness_testing_to_svg()Evaluation
6Regression Fairnessregression_fairness_to_svg()Evaluation
7CI/CD Pipeline Gatecicd_pipeline_to_svg()Operations
7Hierarchical Gatehierarchical_gate_to_svg()Operations
8Monitoring Dashboardmonitoring_dashboard_to_svg()Operations
8Drift Reportdrift_report_to_svg()Operations
9Executive Dashboardreporting_dashboard_to_svg()Reporting
9Operational Dashboardreporting_dashboard_to_svg()Reporting
9Technical Dashboardreporting_dashboard_to_svg()Reporting
10EU AI Act Model Card (GPAI)report_card_to_svg()Reporting

Workflow Integration

The audit above produces artifacts manually. To automate and enforce these checks in your development pipeline, vfairness provides workflow integration tools that plug directly into MLOps, CI/CD, version control, and testing infrastructure. See the Workflow Integration guide for full documentation.

1. Experiment Tracking

Log every LLM evaluation run’s fairness metrics to MLflow or Weights & Biases so you can compare across prompt versions, model providers, and API updates.

python
# Option A: explicit logging after Phase 5 evaluation
from vfairness import log_fairness_to_mlflow
import mlflow

with mlflow.start_run(run_name="gpt4o-resume-prompt-v3"):
    logged = log_fairness_to_mlflow(report, prefix="llm_resume")
    print(f"Logged {logged} metrics to MLflow")

# Option B: auto-logging decorator — wraps LLM evaluation functions
from vfairness import auto_log_fairness

@auto_log_fairness(backend="wandb", prefix="llm_resume")
def evaluate_llm_fairness(predictions, ground_truth, sensitive):
    # predictions from counterfactual prompt evaluation
    return predictions, ground_truth, sensitive

2. Pre-Commit Hooks

Enforce documentation standards before code reaches the repository. Particularly important for LLM deployments where prompt templates and evaluation configs change frequently.

yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/your-org/vfairness
    rev: v0.0.8
    hooks:
      - id: vfairness-check-config    # validates fairness JSON configs
      - id: vfairness-check-model-card # ensures model cards include fairness sections

3. pytest Integration

Embed fairness assertions in your test suite. For LLM audits, this is especially valuable for testing counterfactual prompt pairs and regression-style output scoring — same checks as Phases 5–6, but automated on every commit.

python
import pytest
from vfairness import assert_fairness, FairnessTestSuite

# Test counterfactual fairness of LLM output scores
def test_llm_resume_fairness():
    assert_fairness(
        scores_ground_truth, scores_predicted, sensitive_test["gender"],
        metrics=["demographic_parity_difference", "equalized_odds_difference"],
        thresholds={"demographic_parity_difference": 0.10, "equalized_odds_difference": 0.15},
    )

# Full test suite covering all protected attributes
suite = FairnessTestSuite(
    protected_attributes=["gender", "ethnicity", "age_group"],
    metrics=["demographic_parity_difference"],
    thresholds={"demographic_parity_difference": 0.10},
)
results = suite.test_predictions(y_true, y_pred, sensitive_test)
xml = suite.to_junit_xml()  # attach to CI pipeline artifacts

4. CI/CD Automation & PR Comments

Gate decisions from Phase 7 can be posted directly to GitHub as check results and PR comments. For GPAI deployments, this ensures that prompt template or evaluation pipeline changes are fairness-validated before merge.

python
# Post gate result as a GitHub Check (from Phase 7)
check_payload = gate.create_github_check(decision)
# → POST to /repos/{owner}/{repo}/check-runs

# Generate PR comment with full fairness report card
from vfairness import FairnessReportCard
card = FairnessReportCard(decision, model_name="GPT-4o-Resume-Screener-v3")
comment_payload = card.to_github_comment_payload()
# → POST to /repos/{owner}/{repo}/issues/{pr_number}/comments
yaml
# .github/workflows/fairness-checks.yml
name: LLM Fairness Gate
on: [pull_request]
jobs:
  fairness:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install vfairness
      - run: python -m pytest tests/ -m fairness --junitxml=fairness-results.xml
      - run: python scripts/evaluate_llm_gate.py  # runs gate + posts PR comment
Mapping audit phases to workflow tools

Phase 5 (Evaluation) → @auto_log_fairness + assert_fairness()
Phase 7 (CI/CD) → create_github_check() + GitHub Actions YAML
Phase 10 (GPAI Model Card) → FairnessReportCard.to_github_comment_payload()
Every commit → Pre-commit hooks validate configs + model cards

EU AI Act Compliance Checklist

Each requirement below is addressed by one or more artifacts from this audit. For GPAI deployments, the checklist includes both deployer obligations and documentation of GPAI provider information availability.

Art. 9 — Risk Management
Output validation, bias detection, counterfactual probing, and robustness testing form the risk management system. LLM-specific risks (model updates, prompt sensitivity) documented.
Art. 10 — Data Governance
Training data unavailable from GPAI provider (documented). Evaluation dataset governance fully documented: 5,000 resumes with demographic metadata, 1,000-resume human validation panel.
Art. 11 — Technical Documentation
22 SVG artifacts plus model card provide comprehensive technical documentation. Black-box constraints explicitly documented per Article 25 requirements.
Art. 12 — Record-Keeping
MetricsStore logs all fairness metrics with timestamps. API response headers tracked to detect silent model updates by the GPAI provider.
Art. 13 — Transparency
Multi-tier reporting provides stakeholder-appropriate transparency. Third-party model dependency and its limitations are prominently disclosed.
Art. 14 — Human Oversight
All shortlisting decisions reviewed by human recruiters. No fully automated rejections. Monthly counterfactual re-probing and quarterly fairness review board.
Art. 15 — Accuracy & Robustness
Calibration analysis, permutation tests, regression fairness, and sensitivity analysis demonstrate output reliability. LLM-specific robustness risks (prompt injection, model drift) addressed.
Art. 25 — Value Chain
Responsibilities between GPAI provider and deployer explicitly documented. Information gaps from the provider compensated through enhanced output-level evaluation.
Art. 51–56 — GPAI Obligations
Provider obligations documented: model card availability, technical documentation status, copyright compliance status, and training data summary disclosure (or lack thereof).
Art. 72 — Post-Market Monitoring
Continuous monitoring with drift detection and automated alerts. Weekly review cadence (elevated due to third-party dependency). API version tracking for silent model updates.
Next Steps

This walkthrough used demo data for illustration. To apply this process to your own LLM deployment:

  1. Build a demographically diverse evaluation dataset with ground-truth labels
  2. Design counterfactual probes tailored to your use case and protected attributes
  3. Request the GPAI provider’s model card and technical documentation (Article 53)
  4. Adjust thresholds per your organization’s risk appetite and domain regulations
  5. Implement API response header logging to detect silent model updates
  6. Schedule weekly re-probing and monthly full re-audits for third-party LLM deployments

Compare this with the Credit Scoring Audit (full training data access), explore all 49 SVG templates in the SVG Gallery, or learn about the full library architecture in the Getting Started guide.