Spaces:
Runtime error
Runtime error
| package hf | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "net/http" | |
| "regexp" | |
| "strconv" | |
| "strings" | |
| "time" | |
| ) | |
| // Client queries the Hugging Face Hub for model metadata. | |
| type Client struct { | |
| BaseURL string | |
| Token string | |
| HTTP *http.Client | |
| } | |
| // NewClient builds a Client. token may be empty for public models. | |
| func NewClient(token string) *Client { | |
| return &Client{ | |
| BaseURL: "https://huggingface.co", | |
| Token: token, | |
| HTTP: &http.Client{Timeout: 30 * time.Second}, | |
| } | |
| } | |
| // hubModelInfo mirrors the relevant fields of the HF /api/models response. | |
| type hubModelInfo struct { | |
| ID string `json:"id"` | |
| ModelID string `json:"modelId"` | |
| PipelineTag string `json:"pipeline_tag"` | |
| LibraryName string `json:"library_name"` | |
| Tags []string `json:"tags"` | |
| Gated any `json:"gated"` | |
| Private bool `json:"private"` | |
| CardData struct { | |
| License any `json:"license"` | |
| BaseModel any `json:"base_model"` | |
| } `json:"cardData"` | |
| Config struct { | |
| ModelType string `json:"model_type"` | |
| } `json:"config"` | |
| Siblings []struct { | |
| Rfilename string `json:"rfilename"` | |
| } `json:"siblings"` | |
| SafeTensors struct { | |
| Total int64 `json:"total"` | |
| } `json:"safetensors"` | |
| } | |
| // Inspect resolves a model reference into Metadata. It contacts the Hugging | |
| // Face Hub API; a clear error is returned when the network is unavailable or | |
| // the model is gated/private without a token. | |
| func (c *Client) Inspect(ctx context.Context, ref Ref) (*Metadata, error) { | |
| u := fmt.Sprintf("%s/api/models/%s/revision/%s", c.BaseURL, ref.RepoID(), ref.Revision) | |
| req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) | |
| if err != nil { | |
| return nil, fmt.Errorf("build hf request: %w", err) | |
| } | |
| applyAuth(req, c.Token) | |
| resp, err := c.HTTP.Do(req) | |
| if err != nil { | |
| return nil, fmt.Errorf("hugging face request failed (no network or DNS?): %w", err) | |
| } | |
| defer func() { _ = resp.Body.Close() }() | |
| switch resp.StatusCode { | |
| case http.StatusOK: | |
| // fall through | |
| case http.StatusUnauthorized, http.StatusForbidden: | |
| return nil, fmt.Errorf("model %q is gated or private; set HF_TOKEN with access (status %d)", ref.RepoID(), resp.StatusCode) | |
| case http.StatusNotFound: | |
| return nil, fmt.Errorf("model %q not found at revision %q", ref.RepoID(), ref.Revision) | |
| default: | |
| return nil, fmt.Errorf("hugging face returned status %d for %q", resp.StatusCode, ref.RepoID()) | |
| } | |
| var info hubModelInfo | |
| if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { | |
| return nil, fmt.Errorf("decode hf response: %w", err) | |
| } | |
| meta := buildMetadata(ref, &info) | |
| return meta, nil | |
| } | |
| func buildMetadata(ref Ref, info *hubModelInfo) *Metadata { | |
| m := &Metadata{ | |
| Model: "hf:" + ref.RepoID(), | |
| Source: "huggingface", | |
| Namespace: ref.Namespace, | |
| Name: ref.Name, | |
| Revision: ref.Revision, | |
| PipelineTag: info.PipelineTag, | |
| LibraryName: info.LibraryName, | |
| ModelType: info.Config.ModelType, | |
| Tags: info.Tags, | |
| Private: info.Private, | |
| } | |
| m.Gated = gatedTrue(info.Gated) | |
| m.License = licenseFrom(info) | |
| // Parameter estimate: prefer safetensors total, else infer from the name. | |
| if info.SafeTensors.Total > 0 { | |
| m.EstimatedParameters = info.SafeTensors.Total | |
| } else { | |
| m.EstimatedParameters = EstimateParamsFromName(ref.Name) | |
| } | |
| m.RecommendedRuntime, m.RequiresGPU = recommendRuntime(m) | |
| return m | |
| } | |
| func gatedTrue(v any) bool { | |
| switch t := v.(type) { | |
| case bool: | |
| return t | |
| case string: | |
| return t != "" && t != "false" | |
| default: | |
| return false | |
| } | |
| } | |
| func licenseFrom(info *hubModelInfo) string { | |
| switch l := info.CardData.License.(type) { | |
| case string: | |
| return l | |
| case []any: | |
| if len(l) > 0 { | |
| if s, ok := l[0].(string); ok { | |
| return s | |
| } | |
| } | |
| } | |
| for _, t := range info.Tags { | |
| if strings.HasPrefix(t, "license:") { | |
| return strings.TrimPrefix(t, "license:") | |
| } | |
| } | |
| return "" | |
| } | |
| var paramSizeRe = regexp.MustCompile(`(?i)(\d+(?:\.\d+)?)\s*([bm])\b`) | |
| // EstimateParamsFromName infers a parameter count from a model name such as | |
| // "Qwen2.5-7B-Instruct" (7B) or "phi-3-mini-128k" (no match -> 0). | |
| func EstimateParamsFromName(name string) int64 { | |
| matches := paramSizeRe.FindAllStringSubmatch(name, -1) | |
| var best float64 | |
| var unit string | |
| for _, mm := range matches { | |
| // Skip context-window-like tokens (e.g. 128k handled by 'k', not matched here). | |
| val, err := strconv.ParseFloat(mm[1], 64) | |
| if err != nil { | |
| continue | |
| } | |
| if val > best { | |
| best = val | |
| unit = strings.ToLower(mm[2]) | |
| } | |
| } | |
| switch unit { | |
| case "b": | |
| return int64(best * 1e9) | |
| case "m": | |
| return int64(best * 1e6) | |
| } | |
| return 0 | |
| } | |
| // recommendRuntime picks a serving runtime and GPU requirement based on the | |
| // metadata. The heuristic is intentionally simple for the MVP. | |
| func recommendRuntime(m *Metadata) (string, bool) { | |
| gguf := false | |
| for _, t := range m.Tags { | |
| if strings.Contains(strings.ToLower(t), "gguf") { | |
| gguf = true | |
| } | |
| } | |
| if gguf { | |
| return "ollama", false | |
| } | |
| if m.EstimatedParameters >= 7_000_000_000 { | |
| return "vllm", true | |
| } | |
| if m.EstimatedParameters > 0 && m.EstimatedParameters < 3_000_000_000 { | |
| return "ollama", false | |
| } | |
| return "vllm", m.EstimatedParameters >= 7_000_000_000 | |
| } | |