perspt_sdk/model/
family.rs1use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(rename_all = "snake_case")]
19pub enum ModelFamily {
20 OpenAiGpt,
21 AnthropicClaude,
22 GoogleGemini,
23 XaiGrok,
24 DeepSeek,
25 CohereCommand,
26 Llama,
27 Qwen,
28 Mistral,
29 Other(String),
30}
31
32impl ModelFamily {
33 pub fn from_model_name(name: &str) -> Self {
40 let n = name.to_ascii_lowercase();
41 if n.contains("claude") {
43 ModelFamily::AnthropicClaude
44 } else if n.contains("gemini") || n.contains("gemma") {
45 ModelFamily::GoogleGemini
46 } else if n.contains("grok") {
47 ModelFamily::XaiGrok
48 } else if n.contains("deepseek") {
49 ModelFamily::DeepSeek
50 } else if n.contains("command") && !n.contains("gpt") {
51 ModelFamily::CohereCommand
52 } else if n.contains("llama") {
53 ModelFamily::Llama
54 } else if n.contains("qwen") {
55 ModelFamily::Qwen
56 } else if n.contains("mistral") || n.contains("mixtral") || n.contains("codestral") {
57 ModelFamily::Mistral
58 } else if n.contains("gpt") || n.starts_with("o1") || n.starts_with("o3") {
59 ModelFamily::OpenAiGpt
60 } else {
61 ModelFamily::Other(name.to_string())
62 }
63 }
64
65 pub fn is_distinct_from(&self, other: &ModelFamily) -> bool {
70 self != other
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn classifies_by_model_name_not_host() {
80 assert_eq!(
81 ModelFamily::from_model_name("llama-3.3-70b-versatile"),
82 ModelFamily::Llama
83 );
84 assert_eq!(
85 ModelFamily::from_model_name("claude-opus-5"),
86 ModelFamily::AnthropicClaude
87 );
88 assert_eq!(
89 ModelFamily::from_model_name("gemini-3.5-flash"),
90 ModelFamily::GoogleGemini
91 );
92 assert_eq!(
93 ModelFamily::from_model_name("gpt-5.5"),
94 ModelFamily::OpenAiGpt
95 );
96 assert_eq!(
97 ModelFamily::from_model_name("qwen2.5-coder"),
98 ModelFamily::Qwen
99 );
100 }
101
102 #[test]
103 fn unknown_names_stay_other_instead_of_being_guessed() {
104 assert_eq!(
105 ModelFamily::from_model_name("sonnetina-9000"),
106 ModelFamily::Other("sonnetina-9000".to_string())
107 );
108 }
109
110 #[test]
111 fn same_lineage_on_two_hosts_is_not_distinct() {
112 let groq = ModelFamily::from_model_name("llama-3.3-70b-versatile");
113 let ollama = ModelFamily::from_model_name("llama3.3:70b");
114 assert!(!groq.is_distinct_from(&ollama));
115 }
116}