Spaces:
Runtime error
Runtime error
Deploy hidden gems MCP server
Browse files- Dockerfile +3 -23
- README.md +80 -28
- hidden-gems.md +24 -44
- hidden_gems_tool.py +119 -176
Dockerfile
CHANGED
|
@@ -1,38 +1,18 @@
|
|
| 1 |
FROM python:3.13-slim
|
| 2 |
|
| 3 |
-
# Install system dependencies required by fast-agent and HF Spaces
|
| 4 |
RUN apt-get update && \
|
| 5 |
-
apt-get install -y \
|
| 6 |
-
|
| 7 |
-
git git-lfs \
|
| 8 |
-
wget curl procps \
|
| 9 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
|
| 11 |
-
# Install uv for fast, reliable package management
|
| 12 |
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
| 13 |
|
| 14 |
-
# Set working directory
|
| 15 |
WORKDIR /app
|
| 16 |
-
|
| 17 |
-
# Install fast-agent-mcp from PyPI
|
| 18 |
RUN uv pip install --system --no-cache fast-agent-mcp
|
| 19 |
|
| 20 |
-
# Copy all files from the Space repository to /app
|
| 21 |
COPY --link ./ /app
|
| 22 |
-
|
| 23 |
-
# Ensure /app is owned by uid 1000 (required for HF Spaces)
|
| 24 |
RUN chown -R 1000:1000 /app
|
| 25 |
-
|
| 26 |
-
# Switch to non-root user
|
| 27 |
USER 1000
|
| 28 |
|
| 29 |
-
# Expose port 7860 (HF Spaces default)
|
| 30 |
EXPOSE 7860
|
| 31 |
|
| 32 |
-
|
| 33 |
-
CMD ["fast-agent", "serve", \
|
| 34 |
-
"--card", "hidden-gems.md", \
|
| 35 |
-
"--transport", "http", \
|
| 36 |
-
"--instance-scope", "request", \
|
| 37 |
-
"--host", "0.0.0.0", \
|
| 38 |
-
"--port", "7860"]
|
|
|
|
| 1 |
FROM python:3.13-slim
|
| 2 |
|
|
|
|
| 3 |
RUN apt-get update && \
|
| 4 |
+
apt-get install -y bash git git-lfs wget curl procps && \
|
| 5 |
+
rm -rf /var/lib/apt/lists/*
|
|
|
|
|
|
|
|
|
|
| 6 |
|
|
|
|
| 7 |
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
| 8 |
|
|
|
|
| 9 |
WORKDIR /app
|
|
|
|
|
|
|
| 10 |
RUN uv pip install --system --no-cache fast-agent-mcp
|
| 11 |
|
|
|
|
| 12 |
COPY --link ./ /app
|
|
|
|
|
|
|
| 13 |
RUN chown -R 1000:1000 /app
|
|
|
|
|
|
|
| 14 |
USER 1000
|
| 15 |
|
|
|
|
| 16 |
EXPOSE 7860
|
| 17 |
|
| 18 |
+
CMD ["fast-agent", "serve", "--card", "hidden-gems.md", "--transport", "http", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,43 +1,95 @@
|
|
| 1 |
---
|
| 2 |
-
title: Hidden Gems
|
| 3 |
-
emoji: 💎
|
| 4 |
-
colorFrom: purple
|
| 5 |
-
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
-
license: mit
|
| 9 |
-
short_description: Discover undervalued HF models with high engagement ratios
|
| 10 |
---
|
| 11 |
|
| 12 |
-
# Hidden Gems Finder
|
| 13 |
|
| 14 |
-
An MCP
|
| 15 |
|
| 16 |
-
## What
|
| 17 |
|
| 18 |
-
The Hidden
|
| 19 |
-
- The community engagement (likes) is high relative to downloads
|
| 20 |
-
- Downloads are in the "sweet spot" (not too obscure, not too popular)
|
| 21 |
-
- Model quality signals suggest undervalued potential
|
| 22 |
|
| 23 |
-
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
- Find hidden gems across all model types
|
| 27 |
-
- Search for gems in specific categories (text-generation, image-to-text, etc.)
|
| 28 |
-
- Get detailed model information with gem scores
|
| 29 |
-
- Export results as JSON
|
| 30 |
|
| 31 |
-
##
|
| 32 |
|
| 33 |
-
|
| 34 |
-
1. **Ratio** = likes per 1000 downloads (raw quality signal)
|
| 35 |
-
2. **Age balance** - excludes very new (<14 days) and very old (>2 years) models
|
| 36 |
-
3. **Download sweet spot** - boosts models with 100-10k downloads
|
| 37 |
-
4. **Minimum thresholds** - filters out low-engagement models
|
| 38 |
|
| 39 |
-
|
| 40 |
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Hidden Gems - Undervalued HF Models
|
|
|
|
|
|
|
|
|
|
| 3 |
sdk: docker
|
| 4 |
app_port: 7860
|
|
|
|
|
|
|
| 5 |
---
|
| 6 |
|
| 7 |
+
# 🔍 Hidden Gems Finder
|
| 8 |
|
| 9 |
+
An MCP Server that discovers undervalued Hugging Face models with high likes-to-downloads ratios — quality models that haven't gone viral yet!
|
| 10 |
|
| 11 |
+
## What are Hidden Gems?
|
| 12 |
|
| 13 |
+
The **Hidden Gem Score** = Likes / Downloads
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
+
- **High ratio** = Loved by the community, but not widely adopted (undervalued!)
|
| 16 |
+
- **Low ratio** = Popular but mainstream (already discovered)
|
| 17 |
|
| 18 |
+
This helps you find quality models before they blow up.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
## MCP Tools
|
| 21 |
|
| 22 |
+
### `find_hidden_gems`
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
Search for undervalued models with various filters:
|
| 25 |
|
| 26 |
+
**Parameters:**
|
| 27 |
+
- `limit` (int): Models to fetch from API (default: 100)
|
| 28 |
+
- `min_downloads` (int): Filter out brand-new models (default: 100)
|
| 29 |
+
- `top` (int): Number of results to return (default: 20)
|
| 30 |
+
- `pipeline_tag` (str, optional): Filter by type like "text-generation", "image-to-text"
|
| 31 |
+
- `sort_by` (str): Sort by "ratio" (default), "likes", "downloads", or "trending"
|
| 32 |
|
| 33 |
+
**Example:** Find text generation gems
|
| 34 |
+
```json
|
| 35 |
+
{
|
| 36 |
+
"limit": 200,
|
| 37 |
+
"min_downloads": 100,
|
| 38 |
+
"top": 10,
|
| 39 |
+
"pipeline_tag": "text-generation",
|
| 40 |
+
"sort_by": "ratio"
|
| 41 |
+
}
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
### `get_model_details`
|
| 45 |
+
|
| 46 |
+
Get detailed information about a specific model:
|
| 47 |
+
|
| 48 |
+
**Parameters:**
|
| 49 |
+
- `model_id` (str): The model ID (e.g., "microsoft/DialoGPT-medium")
|
| 50 |
+
|
| 51 |
+
**Example:**
|
| 52 |
+
```json
|
| 53 |
+
{"model_id": "openbmb/MiniCPM-SALA"}
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## Usage Examples
|
| 57 |
+
|
| 58 |
+
### Find Top 20 Hidden Gems
|
| 59 |
+
```
|
| 60 |
+
find_hidden_gems()
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Find Text Generation Gems
|
| 64 |
+
```
|
| 65 |
+
find_hidden_gems(pipeline_tag="text-generation", top=15)
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
### Deep Search
|
| 69 |
+
```
|
| 70 |
+
find_hidden_gems(limit=500, min_downloads=500, top=50, sort_by="likes")
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### Check Specific Model
|
| 74 |
+
```
|
| 75 |
+
get_model_details("xai-org/grok-1")
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
## Connecting to Claude Desktop
|
| 79 |
+
|
| 80 |
+
Add to your `claude_desktop_config.json`:
|
| 81 |
+
|
| 82 |
+
```json
|
| 83 |
+
{
|
| 84 |
+
"mcpServers": {
|
| 85 |
+
"hidden-gems": {
|
| 86 |
+
"command": "npx",
|
| 87 |
+
"args": ["-y", "mcp-remote", "https://evalstate-hidden-gems.hf.space/mcp"]
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
## Environment Variables
|
| 94 |
+
|
| 95 |
+
- `HF_TOKEN`: Optional Hugging Face token for higher API rate limits
|
hidden-gems.md
CHANGED
|
@@ -1,65 +1,45 @@
|
|
| 1 |
---
|
| 2 |
type: agent
|
| 3 |
name: hidden-gems
|
| 4 |
-
default: true
|
| 5 |
-
description: |
|
| 6 |
-
Find undervalued Hugging Face models with high likes-to-downloads ratio.
|
| 7 |
-
Discover hidden gems - quality models that haven't blown up yet.
|
| 8 |
function_tools:
|
| 9 |
- hidden_gems_tool.py:find_hidden_gems
|
| 10 |
- hidden_gems_tool.py:get_model_details
|
| 11 |
-
|
| 12 |
-
|
| 13 |
---
|
| 14 |
|
| 15 |
# Hidden Gems Finder
|
| 16 |
|
| 17 |
-
You are a specialized
|
| 18 |
|
| 19 |
-
##
|
| 20 |
|
| 21 |
-
|
| 22 |
-
-
|
| 23 |
-
-
|
| 24 |
-
-
|
| 25 |
-
- **Community loves it** but it hasn't gone mainstream yet
|
| 26 |
|
| 27 |
-
|
| 28 |
|
| 29 |
-
|
| 30 |
-
2. **Filter by type** - Help users find gems in specific categories (text-generation, image-to-text, etc.)
|
| 31 |
-
3. **Get details** - Use `get_model_details` for deep dives on specific models
|
| 32 |
-
4. **Explain scores** - Interpret gem scores and ratios for users
|
| 33 |
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
|
| 37 |
-
- **Gem Score**: Adjusted ratio accounting for age, downloads, and timing
|
| 38 |
-
- **Score > 100**: Exceptional quality signal
|
| 39 |
-
- **Score 50-100**: Strong hidden gem
|
| 40 |
-
- **Score 20-50**: Worth investigating
|
| 41 |
-
- **Score < 20**: Decent but more mainstream
|
| 42 |
|
| 43 |
-
##
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
- `text-to-speech` - TTS models
|
| 51 |
-
- `automatic-speech-recognition` - ASR models
|
| 52 |
-
- `audio-to-audio` - Audio processing models
|
| 53 |
|
| 54 |
## Response Style
|
| 55 |
|
| 56 |
-
Be enthusiastic about discoveries
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
Score: {gem_score} | Ratio: {ratio} likes/1k downloads
|
| 61 |
-
Likes: {likes} | Downloads: {downloads}
|
| 62 |
-
Type: {pipeline_tag} | Age: {age_days} days
|
| 63 |
-
```
|
| 64 |
-
|
| 65 |
-
Help users understand WHY a model is a gem and what makes it special.
|
|
|
|
| 1 |
---
|
| 2 |
type: agent
|
| 3 |
name: hidden-gems
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
function_tools:
|
| 5 |
- hidden_gems_tool.py:find_hidden_gems
|
| 6 |
- hidden_gems_tool.py:get_model_details
|
| 7 |
+
default: true
|
| 8 |
+
description: Discover undervalued Hugging Face models with high likes-to-downloads ratios - hidden gems that haven't gone viral yet
|
| 9 |
---
|
| 10 |
|
| 11 |
# Hidden Gems Finder
|
| 12 |
|
| 13 |
+
You are a specialized tool for discovering undervalued Hugging Face models. Your purpose is to help users find "hidden gems" - high-quality models that have received significant community appreciation (likes) relative to their download numbers.
|
| 14 |
|
| 15 |
+
## Capabilities
|
| 16 |
|
| 17 |
+
1. **Search for hidden gems** across different categories:
|
| 18 |
+
- Filter by minimum downloads to avoid brand-new models
|
| 19 |
+
- Filter by pipeline tag (text-generation, image-to-image, etc.)
|
| 20 |
+
- Sort by ratio (default), likes, downloads, or trending score
|
|
|
|
| 21 |
|
| 22 |
+
2. **Get detailed information** about specific models
|
| 23 |
|
| 24 |
+
## The "Hidden Gem Score"
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
The key metric is **Likes / Downloads ratio**:
|
| 27 |
+
- High ratio = Model is loved by the community but not widely adopted yet
|
| 28 |
+
- Low ratio = Model is popular but mainstream
|
| 29 |
|
| 30 |
+
This helps identify quality models before they blow up!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
+
## Usage Guidelines
|
| 33 |
|
| 34 |
+
When users ask about hidden gems:
|
| 35 |
+
1. Ask if they have a specific category/pipeline in mind
|
| 36 |
+
2. Suggest appropriate filters based on their needs
|
| 37 |
+
3. Present results in a clear, ranked format
|
| 38 |
+
4. Highlight interesting findings with context
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
## Response Style
|
| 41 |
|
| 42 |
+
- Be enthusiastic about discoveries
|
| 43 |
+
- Explain why high-ratio models are valuable
|
| 44 |
+
- Suggest next steps (trying the model, checking the model card, etc.)
|
| 45 |
+
- Use data to back up recommendations
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hidden_gems_tool.py
CHANGED
|
@@ -1,205 +1,148 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Hidden Gems Finder Tool
|
| 3 |
|
| 4 |
-
Finds Hugging Face models with high likes-to-downloads ratio - undervalued
|
| 5 |
-
quality models that haven't blown up yet.
|
| 6 |
-
"""
|
| 7 |
-
|
| 8 |
-
import json
|
| 9 |
import os
|
| 10 |
-
import
|
| 11 |
-
from
|
| 12 |
-
from
|
| 13 |
-
|
| 14 |
-
HF_API_BASE = "https://huggingface.co/api"
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
def fetch_models(limit: int = 500, model_type: str | None = None) -> list[dict[str, Any]]:
|
| 18 |
-
"""Fetch models from Hugging Face API."""
|
| 19 |
-
url = f"{HF_API_BASE}/models?limit={limit}"
|
| 20 |
-
if model_type:
|
| 21 |
-
url += f"&filter={model_type}"
|
| 22 |
-
|
| 23 |
-
headers = {}
|
| 24 |
-
if token := os.environ.get("HF_TOKEN"):
|
| 25 |
-
headers["Authorization"] = f"Bearer {token}"
|
| 26 |
-
|
| 27 |
-
req = urllib.request.Request(url, headers=headers)
|
| 28 |
-
with urllib.request.urlopen(req, timeout=60) as response:
|
| 29 |
-
return json.loads(response.read().decode("utf-8"))
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
def parse_date(date_str: str) -> datetime:
|
| 33 |
-
"""Parse ISO date string."""
|
| 34 |
-
return datetime.fromisoformat(date_str.replace("Z", "+00:00"))
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
def calculate_gem_score(model: dict[str, Any]) -> dict[str, Any]:
|
| 38 |
-
"""Calculate a 'hidden gem' score based on likes-to-downloads ratio."""
|
| 39 |
-
downloads = model.get("downloads", 0)
|
| 40 |
-
likes = model.get("likes", 0)
|
| 41 |
-
created_str = model.get("createdAt", "")
|
| 42 |
-
|
| 43 |
-
if not downloads or not likes or not created_str:
|
| 44 |
-
return {**model, "gem_score": 0.0, "ratio": 0.0}
|
| 45 |
-
|
| 46 |
-
# Calculate raw ratio (likes per 1000 downloads)
|
| 47 |
-
ratio = (likes / downloads) * 1000 if downloads > 0 else 0
|
| 48 |
-
|
| 49 |
-
# Parse creation date
|
| 50 |
-
created_at = parse_date(created_str)
|
| 51 |
-
age_days = (datetime.now(datetime.now().astimezone().tzinfo) - created_at).days
|
| 52 |
-
|
| 53 |
-
# Penalize very new models (< 14 days)
|
| 54 |
-
newness_penalty = max(0, (14 - age_days) / 14) if age_days < 14 else 0
|
| 55 |
-
|
| 56 |
-
# Penalize very old models (> 2 years)
|
| 57 |
-
age_penalty = max(0, (age_days - 730) / 365) if age_days > 730 else 0
|
| 58 |
-
|
| 59 |
-
# Penalize very low downloads (< 100)
|
| 60 |
-
download_penalty = max(0, (100 - downloads) / 100) if downloads < 100 else 0
|
| 61 |
-
|
| 62 |
-
# Boost for moderate downloads (100-10k)
|
| 63 |
-
download_boost = 1.0
|
| 64 |
-
if 100 <= downloads <= 10000:
|
| 65 |
-
download_boost = 1.2
|
| 66 |
-
elif downloads > 100000:
|
| 67 |
-
download_boost = 0.7
|
| 68 |
-
|
| 69 |
-
# Calculate adjusted gem score
|
| 70 |
-
base_score = ratio * download_boost
|
| 71 |
-
gem_score = base_score * (1 - newness_penalty) * (1 - age_penalty) * (1 - download_penalty)
|
| 72 |
-
|
| 73 |
-
return {
|
| 74 |
-
"id": model["id"],
|
| 75 |
-
"likes": likes,
|
| 76 |
-
"downloads": downloads,
|
| 77 |
-
"ratio": round(ratio, 2),
|
| 78 |
-
"gem_score": round(gem_score, 2),
|
| 79 |
-
"pipeline_tag": model.get("pipeline_tag") or "unknown",
|
| 80 |
-
"created_at": created_str,
|
| 81 |
-
"age_days": age_days,
|
| 82 |
-
"tags": model.get("tags", [])[:5],
|
| 83 |
-
}
|
| 84 |
|
| 85 |
|
| 86 |
def find_hidden_gems(
|
| 87 |
-
limit: int =
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
max_downloads: int = 500000,
|
| 93 |
) -> str:
|
| 94 |
"""
|
| 95 |
-
Find hidden gem models
|
| 96 |
|
| 97 |
Args:
|
| 98 |
-
limit: Number of models to fetch from API (default:
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
max_downloads: Maximum downloads to exclude popular models (default: 500000)
|
| 104 |
|
| 105 |
Returns:
|
| 106 |
-
JSON string with list of hidden gem models
|
| 107 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
try:
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
|
|
|
|
|
|
| 125 |
|
| 126 |
-
|
| 127 |
-
"total_analyzed": len(models),
|
| 128 |
-
"candidates": len(filtered),
|
| 129 |
-
"gems_found": len(gems),
|
| 130 |
-
"model_type_filter": model_type,
|
| 131 |
-
"gems": gems,
|
| 132 |
-
}
|
| 133 |
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
|
| 140 |
def get_model_details(model_id: str) -> str:
|
| 141 |
"""
|
| 142 |
-
Get detailed information about a specific model.
|
| 143 |
|
| 144 |
Args:
|
| 145 |
-
model_id: The model ID (e.g.,
|
| 146 |
|
| 147 |
Returns:
|
| 148 |
-
JSON string with model
|
| 149 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
try:
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
headers = {}
|
| 154 |
-
if token := os.environ.get("HF_TOKEN"):
|
| 155 |
-
headers["Authorization"] = f"Bearer {token}"
|
| 156 |
-
|
| 157 |
-
req = urllib.request.Request(url, headers=headers)
|
| 158 |
-
with urllib.request.urlopen(req, timeout=30) as response:
|
| 159 |
model = json.loads(response.read().decode("utf-8"))
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
details = {
|
| 165 |
-
**scored,
|
| 166 |
-
"library_name": model.get("library_name"),
|
| 167 |
-
"config": model.get("config", {}),
|
| 168 |
-
"siblings_count": len(model.get("siblings", [])),
|
| 169 |
-
"card_data": model.get("cardData", {}),
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
return json.dumps(details, indent=2)
|
| 173 |
-
|
| 174 |
except Exception as e:
|
| 175 |
-
return json.dumps({"error": str(e)}
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
def list_model_types() -> str:
|
| 179 |
-
"""
|
| 180 |
-
List common model types/pipeline tags available for filtering.
|
| 181 |
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
""
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
"
|
| 189 |
-
"
|
| 190 |
-
"
|
| 191 |
-
"
|
| 192 |
-
"
|
| 193 |
-
"
|
| 194 |
-
"
|
| 195 |
-
"
|
| 196 |
-
"
|
| 197 |
-
"
|
| 198 |
-
"
|
| 199 |
-
"
|
| 200 |
-
"
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
return json.dumps({"model_types": common_types}, indent=2)
|
|
|
|
| 1 |
+
"""Hidden Gems Tool - Find undervalued Hugging Face models."""
|
|
|
|
| 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import os
|
| 4 |
+
import json
|
| 5 |
+
from typing import Optional
|
| 6 |
+
from urllib.request import urlopen, Request
|
| 7 |
+
from urllib.error import HTTPError
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
def find_hidden_gems(
|
| 11 |
+
limit: int = 100,
|
| 12 |
+
min_downloads: int = 100,
|
| 13 |
+
top: int = 20,
|
| 14 |
+
pipeline_tag: Optional[str] = None,
|
| 15 |
+
sort_by: str = "ratio"
|
|
|
|
| 16 |
) -> str:
|
| 17 |
"""
|
| 18 |
+
Find hidden gem models with high likes-to-downloads ratios.
|
| 19 |
|
| 20 |
Args:
|
| 21 |
+
limit: Number of models to fetch from API (default: 100)
|
| 22 |
+
min_downloads: Minimum downloads to filter out very new models (default: 100)
|
| 23 |
+
top: Number of top results to return (default: 20)
|
| 24 |
+
pipeline_tag: Filter by pipeline tag like "text-generation", "image-to-text" (optional)
|
| 25 |
+
sort_by: Sort results by "ratio" (default), "likes", "downloads", or "trending"
|
|
|
|
| 26 |
|
| 27 |
Returns:
|
| 28 |
+
JSON string with list of hidden gem models ranked by score
|
| 29 |
"""
|
| 30 |
+
# Build API URL
|
| 31 |
+
api_url = f"https://huggingface.co/api/models?limit={limit}"
|
| 32 |
+
if pipeline_tag:
|
| 33 |
+
api_url += f"&pipeline_tag={pipeline_tag}"
|
| 34 |
+
|
| 35 |
+
# Fetch models
|
| 36 |
+
headers = {}
|
| 37 |
+
token = os.environ.get("HF_TOKEN")
|
| 38 |
+
if token:
|
| 39 |
+
headers["Authorization"] = f"Bearer {token}"
|
| 40 |
+
|
| 41 |
try:
|
| 42 |
+
req = Request(api_url, headers=headers)
|
| 43 |
+
with urlopen(req, timeout=60) as response:
|
| 44 |
+
models = json.loads(response.read().decode("utf-8"))
|
| 45 |
+
except HTTPError as e:
|
| 46 |
+
return json.dumps({"error": f"API error: {e.code} - {e.reason}"})
|
| 47 |
+
except Exception as e:
|
| 48 |
+
return json.dumps({"error": f"Failed to fetch models: {str(e)}"})
|
| 49 |
+
|
| 50 |
+
# Calculate hidden gem scores
|
| 51 |
+
results = []
|
| 52 |
+
for model in models:
|
| 53 |
+
likes = model.get("likes")
|
| 54 |
+
downloads = model.get("downloads")
|
| 55 |
|
| 56 |
+
if likes is None or downloads is None:
|
| 57 |
+
continue
|
| 58 |
+
if downloads < min_downloads:
|
| 59 |
+
continue
|
| 60 |
|
| 61 |
+
ratio = likes / downloads if downloads > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
+
results.append({
|
| 64 |
+
"id": model.get("id"),
|
| 65 |
+
"likes": likes,
|
| 66 |
+
"downloads": downloads,
|
| 67 |
+
"ratio": round(ratio, 6),
|
| 68 |
+
"pipeline_tag": model.get("pipeline_tag") or "unknown",
|
| 69 |
+
"library_name": model.get("library_name") or "unknown",
|
| 70 |
+
"createdAt": model.get("createdAt") or "unknown",
|
| 71 |
+
"trendingScore": model.get("trendingScore") or 0,
|
| 72 |
+
"tags": model.get("tags", [])
|
| 73 |
+
})
|
| 74 |
+
|
| 75 |
+
# Sort results
|
| 76 |
+
sort_key = {
|
| 77 |
+
"likes": "likes",
|
| 78 |
+
"downloads": "downloads",
|
| 79 |
+
"trending": "trendingScore"
|
| 80 |
+
}.get(sort_by, "ratio")
|
| 81 |
+
|
| 82 |
+
results.sort(key=lambda x: x[sort_key], reverse=True)
|
| 83 |
+
|
| 84 |
+
# Take top N
|
| 85 |
+
top_results = results[:top]
|
| 86 |
+
|
| 87 |
+
return json.dumps({
|
| 88 |
+
"count": len(results),
|
| 89 |
+
"showing": len(top_results),
|
| 90 |
+
"filters": {
|
| 91 |
+
"min_downloads": min_downloads,
|
| 92 |
+
"pipeline_tag": pipeline_tag,
|
| 93 |
+
"sort_by": sort_by
|
| 94 |
+
},
|
| 95 |
+
"gems": top_results
|
| 96 |
+
}, indent=2)
|
| 97 |
|
| 98 |
|
| 99 |
def get_model_details(model_id: str) -> str:
|
| 100 |
"""
|
| 101 |
+
Get detailed information about a specific Hugging Face model.
|
| 102 |
|
| 103 |
Args:
|
| 104 |
+
model_id: The model ID (e.g., "microsoft/DialoGPT-medium")
|
| 105 |
|
| 106 |
Returns:
|
| 107 |
+
JSON string with detailed model information
|
| 108 |
"""
|
| 109 |
+
api_url = f"https://huggingface.co/api/models/{model_id}"
|
| 110 |
+
|
| 111 |
+
headers = {}
|
| 112 |
+
token = os.environ.get("HF_TOKEN")
|
| 113 |
+
if token:
|
| 114 |
+
headers["Authorization"] = f"Bearer {token}"
|
| 115 |
+
|
| 116 |
try:
|
| 117 |
+
req = Request(api_url, headers=headers)
|
| 118 |
+
with urlopen(req, timeout=30) as response:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
model = json.loads(response.read().decode("utf-8"))
|
| 120 |
+
except HTTPError as e:
|
| 121 |
+
if e.code == 404:
|
| 122 |
+
return json.dumps({"error": f"Model '{model_id}' not found"})
|
| 123 |
+
return json.dumps({"error": f"API error: {e.code} - {e.reason}"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
except Exception as e:
|
| 125 |
+
return json.dumps({"error": f"Failed to fetch model: {str(e)}"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
+
# Calculate hidden gem score
|
| 128 |
+
likes = model.get("likes", 0)
|
| 129 |
+
downloads = model.get("downloads", 0)
|
| 130 |
+
ratio = likes / downloads if downloads > 0 else 0
|
| 131 |
+
|
| 132 |
+
result = {
|
| 133 |
+
"id": model.get("id"),
|
| 134 |
+
"likes": likes,
|
| 135 |
+
"downloads": downloads,
|
| 136 |
+
"hidden_gem_score": round(ratio, 6),
|
| 137 |
+
"pipeline_tag": model.get("pipeline_tag") or "unknown",
|
| 138 |
+
"library_name": model.get("library_name") or "unknown",
|
| 139 |
+
"tags": model.get("tags", []),
|
| 140 |
+
"createdAt": model.get("createdAt"),
|
| 141 |
+
"lastModified": model.get("lastModified"),
|
| 142 |
+
"cardExists": model.get("cardExists", False),
|
| 143 |
+
"widgetData": model.get("widgetData", []),
|
| 144 |
+
"siblings": [s.get("rfilename") for s in model.get("siblings", [])[:10]],
|
| 145 |
+
"description": (model.get("cardData") or {}).get("tags", [])
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
return json.dumps(result, indent=2)
|
|
|
|
|
|