use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AliasEntry {
pub alias: String,
pub target: String,
pub default_quant: Option<String>,
pub variants: HashMap<String, String>,
pub description: Option<String>,
}
impl AliasEntry {
#[must_use]
pub fn new(alias: impl Into<String>, target: impl Into<String>) -> Self {
Self {
alias: alias.into(),
target: target.into(),
default_quant: None,
variants: HashMap::new(),
description: None,
}
}
#[must_use]
pub fn with_default_quant(mut self, quant: impl Into<String>) -> Self {
self.default_quant = Some(quant.into());
self
}
#[must_use]
pub fn with_variant(mut self, tag: impl Into<String>, target: impl Into<String>) -> Self {
self.variants.insert(tag.into(), target.into());
self
}
#[must_use]
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
#[must_use]
pub fn resolve_variant(&self, variant: Option<&str>) -> &str {
match variant {
Some(v) => self.variants.get(v).map(String::as_str).unwrap_or(&self.target),
None => &self.target,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedRef {
pub name: String,
pub variant: Option<String>,
pub quantization: Option<String>,
}
impl ParsedRef {
#[must_use]
pub fn parse(s: &str) -> Self {
if s.contains("://") {
return Self { name: s.to_string(), variant: None, quantization: None };
}
let (name_part, tag_part) =
if let Some(idx) = s.find(':') { (&s[..idx], Some(&s[idx + 1..])) } else { (s, None) };
let (name, name_quant) = extract_quant_suffix(name_part);
let (variant, tag_quant) =
if let Some(tag) = tag_part { parse_tag(tag) } else { (None, None) };
let quantization = tag_quant.or(name_quant);
Self { name: name.to_string(), variant, quantization }
}
#[must_use]
pub fn to_string_repr(&self) -> String {
let mut s = self.name.clone();
if let Some(ref v) = self.variant {
s.push(':');
s.push_str(v);
}
if let Some(ref q) = self.quantization {
if self.variant.is_some() {
s.push('-');
} else {
s.push(':');
}
s.push_str(q);
}
s
}
}
fn extract_quant_suffix(s: &str) -> (&str, Option<String>) {
let quant_patterns = [
"-q4", "-q5", "-q6", "-q8", "-Q4_K_M", "-Q4_K_S", "-Q5_K_M", "-Q5_K_S", "-Q6_K", "-Q8_0",
"-Q8_K", "-f16", "-f32", "-bf16",
];
for pattern in quant_patterns {
if let Some(idx) = s.to_lowercase().rfind(&pattern.to_lowercase()) {
return (&s[..idx], Some(s[idx + 1..].to_string()));
}
}
(s, None)
}
fn parse_tag(tag: &str) -> (Option<String>, Option<String>) {
if let Some(idx) = tag.rfind('-') {
let (variant, quant) = (&tag[..idx], &tag[idx + 1..]);
if is_quant_tag(quant) {
return (Some(variant.to_string()), Some(quant.to_string()));
}
}
if is_quant_tag(tag) {
return (None, Some(tag.to_string()));
}
(Some(tag.to_string()), None)
}
fn is_quant_tag(s: &str) -> bool {
let lower = s.to_lowercase();
lower.starts_with("q") && lower.chars().nth(1).is_some_and(|c| c.is_ascii_digit())
|| lower.starts_with("iq")
|| lower == "f16"
|| lower == "f32"
|| lower == "fp16"
|| lower == "fp32"
|| lower == "bf16"
}
#[derive(Debug, Clone, Default)]
pub struct AliasRegistry {
aliases: HashMap<String, AliasEntry>,
}
impl AliasRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_defaults() -> Self {
let mut registry = Self::new();
registry.add(
AliasEntry::new("llama3", "hf://meta-llama/Meta-Llama-3-8B-Instruct")
.with_default_quant("Q4_K_M")
.with_variant("8b", "hf://meta-llama/Meta-Llama-3-8B-Instruct")
.with_variant("70b", "hf://meta-llama/Meta-Llama-3-70B-Instruct")
.with_description("Meta's Llama 3 family"),
);
registry.add(
AliasEntry::new("llama3.1", "hf://meta-llama/Meta-Llama-3.1-8B-Instruct")
.with_default_quant("Q4_K_M")
.with_variant("8b", "hf://meta-llama/Meta-Llama-3.1-8B-Instruct")
.with_variant("70b", "hf://meta-llama/Meta-Llama-3.1-70B-Instruct")
.with_variant("405b", "hf://meta-llama/Meta-Llama-3.1-405B-Instruct")
.with_description("Meta's Llama 3.1 family"),
);
registry.add(
AliasEntry::new("mistral", "hf://mistralai/Mistral-7B-Instruct-v0.3")
.with_default_quant("Q4_K_M")
.with_variant("7b", "hf://mistralai/Mistral-7B-Instruct-v0.3")
.with_description("Mistral AI's 7B model"),
);
registry.add(
AliasEntry::new("mixtral", "hf://mistralai/Mixtral-8x7B-Instruct-v0.1")
.with_default_quant("Q4_K_M")
.with_variant("8x7b", "hf://mistralai/Mixtral-8x7B-Instruct-v0.1")
.with_variant("8x22b", "hf://mistralai/Mixtral-8x22B-Instruct-v0.1")
.with_description("Mistral AI's Mixtral MoE"),
);
registry.add(
AliasEntry::new("phi3", "hf://microsoft/Phi-3-mini-4k-instruct")
.with_default_quant("Q4_K_M")
.with_variant("mini", "hf://microsoft/Phi-3-mini-4k-instruct")
.with_variant("small", "hf://microsoft/Phi-3-small-8k-instruct")
.with_variant("medium", "hf://microsoft/Phi-3-medium-4k-instruct")
.with_description("Microsoft's Phi-3 family"),
);
registry.add(
AliasEntry::new("gemma", "hf://google/gemma-7b-it")
.with_default_quant("Q4_K_M")
.with_variant("2b", "hf://google/gemma-2b-it")
.with_variant("7b", "hf://google/gemma-7b-it")
.with_description("Google's Gemma family"),
);
registry.add(
AliasEntry::new("gemma2", "hf://google/gemma-2-9b-it")
.with_default_quant("Q4_K_M")
.with_variant("2b", "hf://google/gemma-2-2b-it")
.with_variant("9b", "hf://google/gemma-2-9b-it")
.with_variant("27b", "hf://google/gemma-2-27b-it")
.with_description("Google's Gemma 2 family"),
);
registry.add(
AliasEntry::new("qwen2", "hf://Qwen/Qwen2-7B-Instruct")
.with_default_quant("Q4_K_M")
.with_variant("0.5b", "hf://Qwen/Qwen2-0.5B-Instruct")
.with_variant("1.5b", "hf://Qwen/Qwen2-1.5B-Instruct")
.with_variant("7b", "hf://Qwen/Qwen2-7B-Instruct")
.with_variant("72b", "hf://Qwen/Qwen2-72B-Instruct")
.with_description("Alibaba's Qwen2 family"),
);
registry.add(
AliasEntry::new("codellama", "hf://codellama/CodeLlama-7b-Instruct-hf")
.with_default_quant("Q4_K_M")
.with_variant("7b", "hf://codellama/CodeLlama-7b-Instruct-hf")
.with_variant("13b", "hf://codellama/CodeLlama-13b-Instruct-hf")
.with_variant("34b", "hf://codellama/CodeLlama-34b-Instruct-hf")
.with_description("Meta's CodeLlama"),
);
registry.add(
AliasEntry::new("deepseek-coder", "hf://deepseek-ai/deepseek-coder-6.7b-instruct")
.with_default_quant("Q4_K_M")
.with_variant("1.3b", "hf://deepseek-ai/deepseek-coder-1.3b-instruct")
.with_variant("6.7b", "hf://deepseek-ai/deepseek-coder-6.7b-instruct")
.with_variant("33b", "hf://deepseek-ai/deepseek-coder-33b-instruct")
.with_description("DeepSeek AI Coder"),
);
registry.add(
AliasEntry::new("starcoder2", "hf://bigcode/starcoder2-7b")
.with_default_quant("Q4_K_M")
.with_variant("3b", "hf://bigcode/starcoder2-3b")
.with_variant("7b", "hf://bigcode/starcoder2-7b")
.with_variant("15b", "hf://bigcode/starcoder2-15b")
.with_description("BigCode's StarCoder 2"),
);
registry.add(
AliasEntry::new("nomic-embed", "hf://nomic-ai/nomic-embed-text-v1.5")
.with_description("Nomic AI embedding model"),
);
registry.add(
AliasEntry::new("bge", "hf://BAAI/bge-large-en-v1.5")
.with_variant("small", "hf://BAAI/bge-small-en-v1.5")
.with_variant("base", "hf://BAAI/bge-base-en-v1.5")
.with_variant("large", "hf://BAAI/bge-large-en-v1.5")
.with_description("BGE embedding models"),
);
registry
}
pub fn add(&mut self, entry: AliasEntry) {
self.aliases.insert(entry.alias.clone(), entry);
}
#[must_use]
pub fn get(&self, alias: &str) -> Option<&AliasEntry> {
self.aliases.get(alias)
}
#[must_use]
pub fn contains(&self, alias: &str) -> bool {
self.aliases.contains_key(alias)
}
#[must_use]
pub fn list(&self) -> Vec<&AliasEntry> {
let mut entries: Vec<_> = self.aliases.values().collect();
entries.sort_by(|a, b| a.alias.cmp(&b.alias));
entries
}
#[must_use]
pub fn resolve(&self, reference: &str) -> ResolvedAlias {
let parsed = ParsedRef::parse(reference);
if let Some(entry) = self.aliases.get(&parsed.name) {
let target = entry.resolve_variant(parsed.variant.as_deref());
let quant = parsed.quantization.or_else(|| entry.default_quant.clone());
ResolvedAlias { uri: target.to_string(), quantization: quant, is_alias: true }
} else {
let uri = if parsed.name.contains("://") {
parsed.name.clone()
} else if parsed.name.contains('/') {
format!("hf://{}", parsed.name)
} else {
format!("pacha://{}", parsed.name)
};
ResolvedAlias { uri, quantization: parsed.quantization, is_alias: false }
}
}
#[must_use]
pub fn len(&self) -> usize {
self.aliases.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.aliases.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedAlias {
pub uri: String,
pub quantization: Option<String>,
pub is_alias: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alias_entry_new() {
let entry = AliasEntry::new("llama3", "hf://meta-llama/Llama-3-8B");
assert_eq!(entry.alias, "llama3");
assert_eq!(entry.target, "hf://meta-llama/Llama-3-8B");
}
#[test]
fn test_alias_entry_builder() {
let entry = AliasEntry::new("llama3", "hf://meta-llama/Llama-3-8B")
.with_default_quant("Q4_K_M")
.with_variant("70b", "hf://meta-llama/Llama-3-70B")
.with_description("Llama 3 family");
assert_eq!(entry.default_quant, Some("Q4_K_M".to_string()));
assert!(entry.variants.contains_key("70b"));
assert!(entry.description.is_some());
}
#[test]
fn test_alias_entry_resolve_variant() {
let entry = AliasEntry::new("llama3", "hf://default")
.with_variant("8b", "hf://8b-model")
.with_variant("70b", "hf://70b-model");
assert_eq!(entry.resolve_variant(None), "hf://default");
assert_eq!(entry.resolve_variant(Some("8b")), "hf://8b-model");
assert_eq!(entry.resolve_variant(Some("70b")), "hf://70b-model");
assert_eq!(entry.resolve_variant(Some("unknown")), "hf://default");
}
#[test]
fn test_parsed_ref_name_only() {
let parsed = ParsedRef::parse("llama3");
assert_eq!(parsed.name, "llama3");
assert!(parsed.variant.is_none());
assert!(parsed.quantization.is_none());
}
#[test]
fn test_parsed_ref_with_variant() {
let parsed = ParsedRef::parse("llama3:8b");
assert_eq!(parsed.name, "llama3");
assert_eq!(parsed.variant, Some("8b".to_string()));
assert!(parsed.quantization.is_none());
}
#[test]
fn test_parsed_ref_with_variant_and_quant() {
let parsed = ParsedRef::parse("llama3:8b-q4");
assert_eq!(parsed.name, "llama3");
assert_eq!(parsed.variant, Some("8b".to_string()));
assert_eq!(parsed.quantization, Some("q4".to_string()));
}
#[test]
fn test_parsed_ref_quant_only() {
let parsed = ParsedRef::parse("llama3:q4");
assert_eq!(parsed.name, "llama3");
assert!(parsed.variant.is_none());
assert_eq!(parsed.quantization, Some("q4".to_string()));
}
#[test]
fn test_parsed_ref_uppercase_quant() {
let parsed = ParsedRef::parse("llama3:8b-Q4_K_M");
assert_eq!(parsed.name, "llama3");
assert_eq!(parsed.variant, Some("8b".to_string()));
assert_eq!(parsed.quantization, Some("Q4_K_M".to_string()));
}
#[test]
fn test_parsed_ref_to_string() {
let parsed = ParsedRef::parse("llama3:8b-q4");
assert_eq!(parsed.to_string_repr(), "llama3:8b-q4");
let parsed = ParsedRef::parse("llama3:q4");
assert_eq!(parsed.to_string_repr(), "llama3:q4");
let parsed = ParsedRef::parse("llama3");
assert_eq!(parsed.to_string_repr(), "llama3");
}
#[test]
fn test_is_quant_tag() {
assert!(is_quant_tag("q4"));
assert!(is_quant_tag("Q4_K_M"));
assert!(is_quant_tag("q8"));
assert!(is_quant_tag("f16"));
assert!(is_quant_tag("fp16"));
assert!(is_quant_tag("bf16"));
assert!(is_quant_tag("iq4"));
assert!(!is_quant_tag("8b"));
assert!(!is_quant_tag("70b"));
assert!(!is_quant_tag("instruct"));
}
#[test]
fn test_alias_registry_new() {
let registry = AliasRegistry::new();
assert!(registry.is_empty());
assert_eq!(registry.len(), 0);
}
#[test]
fn test_alias_registry_with_defaults() {
let registry = AliasRegistry::with_defaults();
assert!(!registry.is_empty());
assert!(registry.contains("llama3"));
assert!(registry.contains("mistral"));
assert!(registry.contains("phi3"));
}
#[test]
fn test_alias_registry_add() {
let mut registry = AliasRegistry::new();
registry.add(AliasEntry::new("test", "hf://test/model"));
assert!(registry.contains("test"));
assert_eq!(registry.len(), 1);
}
#[test]
fn test_alias_registry_get() {
let registry = AliasRegistry::with_defaults();
let entry = registry.get("llama3");
assert!(entry.is_some());
assert!(entry.unwrap().target.contains("meta-llama"));
}
#[test]
fn test_alias_registry_list() {
let mut registry = AliasRegistry::new();
registry.add(AliasEntry::new("zzz", "hf://zzz"));
registry.add(AliasEntry::new("aaa", "hf://aaa"));
let list = registry.list();
assert_eq!(list.len(), 2);
assert_eq!(list[0].alias, "aaa"); assert_eq!(list[1].alias, "zzz");
}
#[test]
fn test_alias_registry_resolve_known() {
let registry = AliasRegistry::with_defaults();
let resolved = registry.resolve("llama3");
assert!(resolved.is_alias);
assert!(resolved.uri.contains("meta-llama"));
}
#[test]
fn test_alias_registry_resolve_with_variant() {
let registry = AliasRegistry::with_defaults();
let resolved = registry.resolve("llama3:70b");
assert!(resolved.is_alias);
assert!(resolved.uri.contains("70B"));
}
#[test]
fn test_alias_registry_resolve_with_quant() {
let registry = AliasRegistry::with_defaults();
let resolved = registry.resolve("llama3:8b-q8");
assert!(resolved.is_alias);
assert_eq!(resolved.quantization, Some("q8".to_string()));
}
#[test]
fn test_alias_registry_resolve_default_quant() {
let registry = AliasRegistry::with_defaults();
let resolved = registry.resolve("llama3");
assert!(resolved.is_alias);
assert_eq!(resolved.quantization, Some("Q4_K_M".to_string()));
}
#[test]
fn test_alias_registry_resolve_unknown() {
let registry = AliasRegistry::with_defaults();
let resolved = registry.resolve("unknown-model");
assert!(!resolved.is_alias);
assert_eq!(resolved.uri, "pacha://unknown-model");
}
#[test]
fn test_alias_registry_resolve_huggingface_style() {
let registry = AliasRegistry::with_defaults();
let resolved = registry.resolve("Qwen/Qwen2.5-Coder-0.5B-Instruct");
assert!(!resolved.is_alias);
assert_eq!(resolved.uri, "hf://Qwen/Qwen2.5-Coder-0.5B-Instruct");
let resolved = registry.resolve("TheBloke/Llama-2-7B-GGUF");
assert!(!resolved.is_alias);
assert_eq!(resolved.uri, "hf://TheBloke/Llama-2-7B-GGUF");
}
#[test]
fn test_alias_registry_resolve_full_uri() {
let registry = AliasRegistry::with_defaults();
let resolved = registry.resolve("hf://some/model");
assert!(!resolved.is_alias);
assert_eq!(resolved.uri, "hf://some/model");
}
#[test]
fn test_alias_entry_serialization() {
let entry = AliasEntry::new("llama3", "hf://test")
.with_default_quant("Q4_K_M")
.with_variant("70b", "hf://test-70b");
let json = serde_json::to_string(&entry).unwrap();
assert!(json.contains("llama3"));
assert!(json.contains("Q4_K_M"));
let parsed: AliasEntry = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.alias, "llama3");
assert_eq!(parsed.default_quant, Some("Q4_K_M".to_string()));
}
#[test]
fn test_parsed_ref_complex_name() {
let parsed = ParsedRef::parse("deepseek-coder:6.7b-q4");
assert_eq!(parsed.name, "deepseek-coder");
assert_eq!(parsed.variant, Some("6.7b".to_string()));
assert_eq!(parsed.quantization, Some("q4".to_string()));
}
#[test]
fn test_parsed_ref_numbers_in_name() {
let parsed = ParsedRef::parse("llama3.1:8b");
assert_eq!(parsed.name, "llama3.1");
assert_eq!(parsed.variant, Some("8b".to_string()));
}
#[test]
fn test_resolve_quant_override() {
let registry = AliasRegistry::with_defaults();
let resolved = registry.resolve("llama3:q8");
assert_eq!(resolved.quantization, Some("q8".to_string()));
}
#[test]
fn test_gemma_variants() {
let registry = AliasRegistry::with_defaults();
let resolved_2b = registry.resolve("gemma:2b");
assert!(resolved_2b.uri.contains("2b"));
let resolved_7b = registry.resolve("gemma:7b");
assert!(resolved_7b.uri.contains("7b"));
}
#[test]
fn test_embedding_models() {
let registry = AliasRegistry::with_defaults();
assert!(registry.contains("nomic-embed"));
assert!(registry.contains("bge"));
let resolved = registry.resolve("bge:large");
assert!(resolved.uri.contains("large"));
}
}