Views
No views yet
| Head | Sub-metrics | Count | Description |
|---|---|---|---|
| Opening | Use of call opening phrase | 1 | Evaluates proper call initiation protocols |
| Listening | Non-interruption, empathy, paraphrasing, politeness, confidence | 5 | Assesses active listening and communication skills |
| Proactiveness | Extra issue solving, satisfaction confirmation, follow-up | 3 | Measures proactive service approach |
| Resolution | Information accuracy, language use, consultation, process adherence, clarity | 5 | Evaluates problem-solving effectiveness |
| Hold | Hold explanation, gratitude for waiting | 2 | Assesses proper hold procedures |
| Closing | Proper closing phrase | 1 | Evaluates professional call conclusion |
| Head | Accuracy | Precision | Recall | F1 Score | Performance Level |
|---|---|---|---|---|---|
| Closing | 100.0% | 100.0% | 100.0% | 100.0% | Perfect |
| Resolution | 90.5% | 98.5% | 98.5% | 98.5% | Excellent |
| Hold | 90.5% | 66.7% | 100.0% | 80.0% | Good |
| Proactiveness | 85.7% | 91.7% | 95.7% | 93.6% | Good |
| Opening | 85.7% | 85.7% | 85.7% | 85.7% | Good |
| Listening | 71.4% | 98.5% | 93.1% | 95.7% | Mixed Performance |
pip install transformers torch1import torch
2import torch.nn as nn
3from transformers import DistilBertModel, DistilBertPreTrainedModel, AutoTokenizer
4
5class MultiHeadQAClassifier(DistilBertPreTrainedModel):
6 """
7 Multi-head QA classifier for call center quality assessment.
8 Each head corresponds to a different QA metric with specific sub-metrics.
9 """
10
11 def __init__(self, config):
12 super().__init__(config)
13
14 # QA heads configuration
15 self.heads_config = getattr(config, 'heads_config', {
16 "opening": 1,
17 "listening": 5,
18 "proactiveness": 3,
19 "resolution": 5,
20 "hold": 2,
21 "closing": 1
22 })
23
24 self.bert = DistilBertModel(config)
25 classifier_dropout = getattr(config, 'classifier_dropout', 0.1)
26 self.dropout = nn.Dropout(classifier_dropout)
27
28 # Multiple classification heads
29 self.classifiers = nn.ModuleDict({
30 head_name: nn.Linear(config.hidden_size, num_labels)
31 for head_name, num_labels in self.heads_config.items()
32 })
33
34 # Initialize weights
35 self.post_init()
36
37 def forward(self, input_ids, attention_mask, labels=None):
38 outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask)
39 pooled_output = self.dropout(outputs.last_hidden_state[:, 0]) # [CLS] token
40
41 logits = {}
42 losses = {}
43 total_loss = 0
44
45 for head_name, classifier in self.classifiers.items():
46 head_logits = classifier(pooled_output)
47 logits[head_name] = torch.sigmoid(head_logits) # Convert to probabilities
48
49 # Calculate loss if labels provided
50 if labels is not None and head_name in labels:
51 loss_fn = nn.BCEWithLogitsLoss()
52 loss = loss_fn(head_logits, labels[head_name])
53 losses[head_name] = loss.item()
54 total_loss += loss
55
56 return {
57 "logits": logits,
58 "loss": total_loss if labels is not None else None,
59 "losses": losses if labels is not None else None
60 }1def predict_qa_metrics(text: str, model, tokenizer, threshold: float = 0.5, device=None):
2 """
3 Predict QA metrics for a helpline transcript with beautiful output formatting.
4
5 Args:
6 text: Input transcript text
7 model: Loaded MultiHeadQAClassifier model
8 tokenizer: DistilBERT tokenizer
9 threshold: Classification threshold (default: 0.5)
10 device: Device to use for inference
11
12 Returns:
13 Dictionary with predictions and probabilities for each QA metric
14 """
15 if device is None:
16 device = next(model.parameters()).device
17
18 model.eval()
19
20 # Sub-metric labels for formatted output
21 HEAD_SUBMETRIC_LABELS = {
22 "opening": ["Use of call opening phrase"],
23 "listening": [
24 "Caller was not interrupted",
25 "Empathizes with the caller",
26 "Paraphrases or rephrases the issue",
27 "Uses 'please' and 'thank you'",
28 "Does not hesitate or sound unsure"
29 ],
30 "proactiveness": [
31 "Willing to solve extra issues",
32 "Confirms satisfaction with action points",
33 "Follows up on case updates"
34 ],
35 "resolution": [
36 "Gives accurate information",
37 "Correct language use",
38 "Consults if unsure",
39 "Follows correct steps",
40 "Explains solution process clearly"
41 ],
42 "hold": [
43 "Explains before placing on hold",
44 "Thanks caller for holding"
45 ],
46 "closing": ["Proper call closing phrase used"]
47 }
48
49 # Tokenize input
50 encoding = tokenizer(
51 text,
52 return_tensors="pt",
53 padding="max_length",
54 truncation=True,
55 max_length=512
56 )
57
58 input_ids = encoding["input_ids"].to(device)
59 attention_mask = encoding["attention_mask"].to(device)
60
61 # Forward pass
62 with torch.no_grad():
63 outputs = model(input_ids=input_ids, attention_mask=attention_mask)
64 logits = outputs["logits"]
65
66 # Format results
67 results = {}
68 print(f"📞 Transcript: {text}\n")
69
70 total_positive = 0
71 total_metrics = 0
72
73 for head_name, probs in logits.items():
74 probs_np = probs.cpu().numpy()[0]
75 submetrics = HEAD_SUBMETRIC_LABELS.get(head_name, [f"Submetric {i+1}" for i in range(len(probs_np))])
76
77 print(f"🔹 {head_name.upper()}:")
78 head_results = []
79
80 for prob, submetric in zip(probs_np, submetrics):
81 prediction = prob > threshold
82 indicator = "✓" if prediction else "✗"
83
84 if prediction:
85 total_positive += 1
86 total_metrics += 1
87
88 result_item = {
89 "submetric": submetric,
90 "probability": float(prob),
91 "prediction": bool(prediction),
92 "indicator": indicator
93 }
94 head_results.append(result_item)
95
96 print(f" ➤ {submetric}: P={prob:.3f} → {indicator}")
97
98 results[head_name] = head_results
99
100 # Overall summary
101 overall_accuracy = (total_positive / total_metrics) * 100
102 print(f"\n Overall Score: {total_positive}/{total_metrics} ({overall_accuracy:.1f}%)")
103
104 results["summary"] = {
105 "total_positive": total_positive,
106 "total_metrics": total_metrics,
107 "accuracy": overall_accuracy
108 }
109
110 return results1from transformers import AutoTokenizer
2import torch
3
4# Load model and tokenizer
5MODEL_NAME = "openchs/qa-helpline-distilbert-v1"
6tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
7model = MultiHeadQAClassifier.from_pretrained(MODEL_NAME)
8
9# Set device
10device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
11model.to(device)
12model.eval()
13
14# Example helpline transcript
15transcript = """
16Hello, thank you for calling our child helpline. My name is Sarah, how can I help you today?
17I understand your concern completely and I want to help you through this difficult situation.
18Let me check what resources we have available for you. Please hold for just a moment while I
19look into this. Thank you for holding. I've found several support options that can help.
20Is there anything else I can assist you with today? Thank you for reaching out to us,
21and please don't hesitate to call again if you need further support.
22"""
23
24# Run prediction
25results = predict_qa_metrics(transcript, model, tokenizer, threshold=0.5, device=device)
26
27# Access specific results
28opening_results = results["opening"]
29listening_results = results["listening"]
30overall_summary = results["summary"]1from fastapi import FastAPI, HTTPException
2from pydantic import BaseModel
3from typing import Optional
4
5app = FastAPI(title="QA Helpline Metrics API")
6
7class TranscriptInput(BaseModel):
8 text: str
9 threshold: Optional[float] = 0.5
10
11@app.post("/predict")
12async def predict_transcript_quality(input_data: TranscriptInput):
13 try:
14 results = predict_qa_metrics(
15 text=input_data.text,
16 model=model,
17 tokenizer=tokenizer,
18 threshold=input_data.threshold
19 )
20 return {"success": True, "predictions": results}
21 except Exception as e:
22 raise HTTPException(status_code=500, detail=str(e))1@model{qa_helpline_distilbert_2025,
2 title={QA Multi-Head DistilBERT for Helpline Quality Assessment},
3 author={BITZ IT Consulting Team},
4 year={2025},
5 publisher={Hugging Face},
6 journal={Hugging Face Model Hub},
7 howpublished={\url{https://huggingface.co/openchs/qa-helpline-distilbert-v1}},
8 note={AI for Social Impact: Child Helplines and Crisis Support in East Africa}
9}