Lightweight verifier model for evaluating LLM outputs across multiple domains, handling diverse answer types through meta-error pattern analysis.
Scanned 9/9/2026
Install to Claude Code
npx -y skills add ADu2021/skillXiv --skill compass-verifier-unified-llm-evaluation --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Compass Verifier Unified Llm Evaluation?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adu2021-compass-verifier-unified-llm-evaluation)More formats (shields.io, HTML) on the badges page.
---
name: compass-verifier-unified-llm-evaluation
title: CompassVerifier - Unified Robust Verifier for LLMs Evaluation
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: https://arxiv.org/abs/2508.03686
keywords: [verification, reward-model, llm-evaluation, answer-matching]
description: "Lightweight verifier model for evaluating LLM outputs across multiple domains, handling diverse answer types through meta-error pattern analysis."
---
## CompassVerifier: Unified LLM Output Verification
CompassVerifier is a specialized reward model designed to verify LLM-generated answers against reference solutions across diverse domains (math, knowledge, reasoning). Rather than using brittle regex matching or computationally expensive general-purpose LLMs, it functions as a lightweight, domain-aware verifier that handles complex answer formats and edge cases.
### Core Concept
LLM evaluation requires matching unstructured outputs to reference answers—a task fraught with pitfalls. Simple string matching fails for equivalent answers (e.g., "1/2" vs "0.5"), general LLM verifiers are expensive and inconsistent, and handcrafted rules don't generalize. CompassVerifier combines the efficiency of specialized models with the generality of learned patterns, achieving this through meta-error analysis: studying common failure modes to anticipate edge cases.
### Architecture Overview
- **Lightweight Verifier Model**: Smaller than full instruction-tuned LLMs, trained specifically for verification rather than general capability
- **Multi-Domain Training**: Unified architecture handling mathematics, knowledge QA, and diverse reasoning tasks
- **Meta-Error Pattern Analysis**: Systematic analysis of error patterns (e.g., formatting variations, numerical equivalences) to inform training data augmentation
- **VerifierBench Benchmark**: Comprehensive dataset of model outputs augmented with pattern-based variations to stress-test verification robustness
- **Robustness to Abnormalities**: Detects malformed, incomplete, or nonsensical responses without explicit rules
### Implementation Steps
**Step 1: Construct VerifierBench with Meta-Error Analysis**
Analyze error patterns from existing evaluations to create comprehensive training data:
```python
import json
import re
from collections import defaultdict
def analyze_meta_error_patterns(llm_outputs, reference_answers):
"""
Systematically identify common failure modes in LLM outputs.
Use these patterns to augment training data for robustness.
"""
error_patterns = defaultdict(list)
for output, reference in zip(llm_outputs, reference_answers):
# Tokenize both
output_tokens = output.lower().split()
ref_tokens = reference.lower().split()
# Pattern 1: Correct content but different formatting
if normalize_text(output) == normalize_text(reference):
error_patterns['format_variation'].append((output, reference))
# Pattern 2: Correct numerical value but different representation
if extract_numbers(output) == extract_numbers(reference):
error_patterns['numerical_equivalence'].append((output, reference))
# Pattern 3: Partially correct (e.g., multi-part question, got some parts)
partial_match_ratio = len(set(output_tokens) & set(ref_tokens)) / max(len(output_tokens), len(ref_tokens))
if 0.5 < partial_match_ratio < 1.0:
error_patterns['partial_correctness'].append((output, reference, partial_match_ratio))
# Pattern 4: Correct answer but with extra explanation
if reference in output:
error_patterns['answer_within_text'].append((output, reference))
# Pattern 5: Off-by-one or minor numerical errors
nums_out = extract_numbers(output)
nums_ref = extract_numbers(reference)
if len(nums_out) == len(nums_ref) and all(abs(o - r) <= 1 for o, r in zip(nums_out, nums_ref)):
error_patterns['off_by_one'].append((output, reference))
return error_patterns
def normalize_text(text):
"""Remove punctuation, extra whitespace, normalize case."""
text = text.lower()
text = re.sub(r'[^\w\s]', '', text)
text = ' '.join(text.split())
return text
def extract_numbers(text):
"""Extract all numerical values from text."""
return [float(x) for x in re.findall(r'-?\d+\.?\d*', text)]
# Analyze patterns from development set
patterns = analyze_meta_error_patterns(dev_outputs, dev_references)
# Print pattern summary
for pattern_type, examples in patterns.items():
print(f"{pattern_type}: {len(examples)} instances")
```
**Step 2: Augment Training Data Using Meta-Error Patterns**
Create synthetic variations based on discovered patterns:
```python
def augment_training_data_by_patterns(base_examples, patterns, augmentation_factor=3):
"""
Generate augmented training examples by applying discovered error patterns.
This teaches the verifier to handle real-world variations.
"""
augmented = base_examples.copy()
# For each pattern type, generate variations
for pattern_type, examples in patterns.items():
for output, reference in examples[:augmentation_factor]:
if pattern_type == 'format_variation':
# Add variations with different spacing, punctuation
variations = [
output.replace(' ', ''), # Remove spaces
output.title(), # Title case
' '.join(output.split()), # Normalize whitespace
]
for var in variations:
augmented.append({'output': var, 'reference': reference, 'correct': 1})
elif pattern_type == 'numerical_equivalence':
# Add equivalent numerical representations
nums = extract_numbers(output)
if len(nums) == 1:
num = nums[0]
variations = [
output.replace(str(int(num)), str(num / 2) + ' * 2'), # Different form
output.replace(str(int(num)), f'{num:.4f}'), # Different precision
]
for var in variations:
augmented.append({'output': var, 'reference': reference, 'correct': 1})
elif pattern_type == 'answer_within_text':
# Add answer embedded in explanation
variations = [
output + " So the answer is " + reference,
"The answer to this is " + reference + ". " + output,
]
for var in variations:
augmented.append({'output': var, 'reference': reference, 'correct': 1})
return augmented
augmented_data = augment_training_data_by_patterns(base_train_data, patterns)
```
**Step 3: Design the Verifier Architecture**
Build a lightweight but expressive model for answer verification:
```python
import torch
import torch.nn as nn
from transformers import AutoTokenizer, AutoModel
class CompassVerifier(nn.Module):
"""
Lightweight verifier that takes (output, reference) pair and predicts correctness.
"""
def __init__(self, base_model='distilbert-base-uncased', hidden_dim=256):
super().__init__()
self.tokenizer = AutoTokenizer.from_pretrained(base_model)
self.encoder = AutoModel.from_pretrained(base_model)
# Classification head
self.classifier = nn.Sequential(
nn.Linear(self.encoder.config.hidden_size * 2, hidden_dim),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(hidden_dim, 64),
nn.ReLU(),
nn.Linear(64, 2) # Binary: correct or incorrect
)
def forward(self, outputs, references):
"""
Args:
outputs: List of LLM-generated answers
references: List of reference answers
Returns:
scores: (B, 2) logits for [incorrect, correct]
"""
# Encode output-reference pairs jointly and separately
pair_texts = [f"Output: {o} Reference: {r}" for o, r in zip(outputs, references)]
pair_encoded = self.tokenizer(pair_texts, return_tensors='pt', padding=True, truncation=True)
pair_reps = self.encoder(**pair_encoded).pooler_output
# Also encode separately to capture differences
output_encoded = self.tokenizer(outputs, return_tensors='pt', padding=True, truncation=True)
output_reps = self.encoder(**output_encoded).pooler_output
ref_encoded = self.tokenizer(references, return_tensors='pt', padding=True, truncation=True)
ref_reps = self.encoder(**ref_encoded).pooler_output
# Concatenate: joint representation + difference
combined = torch.cat([pair_reps, (output_reps - ref_reps).abs()], dim=1)
# Classify
logits = self.classifier(combined)
return logits
def predict(self, outputs, references):
"""Return probability of correctness."""
with torch.no_grad():
logits = self.forward(outputs, references)
probs = torch.softmax(logits, dim=1)
return probs[:, 1] # Probability of correct class
```
**Step 4: Train with Domain-Specific Loss**
Account for domain-specific verification characteristics:
```python
def domain_specific_loss(logits, labels, domain_type='math'):
"""
Adjust loss weights based on domain characteristics.
Math: high precision required, penalize false positives more.
Knowledge: more tolerance for paraphrasing, penalize false negatives.
"""
base_loss = torch.nn.functional.cross_entropy(logits, labels, reduction='none')
if domain_type == 'math':
# Penalize false positives (incorrect answers verified as correct) more heavily
weights = torch.where(labels == 0, torch.tensor(2.0), torch.tensor(1.0))
elif domain_type == 'knowledge':
# Penalize false negatives (correct paraphrases marked incorrect) more
weights = torch.where(labels == 1, torch.tensor(2.0), torch.tensor(1.0))
else:
weights = torch.ones_like(labels, dtype=torch.float)
weighted_loss = base_loss * weights
return weighted_loss.mean()
# Training loop with multi-domain batching
for batch in dataloader:
outputs = batch['output']
references = batch['reference']
labels = batch['correct']
domain = batch['domain']
logits = model(outputs, references)
loss = domain_specific_loss(logits, labels, domain)
loss.backward()
optimizer.step()
```
**Step 5: Implement Abnormality Detection**
Identify and handle malformed or suspicious outputs:
```python
def detect_abnormalities(output, reference, confidence_threshold=0.1):
"""
Detect outputs that are clearly invalid regardless of reference.
Returns abnormality flags and confidence penalty.
"""
flags = {'is_abnormal': False, 'reasons': []}
confidence_penalty = 1.0
# Check 1: Output is empty or suspiciously short
if len(output.strip()) < 3:
flags['is_abnormal'] = True
flags['reasons'].append('too_short')
confidence_penalty *= 0.1
# Check 2: Output contains only special tokens/gibberish
if len(re.sub(r'[\W_]', '', output)) < len(output) * 0.3:
flags['is_abnormal'] = True
flags['reasons'].append('mostly_special_chars')
confidence_penalty *= 0.3
# Check 3: Length mismatch suggests hallucination (>5x reference length)
if len(output.split()) > 5 * len(reference.split()) + 50:
flags['is_abnormal'] = True
flags['reasons'].append('excessive_length')
confidence_penalty *= 0.5
# Check 4: Repeated tokens (indicator of decoding failure)
tokens = output.split()
if len(tokens) > 10:
max_freq = max(tokens, key=tokens.count)
freq = tokens.count(max_freq) / len(tokens)
if freq > 0.3:
flags['is_abnormal'] = True
flags['reasons'].append('token_repetition')
confidence_penalty *= 0.4
return flags, confidence_penalty
def verify_with_abnormality_handling(model, outputs, references):
"""
Verify answers while accounting for abnormalities.
"""
scores = model.predict(outputs, references)
for i, (output, reference) in enumerate(zip(outputs, references)):
abnormality_flags, penalty = detect_abnormalities(output, reference)
if abnormality_flags['is_abnormal']:
scores[i] *= penalty
return scores
```
**Step 6: Evaluate on VerifierBench**
Comprehensive evaluation across domains and error patterns:
```python
def evaluate_verifier(model, test_set, verbose=True):
"""
Evaluate verifier on VerifierBench with per-domain and per-pattern metrics.
"""
metrics = {}
# Overall accuracy
outputs = [ex['output'] for ex in test_set]
references = [ex['reference'] for ex in test_set]
labels = [ex['correct'] for ex in test_set]
preds = model.predict(outputs, references) > 0.5
accuracy = (preds == labels).float().mean()
metrics['overall_accuracy'] = accuracy
# Per-domain metrics
by_domain = defaultdict(list)
for ex, pred in zip(test_set, preds):
by_domain[ex.get('domain', 'unknown')].append((pred, ex['correct']))
for domain, domain_results in by_domain.items():
preds_d = torch.tensor([p for p, _ in domain_results])
labels_d = torch.tensor([l for _, l in domain_results])
acc = (preds_d == labels_d).float().mean()
metrics[f'accuracy_{domain}'] = acc
if verbose:
print(f"{domain}: {acc:.3f}")
# Per-error-pattern metrics
by_pattern = defaultdict(list)
for ex, pred in zip(test_set, preds):
pattern = ex.get('error_pattern', 'clean')
by_pattern[pattern].append((pred, ex['correct']))
for pattern, pattern_results in by_pattern.items():
preds_p = torch.tensor([p for p, _ in pattern_results])
labels_p = torch.tensor([l for _, l in pattern_results])
acc = (preds_p == labels_p).float().mean()
metrics[f'pattern_{pattern}'] = acc
return metrics
```
### Practical Guidance
**When to Use:**
- Evaluating LLM outputs across multiple domains (math, knowledge, reasoning)
- Scenarios requiring high-precision answer verification without false positives
- Applications where verification cost is significant (training reward models, filtering)
- Cases with diverse answer formats (numerical, textual, mixed)
**When NOT to Use:**
- Simple binary judgments where ground truth is readily available
- Domains with very specific answer formats (medical coding, legal citations)
- Real-time inference with <10ms latency requirements
- Scenarios where a single general-purpose LLM verifier is acceptable
**Hyperparameters:**
| Parameter | Default | Impact |
|-----------|---------|--------|
| `hidden_dim` | 256 | Model capacity; larger = more expressive but slower |
| `confidence_threshold` | 0.5 | Adjust per use case; higher = only very confident predictions |
| `domain_loss_weight_math` | 2.0 | Penalize false positives in math; increase for stricter verification |
| `domain_loss_weight_knowledge` | 1.5 | Penalize false negatives; account for paraphrasing tolerance |
| `abnormality_penalty_threshold` | 0.1 | Confidence floor for abnormal outputs; lower = more tolerance |
**Training Tips:**
- Start with base model pre-trained on verification-adjacent tasks (semantic similarity)
- Use stratified sampling to balance domains and error patterns during training
- Monitor false positive and false negative rates separately (domain-specific trade-offs)
- Validate on held-out domains not seen during training to measure generalization
### Reference
**Paper**: CompassVerifier: A Unified and Robust Verifier for LLMs Evaluation and Outcome Reward (2508.03686)
- Lightweight alternative to general-purpose LLM verifiers
- Robust to answer format variations through meta-error pattern analysis
- Comprehensive evaluation on VerifierBench across mathematics, knowledge, and reasoning domains
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!