models.go

v0.7.0
Doc Versions Source
1
package mistral
2
3
import (
4
	"encoding/json"
5
	"fmt"
6
	"net/http"
7
	"sort"
8
	"strings"
9
)
10
11
const modelsURL = "https://api.mistral.ai/v1/models"
12
13
type Model struct {
14
	ID string `json:"id"`
15
}
16
17
func (m Model) DisplayName() string {
18
	return m.ID
19
}
20
21
// family extracts the model family from an ID like "mistral-large-latest" -> "Mistral Large".
22
func (m Model) family() string {
23
	id := m.ID
24
25
	// Strip version suffixes like "-latest", "-2501", "-2412".
26
	id = strings.TrimSuffix(id, "-latest")
27
	for _, suffix := range []string{"-2501", "-2502", "-2503", "-2504", "-2505", "-2506", "-2507", "-2508", "-2509", "-2510", "-2511", "-2512",
28
		"-2401", "-2402", "-2403", "-2404", "-2405", "-2406", "-2407", "-2408", "-2409", "-2410", "-2411", "-2412"} {
29
		id = strings.TrimSuffix(id, suffix)
30
	}
31
32
	// Capitalize words for display.
33
	parts := strings.Split(id, "-")
34
	for i, p := range parts {
35
		if len(p) > 0 {
36
			parts[i] = strings.ToUpper(p[:1]) + p[1:]
37
		}
38
	}
39
	return strings.Join(parts, " ")
40
}
41
42
func ListModels(apiKey string) ([]Model, error) {
43
	req, err := http.NewRequest("GET", modelsURL, nil)
44
	if err != nil {
45
		return nil, err
46
	}
47
	if apiKey != "" {
48
		req.Header.Set("Authorization", "Bearer "+apiKey)
49
	}
50
	req.Header.Set("Accept", "application/json")
51
52
	resp, err := http.DefaultClient.Do(req)
53
	if err != nil {
54
		return nil, fmt.Errorf("fetching models: %w", err)
55
	}
56
	defer resp.Body.Close()
57
58
	if resp.StatusCode != http.StatusOK {
59
		return nil, fmt.Errorf("models endpoint returned %s", resp.Status)
60
	}
61
62
	var result struct {
63
		Data []Model `json:"data"`
64
	}
65
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
66
		return nil, fmt.Errorf("parsing models: %w", err)
67
	}
68
69
	// Filter out non-API models (e.g. "mistral-vibe-cli", embedding, moderation models).
70
	var filtered []Model
71
	for _, m := range result.Data {
72
		if isUsableModel(m.ID) {
73
			filtered = append(filtered, m)
74
		}
75
	}
76
77
	return filtered, nil
78
}
79
80
// isUsableModel returns true for chat/completion models usable as LLM backends.
81
func isUsableModel(id string) bool {
82
	for _, prefix := range []string{
83
		"mistral-vibe",  // Mistral Vibe CLI internal models
84
		"mistral-embed", // embedding models
85
		"mistral-moder", // moderation models
86
		"mistral-ocr",   // OCR models
87
		"voxtral",       // audio transcription models
88
		"ministral",     // small edge/on-device models
89
		"magistral",     // reasoning models (not suitable for agentic coding)
90
	} {
91
		if strings.HasPrefix(id, prefix) {
92
			return false
93
		}
94
	}
95
	return true
96
}
97
98
func CategorizeModels(models []Model) ([]string, map[string][]Model) {
99
	grouped := make(map[string][]Model)
100
	for _, m := range models {
101
		fam := m.family()
102
		grouped[fam] = append(grouped[fam], m)
103
	}
104
105
	// Sort models within each family.
106
	for fam := range grouped {
107
		sort.Slice(grouped[fam], func(i, j int) bool {
108
			return grouped[fam][i].ID < grouped[fam][j].ID
109
		})
110
	}
111
112
	// Collect and sort family names.
113
	var families []string
114
	for fam := range grouped {
115
		families = append(families, fam)
116
	}
117
	sort.Strings(families)
118
119
	return families, grouped
120
}
121
122
func AllModelIDs(models []Model) map[string]bool {
123
	ids := make(map[string]bool, len(models))
124
	for _, m := range models {
125
		ids[m.ID] = true
126
	}
127
	return ids
128
}
129

Source Files