Ali-Bhai commited on
Commit
bf58e9a
·
verified ·
1 Parent(s): e4e8a99

Add final DeBERTa span-level tool hallucination detector

Browse files
.gitattributes CHANGED
@@ -1,35 +1,5 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
  *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
1
  *.bin filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  *.safetensors filter=lfs diff=lfs merge=lfs -text
3
+ *.pt filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
4
  *.zip filter=lfs diff=lfs merge=lfs -text
5
+ *.jsonl filter=lfs diff=lfs merge=lfs -text
 
README.md ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ library_name: transformers
6
+ base_model: microsoft/deberta-v3-small
7
+ tags:
8
+ - hallucination-detection
9
+ - span-detection
10
+ - tool-use
11
+ - deberta-v3
12
+ - ragtruth
13
+ datasets:
14
+ - Ali-Bhai/toolace-ragtruth-style-hallucinations
15
+ metrics:
16
+ - f1
17
+ - accuracy
18
+ ---
19
+
20
+ # DeBERTa Tool Hallucination Span Detector
21
+
22
+ This is the final span-level model for the tool hallucination coursework. It predicts RAGTruth-style hallucinated character spans in assistant answers grounded on tool outputs.
23
+
24
+ ## Task
25
+
26
+ Input record fields:
27
+
28
+ - `query`: user question
29
+ - `context`: tool response or tool evidence
30
+ - `output`: assistant answer
31
+ - `available_tool_names`: optional list of available tools
32
+
33
+ Output:
34
+
35
+ - row-level hallucination decision
36
+ - hallucination type
37
+ - predicted character spans in `output`
38
+
39
+ ## Method
40
+
41
+ The model is a `microsoft/deberta-v3-small` token classifier. Because some answers are long, inference uses overlapping answer windows. Each window is placed first in the sequence, followed by question, tool responses, and available tool names. Only answer-window tokens are supervised during training.
42
+
43
+ ## Decoding configuration
44
+
45
+ The validation-selected decoding configuration is:
46
+
47
+ ```json
48
+ {
49
+ "threshold": 0.5,
50
+ "min_span_chars": 1,
51
+ "min_span_tokens": 1,
52
+ "merge_gap_chars": 1,
53
+ "strip_predicted_span_whitespace": true,
54
+ "drop_spans_without_alnum": true,
55
+ "score_name": "sum_non_O_probability"
56
+ }
57
+ ```
58
+
59
+ ## Held-out test results
60
+
61
+ ```json
62
+ {
63
+ "row_accuracy": 0.9516908212560387,
64
+ "row_macro_f1": 0.9553091397849462,
65
+ "binary_f1": 0.9561403508771931,
66
+ "exact_span_f1": 0.8207171314741037,
67
+ "overlap_span_f1_iou_0_01": 0.8685258964143425,
68
+ "overlap_span_f1_iou_0_50": 0.8366533864541832,
69
+ "char_span_precision": 0.971356003950896,
70
+ "char_span_recall": 0.9955169920462762,
71
+ "char_span_f1": 0.9832881016997572
72
+ }
73
+ ```
74
+
75
+ ## Loading
76
+
77
+ ```python
78
+ from transformers import AutoTokenizer, AutoModelForTokenClassification
79
+
80
+ repo_id = "Ali-Bhai/deberta-tool-hallucination-span-detector"
81
+ tokenizer = AutoTokenizer.from_pretrained(repo_id, use_fast=True)
82
+ model = AutoModelForTokenClassification.from_pretrained(repo_id)
83
+ ```
84
+
85
+ ## Inference
86
+
87
+ ```python
88
+ from transformers import AutoTokenizer, AutoModelForTokenClassification
89
+ from span_inference import predict_record
90
+
91
+ repo_id = "Ali-Bhai/deberta-tool-hallucination-span-detector"
92
+ tokenizer = AutoTokenizer.from_pretrained(repo_id, use_fast=True)
93
+ model = AutoModelForTokenClassification.from_pretrained(repo_id)
94
+
95
+ record = {
96
+ "query": "What did the tool return?",
97
+ "context": "... tool output ...",
98
+ "output": "... assistant answer ...",
99
+ "available_tool_names": [],
100
+ }
101
+
102
+ prediction = predict_record(record, model, tokenizer, device="cuda")
103
+ print(prediction["predicted_spans"])
104
+ ```
105
+
106
+ ## Related repo
107
+
108
+ The earlier row-level classifier is here: https://huggingface.co/Ali-Bhai/deberta-tool-hallucination-detector.
config.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "DebertaV2ForTokenClassification"
4
+ ],
5
+ "attention_probs_dropout_prob": 0.1,
6
+ "bos_token_id": null,
7
+ "dtype": "float32",
8
+ "eos_token_id": null,
9
+ "hidden_act": "gelu",
10
+ "hidden_dropout_prob": 0.1,
11
+ "hidden_size": 768,
12
+ "id2label": {
13
+ "0": "O",
14
+ "1": "tool_output_conflict",
15
+ "2": "overgeneration",
16
+ "3": "missing_tool_action_recommendation"
17
+ },
18
+ "initializer_range": 0.02,
19
+ "intermediate_size": 3072,
20
+ "label2id": {
21
+ "O": 0,
22
+ "missing_tool_action_recommendation": 3,
23
+ "overgeneration": 2,
24
+ "tool_output_conflict": 1
25
+ },
26
+ "layer_norm_eps": 1e-07,
27
+ "legacy": true,
28
+ "max_position_embeddings": 512,
29
+ "max_relative_positions": -1,
30
+ "model_type": "deberta-v2",
31
+ "norm_rel_ebd": "layer_norm",
32
+ "num_attention_heads": 12,
33
+ "num_hidden_layers": 6,
34
+ "pad_token_id": 0,
35
+ "pooler_dropout": 0,
36
+ "pooler_hidden_act": "gelu",
37
+ "pooler_hidden_size": 768,
38
+ "pos_att_type": [
39
+ "p2c",
40
+ "c2p"
41
+ ],
42
+ "position_biased_input": false,
43
+ "position_buckets": 256,
44
+ "relative_attention": true,
45
+ "share_att_key": true,
46
+ "tie_word_embeddings": true,
47
+ "transformers_version": "5.0.0",
48
+ "type_vocab_size": 0,
49
+ "vocab_size": 128100
50
+ }
eval/span_deberta_test_predictions.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
eval/span_deberta_test_token_predictions.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:695c67472ce44d19d9513e28a32c0524d64e7a0f000fe675e8a45c6b139a2262
3
+ size 13126758
eval/span_deberta_validation_predictions.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
eval/span_deberta_validation_token_predictions.jsonl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:529e7ad1712b611066deda3415d4f24cdbdb7a19e24f1d694184f8943e54111f
3
+ size 13225709
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c936df2b89d254c05444e66afa788bc3f553f7ec36e4e7b6cc230bd11e74ce24
3
+ size 565242144
reports/cell26_span_deberta_training_report.json ADDED
The diff for this file is too large to render. See raw diff
 
reports/final_notebook_results_text.md ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Final result summary
2
+
3
+ The final system has two parts.
4
+
5
+ First, I trained a DeBERTa-v3-small row-level classifier for the four labels: clean, tool_output_conflict, overgeneration, and missing_tool_action_recommendation. This model is useful for quick answer-level detection. On the held-out test split it reached accuracy 0.9275, macro F1 0.9249, and binary clean-vs-hallucinated macro F1 0.9418.
6
+
7
+ Second, to satisfy the RAGTruth-style span requirement, I trained a DeBERTa-v3-small token classifier over sliding answer windows. The model predicts answer-token labels and then decodes them back into character spans. The threshold and span cleanup settings were selected on validation only. On the held-out test split the span model reached row accuracy 0.9517, row macro F1 0.9553, binary F1 0.9561, exact span F1 0.8207, overlap span F1 0.8685, and character-level span F1 0.9833.
8
+
9
+ I also evaluated the required baselines. LettuceDetect reached binary F1 0.7718, exact span F1 0.1652, overlap span F1 0.4661, and character-level span F1 0.5653. The resource-aware LookBackLens-style attention baseline reached binary F1 0.7000, exact span F1 0.0095, overlap span F1 0.1956, and character-level span F1 0.2278.
10
+
11
+ The strongest final span model was trained for 8 epochs. Its selected decoding configuration was:
12
+
13
+ ```json
14
+ {
15
+ "threshold": 0.5,
16
+ "min_span_chars": 1,
17
+ "min_span_tokens": 1,
18
+ "merge_gap_chars": 1,
19
+ "strip_predicted_span_whitespace": true,
20
+ "drop_spans_without_alnum": true,
21
+ "score_name": "sum_non_O_probability"
22
+ }
23
+ ```
24
+
25
+ ## Test-set comparison
26
+
27
+ | System | Scope | Row acc. | Row macro F1 | Binary F1 | Exact span F1 | Overlap F1 | Char span F1 |
28
+ |---|---|---|---|---|---|---|---|
29
+ | DeBERTa-v3-small row classifier | row classification | 0.9275 | 0.9249 | 0.9418 | n/a | n/a | n/a |
30
+ | LettuceDetect | span detection | 0.7343 | n/a | 0.7718 | 0.1652 | 0.4661 | 0.5653 |
31
+ | LookBackLens-style attention baseline | span detection | 0.6232 | n/a | 0.7000 | 0.0095 | 0.1956 | 0.2278 |
32
+ | DeBERTa-v3-small span-token classifier | row + span detection | 0.9517 | 0.9553 | 0.9561 | 0.8207 | 0.8685 | 0.9833 |
33
+
34
+ ## Repositories
35
+
36
+ Dataset: https://huggingface.co/datasets/Ali-Bhai/toolace-ragtruth-style-hallucinations
37
+ Model: https://huggingface.co/Ali-Bhai/deberta-tool-hallucination-detector
38
+
39
+ ## Short discussion
40
+
41
+ The row classifier performs well for answer-level decisions but cannot return exact hallucinated text. The final span-token classifier fixes this by using RAGTruth-style character spans during training. A single 512-token sequence lost a small number of late answer spans, so I switched to overlapping answer windows and audited the alignment before training. This preserved every gold span and allowed the model to output exact predicted spans. The final model strongly outperformed LettuceDetect and the LookBackLens-style attention baseline on this constructed held-out test split, especially on character-level span F1.
reports/final_results_comparison_summary.json ADDED
@@ -0,0 +1,589 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "created": "2026-05-23T11:17:16",
3
+ "runtime_report": {
4
+ "python": "3.12.13 (main, Mar 4 2026, 09:23:07) [GCC 11.4.0]",
5
+ "platform": "Linux-6.6.122+-x86_64-with-glibc2.35",
6
+ "packages": {
7
+ "numpy": "2.0.2",
8
+ "torch": "2.10.0+cu128",
9
+ "transformers": "5.0.0",
10
+ "huggingface_hub": "1.11.0"
11
+ }
12
+ },
13
+ "paths": {
14
+ "sequence_selected_config": "/content/drive/MyDrive/TLLM/04_submission/final_deberta_tool_hallucination_submission/selected_model_config.json",
15
+ "sequence_final_report": "/content/drive/MyDrive/TLLM/04_submission/final_deberta_tool_hallucination_submission/final_submission_report.json",
16
+ "lettucedetect_report": "/content/drive/MyDrive/TLLM/00_reports/official_baselines_and_spans/cell21_lettucedetect_full_report.json",
17
+ "lookbacklens_report": "/content/drive/MyDrive/TLLM/00_reports/official_baselines_and_spans/cell23_lookbacklens_full_report.json",
18
+ "span_report": "/content/drive/MyDrive/TLLM/00_reports/official_baselines_and_spans/cell26_span_deberta_training_report.json",
19
+ "span_config": "/content/drive/MyDrive/TLLM/03_models/span_deberta_v3_small_sliding_windows/selected_span_model_config.json",
20
+ "hf_publication_summary": "/content/drive/MyDrive/TLLM/04_submission/huggingface_publication_summary.json",
21
+ "hf_verification_summary": "/content/drive/MyDrive/TLLM/04_submission/huggingface_verification_and_card_polish_summary.json"
22
+ },
23
+ "path_status": {
24
+ "sequence_selected_config": true,
25
+ "sequence_final_report": true,
26
+ "lettucedetect_report": true,
27
+ "lookbacklens_report": true,
28
+ "span_report": true,
29
+ "span_config": true,
30
+ "hf_publication_summary": true,
31
+ "hf_verification_summary": true
32
+ },
33
+ "source_report_summary": {
34
+ "sequence_selected_model": {
35
+ "selected_run_name": "phase4e_deberta_v3_small_answer_evidence_pair_unweighted",
36
+ "base_model_name": "microsoft/deberta-v3-small",
37
+ "text_mode": "answer_evidence_pair",
38
+ "calibration": {
39
+ "mode": "conflict_gate",
40
+ "name": "conflict_gate__minp_0.00__margin_0.20__fallback_best_non_conflict",
41
+ "min_conflict_prob": 0.0,
42
+ "min_conflict_margin": 0.2,
43
+ "fallback_strategy": "best_non_conflict"
44
+ }
45
+ },
46
+ "lettucedetect": {
47
+ "model": "KRLabsOrg/lettucedect-base-modernbert-en-v1",
48
+ "selected_config": {
49
+ "threshold": 0.95,
50
+ "min_span_chars": 5,
51
+ "min_span_tokens": 2,
52
+ "merge_gap_chars": 1,
53
+ "hallucination_label_ids": [
54
+ 1
55
+ ]
56
+ }
57
+ },
58
+ "lookbacklens_style": {
59
+ "model": "distilgpt2",
60
+ "selected_config": {
61
+ "threshold": 0.8,
62
+ "min_span_chars": 12,
63
+ "min_span_tokens": 2,
64
+ "merge_gap_chars": 1,
65
+ "drop_spans_without_alnum": true,
66
+ "score_name": "hallucination_score_mean"
67
+ }
68
+ },
69
+ "span_deberta": {
70
+ "model_name": "span_deberta_v3_small_sliding_windows",
71
+ "base_model_name": "microsoft/deberta-v3-small",
72
+ "best_epoch": 8,
73
+ "selected_config": {
74
+ "threshold": 0.5,
75
+ "min_span_chars": 1,
76
+ "min_span_tokens": 1,
77
+ "merge_gap_chars": 1,
78
+ "strip_predicted_span_whitespace": true,
79
+ "drop_spans_without_alnum": true,
80
+ "score_name": "sum_non_O_probability"
81
+ }
82
+ },
83
+ "huggingface": {
84
+ "publication_summary_available": true,
85
+ "verification_summary_available": true,
86
+ "dataset_url": "https://huggingface.co/datasets/Ali-Bhai/toolace-ragtruth-style-hallucinations",
87
+ "model_url": "https://huggingface.co/Ali-Bhai/deberta-tool-hallucination-detector"
88
+ }
89
+ },
90
+ "comparison_rows": [
91
+ {
92
+ "system": "DeBERTa-v3-small row classifier",
93
+ "type": "Our row-level classifier",
94
+ "scope": "row classification",
95
+ "row_accuracy": 0.927536231884058,
96
+ "row_macro_f1": 0.9249477834493081,
97
+ "row_weighted_f1": 0.9287091179440344,
98
+ "binary_f1": 0.9417994376757264,
99
+ "clean_f1": 0.9381443298969072,
100
+ "conflict_f1": 0.8461538461538461,
101
+ "overgeneration_f1": 0.9577464788732394,
102
+ "missing_tool_f1": 0.9577464788732394,
103
+ "exact_span_f1": null,
104
+ "overlap_span_f1_iou_0_01": null,
105
+ "overlap_span_f1_iou_0_50": null,
106
+ "char_span_f1": null,
107
+ "notes": "Best row-level model used answer/evidence pair input and validation-selected conflict calibration. It does not predict exact character spans.",
108
+ "raw_metrics": {
109
+ "accuracy": 0.927536231884058,
110
+ "macro_f1": 0.9249477834493081,
111
+ "weighted_f1": 0.9287091179440344,
112
+ "binary_macro_f1": 0.9417994376757264,
113
+ "clean_f1": 0.9381443298969072,
114
+ "clean_recall": 0.9479166666666666,
115
+ "conflict_f1": 0.8461538461538461,
116
+ "conflict_precision": 0.8048780487804879,
117
+ "conflict_recall": 0.8918918918918919,
118
+ "overgeneration_f1": 0.9577464788732394,
119
+ "missing_tool_f1": 0.9577464788732394
120
+ }
121
+ },
122
+ {
123
+ "system": "LettuceDetect",
124
+ "type": "Official baseline",
125
+ "scope": "span detection",
126
+ "row_accuracy": 0.7342995169082126,
127
+ "row_macro_f1": null,
128
+ "row_weighted_f1": null,
129
+ "binary_f1": 0.7717842323651452,
130
+ "clean_f1": null,
131
+ "conflict_f1": null,
132
+ "overgeneration_f1": null,
133
+ "missing_tool_f1": null,
134
+ "exact_span_f1": 0.16519174041297932,
135
+ "overlap_span_f1_iou_0_01": 0.46607669616519176,
136
+ "overlap_span_f1_iou_0_50": 0.2949852507374631,
137
+ "char_span_f1": 0.5653136531365313,
138
+ "span_precision_char": 0.4653271028037383,
139
+ "span_recall_char": 0.7200289226319595,
140
+ "num_gold_spans": 111,
141
+ "num_predicted_spans": 228,
142
+ "selected_config": {
143
+ "threshold": 0.95,
144
+ "min_span_chars": 5,
145
+ "min_span_tokens": 2,
146
+ "merge_gap_chars": 1,
147
+ "hallucination_label_ids": [
148
+ 1
149
+ ]
150
+ },
151
+ "notes": "Official token-classification baseline run directly from Hugging Face. Validation-only threshold tuning was used for span decoding.",
152
+ "raw_metrics": {
153
+ "config": {
154
+ "threshold": 0.95,
155
+ "min_span_chars": 5,
156
+ "min_span_tokens": 2,
157
+ "merge_gap_chars": 1,
158
+ "hallucination_label_ids": [
159
+ 1
160
+ ]
161
+ },
162
+ "num_rows": 207,
163
+ "binary_example_metrics": {
164
+ "accuracy": 0.7342995169082126,
165
+ "precision": 0.7153846153846154,
166
+ "recall": 0.8378378378378378,
167
+ "f1": 0.7717842323651452,
168
+ "tp": 93,
169
+ "fp": 37,
170
+ "fn": 18,
171
+ "tn": 59
172
+ },
173
+ "exact_span_metrics": {
174
+ "precision": 0.12280701754385964,
175
+ "recall": 0.25225225225225223,
176
+ "f1": 0.16519174041297932,
177
+ "matched": 28,
178
+ "gold_total": 111,
179
+ "pred_total": 228
180
+ },
181
+ "overlap_span_metrics_iou_0_01": {
182
+ "precision": 0.34649122807017546,
183
+ "recall": 0.7117117117117117,
184
+ "f1": 0.46607669616519176,
185
+ "matched": 79,
186
+ "gold_total": 111,
187
+ "pred_total": 228,
188
+ "iou_threshold": 0.01
189
+ },
190
+ "overlap_span_metrics_iou_0_50": {
191
+ "precision": 0.21929824561403508,
192
+ "recall": 0.45045045045045046,
193
+ "f1": 0.2949852507374631,
194
+ "matched": 50,
195
+ "gold_total": 111,
196
+ "pred_total": 228,
197
+ "iou_threshold": 0.5
198
+ },
199
+ "char_micro_metrics": {
200
+ "precision": 0.4653271028037383,
201
+ "recall": 0.7200289226319595,
202
+ "f1": 0.5653136531365313,
203
+ "overlap_chars": 4979,
204
+ "gold_chars": 6915,
205
+ "pred_chars": 10700
206
+ },
207
+ "per_type_char_micro_metrics": {
208
+ "clean": {
209
+ "precision": 0.0,
210
+ "recall": 0.0,
211
+ "f1": 0.0,
212
+ "overlap_chars": 0,
213
+ "gold_chars": 0,
214
+ "pred_chars": 2485
215
+ },
216
+ "missing_tool_action_recommendation": {
217
+ "precision": 0.7734467748318163,
218
+ "recall": 0.8652058432934927,
219
+ "f1": 0.8167572085248641,
220
+ "overlap_chars": 3909,
221
+ "gold_chars": 4518,
222
+ "pred_chars": 5054
223
+ },
224
+ "tool_output_conflict": {
225
+ "precision": 0.07041139240506329,
226
+ "recall": 0.40271493212669685,
227
+ "f1": 0.11986531986531987,
228
+ "overlap_chars": 89,
229
+ "gold_chars": 221,
230
+ "pred_chars": 1264
231
+ },
232
+ "overgeneration": {
233
+ "precision": 0.5171323141802847,
234
+ "recall": 0.4508272058823529,
235
+ "f1": 0.48170881414191014,
236
+ "overlap_chars": 981,
237
+ "gold_chars": 2176,
238
+ "pred_chars": 1897
239
+ }
240
+ },
241
+ "num_predicted_spans": 228,
242
+ "num_gold_spans": 111
243
+ }
244
+ },
245
+ {
246
+ "system": "LookBackLens-style attention baseline",
247
+ "type": "Official-baseline-inspired baseline",
248
+ "scope": "span detection",
249
+ "row_accuracy": 0.6231884057971014,
250
+ "row_macro_f1": null,
251
+ "row_weighted_f1": null,
252
+ "binary_f1": 0.7,
253
+ "clean_f1": null,
254
+ "conflict_f1": null,
255
+ "overgeneration_f1": null,
256
+ "missing_tool_f1": null,
257
+ "exact_span_f1": 0.009463722397476343,
258
+ "overlap_span_f1_iou_0_01": 0.19558359621451105,
259
+ "overlap_span_f1_iou_0_50": 0.11356466876971608,
260
+ "char_span_f1": 0.22782546494992845,
261
+ "span_precision_char": 0.13473995928188043,
262
+ "span_recall_char": 0.7369486623282718,
263
+ "num_gold_spans": 111,
264
+ "num_predicted_spans": 523,
265
+ "selected_config": {
266
+ "threshold": 0.8,
267
+ "min_span_chars": 12,
268
+ "min_span_tokens": 2,
269
+ "merge_gap_chars": 1,
270
+ "drop_spans_without_alnum": true,
271
+ "score_name": "hallucination_score_mean"
272
+ },
273
+ "notes": "Resource-aware implementation of the LookBackLens attention idea using distilgpt2 attention maps.",
274
+ "raw_metrics": {
275
+ "config": {
276
+ "threshold": 0.8,
277
+ "min_span_chars": 12,
278
+ "min_span_tokens": 2,
279
+ "merge_gap_chars": 1,
280
+ "drop_spans_without_alnum": true,
281
+ "score_name": "hallucination_score_mean"
282
+ },
283
+ "num_rows": 207,
284
+ "binary_example_metrics": {
285
+ "accuracy": 0.6231884057971014,
286
+ "precision": 0.610738255033557,
287
+ "recall": 0.8198198198198198,
288
+ "f1": 0.7,
289
+ "tp": 91,
290
+ "fp": 58,
291
+ "fn": 20,
292
+ "tn": 38
293
+ },
294
+ "exact_span_metrics": {
295
+ "precision": 0.0057361376673040155,
296
+ "recall": 0.02702702702702703,
297
+ "f1": 0.009463722397476343,
298
+ "matched": 3,
299
+ "gold_total": 111,
300
+ "pred_total": 523
301
+ },
302
+ "overlap_span_metrics_iou_0_01": {
303
+ "precision": 0.11854684512428298,
304
+ "recall": 0.5585585585585585,
305
+ "f1": 0.19558359621451105,
306
+ "matched": 62,
307
+ "gold_total": 111,
308
+ "pred_total": 523,
309
+ "iou_threshold": 0.01
310
+ },
311
+ "overlap_span_metrics_iou_0_50": {
312
+ "precision": 0.06883365200764818,
313
+ "recall": 0.32432432432432434,
314
+ "f1": 0.11356466876971608,
315
+ "matched": 36,
316
+ "gold_total": 111,
317
+ "pred_total": 523,
318
+ "iou_threshold": 0.5
319
+ },
320
+ "char_micro_metrics": {
321
+ "precision": 0.13473995928188043,
322
+ "recall": 0.7369486623282718,
323
+ "f1": 0.22782546494992845,
324
+ "overlap_chars": 5096,
325
+ "gold_chars": 6915,
326
+ "pred_chars": 37821
327
+ },
328
+ "per_type_char_micro_metrics": {
329
+ "clean": {
330
+ "precision": 0.0,
331
+ "recall": 0.0,
332
+ "f1": 0.0,
333
+ "overlap_chars": 0,
334
+ "gold_chars": 0,
335
+ "pred_chars": 13407
336
+ },
337
+ "missing_tool_action_recommendation": {
338
+ "precision": 0.38209050350541746,
339
+ "recall": 0.796148738379814,
340
+ "f1": 0.516365202411714,
341
+ "overlap_chars": 3597,
342
+ "gold_chars": 4518,
343
+ "pred_chars": 9414
344
+ },
345
+ "tool_output_conflict": {
346
+ "precision": 0.0,
347
+ "recall": 0.0,
348
+ "f1": 0.0,
349
+ "overlap_chars": 0,
350
+ "gold_chars": 221,
351
+ "pred_chars": 7651
352
+ },
353
+ "overgeneration": {
354
+ "precision": 0.20397332970472173,
355
+ "recall": 0.6888786764705882,
356
+ "f1": 0.31475065616797904,
357
+ "overlap_chars": 1499,
358
+ "gold_chars": 2176,
359
+ "pred_chars": 7349
360
+ }
361
+ },
362
+ "num_predicted_spans": 523,
363
+ "num_gold_spans": 111
364
+ }
365
+ },
366
+ {
367
+ "system": "DeBERTa-v3-small span-token classifier",
368
+ "type": "Our span-level model",
369
+ "scope": "row + span detection",
370
+ "row_accuracy": 0.9516908212560387,
371
+ "row_macro_f1": 0.9553091397849462,
372
+ "row_weighted_f1": 0.9527232351566153,
373
+ "binary_f1": 0.9561403508771931,
374
+ "clean_f1": 0.946236559139785,
375
+ "conflict_f1": 0.875,
376
+ "overgeneration_f1": 1.0,
377
+ "missing_tool_f1": 1.0,
378
+ "exact_span_f1": 0.8207171314741037,
379
+ "overlap_span_f1_iou_0_01": 0.8685258964143425,
380
+ "overlap_span_f1_iou_0_50": 0.8366533864541832,
381
+ "char_span_f1": 0.9832881016997572,
382
+ "span_precision_char": 0.971356003950896,
383
+ "span_recall_char": 0.9955169920462762,
384
+ "num_gold_spans": 111,
385
+ "num_predicted_spans": 140,
386
+ "token_macro_f1": 0.8905429210772722,
387
+ "token_weighted_f1": 0.9957200208094352,
388
+ "selected_config": {
389
+ "threshold": 0.5,
390
+ "min_span_chars": 1,
391
+ "min_span_tokens": 1,
392
+ "merge_gap_chars": 1,
393
+ "strip_predicted_span_whitespace": true,
394
+ "drop_spans_without_alnum": true,
395
+ "score_name": "sum_non_O_probability"
396
+ },
397
+ "best_epoch": 8,
398
+ "notes": "Final span-level model. It uses sliding answer windows, supervises only answer-window tokens, and decodes RAGTruth-style character spans.",
399
+ "raw_metrics": {
400
+ "config": {
401
+ "threshold": 0.5,
402
+ "min_span_chars": 1,
403
+ "min_span_tokens": 1,
404
+ "merge_gap_chars": 1,
405
+ "strip_predicted_span_whitespace": true,
406
+ "drop_spans_without_alnum": true,
407
+ "score_name": "sum_non_O_probability"
408
+ },
409
+ "num_rows": 207,
410
+ "row_multiclass_metrics": {
411
+ "clean": {
412
+ "precision": 0.9777777777777777,
413
+ "recall": 0.9166666666666666,
414
+ "f1": 0.946236559139785,
415
+ "support": 96,
416
+ "tp": 88,
417
+ "fp": 2,
418
+ "fn": 8
419
+ },
420
+ "tool_output_conflict": {
421
+ "precision": 0.813953488372093,
422
+ "recall": 0.9459459459459459,
423
+ "f1": 0.875,
424
+ "support": 37,
425
+ "tp": 35,
426
+ "fp": 8,
427
+ "fn": 2
428
+ },
429
+ "overgeneration": {
430
+ "precision": 1.0,
431
+ "recall": 1.0,
432
+ "f1": 1.0,
433
+ "support": 37,
434
+ "tp": 37,
435
+ "fp": 0,
436
+ "fn": 0
437
+ },
438
+ "missing_tool_action_recommendation": {
439
+ "precision": 1.0,
440
+ "recall": 1.0,
441
+ "f1": 1.0,
442
+ "support": 37,
443
+ "tp": 37,
444
+ "fp": 0,
445
+ "fn": 0
446
+ },
447
+ "accuracy": 0.9516908212560387,
448
+ "macro_f1": 0.9553091397849462,
449
+ "weighted_f1": 0.9527232351566153,
450
+ "support": 207
451
+ },
452
+ "binary_example_metrics": {
453
+ "accuracy": 0.9516908212560387,
454
+ "precision": 0.9316239316239316,
455
+ "recall": 0.9819819819819819,
456
+ "f1": 0.9561403508771931,
457
+ "tp": 109,
458
+ "fp": 8,
459
+ "fn": 2,
460
+ "tn": 88
461
+ },
462
+ "exact_span_metrics": {
463
+ "precision": 0.7357142857142858,
464
+ "recall": 0.9279279279279279,
465
+ "f1": 0.8207171314741037,
466
+ "matched": 103,
467
+ "gold_total": 111,
468
+ "pred_total": 140
469
+ },
470
+ "overlap_span_metrics_iou_0_01": {
471
+ "precision": 0.7785714285714286,
472
+ "recall": 0.9819819819819819,
473
+ "f1": 0.8685258964143425,
474
+ "matched": 109,
475
+ "gold_total": 111,
476
+ "pred_total": 140,
477
+ "iou_threshold": 0.01
478
+ },
479
+ "overlap_span_metrics_iou_0_50": {
480
+ "precision": 0.75,
481
+ "recall": 0.9459459459459459,
482
+ "f1": 0.8366533864541832,
483
+ "matched": 105,
484
+ "gold_total": 111,
485
+ "pred_total": 140,
486
+ "iou_threshold": 0.5
487
+ },
488
+ "char_micro_metrics": {
489
+ "precision": 0.971356003950896,
490
+ "recall": 0.9955169920462762,
491
+ "f1": 0.9832881016997572,
492
+ "overlap_chars": 6884,
493
+ "gold_chars": 6915,
494
+ "pred_chars": 7087
495
+ },
496
+ "per_type_char_micro_metrics": {
497
+ "clean": {
498
+ "precision": 0.0,
499
+ "recall": 0.0,
500
+ "f1": 0.0,
501
+ "overlap_chars": 0,
502
+ "gold_chars": 0,
503
+ "pred_chars": 72
504
+ },
505
+ "missing_tool_action_recommendation": {
506
+ "precision": 0.9879728843210146,
507
+ "recall": 1.0,
508
+ "f1": 0.9939500604993949,
509
+ "overlap_chars": 4518,
510
+ "gold_chars": 4518,
511
+ "pred_chars": 4573
512
+ },
513
+ "tool_output_conflict": {
514
+ "precision": 0.7251908396946565,
515
+ "recall": 0.8597285067873304,
516
+ "f1": 0.7867494824016563,
517
+ "overlap_chars": 190,
518
+ "gold_chars": 221,
519
+ "pred_chars": 262
520
+ },
521
+ "overgeneration": {
522
+ "precision": 0.998165137614679,
523
+ "recall": 1.0,
524
+ "f1": 0.9990817263544537,
525
+ "overlap_chars": 2176,
526
+ "gold_chars": 2176,
527
+ "pred_chars": 2180
528
+ }
529
+ },
530
+ "num_gold_spans": 111,
531
+ "num_predicted_spans": 140
532
+ },
533
+ "raw_token_metrics": {
534
+ "O": {
535
+ "precision": 0.9994592359321818,
536
+ "recall": 0.9951225692025084,
537
+ "f1": 0.9972861881258828,
538
+ "support": 31574,
539
+ "tp": 31420,
540
+ "fp": 17,
541
+ "fn": 154
542
+ },
543
+ "tool_output_conflict": {
544
+ "precision": 0.4188679245283019,
545
+ "recall": 0.8671875,
546
+ "f1": 0.564885496183206,
547
+ "support": 128,
548
+ "tp": 111,
549
+ "fp": 154,
550
+ "fn": 17
551
+ },
552
+ "overgeneration": {
553
+ "precision": 1.0,
554
+ "recall": 1.0,
555
+ "f1": 1.0,
556
+ "support": 406,
557
+ "tp": 406,
558
+ "fp": 0,
559
+ "fn": 0
560
+ },
561
+ "missing_tool_action_recommendation": {
562
+ "precision": 1.0,
563
+ "recall": 1.0,
564
+ "f1": 1.0,
565
+ "support": 925,
566
+ "tp": 925,
567
+ "fp": 0,
568
+ "fn": 0
569
+ },
570
+ "accuracy": 0.9948233584597221,
571
+ "macro_f1": 0.8905429210772722,
572
+ "weighted_f1": 0.9957200208094352,
573
+ "support": 33033
574
+ }
575
+ }
576
+ ],
577
+ "comparison_markdown_table": "| System | Scope | Row acc. | Row macro F1 | Binary F1 | Exact span F1 | Overlap F1 | Char span F1 |\n|---|---|---|---|---|---|---|---|\n| DeBERTa-v3-small row classifier | row classification | 0.9275 | 0.9249 | 0.9418 | n/a | n/a | n/a |\n| LettuceDetect | span detection | 0.7343 | n/a | 0.7718 | 0.1652 | 0.4661 | 0.5653 |\n| LookBackLens-style attention baseline | span detection | 0.6232 | n/a | 0.7000 | 0.0095 | 0.1956 | 0.2278 |\n| DeBERTa-v3-small span-token classifier | row + span detection | 0.9517 | 0.9553 | 0.9561 | 0.8207 | 0.8685 | 0.9833 |",
578
+ "notebook_text": "# Final result summary\n\nThe final system has two parts.\n\nFirst, I trained a DeBERTa-v3-small row-level classifier for the four labels: clean, tool_output_conflict, overgeneration, and missing_tool_action_recommendation. This model is useful for quick answer-level detection. On the held-out test split it reached accuracy 0.9275, macro F1 0.9249, and binary clean-vs-hallucinated macro F1 0.9418.\n\nSecond, to satisfy the RAGTruth-style span requirement, I trained a DeBERTa-v3-small token classifier over sliding answer windows. The model predicts answer-token labels and then decodes them back into character spans. The threshold and span cleanup settings were selected on validation only. On the held-out test split the span model reached row accuracy 0.9517, row macro F1 0.9553, binary F1 0.9561, exact span F1 0.8207, overlap span F1 0.8685, and character-level span F1 0.9833.\n\nI also evaluated the required baselines. LettuceDetect reached binary F1 0.7718, exact span F1 0.1652, overlap span F1 0.4661, and character-level span F1 0.5653. The resource-aware LookBackLens-style attention baseline reached binary F1 0.7000, exact span F1 0.0095, overlap span F1 0.1956, and character-level span F1 0.2278.\n\nThe strongest final span model was trained for 8 epochs. Its selected decoding configuration was:\n\n```json\n{\n \"threshold\": 0.5,\n \"min_span_chars\": 1,\n \"min_span_tokens\": 1,\n \"merge_gap_chars\": 1,\n \"strip_predicted_span_whitespace\": true,\n \"drop_spans_without_alnum\": true,\n \"score_name\": \"sum_non_O_probability\"\n}\n```\n\n## Test-set comparison\n\n| System | Scope | Row acc. | Row macro F1 | Binary F1 | Exact span F1 | Overlap F1 | Char span F1 |\n|---|---|---|---|---|---|---|---|\n| DeBERTa-v3-small row classifier | row classification | 0.9275 | 0.9249 | 0.9418 | n/a | n/a | n/a |\n| LettuceDetect | span detection | 0.7343 | n/a | 0.7718 | 0.1652 | 0.4661 | 0.5653 |\n| LookBackLens-style attention baseline | span detection | 0.6232 | n/a | 0.7000 | 0.0095 | 0.1956 | 0.2278 |\n| DeBERTa-v3-small span-token classifier | row + span detection | 0.9517 | 0.9553 | 0.9561 | 0.8207 | 0.8685 | 0.9833 |\n\n## Repositories\n\nDataset: https://huggingface.co/datasets/Ali-Bhai/toolace-ragtruth-style-hallucinations\nModel: https://huggingface.co/Ali-Bhai/deberta-tool-hallucination-detector\n\n## Short discussion\n\nThe row classifier performs well for answer-level decisions but cannot return exact hallucinated text. The final span-token classifier fixes this by using RAGTruth-style character spans during training. A single 512-token sequence lost a small number of late answer spans, so I switched to overlapping answer windows and audited the alignment before training. This preserved every gold span and allowed the model to output exact predicted spans. The final model strongly outperformed LettuceDetect and the LookBackLens-style attention baseline on this constructed held-out test split, especially on character-level span F1.\n",
579
+ "recommended_final_system": {
580
+ "row_level_model": "DeBERTa-v3-small row classifier",
581
+ "span_level_model": "DeBERTa-v3-small span-token classifier with sliding answer windows",
582
+ "primary_metric_to_report_for_span_task": "character-level span F1",
583
+ "primary_span_test_score": 0.9832881016997572,
584
+ "test_exact_span_f1": 0.8207171314741037,
585
+ "test_overlap_span_f1_iou_0_01": 0.8685258964143425,
586
+ "test_binary_f1": 0.9561403508771931,
587
+ "test_row_macro_f1": 0.9553091397849462
588
+ }
589
+ }
reports/final_results_comparison_summary.md ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Final results comparison summary
2
+
3
+ ## Comparison table
4
+
5
+ | System | Scope | Row acc. | Row macro F1 | Binary F1 | Exact span F1 | Overlap F1 | Char span F1 |
6
+ |---|---|---|---|---|---|---|---|
7
+ | DeBERTa-v3-small row classifier | row classification | 0.9275 | 0.9249 | 0.9418 | n/a | n/a | n/a |
8
+ | LettuceDetect | span detection | 0.7343 | n/a | 0.7718 | 0.1652 | 0.4661 | 0.5653 |
9
+ | LookBackLens-style attention baseline | span detection | 0.6232 | n/a | 0.7000 | 0.0095 | 0.1956 | 0.2278 |
10
+ | DeBERTa-v3-small span-token classifier | row + span detection | 0.9517 | 0.9553 | 0.9561 | 0.8207 | 0.8685 | 0.9833 |
11
+
12
+ ## Compact JSON
13
+
14
+ ```json
15
+ [
16
+ {
17
+ "system": "DeBERTa-v3-small row classifier",
18
+ "type": "Our row-level classifier",
19
+ "scope": "row classification",
20
+ "row_accuracy": 0.927536231884058,
21
+ "row_macro_f1": 0.9249477834493081,
22
+ "binary_f1": 0.9417994376757264,
23
+ "exact_span_f1": null,
24
+ "overlap_span_f1_iou_0_01": null,
25
+ "overlap_span_f1_iou_0_50": null,
26
+ "char_span_f1": null,
27
+ "notes": "Best row-level model used answer/evidence pair input and validation-selected conflict calibration. It does not predict exact character spans."
28
+ },
29
+ {
30
+ "system": "LettuceDetect",
31
+ "type": "Official baseline",
32
+ "scope": "span detection",
33
+ "row_accuracy": 0.7342995169082126,
34
+ "row_macro_f1": null,
35
+ "binary_f1": 0.7717842323651452,
36
+ "exact_span_f1": 0.16519174041297932,
37
+ "overlap_span_f1_iou_0_01": 0.46607669616519176,
38
+ "overlap_span_f1_iou_0_50": 0.2949852507374631,
39
+ "char_span_f1": 0.5653136531365313,
40
+ "notes": "Official token-classification baseline run directly from Hugging Face. Validation-only threshold tuning was used for span decoding."
41
+ },
42
+ {
43
+ "system": "LookBackLens-style attention baseline",
44
+ "type": "Official-baseline-inspired baseline",
45
+ "scope": "span detection",
46
+ "row_accuracy": 0.6231884057971014,
47
+ "row_macro_f1": null,
48
+ "binary_f1": 0.7,
49
+ "exact_span_f1": 0.009463722397476343,
50
+ "overlap_span_f1_iou_0_01": 0.19558359621451105,
51
+ "overlap_span_f1_iou_0_50": 0.11356466876971608,
52
+ "char_span_f1": 0.22782546494992845,
53
+ "notes": "Resource-aware implementation of the LookBackLens attention idea using distilgpt2 attention maps."
54
+ },
55
+ {
56
+ "system": "DeBERTa-v3-small span-token classifier",
57
+ "type": "Our span-level model",
58
+ "scope": "row + span detection",
59
+ "row_accuracy": 0.9516908212560387,
60
+ "row_macro_f1": 0.9553091397849462,
61
+ "binary_f1": 0.9561403508771931,
62
+ "exact_span_f1": 0.8207171314741037,
63
+ "overlap_span_f1_iou_0_01": 0.8685258964143425,
64
+ "overlap_span_f1_iou_0_50": 0.8366533864541832,
65
+ "char_span_f1": 0.9832881016997572,
66
+ "notes": "Final span-level model. It uses sliding answer windows, supervises only answer-window tokens, and decodes RAGTruth-style character spans."
67
+ }
68
+ ]
69
+ ```
reports/span_window_training_manifest.json ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "created": "2026-05-23T10:38:22",
3
+ "ready_for_training": true,
4
+ "reason": "Corrected token-level readiness audit. Whitespace-only character gaps are ignored because SentencePiece offset mappings may not expose them as standalone covered characters.",
5
+ "tokenizer_name": "microsoft/deberta-v3-small",
6
+ "max_length": 512,
7
+ "span_labels": [
8
+ "O",
9
+ "tool_output_conflict",
10
+ "overgeneration",
11
+ "missing_tool_action_recommendation"
12
+ ],
13
+ "label2id": {
14
+ "O": 0,
15
+ "tool_output_conflict": 1,
16
+ "overgeneration": 2,
17
+ "missing_tool_action_recommendation": 3
18
+ },
19
+ "id2label": {
20
+ "0": "O",
21
+ "1": "tool_output_conflict",
22
+ "2": "overgeneration",
23
+ "3": "missing_tool_action_recommendation"
24
+ },
25
+ "window_files": {
26
+ "train": "/content/drive/MyDrive/TLLM/02_processed/span_token_windows_deberta_v3_small/train_span_windows.jsonl",
27
+ "validation": "/content/drive/MyDrive/TLLM/02_processed/span_token_windows_deberta_v3_small/validation_span_windows.jsonl",
28
+ "test": "/content/drive/MyDrive/TLLM/02_processed/span_token_windows_deberta_v3_small/test_span_windows.jsonl"
29
+ },
30
+ "critical_problem_counts": {
31
+ "clean_row_has_positive_tokens": 0,
32
+ "non_clean_row_has_zero_positive_tokens": 0,
33
+ "gold_span_has_no_positive_token": 0,
34
+ "intended_answer_tokens_missing_after_tokenization": 0
35
+ },
36
+ "split_readiness": {
37
+ "train": {
38
+ "clean_rows_with_positive_tokens": 0,
39
+ "non_clean_rows_with_zero_positive_tokens": 0,
40
+ "non_clean_rows_with_uncovered_gold_span": 0,
41
+ "rows_with_missing_intended_answer_tokens": 0,
42
+ "windows_with_missing_intended_answer_tokens": 0
43
+ },
44
+ "validation": {
45
+ "clean_rows_with_positive_tokens": 0,
46
+ "non_clean_rows_with_zero_positive_tokens": 0,
47
+ "non_clean_rows_with_uncovered_gold_span": 0,
48
+ "rows_with_missing_intended_answer_tokens": 0,
49
+ "windows_with_missing_intended_answer_tokens": 0
50
+ },
51
+ "test": {
52
+ "clean_rows_with_positive_tokens": 0,
53
+ "non_clean_rows_with_zero_positive_tokens": 0,
54
+ "non_clean_rows_with_uncovered_gold_span": 0,
55
+ "rows_with_missing_intended_answer_tokens": 0,
56
+ "windows_with_missing_intended_answer_tokens": 0
57
+ }
58
+ },
59
+ "window_counts": {
60
+ "train": 1808,
61
+ "validation": 229,
62
+ "test": 235
63
+ },
64
+ "source_row_counts": {
65
+ "train": 1662,
66
+ "validation": 214,
67
+ "test": 207
68
+ }
69
+ }
selected_span_model_config.json ADDED
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_name": "span_deberta_v3_small_sliding_windows",
3
+ "base_model_name": "microsoft/deberta-v3-small",
4
+ "best_checkpoint_dir": "/content/drive/MyDrive/TLLM/03_models/span_deberta_v3_small_sliding_windows/best_checkpoint",
5
+ "last_checkpoint_dir": "/content/drive/MyDrive/TLLM/03_models/span_deberta_v3_small_sliding_windows/last_checkpoint",
6
+ "best_epoch": 8,
7
+ "selected_config": {
8
+ "threshold": 0.5,
9
+ "min_span_chars": 1,
10
+ "min_span_tokens": 1,
11
+ "merge_gap_chars": 1,
12
+ "strip_predicted_span_whitespace": true,
13
+ "drop_spans_without_alnum": true,
14
+ "score_name": "sum_non_O_probability"
15
+ },
16
+ "labels": [
17
+ "O",
18
+ "tool_output_conflict",
19
+ "overgeneration",
20
+ "missing_tool_action_recommendation"
21
+ ],
22
+ "label2id": {
23
+ "O": 0,
24
+ "tool_output_conflict": 1,
25
+ "overgeneration": 2,
26
+ "missing_tool_action_recommendation": 3
27
+ },
28
+ "id2label": {
29
+ "0": "O",
30
+ "1": "tool_output_conflict",
31
+ "2": "overgeneration",
32
+ "3": "missing_tool_action_recommendation"
33
+ },
34
+ "final_validation_token_metrics": {
35
+ "O": {
36
+ "precision": 0.9997869091167383,
37
+ "recall": 0.9987582487759881,
38
+ "f1": 0.9992723142182701,
39
+ "support": 28186,
40
+ "tp": 28151,
41
+ "fp": 6,
42
+ "fn": 35
43
+ },
44
+ "tool_output_conflict": {
45
+ "precision": 0.7666666666666667,
46
+ "recall": 0.9504132231404959,
47
+ "f1": 0.8487084870848709,
48
+ "support": 121,
49
+ "tp": 115,
50
+ "fp": 35,
51
+ "fn": 6
52
+ },
53
+ "overgeneration": {
54
+ "precision": 1.0,
55
+ "recall": 1.0,
56
+ "f1": 1.0,
57
+ "support": 428,
58
+ "tp": 428,
59
+ "fp": 0,
60
+ "fn": 0
61
+ },
62
+ "missing_tool_action_recommendation": {
63
+ "precision": 1.0,
64
+ "recall": 1.0,
65
+ "f1": 1.0,
66
+ "support": 892,
67
+ "tp": 892,
68
+ "fp": 0,
69
+ "fn": 0
70
+ },
71
+ "accuracy": 0.9986161271812873,
72
+ "macro_f1": 0.9619952003257852,
73
+ "weighted_f1": 0.9986898158940638,
74
+ "support": 29627
75
+ },
76
+ "final_test_token_metrics": {
77
+ "O": {
78
+ "precision": 0.9994592359321818,
79
+ "recall": 0.9951225692025084,
80
+ "f1": 0.9972861881258828,
81
+ "support": 31574,
82
+ "tp": 31420,
83
+ "fp": 17,
84
+ "fn": 154
85
+ },
86
+ "tool_output_conflict": {
87
+ "precision": 0.4188679245283019,
88
+ "recall": 0.8671875,
89
+ "f1": 0.564885496183206,
90
+ "support": 128,
91
+ "tp": 111,
92
+ "fp": 154,
93
+ "fn": 17
94
+ },
95
+ "overgeneration": {
96
+ "precision": 1.0,
97
+ "recall": 1.0,
98
+ "f1": 1.0,
99
+ "support": 406,
100
+ "tp": 406,
101
+ "fp": 0,
102
+ "fn": 0
103
+ },
104
+ "missing_tool_action_recommendation": {
105
+ "precision": 1.0,
106
+ "recall": 1.0,
107
+ "f1": 1.0,
108
+ "support": 925,
109
+ "tp": 925,
110
+ "fp": 0,
111
+ "fn": 0
112
+ },
113
+ "accuracy": 0.9948233584597221,
114
+ "macro_f1": 0.8905429210772722,
115
+ "weighted_f1": 0.9957200208094352,
116
+ "support": 33033
117
+ },
118
+ "final_validation_metrics": {
119
+ "config": {
120
+ "threshold": 0.5,
121
+ "min_span_chars": 1,
122
+ "min_span_tokens": 1,
123
+ "merge_gap_chars": 1,
124
+ "strip_predicted_span_whitespace": true,
125
+ "drop_spans_without_alnum": true,
126
+ "score_name": "sum_non_O_probability"
127
+ },
128
+ "num_rows": 214,
129
+ "row_multiclass_metrics": {
130
+ "clean": {
131
+ "precision": 1.0,
132
+ "recall": 0.9611650485436893,
133
+ "f1": 0.9801980198019802,
134
+ "support": 103,
135
+ "tp": 99,
136
+ "fp": 0,
137
+ "fn": 4
138
+ },
139
+ "tool_output_conflict": {
140
+ "precision": 0.9024390243902439,
141
+ "recall": 1.0,
142
+ "f1": 0.9487179487179488,
143
+ "support": 37,
144
+ "tp": 37,
145
+ "fp": 4,
146
+ "fn": 0
147
+ },
148
+ "overgeneration": {
149
+ "precision": 1.0,
150
+ "recall": 1.0,
151
+ "f1": 1.0,
152
+ "support": 37,
153
+ "tp": 37,
154
+ "fp": 0,
155
+ "fn": 0
156
+ },
157
+ "missing_tool_action_recommendation": {
158
+ "precision": 1.0,
159
+ "recall": 1.0,
160
+ "f1": 1.0,
161
+ "support": 37,
162
+ "tp": 37,
163
+ "fp": 0,
164
+ "fn": 0
165
+ },
166
+ "accuracy": 0.9813084112149533,
167
+ "macro_f1": 0.9822289921299823,
168
+ "weighted_f1": 0.9816026174867667,
169
+ "support": 214
170
+ },
171
+ "binary_example_metrics": {
172
+ "accuracy": 0.9813084112149533,
173
+ "precision": 0.9652173913043478,
174
+ "recall": 1.0,
175
+ "f1": 0.9823008849557522,
176
+ "tp": 111,
177
+ "fp": 4,
178
+ "fn": 0,
179
+ "tn": 99
180
+ },
181
+ "exact_span_metrics": {
182
+ "precision": 0.8333333333333334,
183
+ "recall": 0.9009009009009009,
184
+ "f1": 0.8658008658008659,
185
+ "matched": 100,
186
+ "gold_total": 111,
187
+ "pred_total": 120
188
+ },
189
+ "overlap_span_metrics_iou_0_01": {
190
+ "precision": 0.925,
191
+ "recall": 1.0,
192
+ "f1": 0.961038961038961,
193
+ "matched": 111,
194
+ "gold_total": 111,
195
+ "pred_total": 120,
196
+ "iou_threshold": 0.01
197
+ },
198
+ "overlap_span_metrics_iou_0_50": {
199
+ "precision": 0.9083333333333333,
200
+ "recall": 0.9819819819819819,
201
+ "f1": 0.9437229437229437,
202
+ "matched": 109,
203
+ "gold_total": 111,
204
+ "pred_total": 120,
205
+ "iou_threshold": 0.5
206
+ },
207
+ "char_micro_metrics": {
208
+ "precision": 0.9916501556750636,
209
+ "recall": 0.9984326018808778,
210
+ "f1": 0.9950298210735586,
211
+ "overlap_chars": 7007,
212
+ "gold_chars": 7018,
213
+ "pred_chars": 7066
214
+ },
215
+ "per_type_char_micro_metrics": {
216
+ "clean": {
217
+ "precision": 0.0,
218
+ "recall": 0.0,
219
+ "f1": 0.0,
220
+ "overlap_chars": 0,
221
+ "gold_chars": 0,
222
+ "pred_chars": 18
223
+ },
224
+ "overgeneration": {
225
+ "precision": 0.9948006932409013,
226
+ "recall": 1.0,
227
+ "f1": 0.9973935708079931,
228
+ "overlap_chars": 2296,
229
+ "gold_chars": 2296,
230
+ "pred_chars": 2308
231
+ },
232
+ "tool_output_conflict": {
233
+ "precision": 0.9037656903765691,
234
+ "recall": 0.9515418502202643,
235
+ "f1": 0.927038626609442,
236
+ "overlap_chars": 216,
237
+ "gold_chars": 227,
238
+ "pred_chars": 239
239
+ },
240
+ "missing_tool_action_recommendation": {
241
+ "precision": 0.998666962897134,
242
+ "recall": 1.0,
243
+ "f1": 0.9993330369052912,
244
+ "overlap_chars": 4495,
245
+ "gold_chars": 4495,
246
+ "pred_chars": 4501
247
+ }
248
+ },
249
+ "num_gold_spans": 111,
250
+ "num_predicted_spans": 120
251
+ },
252
+ "final_test_metrics": {
253
+ "config": {
254
+ "threshold": 0.5,
255
+ "min_span_chars": 1,
256
+ "min_span_tokens": 1,
257
+ "merge_gap_chars": 1,
258
+ "strip_predicted_span_whitespace": true,
259
+ "drop_spans_without_alnum": true,
260
+ "score_name": "sum_non_O_probability"
261
+ },
262
+ "num_rows": 207,
263
+ "row_multiclass_metrics": {
264
+ "clean": {
265
+ "precision": 0.9777777777777777,
266
+ "recall": 0.9166666666666666,
267
+ "f1": 0.946236559139785,
268
+ "support": 96,
269
+ "tp": 88,
270
+ "fp": 2,
271
+ "fn": 8
272
+ },
273
+ "tool_output_conflict": {
274
+ "precision": 0.813953488372093,
275
+ "recall": 0.9459459459459459,
276
+ "f1": 0.875,
277
+ "support": 37,
278
+ "tp": 35,
279
+ "fp": 8,
280
+ "fn": 2
281
+ },
282
+ "overgeneration": {
283
+ "precision": 1.0,
284
+ "recall": 1.0,
285
+ "f1": 1.0,
286
+ "support": 37,
287
+ "tp": 37,
288
+ "fp": 0,
289
+ "fn": 0
290
+ },
291
+ "missing_tool_action_recommendation": {
292
+ "precision": 1.0,
293
+ "recall": 1.0,
294
+ "f1": 1.0,
295
+ "support": 37,
296
+ "tp": 37,
297
+ "fp": 0,
298
+ "fn": 0
299
+ },
300
+ "accuracy": 0.9516908212560387,
301
+ "macro_f1": 0.9553091397849462,
302
+ "weighted_f1": 0.9527232351566153,
303
+ "support": 207
304
+ },
305
+ "binary_example_metrics": {
306
+ "accuracy": 0.9516908212560387,
307
+ "precision": 0.9316239316239316,
308
+ "recall": 0.9819819819819819,
309
+ "f1": 0.9561403508771931,
310
+ "tp": 109,
311
+ "fp": 8,
312
+ "fn": 2,
313
+ "tn": 88
314
+ },
315
+ "exact_span_metrics": {
316
+ "precision": 0.7357142857142858,
317
+ "recall": 0.9279279279279279,
318
+ "f1": 0.8207171314741037,
319
+ "matched": 103,
320
+ "gold_total": 111,
321
+ "pred_total": 140
322
+ },
323
+ "overlap_span_metrics_iou_0_01": {
324
+ "precision": 0.7785714285714286,
325
+ "recall": 0.9819819819819819,
326
+ "f1": 0.8685258964143425,
327
+ "matched": 109,
328
+ "gold_total": 111,
329
+ "pred_total": 140,
330
+ "iou_threshold": 0.01
331
+ },
332
+ "overlap_span_metrics_iou_0_50": {
333
+ "precision": 0.75,
334
+ "recall": 0.9459459459459459,
335
+ "f1": 0.8366533864541832,
336
+ "matched": 105,
337
+ "gold_total": 111,
338
+ "pred_total": 140,
339
+ "iou_threshold": 0.5
340
+ },
341
+ "char_micro_metrics": {
342
+ "precision": 0.971356003950896,
343
+ "recall": 0.9955169920462762,
344
+ "f1": 0.9832881016997572,
345
+ "overlap_chars": 6884,
346
+ "gold_chars": 6915,
347
+ "pred_chars": 7087
348
+ },
349
+ "per_type_char_micro_metrics": {
350
+ "clean": {
351
+ "precision": 0.0,
352
+ "recall": 0.0,
353
+ "f1": 0.0,
354
+ "overlap_chars": 0,
355
+ "gold_chars": 0,
356
+ "pred_chars": 72
357
+ },
358
+ "missing_tool_action_recommendation": {
359
+ "precision": 0.9879728843210146,
360
+ "recall": 1.0,
361
+ "f1": 0.9939500604993949,
362
+ "overlap_chars": 4518,
363
+ "gold_chars": 4518,
364
+ "pred_chars": 4573
365
+ },
366
+ "tool_output_conflict": {
367
+ "precision": 0.7251908396946565,
368
+ "recall": 0.8597285067873304,
369
+ "f1": 0.7867494824016563,
370
+ "overlap_chars": 190,
371
+ "gold_chars": 221,
372
+ "pred_chars": 262
373
+ },
374
+ "overgeneration": {
375
+ "precision": 0.998165137614679,
376
+ "recall": 1.0,
377
+ "f1": 0.9990817263544537,
378
+ "overlap_chars": 2176,
379
+ "gold_chars": 2176,
380
+ "pred_chars": 2180
381
+ }
382
+ },
383
+ "num_gold_spans": 111,
384
+ "num_predicted_spans": 140
385
+ }
386
+ }
span_inference.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # span_inference.py
2
+ # Inference helper for the DeBERTa-v3-small span-token hallucination detector.
3
+
4
+ from collections import defaultdict
5
+ import torch
6
+
7
+ MAX_LENGTH = 512
8
+ ANSWER_WINDOW_TOKENS = 384
9
+ ANSWER_WINDOW_STRIDE = 256
10
+
11
+ DEFAULT_DECODING_CONFIG = {
12
+ "threshold": 0.5,
13
+ "min_span_chars": 1,
14
+ "min_span_tokens": 1,
15
+ "merge_gap_chars": 1,
16
+ "strip_predicted_span_whitespace": True,
17
+ "drop_spans_without_alnum": True,
18
+ "score_name": "sum_non_O_probability",
19
+ }
20
+
21
+ SPAN_LABELS = [
22
+ "O",
23
+ "tool_output_conflict",
24
+ "overgeneration",
25
+ "missing_tool_action_recommendation",
26
+ ]
27
+
28
+
29
+ def normalize_tool_names(names, max_names=80):
30
+ if not isinstance(names, list):
31
+ return ""
32
+ out = []
33
+ seen = set()
34
+ for item in names:
35
+ if isinstance(item, str):
36
+ item = " ".join(item.split()).strip()
37
+ if item and item.lower() not in seen:
38
+ out.append(item)
39
+ seen.add(item.lower())
40
+ if len(out) >= max_names:
41
+ break
42
+ return "; ".join(out)
43
+
44
+
45
+ def has_alnum(text):
46
+ return any(ch.isalnum() for ch in text)
47
+
48
+
49
+ def safe_divide(num, den):
50
+ return float(num) / float(den) if den else 0.0
51
+
52
+
53
+ def answer_token_offsets(answer, tokenizer):
54
+ enc = tokenizer(
55
+ answer,
56
+ add_special_tokens=False,
57
+ return_offsets_mapping=True,
58
+ truncation=False,
59
+ )
60
+ offsets = []
61
+ for i, (start, end) in enumerate(enc["offset_mapping"]):
62
+ start = int(start)
63
+ end = int(end)
64
+ if end > start:
65
+ offsets.append({"answer_token_index": i, "start": start, "end": end})
66
+ return offsets
67
+
68
+
69
+ def make_answer_windows(answer, tokenizer):
70
+ offsets = answer_token_offsets(answer, tokenizer)
71
+
72
+ if not offsets:
73
+ return [{
74
+ "window_index": 0,
75
+ "answer_window_start": 0,
76
+ "answer_window_end": len(answer),
77
+ }]
78
+
79
+ n = len(offsets)
80
+ if n <= ANSWER_WINDOW_TOKENS:
81
+ token_ranges = [(0, n)]
82
+ else:
83
+ starts = list(range(0, n, ANSWER_WINDOW_STRIDE))
84
+ final_start = max(0, n - ANSWER_WINDOW_TOKENS)
85
+ if starts[-1] != final_start:
86
+ starts.append(final_start)
87
+ starts = sorted(set(starts))
88
+ token_ranges = [(s, min(s + ANSWER_WINDOW_TOKENS, n)) for s in starts]
89
+
90
+ windows = []
91
+ for wi, (tok_start, tok_end) in enumerate(token_ranges):
92
+ char_start = offsets[tok_start]["start"]
93
+ char_end = offsets[tok_end - 1]["end"]
94
+ windows.append({
95
+ "window_index": wi,
96
+ "answer_window_start": int(char_start),
97
+ "answer_window_end": int(char_end),
98
+ })
99
+ return windows
100
+
101
+
102
+ def build_window_text(record, window):
103
+ answer = record.get("output", "")
104
+ query = record.get("query", "")
105
+ context = record.get("context", "")
106
+ tool_names = normalize_tool_names(record.get("available_tool_names", []))
107
+
108
+ answer_window_text = answer[window["answer_window_start"]:window["answer_window_end"]]
109
+
110
+ prefix = "Answer window:\n"
111
+ text = prefix + answer_window_text
112
+ text += "\n\nQuestion:\n" + query
113
+ text += "\n\nTool responses:\n" + context
114
+
115
+ if tool_names:
116
+ text += "\n\nAvailable tool names:\n" + tool_names
117
+
118
+ return text, answer_window_text, len(prefix), len(prefix) + len(answer_window_text)
119
+
120
+
121
+ def deduplicate_candidates(candidates):
122
+ by_key = {}
123
+ for cand in candidates:
124
+ key = (int(cand["start"]), int(cand["end"]))
125
+ if key not in by_key:
126
+ by_key[key] = dict(cand)
127
+ continue
128
+ old = by_key[key]
129
+ if cand["hallucination_probability"] > old["hallucination_probability"]:
130
+ by_key[key] = dict(cand)
131
+ else:
132
+ old["hallucination_probability"] = max(
133
+ old["hallucination_probability"],
134
+ cand["hallucination_probability"],
135
+ )
136
+ for label, prob in cand["type_probabilities"].items():
137
+ old["type_probabilities"][label] = max(old["type_probabilities"].get(label, 0.0), prob)
138
+ return sorted(by_key.values(), key=lambda x: (x["start"], x["end"]))
139
+
140
+
141
+ def strip_span_whitespace(answer, start, end):
142
+ start = max(0, min(int(start), len(answer)))
143
+ end = max(0, min(int(end), len(answer)))
144
+ while start < end and answer[start].isspace():
145
+ start += 1
146
+ while end > start and answer[end - 1].isspace():
147
+ end -= 1
148
+ return start, end
149
+
150
+
151
+ def merge_candidates_to_spans(answer, candidates, config):
152
+ threshold = float(config.get("threshold", DEFAULT_DECODING_CONFIG["threshold"]))
153
+ min_span_chars = int(config.get("min_span_chars", DEFAULT_DECODING_CONFIG["min_span_chars"]))
154
+ min_span_tokens = int(config.get("min_span_tokens", DEFAULT_DECODING_CONFIG["min_span_tokens"]))
155
+ merge_gap_chars = int(config.get("merge_gap_chars", DEFAULT_DECODING_CONFIG["merge_gap_chars"]))
156
+
157
+ selected = [
158
+ c for c in deduplicate_candidates(candidates)
159
+ if c["hallucination_probability"] >= threshold and c["end"] > c["start"]
160
+ ]
161
+ selected.sort(key=lambda x: (x["start"], x["end"]))
162
+
163
+ merged = []
164
+ cur = None
165
+
166
+ for item in selected:
167
+ if cur is None:
168
+ cur = {
169
+ "start": item["start"],
170
+ "end": item["end"],
171
+ "token_count": 1,
172
+ "score": item["hallucination_probability"],
173
+ "score_sum": item["hallucination_probability"],
174
+ "type_score_sums": defaultdict(float),
175
+ }
176
+ for label, prob in item["type_probabilities"].items():
177
+ cur["type_score_sums"][label] += float(prob)
178
+ continue
179
+
180
+ if item["start"] <= cur["end"] + merge_gap_chars:
181
+ cur["end"] = max(cur["end"], item["end"])
182
+ cur["token_count"] += 1
183
+ cur["score"] = max(cur["score"], item["hallucination_probability"])
184
+ cur["score_sum"] += item["hallucination_probability"]
185
+ for label, prob in item["type_probabilities"].items():
186
+ cur["type_score_sums"][label] += float(prob)
187
+ else:
188
+ merged.append(cur)
189
+ cur = {
190
+ "start": item["start"],
191
+ "end": item["end"],
192
+ "token_count": 1,
193
+ "score": item["hallucination_probability"],
194
+ "score_sum": item["hallucination_probability"],
195
+ "type_score_sums": defaultdict(float),
196
+ }
197
+ for label, prob in item["type_probabilities"].items():
198
+ cur["type_score_sums"][label] += float(prob)
199
+
200
+ if cur is not None:
201
+ merged.append(cur)
202
+
203
+ spans = []
204
+ for span in merged:
205
+ start, end = strip_span_whitespace(answer, span["start"], span["end"])
206
+ if end <= start:
207
+ continue
208
+ if (end - start) < min_span_chars:
209
+ continue
210
+ if span["token_count"] < min_span_tokens:
211
+ continue
212
+ text = answer[start:end]
213
+ if config.get("drop_spans_without_alnum", True) and not has_alnum(text):
214
+ continue
215
+
216
+ type_scores = {
217
+ label: safe_divide(value, span["token_count"])
218
+ for label, value in span["type_score_sums"].items()
219
+ }
220
+ pred_type = max(type_scores.items(), key=lambda x: x[1])[0] if type_scores else "overgeneration"
221
+
222
+ spans.append({
223
+ "start": int(start),
224
+ "end": int(end),
225
+ "text": text,
226
+ "label_type": "Predicted Hallucination",
227
+ "hallucination_type": pred_type,
228
+ "score": float(span["score"]),
229
+ "mean_score": float(safe_divide(span["score_sum"], span["token_count"])),
230
+ "type_scores": type_scores,
231
+ "token_count": int(span["token_count"]),
232
+ })
233
+
234
+ return spans
235
+
236
+
237
+ def predict_record(record, model, tokenizer, device=None, decoding_config=None):
238
+ if device is None:
239
+ device = "cuda" if torch.cuda.is_available() else "cpu"
240
+ if decoding_config is None:
241
+ decoding_config = DEFAULT_DECODING_CONFIG
242
+
243
+ model.to(device)
244
+ model.eval()
245
+
246
+ answer = record.get("output", "")
247
+ candidates = []
248
+
249
+ id2label = getattr(model.config, "id2label", None) or {i: label for i, label in enumerate(SPAN_LABELS)}
250
+ id2label = {int(k): v for k, v in id2label.items()}
251
+
252
+ for window in make_answer_windows(answer, tokenizer):
253
+ text, answer_window_text, local_answer_start, local_answer_end = build_window_text(record, window)
254
+ enc = tokenizer(
255
+ text,
256
+ truncation=True,
257
+ max_length=MAX_LENGTH,
258
+ padding=False,
259
+ return_offsets_mapping=True,
260
+ return_tensors="pt",
261
+ add_special_tokens=True,
262
+ )
263
+ offsets = enc.pop("offset_mapping")[0].tolist()
264
+ inputs = {k: v.to(device) for k, v in enc.items()}
265
+
266
+ with torch.no_grad():
267
+ logits = model(**inputs).logits[0]
268
+ probs = torch.softmax(logits.float(), dim=-1).detach().cpu()
269
+
270
+ for token_idx, (start, end) in enumerate(offsets):
271
+ start = int(start)
272
+ end = int(end)
273
+ if end <= start:
274
+ continue
275
+ if not (end > local_answer_start and start < local_answer_end):
276
+ continue
277
+
278
+ local_start = max(0, start - local_answer_start)
279
+ local_end = min(local_answer_end - local_answer_start, end - local_answer_start)
280
+
281
+ if local_end <= local_start:
282
+ continue
283
+
284
+ global_start = window["answer_window_start"] + local_start
285
+ global_end = window["answer_window_start"] + local_end
286
+
287
+ token_probs = probs[token_idx]
288
+ non_o_probs = token_probs[1:]
289
+ hallucination_probability = float(non_o_probs.sum().item())
290
+ best_non_o_id = int(torch.argmax(non_o_probs).item()) + 1
291
+
292
+ candidates.append({
293
+ "start": int(global_start),
294
+ "end": int(global_end),
295
+ "text": answer[global_start:global_end],
296
+ "hallucination_probability": hallucination_probability,
297
+ "pred_label": id2label.get(best_non_o_id, str(best_non_o_id)),
298
+ "type_probabilities": {
299
+ id2label.get(i, str(i)): float(token_probs[i].item())
300
+ for i in range(1, token_probs.shape[-1])
301
+ },
302
+ "o_probability": float(token_probs[0].item()),
303
+ "window_index": window["window_index"],
304
+ })
305
+
306
+ spans = merge_candidates_to_spans(answer, candidates, decoding_config)
307
+
308
+ if spans:
309
+ best_span = max(spans, key=lambda x: (x.get("score", 0.0), x["end"] - x["start"]))
310
+ row_label = best_span.get("hallucination_type", "overgeneration")
311
+ else:
312
+ row_label = "clean"
313
+
314
+ return {
315
+ "pred_hallucination_type": row_label,
316
+ "pred_is_hallucinated": bool(spans),
317
+ "predicted_spans": spans,
318
+ "num_token_candidates": len(candidates),
319
+ "decoding_config": decoding_config,
320
+ }
span_training_extra_config.json ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "saved_at": "2026-05-23T11:10:39",
3
+ "best_epoch": 8,
4
+ "best_validation_score_char_f1": 0.9950298210735586,
5
+ "best_validation_config": {
6
+ "threshold": 0.5,
7
+ "min_span_chars": 1,
8
+ "min_span_tokens": 1,
9
+ "merge_gap_chars": 1,
10
+ "strip_predicted_span_whitespace": true,
11
+ "drop_spans_without_alnum": true,
12
+ "score_name": "sum_non_O_probability"
13
+ },
14
+ "training_config": {
15
+ "base_model_name": "microsoft/deberta-v3-small",
16
+ "max_length": 512,
17
+ "span_labels": [
18
+ "O",
19
+ "tool_output_conflict",
20
+ "overgeneration",
21
+ "missing_tool_action_recommendation"
22
+ ],
23
+ "label2id": {
24
+ "O": 0,
25
+ "tool_output_conflict": 1,
26
+ "overgeneration": 2,
27
+ "missing_tool_action_recommendation": 3
28
+ },
29
+ "id2label": {
30
+ "0": "O",
31
+ "1": "tool_output_conflict",
32
+ "2": "overgeneration",
33
+ "3": "missing_tool_action_recommendation"
34
+ },
35
+ "micro_batch_size": 4,
36
+ "gradient_accumulation_steps": 4,
37
+ "effective_batch_size": 16,
38
+ "num_epochs": 8,
39
+ "patience": 3,
40
+ "learning_rate": 2e-05,
41
+ "weight_decay": 0.01,
42
+ "warmup_ratio": 0.1,
43
+ "warmup_steps": 90,
44
+ "total_update_steps": 904,
45
+ "max_grad_norm": 1.0,
46
+ "use_amp": false,
47
+ "force_fp32_training": true,
48
+ "amp_disabled_reason": "Previous run failed with GradScaler ValueError: Attempting to unscale FP16 gradients.",
49
+ "use_class_weights": true,
50
+ "class_weights": {
51
+ "O": 0.14653727412223816,
52
+ "tool_output_conflict": 1.7199265956878662,
53
+ "overgeneration": 1.2583367824554443,
54
+ "missing_tool_action_recommendation": 0.8751992583274841
55
+ },
56
+ "threshold_grid": [
57
+ 0.05,
58
+ 0.08,
59
+ 0.1,
60
+ 0.12,
61
+ 0.15,
62
+ 0.18,
63
+ 0.2,
64
+ 0.25,
65
+ 0.3,
66
+ 0.35,
67
+ 0.4,
68
+ 0.45,
69
+ 0.5,
70
+ 0.55,
71
+ 0.6,
72
+ 0.65,
73
+ 0.7,
74
+ 0.75,
75
+ 0.8,
76
+ 0.85,
77
+ 0.9,
78
+ 0.95
79
+ ],
80
+ "min_span_chars_grid": [
81
+ 1,
82
+ 2,
83
+ 3,
84
+ 5,
85
+ 8,
86
+ 12
87
+ ],
88
+ "min_span_tokens_grid": [
89
+ 1,
90
+ 2
91
+ ],
92
+ "merge_gap_chars": 1
93
+ },
94
+ "dataset_report": {
95
+ "tokenizer_class": "DebertaV2Tokenizer",
96
+ "model_max_length": 1000000000000000019884624838656,
97
+ "max_length": 512,
98
+ "train_windows": 1808,
99
+ "validation_windows": 229,
100
+ "test_windows": 235,
101
+ "train_supervised_token_count": 246653,
102
+ "validation_supervised_token_count": 29627,
103
+ "test_supervised_token_count": 33033,
104
+ "train_positive_token_count": 10688,
105
+ "validation_positive_token_count": 1441,
106
+ "test_positive_token_count": 1459,
107
+ "train_token_label_counts": {
108
+ "O": 235965,
109
+ "overgeneration": 3200,
110
+ "missing_tool_action_recommendation": 6615,
111
+ "tool_output_conflict": 873
112
+ },
113
+ "validation_token_label_counts": {
114
+ "O": 28186,
115
+ "overgeneration": 428,
116
+ "tool_output_conflict": 121,
117
+ "missing_tool_action_recommendation": 892
118
+ },
119
+ "test_token_label_counts": {
120
+ "O": 31574,
121
+ "missing_tool_action_recommendation": 925,
122
+ "tool_output_conflict": 128,
123
+ "overgeneration": 406
124
+ },
125
+ "class_weights": {
126
+ "O": 0.14653727412223816,
127
+ "tool_output_conflict": 1.7199265956878662,
128
+ "overgeneration": 1.2583367824554443,
129
+ "missing_tool_action_recommendation": 0.8751992583274841
130
+ }
131
+ }
132
+ }
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": true,
3
+ "backend": "tokenizers",
4
+ "bos_token": "[CLS]",
5
+ "cls_token": "[CLS]",
6
+ "do_lower_case": false,
7
+ "eos_token": "[SEP]",
8
+ "extra_special_tokens": [
9
+ "[PAD]",
10
+ "[CLS]",
11
+ "[SEP]"
12
+ ],
13
+ "is_local": false,
14
+ "mask_token": "[MASK]",
15
+ "model_max_length": 1000000000000000019884624838656,
16
+ "pad_token": "[PAD]",
17
+ "sep_token": "[SEP]",
18
+ "split_by_punct": false,
19
+ "tokenizer_class": "DebertaV2Tokenizer",
20
+ "unk_id": 3,
21
+ "unk_token": "[UNK]",
22
+ "vocab_type": "spm"
23
+ }