File size: 4,176 Bytes
dcedb25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
// Who MADE a model, inferred from its id — as opposed to who is serving it
// (the platform). A Llama on Groq and the same Llama on Cloudflare are both
// Meta's; the Playground shows the maker's mark under a reply, and the host in
// the tooltip. Pure so it can be tested without React.

export type ModelMakerId =
  | 'google' | 'meta' | 'qwen' | 'deepseek' | 'mistral' | 'anthropic' | 'openai'
  | 'nvidia' | 'moonshot' | 'zhipu' | 'minimax' | 'baidu' | 'perplexity' | 'cohere'
  | 'microsoft' | 'xai' | 'tencent' | 'ibm' | 'ai21' | 'thinkingmachines'

export interface ModelMaker {
  id: ModelMakerId
  name: string
}

const MAKERS: Record<ModelMakerId, string> = {
  google: 'Google', meta: 'Meta', qwen: 'Qwen', deepseek: 'DeepSeek', mistral: 'Mistral AI',
  anthropic: 'Anthropic', openai: 'OpenAI', nvidia: 'NVIDIA', moonshot: 'Moonshot AI', zhipu: 'Zhipu AI',
  minimax: 'MiniMax', baidu: 'Baidu', perplexity: 'Perplexity', cohere: 'Cohere', microsoft: 'Microsoft',
  xai: 'xAI', tencent: 'Tencent', ibm: 'IBM', ai21: 'AI21 Labs', thinkingmachines: 'Thinking Machines',
}

// Ordered: the first family marker found in the id wins. Markers are matched on
// word boundaries in a lowercased id with the vendor prefix ("meta-llama/",
// "google/") stripped, so "gpt-oss" lands on OpenAI and "o3" is not found
// inside another word.
const FAMILIES: Array<[RegExp, ModelMakerId]> = [
  // Fine-tunes and distillations name their base too ("deepseek-r1-distill-
  // llama", "llama-3.1-nemotron"), so the derived brand is listed before the
  // base families it is built on.
  [/\bnemotron\b/, 'nvidia'],
  [/\bdeepseek\b/, 'deepseek'],
  [/\b(qwen|qwq|qvq)\b/, 'qwen'],
  [/\b(mistral|mixtral|codestral|magistral|devstral|ministral|pixtral|mathstral|voxtral)\b/, 'mistral'],
  [/\b(gemini|gemma|palm|bison|learnlm)\b/, 'google'],
  [/\b(llama|codellama)\b/, 'meta'],
  [/\bclaude\b/, 'anthropic'],
  // Matched after the letter/digit split below, so "o3-mini" reads "o 3 mini".
  [/\b(gpt|chatgpt|davinci|whisper|dall e|dalle)\b|\bo [134]\b/, 'openai'],
  [/\b(kimi|moonshot)\b/, 'moonshot'],
  [/\b(glm|chatglm|cogview|cogvideo)\b/, 'zhipu'],
  [/\b(minimax|abab|hailuo)\b/, 'minimax'],
  [/\bernie\b/, 'baidu'],
  [/\bsonar\b/, 'perplexity'],
  [/\b(command|aya|c4ai)\b/, 'cohere'],
  [/\b(phi|wizardlm|orca)\b/, 'microsoft'],
  [/\bgrok\b/, 'xai'],
  [/\b(hunyuan|hy)\b/, 'tencent'],
  [/\bgranite\b/, 'ibm'],
  [/\bjamba\b/, 'ai21'],
  [/\binkling\b/, 'thinkingmachines'],
]

// The org segment of a namespaced id ("meta-llama/…", "Qwen/…") is a strong
// signal on its own, and settles ids whose family word is unusual.
const ORGS: Array<[RegExp, ModelMakerId]> = [
  [/^(google|gemini)$/, 'google'],
  [/^(meta|meta-llama|facebook)$/, 'meta'],
  [/^qwen$/, 'qwen'],
  [/^deepseek(-ai)?$/, 'deepseek'],
  [/^mistralai$/, 'mistral'],
  [/^anthropic$/, 'anthropic'],
  [/^openai$/, 'openai'],
  [/^nvidia$/, 'nvidia'],
  [/^moonshotai$/, 'moonshot'],
  [/^(zai-org|thudm|zhipuai|zhipu)$/, 'zhipu'],
  [/^minimaxai$/, 'minimax'],
  [/^baidu$/, 'baidu'],
  [/^perplexity(-ai)?$/, 'perplexity'],
  [/^cohereforai$/, 'cohere'],
  [/^microsoft$/, 'microsoft'],
  [/^(x-ai|xai)$/, 'xai'],
  [/^tencent$/, 'tencent'],
  [/^ibm(-granite)?$/, 'ibm'],
  [/^ai21(labs)?$/, 'ai21'],
  [/^thinkingmachines$/, 'thinkingmachines'],
]

export function modelMakerOf(modelId: string | null | undefined): ModelMaker | null {
  if (!modelId) return null
  const lower = modelId.trim().toLowerCase()
  // "org/name[:tag]" — try the org first, then the name; a bare id is its own name.
  const slash = lower.indexOf('/')
  const org = slash > 0 ? lower.slice(0, slash) : ''
  const name = (slash > 0 ? lower.slice(slash + 1) : lower).replace(/:.*$/, '')
  // Word boundaries need separators between letters and digits too
  // ("llama3", "gemma2", "qwen3", "o3mini", "gpt4o").
  const words = name.replace(/([a-z])(\d)/g, '$1 $2').replace(/(\d)([a-z])/g, '$1 $2').replace(/[-_./]/g, ' ')
  for (const [re, id] of FAMILIES) if (re.test(words)) return { id, name: MAKERS[id] }
  if (org) for (const [re, id] of ORGS) if (re.test(org)) return { id, name: MAKERS[id] }
  return null
}