1use crate::eval::loader::DatasetId;
26use serde::{Deserialize, Serialize};
27use std::collections::HashMap;
28
29pub use crate::eval::coref_resolver::CoreferenceResolver as CoreferenceResolverTrait;
31pub use anno::backends::inference::{
32 DiscontinuousNER as DiscontinuousNERTrait, RelationExtractor as RelationExtractorTrait,
33 ZeroShotNER as ZeroShotNERTrait,
34};
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
38#[non_exhaustive]
39pub enum Task {
40 NER,
42 NED,
44 RelationExtraction,
46 IntraDocCoref,
48 InterDocCoref,
50 AbstractAnaphora,
52 DiscontinuousNER,
54 EventExtraction,
56 TextClassification,
58 SentimentAnalysis,
60 PosTagging,
62 MachineTranslation,
64 LanguageModeling,
66 Temporal,
72 HierarchicalExtraction,
74 DiscourseRelations,
76 DiscourseCoherence,
78 DiscourseSegmentation,
80 SpeechActClassification,
82}
83
84impl Task {
85 pub fn all() -> &'static [Task] {
87 &[
88 Task::NER,
89 Task::NED,
90 Task::RelationExtraction,
91 Task::IntraDocCoref,
92 Task::InterDocCoref,
93 Task::AbstractAnaphora,
94 Task::DiscontinuousNER,
95 Task::EventExtraction,
96 Task::TextClassification,
97 Task::SentimentAnalysis,
98 Task::PosTagging,
99 Task::MachineTranslation,
100 Task::LanguageModeling,
101 Task::Temporal,
102 Task::HierarchicalExtraction,
103 Task::DiscourseRelations,
104 Task::DiscourseCoherence,
105 Task::DiscourseSegmentation,
106 Task::SpeechActClassification,
107 ]
108 }
109
110 pub fn name(&self) -> &'static str {
112 match self {
113 Task::NER => "Named Entity Recognition",
114 Task::NED => "Named Entity Disambiguation",
115 Task::RelationExtraction => "Relation Extraction",
116 Task::IntraDocCoref => "Intra-document Coreference",
117 Task::InterDocCoref => "Inter-document Coreference",
118 Task::AbstractAnaphora => "Abstract Anaphora Resolution",
119 Task::DiscontinuousNER => "Discontinuous NER",
120 Task::EventExtraction => "Event Extraction",
121 Task::TextClassification => "Text Classification",
122 Task::SentimentAnalysis => "Sentiment Analysis",
123 Task::PosTagging => "Part-of-speech Tagging",
124 Task::MachineTranslation => "Machine Translation",
125 Task::LanguageModeling => "Language Modeling",
126 Task::Temporal => "Temporal",
127 Task::HierarchicalExtraction => "Hierarchical Structure Extraction",
128 Task::DiscourseRelations => "Discourse Relations",
129 Task::DiscourseCoherence => "Discourse Coherence",
130 Task::DiscourseSegmentation => "Discourse Segmentation",
131 Task::SpeechActClassification => "Speech Act Classification",
132 }
133 }
134
135 pub fn code(&self) -> &'static str {
137 match self {
138 Task::NER => "ner",
139 Task::NED => "ned",
140 Task::RelationExtraction => "re",
141 Task::IntraDocCoref => "intra-coref",
142 Task::InterDocCoref => "inter-coref",
143 Task::AbstractAnaphora => "abstract-anaphora",
144 Task::DiscontinuousNER => "discontinuous-ner",
145 Task::EventExtraction => "events",
146 Task::TextClassification => "classification",
147 Task::SentimentAnalysis => "sentiment",
148 Task::PosTagging => "pos",
149 Task::MachineTranslation => "mt",
150 Task::LanguageModeling => "lm",
151 Task::Temporal => "temporal",
152 Task::HierarchicalExtraction => "hierarchical",
153 Task::DiscourseRelations => "discourse-relations",
154 Task::DiscourseCoherence => "discourse-coherence",
155 Task::DiscourseSegmentation => "discourse-segmentation",
156 Task::SpeechActClassification => "speech-act-classification",
157 }
158 }
159
160 pub fn from_code(code: &str) -> Option<Self> {
175 match code.to_lowercase().as_str() {
176 "ner" | "sequence_labeling" | "nested-ner" | "mner" | "pii_detection"
178 | "slot_filling" => Some(Task::NER),
179
180 "ned" | "el" | "entity_linking" | "entity-linking" => Some(Task::NED),
182
183 "re" | "relation" | "relation_extraction" | "relation-extraction" => {
185 Some(Task::RelationExtraction)
186 }
187
188 "coref" | "intra-coref" | "intra_coref" | "intracoref" => Some(Task::IntraDocCoref),
190
191 "inter-coref" | "inter_coref" | "intercoref" | "cdcr" | "event_coref" => {
193 Some(Task::InterDocCoref)
194 }
195
196 "abstract-anaphora" | "abstract_anaphora" | "bridging" | "discourse_deixis" => {
198 Some(Task::AbstractAnaphora)
199 }
200
201 "discontinuous-ner" | "discontinuous_ner" | "disc-ner" | "dner" => {
203 Some(Task::DiscontinuousNER)
204 }
205
206 "events" | "event" | "event_extraction" | "event-extraction" | "ee" => {
208 Some(Task::EventExtraction)
209 }
210
211 "classification"
213 | "text_classification"
214 | "text-classification"
215 | "tc"
216 | "bias_evaluation"
217 | "qa"
218 | "harmonic_analysis" => Some(Task::TextClassification),
219
220 "sentiment" | "sentiment_analysis" | "sentiment-analysis" => {
222 Some(Task::SentimentAnalysis)
223 }
224
225 "pos" | "pos_tagging" | "pos-tagging" => Some(Task::PosTagging),
227
228 "mt" | "machine_translation" | "machine-translation" | "translation" => {
230 Some(Task::MachineTranslation)
231 }
232
233 "lm" | "language_modeling" | "language-modeling" => Some(Task::LanguageModeling),
235
236 "temporal" | "timex" | "time_expressions" | "time-expressions" => Some(Task::Temporal),
238
239 "hierarchical" | "hierarchical-extraction" | "he" => Some(Task::HierarchicalExtraction),
241
242 "discourse_relations" | "discourse-relations" => Some(Task::DiscourseRelations),
244 "discourse_coherence" | "discourse-coherence" => Some(Task::DiscourseCoherence),
245 "discourse_segmentation" | "discourse-segmentation" => {
246 Some(Task::DiscourseSegmentation)
247 }
248 "speech_act_classification" | "speech-act-classification" => {
249 Some(Task::SpeechActClassification)
250 }
251
252 _ => None,
253 }
254 }
255
256 pub fn is_ner_family(&self) -> bool {
258 matches!(self, Task::NER | Task::DiscontinuousNER | Task::NED)
259 }
260
261 pub fn is_coref_family(&self) -> bool {
263 matches!(
264 self,
265 Task::IntraDocCoref | Task::InterDocCoref | Task::AbstractAnaphora
266 )
267 }
268}
269
270pub fn dataset_tasks(dataset: DatasetId) -> Vec<Task> {
278 dataset.tasks_typed()
281}
282
283pub fn task_datasets(task: Task) -> &'static [DatasetId] {
285 match task {
286 Task::NER => &[
287 DatasetId::WikiGold,
288 DatasetId::Wnut17,
289 DatasetId::MitMovie,
290 DatasetId::MitRestaurant,
291 DatasetId::CoNLL2003Sample,
292 DatasetId::OntoNotesSample,
293 DatasetId::MultiNERD,
294 DatasetId::BC5CDR,
295 DatasetId::NCBIDisease,
296 DatasetId::GENIA,
297 DatasetId::AnatEM,
298 DatasetId::BC2GM,
299 DatasetId::BC4CHEMD,
300 DatasetId::TweetNER7,
301 DatasetId::BroadTwitterCorpus,
302 DatasetId::FabNER,
303 DatasetId::FewNERD,
304 DatasetId::CrossNER,
305 DatasetId::UniversalNERBench,
306 DatasetId::WikiANN,
307 DatasetId::MultiCoNER,
308 DatasetId::MultiCoNERv2,
309 DatasetId::WikiNeural,
310 DatasetId::PolyglotNER,
311 DatasetId::UniversalNER,
312 DatasetId::UNER,
313 DatasetId::MSNER,
314 DatasetId::BioMNER,
315 DatasetId::LegNER,
316 DatasetId::OntoNotes50,
317 DatasetId::GermEval2014,
318 DatasetId::HAREM,
319 DatasetId::SemEval2013Task91,
320 DatasetId::MUC6,
321 DatasetId::MUC7,
322 DatasetId::JNLPBA,
323 DatasetId::BC2GMFull,
324 DatasetId::CRAFT,
325 DatasetId::FinNER,
326 DatasetId::LegalNER,
327 DatasetId::SciERCNER,
328 DatasetId::TaggedPBCEsperanto,
330 DatasetId::TaggedPBCKlingon,
331 ],
334 Task::DiscontinuousNER => &[DatasetId::CADEC, DatasetId::ShARe13, DatasetId::ShARe14],
335 Task::RelationExtraction => &[
336 DatasetId::DocRED,
337 DatasetId::ReTACRED,
338 DatasetId::NYTFB,
339 DatasetId::WEBNLG,
340 DatasetId::GoogleRE,
341 DatasetId::BioRED,
342 DatasetId::SciER,
343 DatasetId::MixRED,
344 DatasetId::CovEReD,
345 ],
346 Task::IntraDocCoref => &[
347 DatasetId::GAP,
348 DatasetId::PreCo,
349 DatasetId::LitBank,
350 DatasetId::HumanVoiceAgentInteraction,
351 ],
352 Task::InterDocCoref => &[DatasetId::ECBPlus, DatasetId::WikiCoref],
353 Task::AbstractAnaphora => &[
354 DatasetId::GAP,
355 DatasetId::PreCo,
356 DatasetId::LitBank,
357 DatasetId::HumanVoiceAgentInteraction,
358 ],
359 Task::EventExtraction => &[
360 DatasetId::ACE2005,
361 DatasetId::MAVEN,
362 DatasetId::MAVENArg,
363 DatasetId::CASIE,
364 DatasetId::RAMS,
365 ],
366 Task::NED => &[DatasetId::AIDA, DatasetId::TACKBP],
367 Task::TextClassification => &[
368 DatasetId::MasakhaNEWS,
369 DatasetId::AGNews,
370 DatasetId::DBPedia14,
371 DatasetId::YahooAnswers,
372 DatasetId::TREC,
373 DatasetId::TweetTopic,
374 ],
375 Task::SentimentAnalysis => &[],
378 Task::PosTagging => &[],
379 Task::MachineTranslation => &[],
380 Task::LanguageModeling => &[],
381 Task::Temporal => &[DatasetId::TimexRecognitionSentenceOriginal],
382 Task::HierarchicalExtraction => {
383 &[]
385 }
386 Task::DiscourseRelations => &[
387 DatasetId::DisrptEngDepScidtbRels,
388 DatasetId::DisrptDeuRstPccRels,
389 ],
390 Task::SpeechActClassification => &[DatasetId::ViraDialogActsLive],
391 Task::DiscourseSegmentation => &[
392 DatasetId::DisrptEngDepScidtbConlluSeg,
393 DatasetId::DisrptDeuRstPccConlluSeg,
394 ],
395 Task::DiscourseCoherence => &[],
396 }
397}
398
399pub fn backend_tasks(backend_name: &str) -> &'static [Task] {
405 match backend_name {
406 "pattern" | "RegexNER" => &[Task::NER], "heuristic" | "HeuristicNER" => &[Task::NER],
409 "stacked" | "StackedNER" => &[Task::NER],
410 "crf" | "CrfNER" => &[Task::NER],
411 "hmm" | "HmmNER" => &[Task::NER],
412 "ensemble" | "EnsembleNER" => &[Task::NER],
413 "heuristic_crf" | "heuristic-crf" | "HeuristicCrfNER" => &[Task::NER],
414
415 "bert_onnx" | "BertNEROnnx" => &[Task::NER],
417 "candle_ner" | "CandleNER" => &[Task::NER],
418 "nuner" | "NuNER" | "nuner_4k" | "nunerzero4k" => &[Task::NER], "b2ner" | "B2NER" => &[Task::NER],
420 "deberta_v3" | "DeBERTaV3NER" => &[Task::NER],
421 "albert" | "ALBERTNER" => &[Task::NER],
422
423 "gliner_onnx" | "GLiNEROnnx" => &[Task::NER],
425 "gliner_candle" | "GLiNERCandle" => &[Task::NER],
426 "gliner_poly" | "GLiNERPoly" => &[Task::NER],
427 "gliner_pii" | "pii_ml" => &[Task::NER], "gliner_relex" | "relex" => &[Task::NER, Task::RelationExtraction],
429 "universal_ner" | "UniversalNER" => &[Task::NER],
430
431 "gliner_multitask"
433 | "GLiNERMultitask"
434 | "GLiNERMultitaskOnnx"
435 | "GLiNERMultitaskCandle" => &[
436 Task::NER,
437 Task::TextClassification,
438 Task::SpeechActClassification,
439 Task::Temporal,
440 Task::HierarchicalExtraction,
441 Task::DiscourseRelations,
442 Task::DiscourseSegmentation,
443 Task::EventExtraction,
446 Task::RelationExtraction, ],
448
449 "w2ner" | "W2NER" => &[Task::NER, Task::DiscontinuousNER],
451
452 "tplinker" | "TPLinker" => &[Task::RelationExtraction],
455
456 "fcoref" | "f-coref" => &[Task::IntraDocCoref],
458
459 "coref_resolver" | "CorefResolver" | "SimpleCorefResolver" | "DiscourseAwareResolver" => &[
464 Task::IntraDocCoref,
465 Task::InterDocCoref,
466 Task::AbstractAnaphora,
467 ],
468 "mention_ranking" | "MentionRankingCoref" => &[Task::IntraDocCoref, Task::InterDocCoref],
469
470 _ => &[],
471 }
472}
473
474pub fn detect_backend_capabilities(backend: &dyn crate::Model) -> Vec<Task> {
482 let backend_name = backend.name();
485 detect_backend_capabilities_by_name(backend_name)
486}
487
488pub fn detect_backend_capabilities_by_name(backend_name: &str) -> Vec<Task> {
492 backend_tasks(backend_name).to_vec()
493}
494
495pub fn get_dataset_tasks(dataset: DatasetId) -> Vec<Task> {
497 dataset_tasks(dataset)
498}
499
500pub fn get_task_datasets(task: Task) -> Vec<DatasetId> {
502 task_datasets(task)
508 .iter()
509 .copied()
510 .filter(|d| dataset_tasks(*d).contains(&task))
511 .collect()
512}
513
514pub fn get_task_backends(task: Task) -> Vec<&'static str> {
519 let mut backends = Vec::new();
520 for backend in [
521 "stacked",
523 "crf",
524 "hmm",
525 "heuristic",
526 "ensemble",
527 "heuristic_crf",
528 "bert_onnx",
533 "candle_ner",
534 "nuner",
535 "nuner_4k",
536 "b2ner",
537 "gliner_onnx",
538 "gliner_candle",
539 "gliner_multitask",
540 "gliner_pii",
541 "gliner_relex",
542 "w2ner",
543 "tplinker",
545 "gliner_poly",
546 "deberta_v3",
547 "albert",
548 "universal_ner",
549 "coref_resolver",
551 "mention_ranking",
552 "fcoref",
553 ] {
554 if backend_tasks(backend).contains(&task) {
555 backends.push(backend);
556 }
557 }
558 backends
559}
560
561#[derive(Debug, Clone, Serialize, Deserialize)]
563pub struct TaskMapping {
564 pub task_to_datasets: HashMap<String, Vec<String>>,
566 pub dataset_to_tasks: HashMap<String, Vec<String>>,
568 pub backend_to_tasks: HashMap<String, Vec<String>>,
570 pub task_to_backends: HashMap<String, Vec<String>>,
572}
573
574impl TaskMapping {
575 pub fn build() -> Self {
577 let mut task_to_datasets = HashMap::new();
578 let mut dataset_to_tasks = HashMap::new();
579 let mut backend_to_tasks = HashMap::new();
580 let mut task_to_backends = HashMap::new();
581
582 for task in Task::all() {
584 let datasets = get_task_datasets(*task)
585 .iter()
586 .map(|d| format!("{:?}", d))
587 .collect();
588 task_to_datasets.insert(task.code().to_string(), datasets);
589 }
590
591 for dataset in DatasetId::all() {
593 let tasks = get_dataset_tasks(*dataset)
594 .iter()
595 .map(|t| t.code().to_string())
596 .collect();
597 dataset_to_tasks.insert(format!("{:?}", dataset), tasks);
598 }
599
600 for backend in [
602 "pattern",
603 "heuristic",
604 "stacked",
605 "crf",
606 "hmm",
607 "ensemble",
608 "heuristic_crf",
609 "bert_onnx",
610 "candle_ner",
611 "nuner",
612 "deberta_v3",
613 "albert",
614 "gliner_onnx",
615 "gliner_candle",
616 "gliner_poly",
617 "gliner_multitask",
618 "universal_ner",
619 "w2ner",
620 "tplinker",
621 "coref_resolver",
622 "mention_ranking",
623 ] {
624 let tasks = backend_tasks(backend)
625 .iter()
626 .map(|t| t.code().to_string())
627 .collect();
628 backend_to_tasks.insert(backend.to_string(), tasks);
629 }
630
631 for task in Task::all() {
633 let backends: Vec<String> = get_task_backends(*task)
634 .iter()
635 .map(|s| s.to_string())
636 .collect();
637 task_to_backends.insert(task.code().to_string(), backends);
638 }
639
640 Self {
641 task_to_datasets,
642 dataset_to_tasks,
643 backend_to_tasks,
644 task_to_backends,
645 }
646 }
647
648 pub fn datasets_for_task(&self, task: &str) -> Option<&Vec<String>> {
650 self.task_to_datasets.get(task)
651 }
652
653 pub fn tasks_for_dataset(&self, dataset: &str) -> Option<&Vec<String>> {
655 self.dataset_to_tasks.get(dataset)
656 }
657
658 pub fn tasks_for_backend(&self, backend: &str) -> Option<&Vec<String>> {
660 self.backend_to_tasks.get(backend)
661 }
662
663 pub fn backends_for_task(&self, task: &str) -> Option<&Vec<String>> {
665 self.task_to_backends.get(task)
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672
673 #[test]
674 fn test_task_mapping() {
675 let mapping = TaskMapping::build();
676 assert!(mapping.datasets_for_task("ner").is_some());
677 assert!(mapping.tasks_for_dataset("WikiGold").is_some());
678 assert!(mapping.tasks_for_backend("gliner_multitask").is_some());
679 assert!(mapping.backends_for_task("ner").is_some());
680 }
681
682 #[test]
683 fn test_gliner_multitask_capabilities() {
684 let tasks = backend_tasks("gliner_multitask");
685 assert!(tasks.contains(&Task::NER));
686 assert!(tasks.contains(&Task::TextClassification));
687 assert!(tasks.contains(&Task::HierarchicalExtraction));
688 assert!(tasks.contains(&Task::RelationExtraction));
689 }
690
691 #[test]
692 fn test_dataset_tasks_are_deduced_from_registry() {
693 let tasks = dataset_tasks(DatasetId::WikiGold);
696 assert!(tasks.contains(&Task::NER));
697 }
698
699 #[test]
700 fn test_registry_task_codes_are_parseable() {
701 fn is_in_scope_task_code(task_str: &str) -> bool {
708 matches!(
709 task_str.to_lowercase().as_str(),
710 "ner"
712 | "sequence_labeling"
713 | "nested-ner"
714 | "mner"
715 | "pii_detection"
716 | "slot_filling"
717 | "coref"
719 | "intra-coref"
720 | "intra_coref"
721 | "intracoref"
722 | "inter-coref"
723 | "inter_coref"
724 | "intercoref"
725 | "cdcr"
726 | "event_coref"
727 | "re"
729 | "relation"
730 | "relation_extraction"
731 | "relation-extraction"
732 | "events"
733 | "event"
734 | "event_extraction"
735 | "event-extraction"
736 | "ee"
737 | "abstract-anaphora"
739 | "abstract_anaphora"
740 | "bridging"
741 | "discourse_deixis"
742 | "discourse_relations"
743 | "discourse-relations"
744 | "discourse_coherence"
745 | "discourse-coherence"
746 | "discourse_segmentation"
747 | "discourse-segmentation"
748 | "speech_act_classification"
749 | "speech-act-classification"
750 | "classification"
752 | "text_classification"
753 | "text-classification"
754 | "tc"
755 | "bias_evaluation"
756 | "qa"
757 | "harmonic_analysis"
758 | "sentiment"
759 | "sentiment_analysis"
760 | "sentiment-analysis"
761 | "pos"
762 | "pos_tagging"
763 | "pos-tagging"
764 | "mt"
766 | "machine_translation"
767 | "machine-translation"
768 | "translation"
769 | "lm"
770 | "language_modeling"
771 | "language-modeling"
772 )
773 }
774
775 for ds in DatasetId::all() {
776 for &task_str in ds.tasks() {
777 if !is_in_scope_task_code(task_str) {
778 continue;
779 }
780 assert!(
781 Task::from_code(task_str).is_some(),
782 "Unrecognized task code '{}' on dataset {:?}",
783 task_str,
784 ds
785 );
786 }
787 }
788 }
789}