Spaces:
Configuration error
Configuration error
Commit Β·
af2c3f6
0
Parent(s):
Agentic Model Selector
Browse files- .devcontainer/devcontainer.json +35 -0
- .github/workflows/ci.yml +105 -0
- .gitignore +56 -0
- README.md +26 -0
- Scripts/setup-agent-env.sh +15 -0
- docker/docker-compose.yml +29 -0
- requirements.txt +28 -0
- run.py +91 -0
- setup.py +30 -0
- src/__init__.py +0 -0
- src/agents/__init__.py +0 -0
- src/agents/benchmarking_agent.py +347 -0
- src/agents/deployment_agent.py +402 -0
- src/agents/evaluation_agent.py +312 -0
- src/agents/input_agent.py +369 -0
- src/agents/research_agent.py +560 -0
- src/config/__init__.py +0 -0
- src/main.py +186 -0
- src/models/__init__.py +0 -0
- src/models/schemas.py +224 -0
- src/utils/__init__.py +0 -0
- structure.txt +37 -0
.devcontainer/devcontainer.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Agentic Model Selector",
|
| 3 |
+
"image": "mcr.microsoft.com/devcontainers/python:3.11",
|
| 4 |
+
"features": {
|
| 5 |
+
"ghcr.io/devcontainers/features/python:1": {
|
| 6 |
+
"version": "3.11"
|
| 7 |
+
},
|
| 8 |
+
"ghcr.io/devcontainers/features/nvidia-cuda:1": {
|
| 9 |
+
"version": "11.8"
|
| 10 |
+
}
|
| 11 |
+
},
|
| 12 |
+
"postCreateCommand": "pip install -r requirements.txt && python -m spacy download en_core_web_sm",
|
| 13 |
+
"postStartCommand": "echo 'π Ready for agentic development!'",
|
| 14 |
+
"forwardPorts": [8000, 7860, 3000],
|
| 15 |
+
"portsAttributes": {
|
| 16 |
+
"8000": {
|
| 17 |
+
"label": "FastAPI",
|
| 18 |
+
"onAutoForward": "notify"
|
| 19 |
+
},
|
| 20 |
+
"7860": {
|
| 21 |
+
"label": "Gradio",
|
| 22 |
+
"onAutoForward": "notify"
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
"customizations": {
|
| 26 |
+
"vscode": {
|
| 27 |
+
"extensions": [
|
| 28 |
+
"ms-python.python",
|
| 29 |
+
"ms-toolsai.jupyter",
|
| 30 |
+
"GitHub.copilot",
|
| 31 |
+
"GitHub.copilot-chat"
|
| 32 |
+
]
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
}
|
.github/workflows/ci.yml
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: CI/CD Pipeline
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ main, develop ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ main ]
|
| 8 |
+
release:
|
| 9 |
+
types: [ published ]
|
| 10 |
+
|
| 11 |
+
jobs:
|
| 12 |
+
test:
|
| 13 |
+
runs-on: ubuntu-latest
|
| 14 |
+
strategy:
|
| 15 |
+
matrix:
|
| 16 |
+
python-version: [3.8, 3.9, "3.10", "3.11"]
|
| 17 |
+
|
| 18 |
+
steps:
|
| 19 |
+
- uses: actions/checkout@v3
|
| 20 |
+
|
| 21 |
+
- name: Set up Python ${{ matrix.python-version }}
|
| 22 |
+
uses: actions/setup-python@v4
|
| 23 |
+
with:
|
| 24 |
+
python-version: ${{ matrix.python-version }}
|
| 25 |
+
cache: 'pip'
|
| 26 |
+
|
| 27 |
+
- name: Install dependencies
|
| 28 |
+
run: |
|
| 29 |
+
python -m pip install --upgrade pip
|
| 30 |
+
pip install -r requirements.txt
|
| 31 |
+
pip install pytest pytest-cov flake8 black
|
| 32 |
+
|
| 33 |
+
- name: Lint with flake8
|
| 34 |
+
run: |
|
| 35 |
+
flake8 src/ --count --select=E9,F63,F7,F82 --show-source --statistics
|
| 36 |
+
flake8 src/ --count --exit-zero --max-complexity=10 --statistics
|
| 37 |
+
|
| 38 |
+
- name: Format with black
|
| 39 |
+
run: black --check src/
|
| 40 |
+
|
| 41 |
+
- name: Test with pytest
|
| 42 |
+
run: |
|
| 43 |
+
pytest tests/ --cov=src/ --cov-report=xml
|
| 44 |
+
|
| 45 |
+
- name: Upload coverage to Codecov
|
| 46 |
+
uses: codecov/codecov-action@v3
|
| 47 |
+
with:
|
| 48 |
+
file: ./coverage.xml
|
| 49 |
+
flags: unittests
|
| 50 |
+
|
| 51 |
+
build-docker:
|
| 52 |
+
needs: test
|
| 53 |
+
runs-on: ubuntu-latest
|
| 54 |
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
| 55 |
+
|
| 56 |
+
steps:
|
| 57 |
+
- uses: actions/checkout@v3
|
| 58 |
+
|
| 59 |
+
- name: Set up Docker Buildx
|
| 60 |
+
uses: docker/setup-buildx-action@v2
|
| 61 |
+
|
| 62 |
+
- name: Login to GitHub Container Registry
|
| 63 |
+
uses: docker/login-action@v2
|
| 64 |
+
with:
|
| 65 |
+
registry: ghcr.io
|
| 66 |
+
username: ${{ github.actor }}
|
| 67 |
+
password: ${{ secrets.GITHUB_TOKEN }}
|
| 68 |
+
|
| 69 |
+
- name: Build and push Docker image
|
| 70 |
+
uses: docker/build-push-action@v4
|
| 71 |
+
with:
|
| 72 |
+
context: .
|
| 73 |
+
file: docker/Dockerfile
|
| 74 |
+
push: true
|
| 75 |
+
tags: |
|
| 76 |
+
ghcr.io/${{ github.repository }}/hf-selector:latest
|
| 77 |
+
ghcr.io/${{ github.repository }}/hf-selector:${{ github.sha }}
|
| 78 |
+
cache-from: type=gha
|
| 79 |
+
cache-to: type=gha,mode=max
|
| 80 |
+
|
| 81 |
+
deploy-docs:
|
| 82 |
+
needs: test
|
| 83 |
+
runs-on: ubuntu-latest
|
| 84 |
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
| 85 |
+
|
| 86 |
+
steps:
|
| 87 |
+
- uses: actions/checkout@v3
|
| 88 |
+
|
| 89 |
+
- name: Set up Python
|
| 90 |
+
uses: actions/setup-python@v4
|
| 91 |
+
with:
|
| 92 |
+
python-version: '3.9'
|
| 93 |
+
|
| 94 |
+
- name: Install dependencies
|
| 95 |
+
run: |
|
| 96 |
+
pip install mkdocs mkdocs-material
|
| 97 |
+
|
| 98 |
+
- name: Build documentation
|
| 99 |
+
run: mkdocs build
|
| 100 |
+
|
| 101 |
+
- name: Deploy to GitHub Pages
|
| 102 |
+
uses: peaceiris/actions-gh-pages@v3
|
| 103 |
+
with:
|
| 104 |
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
| 105 |
+
publish_dir: ./site
|
.gitignore
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
env/
|
| 8 |
+
venv/
|
| 9 |
+
ENV/
|
| 10 |
+
env.bak/
|
| 11 |
+
venv.bak/
|
| 12 |
+
pythonenv*
|
| 13 |
+
|
| 14 |
+
# Distribution / packaging
|
| 15 |
+
.Python
|
| 16 |
+
build/
|
| 17 |
+
develop-eggs/
|
| 18 |
+
dist/
|
| 19 |
+
downloads/
|
| 20 |
+
eggs/
|
| 21 |
+
.eggs/
|
| 22 |
+
lib/
|
| 23 |
+
lib64/
|
| 24 |
+
parts/
|
| 25 |
+
sdist/
|
| 26 |
+
var/
|
| 27 |
+
wheels/
|
| 28 |
+
*.egg-info/
|
| 29 |
+
.installed.cfg
|
| 30 |
+
*.egg
|
| 31 |
+
|
| 32 |
+
# Virtual Environment
|
| 33 |
+
venv/
|
| 34 |
+
env/
|
| 35 |
+
|
| 36 |
+
# IDE
|
| 37 |
+
.vscode/
|
| 38 |
+
.idea/
|
| 39 |
+
*.swp
|
| 40 |
+
*.swo
|
| 41 |
+
*~
|
| 42 |
+
|
| 43 |
+
# Project specific
|
| 44 |
+
deployments/
|
| 45 |
+
*.log
|
| 46 |
+
*.db
|
| 47 |
+
.DS_Store
|
| 48 |
+
|
| 49 |
+
# Environment variables
|
| 50 |
+
.env
|
| 51 |
+
.env.local
|
| 52 |
+
.env.*.local
|
| 53 |
+
|
| 54 |
+
# Docker
|
| 55 |
+
*.pid
|
| 56 |
+
docker-compose.override.yml
|
README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HuggingFace Model Selector
|
| 2 |
+
|
| 3 |
+
Automatically find, evaluate, and deploy the best HuggingFace models for your specific task.
|
| 4 |
+
|
| 5 |
+
## Features
|
| 6 |
+
|
| 7 |
+
- **Natural Language Understanding**: Describe your task in plain English
|
| 8 |
+
- **Intelligent Search**: Finds relevant models on HuggingFace Hub
|
| 9 |
+
- **Multi-criteria Scoring**: Evaluates models based on downloads, recency, license, size, and performance
|
| 10 |
+
- **Real Benchmarking**: Tests actual inference speed and memory usage
|
| 11 |
+
- **Deployment Generation**: Creates production-ready FastAPI/Gradio/Docker code
|
| 12 |
+
- **Comprehensive Documentation**: Auto-generated README and configuration files
|
| 13 |
+
|
| 14 |
+
## Installation
|
| 15 |
+
|
| 16 |
+
```bash
|
| 17 |
+
# Clone the repository
|
| 18 |
+
git clone https://github.com/yourusername/huggingface-model-selector.git
|
| 19 |
+
cd huggingface-model-selector
|
| 20 |
+
|
| 21 |
+
# Create virtual environment
|
| 22 |
+
python -m venv venv
|
| 23 |
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
| 24 |
+
|
| 25 |
+
# Install dependencies
|
| 26 |
+
pip install -r requirements.txt
|
Scripts/setup-agent-env.sh
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
echo " Setting up Agentic Environment..."
|
| 3 |
+
|
| 4 |
+
# Install agentic frameworks
|
| 5 |
+
pip install langchain langgraph chromadb
|
| 6 |
+
|
| 7 |
+
# Set up memory store
|
| 8 |
+
mkdir -p .memory/vector_store
|
| 9 |
+
|
| 10 |
+
# Configure API keys (from Codespaces secrets)
|
| 11 |
+
if [ -n "$OPENAI_API_KEY" ]; then
|
| 12 |
+
echo "OPENAI_API_KEY configured"
|
| 13 |
+
fi
|
| 14 |
+
|
| 15 |
+
echo " Agentic environment ready!"
|
docker/docker-compose.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
model-selector:
|
| 5 |
+
build:
|
| 6 |
+
context: ..
|
| 7 |
+
dockerfile: docker/Dockerfile
|
| 8 |
+
container_name: hf-model-selector
|
| 9 |
+
volumes:
|
| 10 |
+
- ../deployments:/app/deployments
|
| 11 |
+
- ~/.cache/huggingface:/home/appuser/.cache/huggingface
|
| 12 |
+
environment:
|
| 13 |
+
- HUGGINGFACE_TOKEN=${HUGGINGFACE_TOKEN}
|
| 14 |
+
- CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES:-}
|
| 15 |
+
stdin_open: true
|
| 16 |
+
tty: true
|
| 17 |
+
command: ["sentiment analysis", "--deploy", "fastapi"]
|
| 18 |
+
|
| 19 |
+
# Optional: Jupyter for development
|
| 20 |
+
jupyter:
|
| 21 |
+
image: jupyter/base-notebook:latest
|
| 22 |
+
container_name: hf-jupyter
|
| 23 |
+
ports:
|
| 24 |
+
- "8888:8888"
|
| 25 |
+
volumes:
|
| 26 |
+
- ../:/home/jovyan/work
|
| 27 |
+
environment:
|
| 28 |
+
- JUPYTER_ENABLE_LAB=yes
|
| 29 |
+
command: start-notebook.sh --NotebookApp.token=''
|
requirements.txt
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core dependencies
|
| 2 |
+
transformers>=4.35.0
|
| 3 |
+
torch>=2.0.0
|
| 4 |
+
huggingface-hub>=0.19.0
|
| 5 |
+
numpy>=1.24.0
|
| 6 |
+
|
| 7 |
+
# Web frameworks
|
| 8 |
+
fastapi>=0.104.0
|
| 9 |
+
uvicorn>=0.24.0
|
| 10 |
+
gradio>=4.0.0
|
| 11 |
+
pydantic>=2.0.0
|
| 12 |
+
python-multipart>=0.0.6
|
| 13 |
+
|
| 14 |
+
# Utilities
|
| 15 |
+
pyyaml>=6.0
|
| 16 |
+
Jinja2>=3.1.0
|
| 17 |
+
aiohttp>=3.9.0
|
| 18 |
+
requests>=2.31.0
|
| 19 |
+
|
| 20 |
+
# Testing
|
| 21 |
+
pytest>=7.4.0
|
| 22 |
+
pytest-asyncio>=0.21.0
|
| 23 |
+
httpx>=0.25.0
|
| 24 |
+
|
| 25 |
+
# Add to requirements.txt
|
| 26 |
+
langchain>=0.3.0
|
| 27 |
+
openai>=1.0.0
|
| 28 |
+
anthropic>=0.30.0
|
run.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""
|
| 3 |
+
Entry point for the HuggingFace Model Selector
|
| 4 |
+
Run this file to start the model selection process
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import asyncio
|
| 8 |
+
import argparse
|
| 9 |
+
import sys
|
| 10 |
+
from src.main import HuggingFaceModelSelector
|
| 11 |
+
from src.models.schemas import DeploymentType
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def main():
|
| 15 |
+
"""Main entry point"""
|
| 16 |
+
parser = argparse.ArgumentParser(
|
| 17 |
+
description="HuggingFace Model Selector - Find and deploy the best ML models"
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
parser.add_argument(
|
| 21 |
+
"task",
|
| 22 |
+
type=str,
|
| 23 |
+
help="Task description (e.g., 'sentiment analysis', 'text summarization')"
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
parser.add_argument(
|
| 27 |
+
"--deploy",
|
| 28 |
+
type=str,
|
| 29 |
+
choices=["fastapi", "gradio", "docker"],
|
| 30 |
+
default="fastapi",
|
| 31 |
+
help="Deployment type to generate"
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
parser.add_argument(
|
| 35 |
+
"--no-benchmark",
|
| 36 |
+
action="store_true",
|
| 37 |
+
help="Skip performance benchmarking"
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
parser.add_argument(
|
| 41 |
+
"--top-k",
|
| 42 |
+
type=int,
|
| 43 |
+
default=5,
|
| 44 |
+
help="Number of top models to consider"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
args = parser.parse_args()
|
| 48 |
+
|
| 49 |
+
# Map deployment type
|
| 50 |
+
deploy_map = {
|
| 51 |
+
"fastapi": DeploymentType.FASTAPI,
|
| 52 |
+
"gradio": DeploymentType.GRADIO,
|
| 53 |
+
"docker": DeploymentType.DOCKER
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
print("\n" + "=" * 60)
|
| 57 |
+
print(" HuggingFace Model Selector")
|
| 58 |
+
print("=" * 60)
|
| 59 |
+
print(f"\nTask: {args.task}")
|
| 60 |
+
print(f"Deployment: {args.deploy}")
|
| 61 |
+
print(f"Benchmark: {'No' if args.no_benchmark else 'Yes'}")
|
| 62 |
+
|
| 63 |
+
# Run the selector
|
| 64 |
+
async def run():
|
| 65 |
+
selector = HuggingFaceModelSelector()
|
| 66 |
+
result = await selector.select_and_deploy(
|
| 67 |
+
task_description=args.task,
|
| 68 |
+
deployment_type=deploy_map[args.deploy],
|
| 69 |
+
benchmark=not args.no_benchmark,
|
| 70 |
+
top_k=args.top_k
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
if result.status == "success":
|
| 74 |
+
print("\n" + "=" * 60)
|
| 75 |
+
print(" Selection Complete!")
|
| 76 |
+
print(f"Selected Model: {result.selected_model}")
|
| 77 |
+
print("\nNext steps:")
|
| 78 |
+
print(f"1. cd into the deployment folder")
|
| 79 |
+
print(f"2. pip install -r requirements.txt")
|
| 80 |
+
print(f"3. python app.py")
|
| 81 |
+
print("=" * 60)
|
| 82 |
+
else:
|
| 83 |
+
print("\n Selection failed:", result.error)
|
| 84 |
+
sys.exit(1)
|
| 85 |
+
|
| 86 |
+
# Run async function
|
| 87 |
+
asyncio.run(run())
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
if __name__ == "__main__":
|
| 91 |
+
main()
|
setup.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from setuptools import setup, find_packages
|
| 2 |
+
|
| 3 |
+
with open("README.md", "r", encoding="utf-8") as fh:
|
| 4 |
+
long_description = fh.read()
|
| 5 |
+
|
| 6 |
+
with open("requirements.txt", "r", encoding="utf-8") as fh:
|
| 7 |
+
requirements = [line.strip() for line in fh if line.strip() and not line.startswith("#")]
|
| 8 |
+
|
| 9 |
+
setup(
|
| 10 |
+
name="huggingface-model-selector",
|
| 11 |
+
version="1.0.0",
|
| 12 |
+
author="Your Name",
|
| 13 |
+
description="Automatically select and deploy the best HuggingFace models for your task",
|
| 14 |
+
long_description=long_description,
|
| 15 |
+
long_description_content_type="text/markdown",
|
| 16 |
+
url="https://github.com/yourusername/huggingface-model-selector",
|
| 17 |
+
packages=find_packages(),
|
| 18 |
+
classifiers=[
|
| 19 |
+
"Programming Language :: Python :: 3",
|
| 20 |
+
"License :: OSI Approved :: MIT License",
|
| 21 |
+
"Operating System :: OS Independent",
|
| 22 |
+
],
|
| 23 |
+
python_requires=">=3.8",
|
| 24 |
+
install_requires=requirements,
|
| 25 |
+
entry_points={
|
| 26 |
+
"console_scripts": [
|
| 27 |
+
"hf-selector=run:main",
|
| 28 |
+
],
|
| 29 |
+
},
|
| 30 |
+
)
|
src/__init__.py
ADDED
|
File without changes
|
src/agents/__init__.py
ADDED
|
File without changes
|
src/agents/benchmarking_agent.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Benchmarking Agent - Tests model performance with real inference
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import time
|
| 7 |
+
import asyncio
|
| 8 |
+
import numpy as np
|
| 9 |
+
from transformers import (
|
| 10 |
+
AutoModelForSequenceClassification,
|
| 11 |
+
AutoTokenizer,
|
| 12 |
+
pipeline,
|
| 13 |
+
AutoModelForCausalLM
|
| 14 |
+
)
|
| 15 |
+
from typing import List, Dict, Any, Optional
|
| 16 |
+
|
| 17 |
+
from src.models.schemas import (
|
| 18 |
+
TaskType, UserRequirements, BenchmarkResult
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class BenchmarkingAgent:
|
| 23 |
+
"""
|
| 24 |
+
Benchmarks models with real inference tests to measure latency and memory usage.
|
| 25 |
+
|
| 26 |
+
This agent loads each model, runs warmup inferences, then measures
|
| 27 |
+
performance over multiple iterations.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(self, sample_data: Dict[str, Any] = None):
|
| 31 |
+
self.sample_data = sample_data or self._get_default_samples()
|
| 32 |
+
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 33 |
+
print(f" Using device: {self.device}")
|
| 34 |
+
|
| 35 |
+
async def benchmark_models(self,
|
| 36 |
+
model_ids: List[str],
|
| 37 |
+
task_type: TaskType,
|
| 38 |
+
requirements: UserRequirements,
|
| 39 |
+
max_models: int = 3) -> List[BenchmarkResult]:
|
| 40 |
+
"""
|
| 41 |
+
Benchmark top models with quick inference tests.
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
model_ids: List of model IDs to benchmark
|
| 45 |
+
task_type: Type of ML task
|
| 46 |
+
requirements: User requirements
|
| 47 |
+
max_models: Maximum number of models to benchmark
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
List of BenchmarkResult objects
|
| 51 |
+
"""
|
| 52 |
+
results = []
|
| 53 |
+
|
| 54 |
+
print(f" Benchmarking up to {max_models} models...")
|
| 55 |
+
|
| 56 |
+
for i, model_id in enumerate(model_ids[:max_models]):
|
| 57 |
+
print(f" Testing {i+1}/{min(len(model_ids), max_models)}: {model_id}")
|
| 58 |
+
|
| 59 |
+
try:
|
| 60 |
+
result = await self._benchmark_single_model(
|
| 61 |
+
model_id, task_type, requirements
|
| 62 |
+
)
|
| 63 |
+
results.append(result)
|
| 64 |
+
|
| 65 |
+
if not result.error:
|
| 66 |
+
print(f" Latency: {result.latency_ms:.2f}ms, "
|
| 67 |
+
f"Memory: {result.memory_usage_mb:.2f}MB")
|
| 68 |
+
else:
|
| 69 |
+
print(f" Error: {result.error}")
|
| 70 |
+
|
| 71 |
+
except Exception as e:
|
| 72 |
+
print(f" Failed: {e}")
|
| 73 |
+
# FIXED: Added task_type to the error response
|
| 74 |
+
results.append(BenchmarkResult(
|
| 75 |
+
model_id=model_id,
|
| 76 |
+
task_type=task_type, # This was missing!
|
| 77 |
+
latency_ms=0,
|
| 78 |
+
memory_usage_mb=0,
|
| 79 |
+
error=str(e)
|
| 80 |
+
))
|
| 81 |
+
|
| 82 |
+
# Small delay between models
|
| 83 |
+
await asyncio.sleep(0.5)
|
| 84 |
+
|
| 85 |
+
return results
|
| 86 |
+
|
| 87 |
+
async def _benchmark_single_model(self,
|
| 88 |
+
model_id: str,
|
| 89 |
+
task_type: TaskType,
|
| 90 |
+
requirements: UserRequirements) -> BenchmarkResult:
|
| 91 |
+
"""Benchmark a single model"""
|
| 92 |
+
|
| 93 |
+
model = None
|
| 94 |
+
tokenizer = None
|
| 95 |
+
nlp_pipeline = None
|
| 96 |
+
|
| 97 |
+
# Load model and tokenizer
|
| 98 |
+
try:
|
| 99 |
+
print(f" Loading model...")
|
| 100 |
+
|
| 101 |
+
if task_type == TaskType.TRANSLATION:
|
| 102 |
+
# For translation models, we need to use pipeline with specific task format
|
| 103 |
+
try:
|
| 104 |
+
# Try the standard translation pipeline first
|
| 105 |
+
nlp_pipeline = pipeline(
|
| 106 |
+
"translation",
|
| 107 |
+
model=model_id,
|
| 108 |
+
device=self.device
|
| 109 |
+
)
|
| 110 |
+
except Exception as e:
|
| 111 |
+
# If that fails, try with specific language pair format
|
| 112 |
+
if requirements.translation_reqs:
|
| 113 |
+
src = requirements.translation_reqs.source_language.value
|
| 114 |
+
tgt = requirements.translation_reqs.target_language.value
|
| 115 |
+
task_name = f"translation_{src}_to_{tgt}"
|
| 116 |
+
try:
|
| 117 |
+
nlp_pipeline = pipeline(
|
| 118 |
+
task_name,
|
| 119 |
+
model=model_id,
|
| 120 |
+
device=self.device
|
| 121 |
+
)
|
| 122 |
+
except:
|
| 123 |
+
# If both fail, try loading as a general seq2seq model
|
| 124 |
+
from transformers import AutoModelForSeq2SeqLM
|
| 125 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 126 |
+
model = AutoModelForSeq2SeqLM.from_pretrained(model_id)
|
| 127 |
+
|
| 128 |
+
elif task_type in [TaskType.TEXT_CLASSIFICATION, TaskType.NAMED_ENTITY_RECOGNITION]:
|
| 129 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 130 |
+
model = AutoModelForSequenceClassification.from_pretrained(model_id)
|
| 131 |
+
|
| 132 |
+
elif task_type == TaskType.TEXT_GENERATION:
|
| 133 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 134 |
+
model = AutoModelForCausalLM.from_pretrained(model_id)
|
| 135 |
+
|
| 136 |
+
else:
|
| 137 |
+
# Use pipeline for other tasks
|
| 138 |
+
nlp_pipeline = pipeline(
|
| 139 |
+
task_type.value,
|
| 140 |
+
model=model_id,
|
| 141 |
+
device=self.device
|
| 142 |
+
)
|
| 143 |
+
|
| 144 |
+
except Exception as e:
|
| 145 |
+
return BenchmarkResult(
|
| 146 |
+
model_id=model_id,
|
| 147 |
+
task_type=task_type,
|
| 148 |
+
latency_ms=0,
|
| 149 |
+
memory_usage_mb=0,
|
| 150 |
+
error=f"Failed to load model: {str(e)}"
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# Move model to device
|
| 154 |
+
if model:
|
| 155 |
+
model.to(self.device)
|
| 156 |
+
model.eval()
|
| 157 |
+
|
| 158 |
+
# Get appropriate sample data
|
| 159 |
+
sample = self._get_task_sample(task_type)
|
| 160 |
+
|
| 161 |
+
# Run warmup (first inference is always slower)
|
| 162 |
+
try:
|
| 163 |
+
await self._run_warmup(model_id, task_type, sample, model, tokenizer, nlp_pipeline, requirements)
|
| 164 |
+
except Exception as e:
|
| 165 |
+
print(f" Warmup warning: {e}")
|
| 166 |
+
|
| 167 |
+
# Benchmark inference
|
| 168 |
+
latencies = []
|
| 169 |
+
memory_usage = []
|
| 170 |
+
|
| 171 |
+
for i in range(5): # Run 5 iterations for stable measurement
|
| 172 |
+
# Reset memory stats if using CUDA
|
| 173 |
+
if self.device.type == "cuda":
|
| 174 |
+
torch.cuda.reset_peak_memory_stats()
|
| 175 |
+
start_memory = torch.cuda.memory_allocated()
|
| 176 |
+
|
| 177 |
+
start_time = time.perf_counter()
|
| 178 |
+
|
| 179 |
+
# Run inference
|
| 180 |
+
try:
|
| 181 |
+
with torch.no_grad():
|
| 182 |
+
if task_type == TaskType.TRANSLATION and nlp_pipeline:
|
| 183 |
+
# For translation pipeline
|
| 184 |
+
result = nlp_pipeline(sample["text"], max_length=128)
|
| 185 |
+
|
| 186 |
+
elif task_type == TaskType.TRANSLATION and model and tokenizer:
|
| 187 |
+
# For seq2seq model
|
| 188 |
+
inputs = tokenizer(
|
| 189 |
+
sample["text"],
|
| 190 |
+
return_tensors="pt",
|
| 191 |
+
truncation=True,
|
| 192 |
+
max_length=128
|
| 193 |
+
).to(self.device)
|
| 194 |
+
outputs = model.generate(**inputs, max_new_tokens=50)
|
| 195 |
+
|
| 196 |
+
elif task_type == TaskType.TEXT_CLASSIFICATION and model and tokenizer:
|
| 197 |
+
inputs = tokenizer(
|
| 198 |
+
sample["text"],
|
| 199 |
+
return_tensors="pt",
|
| 200 |
+
truncation=True,
|
| 201 |
+
max_length=128
|
| 202 |
+
).to(self.device)
|
| 203 |
+
outputs = model(**inputs)
|
| 204 |
+
|
| 205 |
+
elif task_type == TaskType.TEXT_GENERATION and model and tokenizer:
|
| 206 |
+
inputs = tokenizer(
|
| 207 |
+
sample["text"],
|
| 208 |
+
return_tensors="pt",
|
| 209 |
+
truncation=True
|
| 210 |
+
).to(self.device)
|
| 211 |
+
outputs = model.generate(**inputs, max_new_tokens=20)
|
| 212 |
+
|
| 213 |
+
elif nlp_pipeline:
|
| 214 |
+
result = nlp_pipeline(sample["text"])
|
| 215 |
+
|
| 216 |
+
except Exception as e:
|
| 217 |
+
return BenchmarkResult(
|
| 218 |
+
model_id=model_id,
|
| 219 |
+
task_type=task_type,
|
| 220 |
+
latency_ms=0,
|
| 221 |
+
memory_usage_mb=0,
|
| 222 |
+
error=f"Inference failed: {str(e)}"
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
end_time = time.perf_counter()
|
| 226 |
+
|
| 227 |
+
# Measure memory
|
| 228 |
+
if self.device.type == "cuda":
|
| 229 |
+
end_memory = torch.cuda.memory_allocated()
|
| 230 |
+
peak_memory = torch.cuda.max_memory_allocated()
|
| 231 |
+
memory_used = (peak_memory - start_memory) / (1024 ** 2) # Convert to MB
|
| 232 |
+
memory_usage.append(memory_used)
|
| 233 |
+
|
| 234 |
+
latency_ms = (end_time - start_time) * 1000
|
| 235 |
+
latencies.append(latency_ms)
|
| 236 |
+
|
| 237 |
+
# Small delay between runs
|
| 238 |
+
await asyncio.sleep(0.1)
|
| 239 |
+
|
| 240 |
+
# Clean up
|
| 241 |
+
if model:
|
| 242 |
+
del model
|
| 243 |
+
if tokenizer:
|
| 244 |
+
del tokenizer
|
| 245 |
+
if torch.cuda.is_available():
|
| 246 |
+
torch.cuda.empty_cache()
|
| 247 |
+
|
| 248 |
+
# Calculate statistics
|
| 249 |
+
avg_latency = float(np.mean(latencies))
|
| 250 |
+
avg_memory = float(np.mean(memory_usage)) if memory_usage else 0
|
| 251 |
+
|
| 252 |
+
return BenchmarkResult(
|
| 253 |
+
model_id=model_id,
|
| 254 |
+
task_type=task_type,
|
| 255 |
+
latency_ms=avg_latency,
|
| 256 |
+
memory_usage_mb=avg_memory,
|
| 257 |
+
throughput=1000 / avg_latency if avg_latency > 0 else 0
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
async def _run_warmup(self, model_id: str, task_type: TaskType, sample: Dict,
|
| 261 |
+
model=None, tokenizer=None, nlp_pipeline=None, requirements=None):
|
| 262 |
+
"""Run warmup inference to initialize model"""
|
| 263 |
+
try:
|
| 264 |
+
with torch.no_grad():
|
| 265 |
+
if task_type == TaskType.TRANSLATION and nlp_pipeline:
|
| 266 |
+
nlp_pipeline(sample["text"], max_length=50)
|
| 267 |
+
|
| 268 |
+
elif task_type == TaskType.TRANSLATION and model and tokenizer:
|
| 269 |
+
inputs = tokenizer(
|
| 270 |
+
sample["text"],
|
| 271 |
+
return_tensors="pt",
|
| 272 |
+
truncation=True
|
| 273 |
+
).to(self.device)
|
| 274 |
+
model.generate(**inputs, max_new_tokens=20)
|
| 275 |
+
|
| 276 |
+
elif task_type == TaskType.TEXT_CLASSIFICATION and model and tokenizer:
|
| 277 |
+
inputs = tokenizer(
|
| 278 |
+
sample["text"],
|
| 279 |
+
return_tensors="pt",
|
| 280 |
+
truncation=True
|
| 281 |
+
).to(self.device)
|
| 282 |
+
model(**inputs)
|
| 283 |
+
|
| 284 |
+
elif task_type == TaskType.TEXT_GENERATION and model and tokenizer:
|
| 285 |
+
inputs = tokenizer(
|
| 286 |
+
sample["text"],
|
| 287 |
+
return_tensors="pt",
|
| 288 |
+
truncation=True
|
| 289 |
+
).to(self.device)
|
| 290 |
+
model.generate(**inputs, max_new_tokens=10)
|
| 291 |
+
|
| 292 |
+
elif nlp_pipeline:
|
| 293 |
+
nlp_pipeline(sample["text"])
|
| 294 |
+
|
| 295 |
+
except Exception as e:
|
| 296 |
+
raise e
|
| 297 |
+
|
| 298 |
+
def _get_task_sample(self, task_type: TaskType) -> Dict[str, Any]:
|
| 299 |
+
"""Get sample data for benchmarking"""
|
| 300 |
+
samples = {
|
| 301 |
+
TaskType.TEXT_CLASSIFICATION: {
|
| 302 |
+
"text": "This is a sample text for classification benchmarking."
|
| 303 |
+
},
|
| 304 |
+
TaskType.TEXT_GENERATION: {
|
| 305 |
+
"text": "Once upon a time in a land far away",
|
| 306 |
+
},
|
| 307 |
+
TaskType.SUMMARIZATION: {
|
| 308 |
+
"text": """Artificial intelligence is transforming industries across the globe.
|
| 309 |
+
From healthcare to finance, AI systems are being deployed to solve complex problems.
|
| 310 |
+
Machine learning algorithms can now diagnose diseases, predict market trends,
|
| 311 |
+
and even create art. The rapid advancement of AI technology brings both opportunities
|
| 312 |
+
and challenges that society must address."""
|
| 313 |
+
},
|
| 314 |
+
TaskType.QUESTION_ANSWERING: {
|
| 315 |
+
"context": "The Eiffel Tower is located in Paris, France.",
|
| 316 |
+
"question": "Where is the Eiffel Tower?"
|
| 317 |
+
},
|
| 318 |
+
TaskType.TRANSLATION: {
|
| 319 |
+
"text": "Hello, how are you today?"
|
| 320 |
+
},
|
| 321 |
+
TaskType.TEXT_TO_SPEECH: {
|
| 322 |
+
"text": "Hello, this is a test of the text to speech system."
|
| 323 |
+
},
|
| 324 |
+
TaskType.SPEECH_TO_TEXT: {
|
| 325 |
+
"text": "This is a sample audio transcription test."
|
| 326 |
+
},
|
| 327 |
+
TaskType.OCR: {
|
| 328 |
+
"text": "Sample text from an image."
|
| 329 |
+
}
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
return samples.get(task_type, {"text": "Sample text for benchmarking."})
|
| 333 |
+
|
| 334 |
+
def _get_default_samples(self) -> Dict[str, Any]:
|
| 335 |
+
"""Get default sample data for various tasks"""
|
| 336 |
+
return {
|
| 337 |
+
"text_classification": [
|
| 338 |
+
{"text": "I love this product, it's amazing!", "label": "positive"},
|
| 339 |
+
{"text": "This is the worst experience ever.", "label": "negative"}
|
| 340 |
+
],
|
| 341 |
+
"summarization": [
|
| 342 |
+
{"text": "Long article about AI advancements..."}
|
| 343 |
+
],
|
| 344 |
+
"translation": [
|
| 345 |
+
{"text": "Hello world", "source_lang": "en", "target_lang": "fr"}
|
| 346 |
+
]
|
| 347 |
+
}
|
src/agents/deployment_agent.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Deployment Agent - Generates deployment code and configuration
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import yaml
|
| 7 |
+
import torch
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from typing import Dict, Optional
|
| 10 |
+
from jinja2 import Template
|
| 11 |
+
|
| 12 |
+
from src.models.schemas import (
|
| 13 |
+
TaskType, DeploymentType, BenchmarkResult, UserRequirements
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DeploymentAgent:
|
| 18 |
+
"""
|
| 19 |
+
Generates production-ready deployment code for selected models.
|
| 20 |
+
|
| 21 |
+
Supports FastAPI, Gradio, and Docker deployments with
|
| 22 |
+
appropriate configuration files.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self, output_dir: str = "deployments"):
|
| 26 |
+
self.output_dir = output_dir
|
| 27 |
+
self._load_templates()
|
| 28 |
+
|
| 29 |
+
def _load_templates(self):
|
| 30 |
+
"""Load deployment templates"""
|
| 31 |
+
self.templates = {}
|
| 32 |
+
|
| 33 |
+
# FastAPI template
|
| 34 |
+
fastapi_template = '''"""
|
| 35 |
+
FastAPI deployment for {{ model_id }}
|
| 36 |
+
Generated by HuggingFace Model Selector
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
from fastapi import FastAPI, HTTPException
|
| 40 |
+
from pydantic import BaseModel
|
| 41 |
+
from transformers import pipeline
|
| 42 |
+
import torch
|
| 43 |
+
import uvicorn
|
| 44 |
+
import time
|
| 45 |
+
from typing import Dict, Any
|
| 46 |
+
|
| 47 |
+
app = FastAPI(
|
| 48 |
+
title="{{ model_id }} API",
|
| 49 |
+
description="Model deployment for {{ task_type }}",
|
| 50 |
+
version="1.0.0"
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
# Load model
|
| 54 |
+
print("Loading model {{ model_id }}...")
|
| 55 |
+
model = pipeline("{{ task_type }}", model="{{ model_id }}")
|
| 56 |
+
print("Model loaded successfully!")
|
| 57 |
+
|
| 58 |
+
class InferenceRequest(BaseModel):
|
| 59 |
+
text: str
|
| 60 |
+
parameters: Dict[str, Any] = {}
|
| 61 |
+
|
| 62 |
+
class InferenceResponse(BaseModel):
|
| 63 |
+
result: Any
|
| 64 |
+
inference_time: float
|
| 65 |
+
|
| 66 |
+
@app.post("/predict", response_model=InferenceResponse)
|
| 67 |
+
async def predict(request: InferenceRequest):
|
| 68 |
+
"""Run inference on input text"""
|
| 69 |
+
try:
|
| 70 |
+
start_time = time.time()
|
| 71 |
+
result = model(request.text, **request.parameters)
|
| 72 |
+
inference_time = time.time() - start_time
|
| 73 |
+
|
| 74 |
+
return InferenceResponse(
|
| 75 |
+
result=result,
|
| 76 |
+
inference_time=inference_time
|
| 77 |
+
)
|
| 78 |
+
except Exception as e:
|
| 79 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 80 |
+
|
| 81 |
+
@app.get("/health")
|
| 82 |
+
async def health():
|
| 83 |
+
"""Health check endpoint"""
|
| 84 |
+
return {"status": "healthy", "model": "{{ model_id }}"}
|
| 85 |
+
|
| 86 |
+
@app.get("/info")
|
| 87 |
+
async def info():
|
| 88 |
+
"""Model information"""
|
| 89 |
+
return {
|
| 90 |
+
"model_id": "{{ model_id }}",
|
| 91 |
+
"task": "{{ task_type }}",
|
| 92 |
+
"device": "cuda" if torch.cuda.is_available() else "cpu"
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
| 97 |
+
'''
|
| 98 |
+
self.templates[DeploymentType.FASTAPI] = Template(fastapi_template)
|
| 99 |
+
|
| 100 |
+
# Gradio template
|
| 101 |
+
gradio_template = '''"""
|
| 102 |
+
Gradio deployment for {{ model_id }}
|
| 103 |
+
Generated by HuggingFace Model Selector
|
| 104 |
+
"""
|
| 105 |
+
|
| 106 |
+
import gradio as gr
|
| 107 |
+
from transformers import pipeline
|
| 108 |
+
import torch
|
| 109 |
+
|
| 110 |
+
# Load model
|
| 111 |
+
print("Loading model {{ model_id }}...")
|
| 112 |
+
model = pipeline("{{ task_type }}", model="{{ model_id }}")
|
| 113 |
+
print("Model loaded successfully!")
|
| 114 |
+
|
| 115 |
+
def predict(text):
|
| 116 |
+
"""Run inference on input text"""
|
| 117 |
+
try:
|
| 118 |
+
result = model(text)
|
| 119 |
+
return result
|
| 120 |
+
except Exception as e:
|
| 121 |
+
return f"Error: {str(e)}"
|
| 122 |
+
|
| 123 |
+
# Create interface
|
| 124 |
+
interface = gr.Interface(
|
| 125 |
+
fn=predict,
|
| 126 |
+
inputs=gr.Textbox(label="Input Text", lines=3),
|
| 127 |
+
outputs=gr.Textbox(label="Result", lines=5),
|
| 128 |
+
title="{{ model_id }}",
|
| 129 |
+
description="Model deployment for {{ task_type }}",
|
| 130 |
+
examples=[["This is a sample input"]]
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
if __name__ == "__main__":
|
| 134 |
+
interface.launch(server_name="0.0.0.0", server_port=7860)
|
| 135 |
+
'''
|
| 136 |
+
self.templates[DeploymentType.GRADIO] = Template(gradio_template)
|
| 137 |
+
|
| 138 |
+
# Dockerfile template
|
| 139 |
+
docker_template = '''FROM python:3.9-slim
|
| 140 |
+
|
| 141 |
+
WORKDIR /app
|
| 142 |
+
|
| 143 |
+
# Install system dependencies
|
| 144 |
+
RUN apt-get update && apt-get install -y \\
|
| 145 |
+
gcc \\
|
| 146 |
+
g++ \\
|
| 147 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 148 |
+
|
| 149 |
+
# Copy requirements
|
| 150 |
+
COPY requirements.txt .
|
| 151 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 152 |
+
|
| 153 |
+
# Copy application code
|
| 154 |
+
COPY . .
|
| 155 |
+
|
| 156 |
+
# Expose port
|
| 157 |
+
EXPOSE 8000
|
| 158 |
+
|
| 159 |
+
# Run the application
|
| 160 |
+
CMD ["python", "app.py"]
|
| 161 |
+
'''
|
| 162 |
+
self.templates["dockerfile"] = Template(docker_template)
|
| 163 |
+
|
| 164 |
+
def generate_deployment(self,
|
| 165 |
+
model_id: str,
|
| 166 |
+
task_type: TaskType,
|
| 167 |
+
deployment_type: DeploymentType,
|
| 168 |
+
benchmark_results: Optional[BenchmarkResult] = None,
|
| 169 |
+
requirements: Optional[UserRequirements] = None) -> Dict[str, str]:
|
| 170 |
+
"""
|
| 171 |
+
Generate deployment code and configuration.
|
| 172 |
+
|
| 173 |
+
Returns:
|
| 174 |
+
Dictionary mapping filenames to file contents
|
| 175 |
+
"""
|
| 176 |
+
deployment_files = {}
|
| 177 |
+
|
| 178 |
+
# Create timestamp for unique folder
|
| 179 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 180 |
+
safe_model_id = model_id.replace("/", "_")
|
| 181 |
+
deploy_folder = f"{self.output_dir}/{safe_model_id}_{timestamp}"
|
| 182 |
+
|
| 183 |
+
# Generate main application file
|
| 184 |
+
if deployment_type == DeploymentType.FASTAPI:
|
| 185 |
+
deployment_files["app.py"] = self.templates[DeploymentType.FASTAPI].render(
|
| 186 |
+
model_id=model_id,
|
| 187 |
+
task_type=task_type.value
|
| 188 |
+
)
|
| 189 |
+
elif deployment_type == DeploymentType.GRADIO:
|
| 190 |
+
deployment_files["app.py"] = self.templates[DeploymentType.GRADIO].render(
|
| 191 |
+
model_id=model_id,
|
| 192 |
+
task_type=task_type.value
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
# Generate requirements.txt
|
| 196 |
+
deployment_files["requirements.txt"] = self._generate_requirements(
|
| 197 |
+
task_type, deployment_type
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
# Generate Dockerfile if requested
|
| 201 |
+
if deployment_type == DeploymentType.DOCKER:
|
| 202 |
+
deployment_files["Dockerfile"] = self.templates["dockerfile"].render()
|
| 203 |
+
|
| 204 |
+
# Generate docker-compose.yml
|
| 205 |
+
deployment_files["docker-compose.yml"] = self._generate_docker_compose(
|
| 206 |
+
model_id, requirements
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
# Generate configuration file
|
| 210 |
+
deployment_files["config.yaml"] = self._generate_config(
|
| 211 |
+
model_id, task_type, benchmark_results, requirements
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
# Generate README
|
| 215 |
+
deployment_files["README.md"] = self._generate_readme(
|
| 216 |
+
model_id, task_type, deployment_type, benchmark_results
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
# Add folder info
|
| 220 |
+
deployment_files["_folder"] = deploy_folder
|
| 221 |
+
|
| 222 |
+
return deployment_files
|
| 223 |
+
|
| 224 |
+
def _generate_requirements(self, task_type: TaskType, deployment_type: DeploymentType) -> str:
|
| 225 |
+
"""Generate requirements.txt"""
|
| 226 |
+
requirements = "# Generated requirements\n"
|
| 227 |
+
requirements += "transformers>=4.35.0\n"
|
| 228 |
+
requirements += "torch>=2.0.0\n"
|
| 229 |
+
requirements += "huggingface-hub>=0.19.0\n"
|
| 230 |
+
|
| 231 |
+
if deployment_type == DeploymentType.FASTAPI:
|
| 232 |
+
requirements += "fastapi>=0.104.0\n"
|
| 233 |
+
requirements += "uvicorn>=0.24.0\n"
|
| 234 |
+
requirements += "pydantic>=2.0.0\n"
|
| 235 |
+
elif deployment_type == DeploymentType.GRADIO:
|
| 236 |
+
requirements += "gradio>=4.0.0\n"
|
| 237 |
+
|
| 238 |
+
# Task-specific requirements - using correct TaskType names
|
| 239 |
+
if task_type in [TaskType.IMAGE_CLASSIFICATION, TaskType.OBJECT_DETECTION]:
|
| 240 |
+
requirements += "pillow>=10.0.0\n"
|
| 241 |
+
elif task_type == TaskType.SPEECH_TO_TEXT: # Changed from SPEECH_RECOGNITION
|
| 242 |
+
requirements += "librosa>=0.10.0\n"
|
| 243 |
+
elif task_type == TaskType.TEXT_TO_SPEECH:
|
| 244 |
+
requirements += "librosa>=0.10.0\n"
|
| 245 |
+
elif task_type == TaskType.OCR:
|
| 246 |
+
requirements += "pillow>=10.0.0\n"
|
| 247 |
+
requirements += "pytesseract>=0.3.10\n"
|
| 248 |
+
|
| 249 |
+
return requirements
|
| 250 |
+
|
| 251 |
+
def _generate_docker_compose(self, model_id: str, requirements: Optional[UserRequirements]) -> str:
|
| 252 |
+
"""Generate docker-compose.yml"""
|
| 253 |
+
compose = "version: '3.8'\n\n"
|
| 254 |
+
compose += "services:\n"
|
| 255 |
+
compose += " model-service:\n"
|
| 256 |
+
compose += " build: .\n"
|
| 257 |
+
compose += " ports:\n"
|
| 258 |
+
compose += " - \"8000:8000\"\n"
|
| 259 |
+
compose += " environment:\n"
|
| 260 |
+
compose += f" - MODEL_ID={model_id}\n"
|
| 261 |
+
compose += " restart: unless-stopped\n"
|
| 262 |
+
|
| 263 |
+
# Add GPU support if needed
|
| 264 |
+
if requirements and any("gpu" in c.value for c in requirements.hardware_constraints):
|
| 265 |
+
compose += " runtime: nvidia\n"
|
| 266 |
+
compose += " environment:\n"
|
| 267 |
+
compose += " - NVIDIA_VISIBLE_DEVICES=all\n"
|
| 268 |
+
|
| 269 |
+
return compose
|
| 270 |
+
|
| 271 |
+
def _generate_config(self, model_id: str, task_type: TaskType,
|
| 272 |
+
benchmark_results: Optional[BenchmarkResult],
|
| 273 |
+
requirements: Optional[UserRequirements]) -> str:
|
| 274 |
+
"""Generate YAML configuration"""
|
| 275 |
+
config = {
|
| 276 |
+
"model": {
|
| 277 |
+
"id": model_id,
|
| 278 |
+
"task": task_type.value,
|
| 279 |
+
},
|
| 280 |
+
"deployment": {
|
| 281 |
+
"batch_size": 1,
|
| 282 |
+
"max_length": 512,
|
| 283 |
+
"device": "cuda" if torch.cuda.is_available() else "cpu"
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
if benchmark_results and not benchmark_results.error:
|
| 288 |
+
config["performance"] = {
|
| 289 |
+
"latency_ms": benchmark_results.latency_ms,
|
| 290 |
+
"memory_mb": benchmark_results.memory_usage_mb,
|
| 291 |
+
"throughput_sps": benchmark_results.throughput
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
if requirements:
|
| 295 |
+
config["requirements"] = {}
|
| 296 |
+
|
| 297 |
+
# Add hardware constraints
|
| 298 |
+
if requirements.hardware_constraints:
|
| 299 |
+
config["requirements"]["hardware_constraints"] = [c.value for c in requirements.hardware_constraints]
|
| 300 |
+
|
| 301 |
+
# Add max model size
|
| 302 |
+
if requirements.max_model_size_gb:
|
| 303 |
+
config["requirements"]["max_model_size_gb"] = requirements.max_model_size_gb
|
| 304 |
+
|
| 305 |
+
# Add task-specific requirements based on task type
|
| 306 |
+
if requirements.task_type == TaskType.TRANSLATION and requirements.translation_reqs:
|
| 307 |
+
req = requirements.translation_reqs
|
| 308 |
+
config["requirements"]["source_language"] = req.source_language.value
|
| 309 |
+
config["requirements"]["target_language"] = req.target_language.value
|
| 310 |
+
if req.domain:
|
| 311 |
+
config["requirements"]["domain"] = req.domain
|
| 312 |
+
|
| 313 |
+
elif requirements.task_type == TaskType.TEXT_TO_SPEECH and requirements.tts_reqs:
|
| 314 |
+
req = requirements.tts_reqs
|
| 315 |
+
config["requirements"]["language"] = req.language.value
|
| 316 |
+
config["requirements"]["voice_type"] = req.voice_type.value
|
| 317 |
+
|
| 318 |
+
elif requirements.task_type == TaskType.SPEECH_TO_TEXT and requirements.stt_reqs:
|
| 319 |
+
req = requirements.stt_reqs
|
| 320 |
+
config["requirements"]["language"] = req.language.value
|
| 321 |
+
if req.domain:
|
| 322 |
+
config["requirements"]["domain"] = req.domain
|
| 323 |
+
|
| 324 |
+
elif requirements.llm_reqs:
|
| 325 |
+
req = requirements.llm_reqs
|
| 326 |
+
config["requirements"]["model_size"] = req.model_size.value
|
| 327 |
+
config["requirements"]["context_length"] = req.context_length
|
| 328 |
+
|
| 329 |
+
elif requirements.ocr_reqs:
|
| 330 |
+
req = requirements.ocr_reqs
|
| 331 |
+
config["requirements"]["languages"] = [lang.value for lang in req.languages]
|
| 332 |
+
config["requirements"]["handwritten"] = req.handwritten
|
| 333 |
+
|
| 334 |
+
return yaml.dump(config, default_flow_style=False)
|
| 335 |
+
|
| 336 |
+
def _generate_readme(self, model_id: str, task_type: TaskType,
|
| 337 |
+
deployment_type: DeploymentType,
|
| 338 |
+
benchmark_results: Optional[BenchmarkResult]) -> str:
|
| 339 |
+
"""Generate README.md"""
|
| 340 |
+
readme = f"# {model_id} Deployment\n\n"
|
| 341 |
+
readme += "This deployment was automatically generated by the HuggingFace Model Selector.\n\n"
|
| 342 |
+
readme += "## Model Information\n\n"
|
| 343 |
+
readme += f"- **Model ID**: {model_id}\n"
|
| 344 |
+
readme += f"- **Task**: {task_type.value}\n"
|
| 345 |
+
readme += f"- **Deployment Type**: {deployment_type.value}\n\n"
|
| 346 |
+
|
| 347 |
+
if benchmark_results and not benchmark_results.error:
|
| 348 |
+
readme += "## Performance Metrics\n\n"
|
| 349 |
+
readme += f"- **Average Latency**: {benchmark_results.latency_ms:.2f} ms\n"
|
| 350 |
+
readme += f"- **Memory Usage**: {benchmark_results.memory_usage_mb:.2f} MB\n"
|
| 351 |
+
readme += f"- **Throughput**: {benchmark_results.throughput_samples_per_second:.2f} samples/second\n\n"
|
| 352 |
+
|
| 353 |
+
readme += "## Quick Start\n\n"
|
| 354 |
+
readme += "### 1. Install dependencies\n"
|
| 355 |
+
readme += "```bash\n"
|
| 356 |
+
readme += "pip install -r requirements.txt\n"
|
| 357 |
+
readme += "```\n\n"
|
| 358 |
+
|
| 359 |
+
readme += "### 2. Run the application\n"
|
| 360 |
+
readme += "```bash\n"
|
| 361 |
+
readme += "python app.py\n"
|
| 362 |
+
readme += "```\n\n"
|
| 363 |
+
|
| 364 |
+
readme += "### 3. Test the API\n\n"
|
| 365 |
+
|
| 366 |
+
if deployment_type == DeploymentType.FASTAPI:
|
| 367 |
+
readme += "```bash\n"
|
| 368 |
+
readme += "# Health check\n"
|
| 369 |
+
readme += "curl http://localhost:8000/health\n\n"
|
| 370 |
+
readme += "# Run inference\n"
|
| 371 |
+
readme += 'curl -X POST http://localhost:8000/predict \\\n'
|
| 372 |
+
readme += ' -H "Content-Type: application/json" \\\n'
|
| 373 |
+
readme += ' -d \'{"text": "Your input text here"}\'\n'
|
| 374 |
+
readme += "```\n"
|
| 375 |
+
elif deployment_type == DeploymentType.GRADIO:
|
| 376 |
+
readme += "Open http://localhost:7860 in your browser to use the Gradio interface.\n"
|
| 377 |
+
|
| 378 |
+
if deployment_type == DeploymentType.DOCKER:
|
| 379 |
+
readme += "\n## Docker Deployment\n\n"
|
| 380 |
+
readme += "```bash\n"
|
| 381 |
+
readme += "# Build the image\n"
|
| 382 |
+
readme += "docker build -t model-service .\n\n"
|
| 383 |
+
readme += "# Run the container\n"
|
| 384 |
+
readme += "docker run -p 8000:8000 model-service\n\n"
|
| 385 |
+
readme += "# Or use docker-compose\n"
|
| 386 |
+
readme += "docker-compose up\n"
|
| 387 |
+
readme += "```\n"
|
| 388 |
+
|
| 389 |
+
return readme
|
| 390 |
+
|
| 391 |
+
def save_deployment_files(self, deployment_files: Dict[str, str]) -> str:
|
| 392 |
+
"""Save generated files to disk"""
|
| 393 |
+
folder = deployment_files.pop("_folder")
|
| 394 |
+
os.makedirs(folder, exist_ok=True)
|
| 395 |
+
|
| 396 |
+
for filename, content in deployment_files.items():
|
| 397 |
+
filepath = os.path.join(folder, filename)
|
| 398 |
+
with open(filepath, "w", encoding="utf-8") as f:
|
| 399 |
+
f.write(content)
|
| 400 |
+
print(f" Created {filepath}")
|
| 401 |
+
|
| 402 |
+
return folder
|
src/agents/evaluation_agent.py
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluation Agent - Scores speech and translation models
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
from datetime import datetime, timezone
|
| 7 |
+
from typing import List, Dict, Optional
|
| 8 |
+
|
| 9 |
+
from src.models.schemas import (
|
| 10 |
+
ModelMetadata, UserRequirements, HardwareConstraint,
|
| 11 |
+
ModelScore, TaskType
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class EvaluationAgent:
|
| 16 |
+
"""
|
| 17 |
+
Evaluates and scores translation, TTS, and STT models.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
def __init__(self, weights: Dict[str, float] = None):
|
| 21 |
+
self.weights = weights or {
|
| 22 |
+
'downloads': 0.20,
|
| 23 |
+
'recency': 0.15,
|
| 24 |
+
'license': 0.10,
|
| 25 |
+
'size': 0.15,
|
| 26 |
+
'performance': 0.25,
|
| 27 |
+
'language_match': 0.15
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
def score_models(self,
|
| 31 |
+
models: List[ModelMetadata],
|
| 32 |
+
requirements: UserRequirements) -> List[ModelScore]:
|
| 33 |
+
"""Score models based on multiple criteria"""
|
| 34 |
+
scored_models = []
|
| 35 |
+
|
| 36 |
+
for model in models:
|
| 37 |
+
scores = {}
|
| 38 |
+
|
| 39 |
+
# Common scores
|
| 40 |
+
scores['downloads'] = self._score_downloads(model.downloads)
|
| 41 |
+
scores['recency'] = self._score_recency(model.last_modified)
|
| 42 |
+
scores['license'] = self._score_license(model.license)
|
| 43 |
+
scores['size'] = self._score_size(model.model_size, requirements)
|
| 44 |
+
scores['performance'] = self._score_performance(model, requirements)
|
| 45 |
+
scores['language_match'] = self._score_language_match(model, requirements)
|
| 46 |
+
|
| 47 |
+
# Task-specific scores
|
| 48 |
+
if model.task_type in [TaskType.TEXT_GENERATION, TaskType.CHAT,
|
| 49 |
+
TaskType.INSTRUCTION_FOLLOWING, TaskType.CODE_GENERATION,
|
| 50 |
+
TaskType.QUESTION_ANSWERING, TaskType.SUMMARIZATION]:
|
| 51 |
+
scores['llm_capabilities'] = self._score_llm_capabilities(model, requirements)
|
| 52 |
+
elif model.task_type in [TaskType.OCR, TaskType.DOCUMENT_UNDERSTANDING]:
|
| 53 |
+
scores['ocr_capabilities'] = self._score_ocr_capabilities(model, requirements)
|
| 54 |
+
|
| 55 |
+
# Calculate weighted total
|
| 56 |
+
total_score = 0.0
|
| 57 |
+
weight_sum = 0.0
|
| 58 |
+
|
| 59 |
+
all_weights = {
|
| 60 |
+
'downloads': 0.15,
|
| 61 |
+
'recency': 0.10,
|
| 62 |
+
'license': 0.05,
|
| 63 |
+
'size': 0.10,
|
| 64 |
+
'performance': 0.20,
|
| 65 |
+
'language_match': 0.15,
|
| 66 |
+
'llm_capabilities': 0.25,
|
| 67 |
+
'ocr_capabilities': 0.25
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
for metric, score in scores.items():
|
| 71 |
+
if metric in all_weights:
|
| 72 |
+
total_score += score * all_weights[metric]
|
| 73 |
+
weight_sum += all_weights[metric]
|
| 74 |
+
|
| 75 |
+
if weight_sum > 0:
|
| 76 |
+
total_score /= weight_sum
|
| 77 |
+
|
| 78 |
+
# Apply hardware penalty
|
| 79 |
+
hardware_penalty = self._check_hardware_constraints(model, requirements)
|
| 80 |
+
total_score *= hardware_penalty
|
| 81 |
+
|
| 82 |
+
scored_models.append(ModelScore(
|
| 83 |
+
model_id=model.model_id,
|
| 84 |
+
task_type=model.task_type,
|
| 85 |
+
total_score=float(total_score),
|
| 86 |
+
component_scores=scores,
|
| 87 |
+
metadata=model
|
| 88 |
+
))
|
| 89 |
+
|
| 90 |
+
return sorted(scored_models, key=lambda x: x.total_score, reverse=True)
|
| 91 |
+
|
| 92 |
+
def _score_downloads(self, downloads: int) -> float:
|
| 93 |
+
"""Score based on downloads (log scale)"""
|
| 94 |
+
if downloads <= 0:
|
| 95 |
+
return 0.0
|
| 96 |
+
log_downloads = np.log10(downloads + 1)
|
| 97 |
+
return min(log_downloads / 6.0, 1.0)
|
| 98 |
+
|
| 99 |
+
def _score_recency(self, last_modified) -> float:
|
| 100 |
+
"""Score based on recency - FIXED timezone issue"""
|
| 101 |
+
if not last_modified:
|
| 102 |
+
return 0.5
|
| 103 |
+
|
| 104 |
+
try:
|
| 105 |
+
# Make last_modified timezone-naive for comparison
|
| 106 |
+
if hasattr(last_modified, 'tzinfo') and last_modified.tzinfo is not None:
|
| 107 |
+
# Convert to timezone-naive by removing timezone info
|
| 108 |
+
last_modified = last_modified.replace(tzinfo=None)
|
| 109 |
+
|
| 110 |
+
# Get current time as timezone-naive
|
| 111 |
+
now = datetime.now()
|
| 112 |
+
|
| 113 |
+
# Calculate days difference
|
| 114 |
+
days_since_update = (now - last_modified).days
|
| 115 |
+
|
| 116 |
+
if days_since_update < 30:
|
| 117 |
+
return 1.0
|
| 118 |
+
elif days_since_update < 90:
|
| 119 |
+
return 0.8
|
| 120 |
+
elif days_since_update < 180:
|
| 121 |
+
return 0.6
|
| 122 |
+
elif days_since_update < 365:
|
| 123 |
+
return 0.4
|
| 124 |
+
else:
|
| 125 |
+
return 0.2
|
| 126 |
+
|
| 127 |
+
except Exception as e:
|
| 128 |
+
print(f" Warning: Error calculating recency: {e}")
|
| 129 |
+
return 0.5
|
| 130 |
+
|
| 131 |
+
def _score_license(self, license: str) -> float:
|
| 132 |
+
"""Score based on license"""
|
| 133 |
+
license_lower = license.lower()
|
| 134 |
+
open_licenses = ['mit', 'apache', 'bsd', 'cc', 'gpl', 'lgpl']
|
| 135 |
+
|
| 136 |
+
if any(open_license in license_lower for open_license in open_licenses):
|
| 137 |
+
return 0.9
|
| 138 |
+
elif 'commercial' in license_lower:
|
| 139 |
+
return 0.5
|
| 140 |
+
else:
|
| 141 |
+
return 0.7
|
| 142 |
+
|
| 143 |
+
def _score_size(self, model_size: Optional[float], requirements: UserRequirements) -> float:
|
| 144 |
+
"""Score based on size - smaller is better"""
|
| 145 |
+
if not model_size:
|
| 146 |
+
return 0.5
|
| 147 |
+
|
| 148 |
+
if requirements.max_model_size_gb:
|
| 149 |
+
if model_size > requirements.max_model_size_gb:
|
| 150 |
+
return 0.0
|
| 151 |
+
size_ratio = 1.0 - (model_size / requirements.max_model_size_gb)
|
| 152 |
+
return 0.5 + (size_ratio * 0.5)
|
| 153 |
+
|
| 154 |
+
# No constraint - smaller is better
|
| 155 |
+
if model_size < 0.5:
|
| 156 |
+
return 1.0
|
| 157 |
+
elif model_size < 1.0:
|
| 158 |
+
return 0.9
|
| 159 |
+
elif model_size < 2.0:
|
| 160 |
+
return 0.7
|
| 161 |
+
elif model_size < 5.0:
|
| 162 |
+
return 0.5
|
| 163 |
+
else:
|
| 164 |
+
return 0.3
|
| 165 |
+
|
| 166 |
+
def _score_performance(self, model: ModelMetadata, requirements: UserRequirements) -> float:
|
| 167 |
+
"""Score based on performance metrics"""
|
| 168 |
+
metrics = model.performance_metrics
|
| 169 |
+
|
| 170 |
+
if not metrics:
|
| 171 |
+
return 0.5
|
| 172 |
+
|
| 173 |
+
if model.task_type == TaskType.TRANSLATION:
|
| 174 |
+
# Prefer BLEU scores
|
| 175 |
+
if 'bleu' in metrics:
|
| 176 |
+
return min(metrics['bleu'] / 50, 1.0) # BLEU up to 50
|
| 177 |
+
return 0.5
|
| 178 |
+
|
| 179 |
+
elif model.task_type == TaskType.SPEECH_TO_TEXT:
|
| 180 |
+
# Prefer low WER
|
| 181 |
+
if 'wer' in metrics:
|
| 182 |
+
return max(0, 1.0 - (metrics['wer'] / 100))
|
| 183 |
+
return 0.5
|
| 184 |
+
|
| 185 |
+
elif model.task_type == TaskType.TEXT_TO_SPEECH:
|
| 186 |
+
# Prefer more voices and higher sample rate
|
| 187 |
+
score = 0.5
|
| 188 |
+
if model.voice_count > 0:
|
| 189 |
+
score += min(model.voice_count / 10, 0.3)
|
| 190 |
+
if model.sample_rate and model.sample_rate >= 16000:
|
| 191 |
+
score += 0.2
|
| 192 |
+
return min(score, 1.0)
|
| 193 |
+
|
| 194 |
+
return 0.5
|
| 195 |
+
|
| 196 |
+
def _score_language_match(self, model: ModelMetadata, requirements: UserRequirements) -> float:
|
| 197 |
+
"""Score based on language support"""
|
| 198 |
+
if requirements.task_type == TaskType.TRANSLATION and requirements.translation_reqs:
|
| 199 |
+
req = requirements.translation_reqs
|
| 200 |
+
source = req.source_language.value
|
| 201 |
+
target = req.target_language.value
|
| 202 |
+
|
| 203 |
+
score = 0.5
|
| 204 |
+
if model.source_languages and source in model.source_languages:
|
| 205 |
+
score += 0.25
|
| 206 |
+
if model.target_languages and target in model.target_languages:
|
| 207 |
+
score += 0.25
|
| 208 |
+
return score
|
| 209 |
+
|
| 210 |
+
elif requirements.task_type == TaskType.TEXT_TO_SPEECH and requirements.tts_reqs:
|
| 211 |
+
req = requirements.tts_reqs
|
| 212 |
+
lang = req.language.value
|
| 213 |
+
|
| 214 |
+
if model.languages and lang in model.languages:
|
| 215 |
+
return 1.0
|
| 216 |
+
return 0.5
|
| 217 |
+
|
| 218 |
+
elif requirements.task_type == TaskType.SPEECH_TO_TEXT and requirements.stt_reqs:
|
| 219 |
+
req = requirements.stt_reqs
|
| 220 |
+
lang = req.language.value
|
| 221 |
+
|
| 222 |
+
if model.languages and lang in model.languages:
|
| 223 |
+
return 1.0
|
| 224 |
+
return 0.5
|
| 225 |
+
|
| 226 |
+
return 0.5
|
| 227 |
+
|
| 228 |
+
def _score_llm_capabilities(self, model: ModelMetadata, requirements: UserRequirements) -> float:
|
| 229 |
+
"""Score LLM based on capabilities"""
|
| 230 |
+
if not requirements.llm_reqs:
|
| 231 |
+
return 0.5
|
| 232 |
+
|
| 233 |
+
req = requirements.llm_reqs
|
| 234 |
+
score = 0.5
|
| 235 |
+
|
| 236 |
+
# Check context length
|
| 237 |
+
if model.context_length and req.context_length:
|
| 238 |
+
if model.context_length >= req.context_length:
|
| 239 |
+
score += 0.2
|
| 240 |
+
else:
|
| 241 |
+
score -= 0.1
|
| 242 |
+
|
| 243 |
+
# Check chat template
|
| 244 |
+
if req.wants_chat_template and model.has_chat_template:
|
| 245 |
+
score += 0.2
|
| 246 |
+
|
| 247 |
+
# Check function calling
|
| 248 |
+
if req.wants_function_calling and model.supports_function_calling:
|
| 249 |
+
score += 0.2
|
| 250 |
+
|
| 251 |
+
# Check code generation
|
| 252 |
+
if req.wants_code_generation and model.supports_code:
|
| 253 |
+
score += 0.2
|
| 254 |
+
|
| 255 |
+
# Check instruction following
|
| 256 |
+
if req.wants_instruction_following and model.supports_instruction:
|
| 257 |
+
score += 0.2
|
| 258 |
+
|
| 259 |
+
# Check quantization support
|
| 260 |
+
if req.quantization and req.quantization in model.quantization_supported:
|
| 261 |
+
score += 0.1
|
| 262 |
+
|
| 263 |
+
return min(score, 1.0)
|
| 264 |
+
|
| 265 |
+
def _score_ocr_capabilities(self, model: ModelMetadata, requirements: UserRequirements) -> float:
|
| 266 |
+
"""Score OCR model based on capabilities"""
|
| 267 |
+
if not requirements.ocr_reqs:
|
| 268 |
+
return 0.5
|
| 269 |
+
|
| 270 |
+
req = requirements.ocr_reqs
|
| 271 |
+
score = 0.5
|
| 272 |
+
|
| 273 |
+
# Check handwriting support
|
| 274 |
+
if req.handwritten and model.supports_handwriting:
|
| 275 |
+
score += 0.3
|
| 276 |
+
elif req.handwritten and not model.supports_handwriting:
|
| 277 |
+
score -= 0.2
|
| 278 |
+
|
| 279 |
+
# Check layout analysis
|
| 280 |
+
if req.wants_layout_analysis and model.supports_layout:
|
| 281 |
+
score += 0.2
|
| 282 |
+
|
| 283 |
+
# Check table extraction
|
| 284 |
+
if req.wants_table_extraction and model.supports_tables:
|
| 285 |
+
score += 0.2
|
| 286 |
+
|
| 287 |
+
# Check formula recognition
|
| 288 |
+
if req.wants_formula_recognition and model.supports_formulas:
|
| 289 |
+
score += 0.2
|
| 290 |
+
|
| 291 |
+
return min(score, 1.0)
|
| 292 |
+
|
| 293 |
+
def _check_hardware_constraints(self, model: ModelMetadata,
|
| 294 |
+
requirements: UserRequirements) -> float:
|
| 295 |
+
"""Apply penalty if hardware constraints not met"""
|
| 296 |
+
if not requirements.hardware_constraints:
|
| 297 |
+
return 1.0
|
| 298 |
+
|
| 299 |
+
hardware = model.hardware_requirements
|
| 300 |
+
|
| 301 |
+
for constraint in requirements.hardware_constraints:
|
| 302 |
+
if constraint == HardwareConstraint.CPU:
|
| 303 |
+
if hardware.get('cpu_compatible', True):
|
| 304 |
+
return 1.0
|
| 305 |
+
elif constraint in [HardwareConstraint.GPU_4GB, HardwareConstraint.GPU_8GB,
|
| 306 |
+
HardwareConstraint.GPU_16GB, HardwareConstraint.GPU_24GB,
|
| 307 |
+
HardwareConstraint.GPU_40GB, HardwareConstraint.GPU_80GB]:
|
| 308 |
+
if hardware.get('gpu_required', False):
|
| 309 |
+
return 0.9 # Small penalty for requiring GPU
|
| 310 |
+
return 1.0
|
| 311 |
+
|
| 312 |
+
return 0.8
|
src/agents/input_agent.py
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Input Agent - Parses user requirements for all model types
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import re
|
| 6 |
+
from typing import List, Optional
|
| 7 |
+
|
| 8 |
+
from src.models.schemas import (
|
| 9 |
+
TaskType, HardwareConstraint, UserRequirements,
|
| 10 |
+
TranslationRequirements, TTSRequirements, STTRequirements,
|
| 11 |
+
LLMRequirements, OCRRequirements,
|
| 12 |
+
Language, VoiceType, ModelSize
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class InputAgent:
|
| 17 |
+
"""
|
| 18 |
+
Analyzes user's task description and extracts structured requirements
|
| 19 |
+
for translation, TTS, STT, LLMs, and OCR.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __init__(self):
|
| 23 |
+
# Map keywords to task types
|
| 24 |
+
self.task_keywords = {
|
| 25 |
+
# Translation
|
| 26 |
+
TaskType.TRANSLATION: [
|
| 27 |
+
"translate", "translation", "convert language", "english to french",
|
| 28 |
+
"en to es", "german", "spanish", "french"
|
| 29 |
+
],
|
| 30 |
+
|
| 31 |
+
# TTS
|
| 32 |
+
TaskType.TEXT_TO_SPEECH: [
|
| 33 |
+
"text to speech", "tts", "speech synthesis", "voice",
|
| 34 |
+
"speak", "audio from text", "read aloud"
|
| 35 |
+
],
|
| 36 |
+
|
| 37 |
+
# STT
|
| 38 |
+
TaskType.SPEECH_TO_TEXT: [
|
| 39 |
+
"speech to text", "stt", "transcribe", "audio to text",
|
| 40 |
+
"asr", "automatic speech recognition", "voice to text"
|
| 41 |
+
],
|
| 42 |
+
|
| 43 |
+
# LLMs
|
| 44 |
+
TaskType.TEXT_GENERATION: [
|
| 45 |
+
"generate", "write", "completion", "continue", "story",
|
| 46 |
+
"llm", "large language model", "gpt", "llama"
|
| 47 |
+
],
|
| 48 |
+
TaskType.CHAT: [
|
| 49 |
+
"chat", "conversation", "dialogue", "chatbot", "assistant"
|
| 50 |
+
],
|
| 51 |
+
TaskType.INSTRUCTION_FOLLOWING: [
|
| 52 |
+
"instruction", "follow instruction", "task", "command"
|
| 53 |
+
],
|
| 54 |
+
TaskType.CODE_GENERATION: [
|
| 55 |
+
"code", "programming", "python", "javascript", "function"
|
| 56 |
+
],
|
| 57 |
+
TaskType.QUESTION_ANSWERING: [
|
| 58 |
+
"question answering", "qa", "answer question", "extract answer"
|
| 59 |
+
],
|
| 60 |
+
TaskType.SUMMARIZATION: [
|
| 61 |
+
"summarize", "summary", "abstract", "condense", "tl;dr"
|
| 62 |
+
],
|
| 63 |
+
|
| 64 |
+
# OCR
|
| 65 |
+
TaskType.OCR: [
|
| 66 |
+
"ocr", "optical character recognition", "extract text from image",
|
| 67 |
+
"read image", "scan document", "image to text", "text from photo"
|
| 68 |
+
],
|
| 69 |
+
TaskType.DOCUMENT_UNDERSTANDING: [
|
| 70 |
+
"document understanding", "document qa", "document analysis",
|
| 71 |
+
"form understanding", "invoice parsing"
|
| 72 |
+
]
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
def parse_requirements(self, task_description: str) -> UserRequirements:
|
| 76 |
+
"""
|
| 77 |
+
Parse user's task description and extract requirements.
|
| 78 |
+
"""
|
| 79 |
+
task_type = self._extract_task_type(task_description)
|
| 80 |
+
hardware_constraints = self._extract_hardware_constraints(task_description)
|
| 81 |
+
max_model_size = self._extract_model_size(task_description)
|
| 82 |
+
|
| 83 |
+
# Parse task-specific requirements
|
| 84 |
+
translation_reqs = None
|
| 85 |
+
tts_reqs = None
|
| 86 |
+
stt_reqs = None
|
| 87 |
+
llm_reqs = None
|
| 88 |
+
ocr_reqs = None
|
| 89 |
+
|
| 90 |
+
if task_type == TaskType.TRANSLATION:
|
| 91 |
+
translation_reqs = self._parse_translation_requirements(task_description)
|
| 92 |
+
elif task_type == TaskType.TEXT_TO_SPEECH:
|
| 93 |
+
tts_reqs = self._parse_tts_requirements(task_description)
|
| 94 |
+
elif task_type == TaskType.SPEECH_TO_TEXT:
|
| 95 |
+
stt_reqs = self._parse_stt_requirements(task_description)
|
| 96 |
+
elif task_type in [TaskType.TEXT_GENERATION, TaskType.CHAT,
|
| 97 |
+
TaskType.INSTRUCTION_FOLLOWING, TaskType.CODE_GENERATION,
|
| 98 |
+
TaskType.QUESTION_ANSWERING, TaskType.SUMMARIZATION]:
|
| 99 |
+
llm_reqs = self._parse_llm_requirements(task_description, task_type)
|
| 100 |
+
elif task_type in [TaskType.OCR, TaskType.DOCUMENT_UNDERSTANDING]:
|
| 101 |
+
ocr_reqs = self._parse_ocr_requirements(task_description)
|
| 102 |
+
|
| 103 |
+
return UserRequirements(
|
| 104 |
+
task_type=task_type,
|
| 105 |
+
hardware_constraints=hardware_constraints,
|
| 106 |
+
max_model_size_gb=max_model_size,
|
| 107 |
+
translation_reqs=translation_reqs,
|
| 108 |
+
tts_reqs=tts_reqs,
|
| 109 |
+
stt_reqs=stt_reqs,
|
| 110 |
+
llm_reqs=llm_reqs,
|
| 111 |
+
ocr_reqs=ocr_reqs
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
def _extract_task_type(self, description: str) -> TaskType:
|
| 115 |
+
"""Identify the task from description"""
|
| 116 |
+
description_lower = description.lower()
|
| 117 |
+
|
| 118 |
+
for task_type, keywords in self.task_keywords.items():
|
| 119 |
+
if any(keyword in description_lower for keyword in keywords):
|
| 120 |
+
return task_type
|
| 121 |
+
|
| 122 |
+
# Default to text generation if unsure
|
| 123 |
+
return TaskType.TEXT_GENERATION
|
| 124 |
+
|
| 125 |
+
def _parse_translation_requirements(self, description: str) -> TranslationRequirements:
|
| 126 |
+
"""Extract translation-specific requirements"""
|
| 127 |
+
description_lower = description.lower()
|
| 128 |
+
|
| 129 |
+
# Default values
|
| 130 |
+
source_lang = Language.ENGLISH
|
| 131 |
+
target_lang = Language.SPANISH
|
| 132 |
+
|
| 133 |
+
# Try to extract language pairs
|
| 134 |
+
patterns = [
|
| 135 |
+
r'(?:from\s+)?(\w+)\s+(?:to|in(?:to)?)\s+(\w+)',
|
| 136 |
+
r'(\w+)[\s-]+to[\s-]+(\w+)',
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
+
for pattern in patterns:
|
| 140 |
+
match = re.search(pattern, description_lower)
|
| 141 |
+
if match:
|
| 142 |
+
lang1, lang2 = match.groups()
|
| 143 |
+
source_lang = self._map_language(lang1)
|
| 144 |
+
target_lang = self._map_language(lang2)
|
| 145 |
+
break
|
| 146 |
+
|
| 147 |
+
# Determine domain
|
| 148 |
+
domain = None
|
| 149 |
+
domains = ["medical", "legal", "technical", "financial", "literary"]
|
| 150 |
+
for d in domains:
|
| 151 |
+
if d in description_lower:
|
| 152 |
+
domain = d
|
| 153 |
+
break
|
| 154 |
+
|
| 155 |
+
# Quality preference
|
| 156 |
+
quality = "balanced"
|
| 157 |
+
if "fast" in description_lower or "quick" in description_lower:
|
| 158 |
+
quality = "speed"
|
| 159 |
+
elif "high quality" in description_lower or "accurate" in description_lower:
|
| 160 |
+
quality = "quality"
|
| 161 |
+
|
| 162 |
+
return TranslationRequirements(
|
| 163 |
+
source_language=source_lang,
|
| 164 |
+
target_language=target_lang,
|
| 165 |
+
domain=domain,
|
| 166 |
+
quality_preference=quality
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
def _parse_tts_requirements(self, description: str) -> TTSRequirements:
|
| 170 |
+
"""Extract TTS-specific requirements"""
|
| 171 |
+
description_lower = description.lower()
|
| 172 |
+
|
| 173 |
+
# Extract language
|
| 174 |
+
language = Language.ENGLISH
|
| 175 |
+
for lang in Language:
|
| 176 |
+
if lang.value in description_lower or lang.name.lower() in description_lower:
|
| 177 |
+
language = lang
|
| 178 |
+
break
|
| 179 |
+
|
| 180 |
+
# Extract voice type
|
| 181 |
+
voice = VoiceType.NEUTRAL
|
| 182 |
+
if "male" in description_lower:
|
| 183 |
+
voice = VoiceType.MALE
|
| 184 |
+
elif "female" in description_lower:
|
| 185 |
+
voice = VoiceType.FEMALE
|
| 186 |
+
|
| 187 |
+
# Check if multiple voices wanted
|
| 188 |
+
multiple = "multiple voices" in description_lower or "different voices" in description_lower
|
| 189 |
+
|
| 190 |
+
return TTSRequirements(
|
| 191 |
+
language=language,
|
| 192 |
+
voice_type=voice,
|
| 193 |
+
wants_multiple_voices=multiple
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
def _parse_stt_requirements(self, description: str) -> STTRequirements:
|
| 197 |
+
"""Extract STT-specific requirements"""
|
| 198 |
+
description_lower = description.lower()
|
| 199 |
+
|
| 200 |
+
# Extract language
|
| 201 |
+
language = Language.ENGLISH
|
| 202 |
+
for lang in Language:
|
| 203 |
+
if lang.value in description_lower or lang.name.lower() in description_lower:
|
| 204 |
+
language = lang
|
| 205 |
+
break
|
| 206 |
+
|
| 207 |
+
# Extract domain
|
| 208 |
+
domain = None
|
| 209 |
+
if "medical" in description_lower:
|
| 210 |
+
domain = "medical"
|
| 211 |
+
elif "telephone" in description_lower or "call" in description_lower:
|
| 212 |
+
domain = "telephony"
|
| 213 |
+
elif "meeting" in description_lower:
|
| 214 |
+
domain = "meeting"
|
| 215 |
+
|
| 216 |
+
# Check for advanced features
|
| 217 |
+
timestamps = "timestamp" in description_lower or "word timing" in description_lower
|
| 218 |
+
diarization = "speaker" in description_lower or "who said" in description_lower
|
| 219 |
+
|
| 220 |
+
return STTRequirements(
|
| 221 |
+
language=language,
|
| 222 |
+
domain=domain,
|
| 223 |
+
wants_word_timestamps=timestamps,
|
| 224 |
+
wants_diarization=diarization
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
def _parse_llm_requirements(self, description: str, task_type: TaskType) -> LLMRequirements:
|
| 228 |
+
"""Extract LLM-specific requirements"""
|
| 229 |
+
description_lower = description.lower()
|
| 230 |
+
|
| 231 |
+
# Determine model size preference
|
| 232 |
+
model_size = ModelSize.MEDIUM
|
| 233 |
+
if any(x in description_lower for x in ["tiny", "small", "lightweight", "fast"]):
|
| 234 |
+
model_size = ModelSize.SMALL
|
| 235 |
+
elif any(x in description_lower for x in ["large", "powerful", "best quality"]):
|
| 236 |
+
model_size = ModelSize.LARGE
|
| 237 |
+
elif any(x in description_lower for x in ["xlarge", "huge", "massive"]):
|
| 238 |
+
model_size = ModelSize.XXLARGE
|
| 239 |
+
|
| 240 |
+
# Context length
|
| 241 |
+
context_length = 2048 # default
|
| 242 |
+
context_match = re.search(r'(\d+)[kK]?\s*(context|token)', description_lower)
|
| 243 |
+
if context_match:
|
| 244 |
+
val = context_match.group(1)
|
| 245 |
+
if 'k' in context_match.group(0).lower():
|
| 246 |
+
context_length = int(val) * 1024
|
| 247 |
+
else:
|
| 248 |
+
context_length = int(val)
|
| 249 |
+
|
| 250 |
+
# Check for specific capabilities
|
| 251 |
+
wants_chat = any(x in description_lower for x in ["chat", "conversation", "dialogue"])
|
| 252 |
+
wants_code = any(x in description_lower for x in ["code", "programming", "python", "javascript"])
|
| 253 |
+
wants_instruction = any(x in description_lower for x in ["instruction", "task", "command"])
|
| 254 |
+
wants_function = any(x in description_lower for x in ["function calling", "tools", "actions"])
|
| 255 |
+
wants_multilingual = any(x in description_lower for x in ["multilingual", "multiple languages"])
|
| 256 |
+
|
| 257 |
+
# Check for quantization
|
| 258 |
+
quantization = None
|
| 259 |
+
if "4bit" in description_lower or "4-bit" in description_lower:
|
| 260 |
+
quantization = "4bit"
|
| 261 |
+
elif "8bit" in description_lower or "8-bit" in description_lower:
|
| 262 |
+
quantization = "8bit"
|
| 263 |
+
|
| 264 |
+
return LLMRequirements(
|
| 265 |
+
model_size=model_size,
|
| 266 |
+
context_length=context_length,
|
| 267 |
+
wants_chat_template=wants_chat,
|
| 268 |
+
wants_function_calling=wants_function,
|
| 269 |
+
wants_code_generation=wants_code,
|
| 270 |
+
wants_instruction_following=wants_instruction,
|
| 271 |
+
wants_multilingual=wants_multilingual,
|
| 272 |
+
quantization=quantization
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
def _parse_ocr_requirements(self, description: str) -> OCRRequirements:
|
| 276 |
+
"""Extract OCR-specific requirements"""
|
| 277 |
+
description_lower = description.lower()
|
| 278 |
+
|
| 279 |
+
# Extract languages
|
| 280 |
+
languages = [Language.ENGLISH]
|
| 281 |
+
for lang in Language:
|
| 282 |
+
if lang.value in description_lower or lang.name.lower() in description_lower:
|
| 283 |
+
languages = [lang]
|
| 284 |
+
break
|
| 285 |
+
|
| 286 |
+
# Check for handwriting
|
| 287 |
+
handwritten = "handwriting" in description_lower or "handwritten" in description_lower
|
| 288 |
+
|
| 289 |
+
# Document type
|
| 290 |
+
doc_type = None
|
| 291 |
+
if "scanned" in description_lower:
|
| 292 |
+
doc_type = "scanned"
|
| 293 |
+
elif "photo" in description_lower or "photograph" in description_lower:
|
| 294 |
+
doc_type = "photo"
|
| 295 |
+
elif "document" in description_lower:
|
| 296 |
+
doc_type = "document"
|
| 297 |
+
|
| 298 |
+
# Check for advanced features
|
| 299 |
+
layout = "layout" in description_lower or "paragraph" in description_lower
|
| 300 |
+
tables = "table" in description_lower or "spreadsheet" in description_lower
|
| 301 |
+
formulas = any(x in description_lower for x in ["formula", "equation", "math"])
|
| 302 |
+
|
| 303 |
+
return OCRRequirements(
|
| 304 |
+
languages=languages,
|
| 305 |
+
handwritten=handwritten,
|
| 306 |
+
document_type=doc_type,
|
| 307 |
+
wants_layout_analysis=layout,
|
| 308 |
+
wants_table_extraction=tables,
|
| 309 |
+
wants_formula_recognition=formulas
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
def _map_language(self, lang_text: str) -> Language:
|
| 313 |
+
"""Map language name/code to Language enum"""
|
| 314 |
+
lang_map = {
|
| 315 |
+
"en": Language.ENGLISH, "english": Language.ENGLISH,
|
| 316 |
+
"es": Language.SPANISH, "spanish": Language.SPANISH,
|
| 317 |
+
"fr": Language.FRENCH, "french": Language.FRENCH,
|
| 318 |
+
"de": Language.GERMAN, "german": Language.GERMAN,
|
| 319 |
+
"it": Language.ITALIAN, "italian": Language.ITALIAN,
|
| 320 |
+
"pt": Language.PORTUGUESE, "portuguese": Language.PORTUGUESE,
|
| 321 |
+
"nl": Language.DUTCH, "dutch": Language.DUTCH,
|
| 322 |
+
"ru": Language.RUSSIAN, "russian": Language.RUSSIAN,
|
| 323 |
+
"zh": Language.CHINESE, "chinese": Language.CHINESE,
|
| 324 |
+
"ja": Language.JAPANESE, "japanese": Language.JAPANESE,
|
| 325 |
+
"ko": Language.KOREAN, "korean": Language.KOREAN,
|
| 326 |
+
"ar": Language.ARABIC, "arabic": Language.ARABIC,
|
| 327 |
+
"hi": Language.HINDI, "hindi": Language.HINDI
|
| 328 |
+
}
|
| 329 |
+
return lang_map.get(lang_text.lower(), Language.ENGLISH)
|
| 330 |
+
|
| 331 |
+
def _extract_hardware_constraints(self, description: str) -> List[HardwareConstraint]:
|
| 332 |
+
"""Extract hardware constraints"""
|
| 333 |
+
constraints = []
|
| 334 |
+
description_lower = description.lower()
|
| 335 |
+
|
| 336 |
+
if any(word in description_lower for word in ["cpu", "no gpu", "without gpu"]):
|
| 337 |
+
constraints.append(HardwareConstraint.CPU)
|
| 338 |
+
if any(word in description_lower for word in ["4gb", "4 gb", "small gpu"]):
|
| 339 |
+
constraints.append(HardwareConstraint.GPU_4GB)
|
| 340 |
+
if any(word in description_lower for word in ["8gb", "8 gb", "medium gpu"]):
|
| 341 |
+
constraints.append(HardwareConstraint.GPU_8GB)
|
| 342 |
+
if any(word in description_lower for word in ["16gb", "16 gb"]):
|
| 343 |
+
constraints.append(HardwareConstraint.GPU_16GB)
|
| 344 |
+
if any(word in description_lower for word in ["24gb", "24 gb"]):
|
| 345 |
+
constraints.append(HardwareConstraint.GPU_24GB)
|
| 346 |
+
if any(word in description_lower for word in ["40gb", "40 gb", "a100"]):
|
| 347 |
+
constraints.append(HardwareConstraint.GPU_40GB)
|
| 348 |
+
if any(word in description_lower for word in ["80gb", "80 gb"]):
|
| 349 |
+
constraints.append(HardwareConstraint.GPU_80GB)
|
| 350 |
+
if "tpu" in description_lower:
|
| 351 |
+
constraints.append(HardwareConstraint.TPU)
|
| 352 |
+
|
| 353 |
+
return constraints if constraints else [HardwareConstraint.CPU]
|
| 354 |
+
|
| 355 |
+
def _extract_model_size(self, description: str) -> Optional[float]:
|
| 356 |
+
"""Extract maximum model size constraint"""
|
| 357 |
+
size_pattern = r'(\d+(?:\.\d+)?)\s*(gb|mb)'
|
| 358 |
+
match = re.search(size_pattern, description.lower())
|
| 359 |
+
|
| 360 |
+
if match:
|
| 361 |
+
size = float(match.group(1))
|
| 362 |
+
unit = match.group(2).lower()
|
| 363 |
+
|
| 364 |
+
if unit == 'gb':
|
| 365 |
+
return size
|
| 366 |
+
elif unit == 'mb':
|
| 367 |
+
return size / 1024
|
| 368 |
+
|
| 369 |
+
return None
|
src/agents/research_agent.py
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Research Agent - Searches HuggingFace for relevant models
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import asyncio
|
| 6 |
+
import aiohttp
|
| 7 |
+
import re
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from typing import List, Optional
|
| 10 |
+
from huggingface_hub import HfApi
|
| 11 |
+
|
| 12 |
+
from src.models.schemas import UserRequirements, ModelMetadata, TaskType
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ResearchAgent:
|
| 16 |
+
"""
|
| 17 |
+
Searches HuggingFace Hub for models matching user requirements.
|
| 18 |
+
|
| 19 |
+
This agent queries the HuggingFace API, fetches model details,
|
| 20 |
+
and creates ModelMetadata objects for candidate models.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
# Pipeline tags for different tasks
|
| 24 |
+
PIPELINE_TAGS = {
|
| 25 |
+
# Translation
|
| 26 |
+
TaskType.TRANSLATION: "translation",
|
| 27 |
+
|
| 28 |
+
# Speech
|
| 29 |
+
TaskType.TEXT_TO_SPEECH: "text-to-speech",
|
| 30 |
+
TaskType.SPEECH_TO_TEXT: "automatic-speech-recognition",
|
| 31 |
+
|
| 32 |
+
# LLMs
|
| 33 |
+
TaskType.TEXT_GENERATION: "text-generation",
|
| 34 |
+
TaskType.CHAT: "text-generation",
|
| 35 |
+
TaskType.INSTRUCTION_FOLLOWING: "text-generation",
|
| 36 |
+
TaskType.CODE_GENERATION: "text-generation",
|
| 37 |
+
TaskType.QUESTION_ANSWERING: "question-answering",
|
| 38 |
+
TaskType.SUMMARIZATION: "summarization",
|
| 39 |
+
|
| 40 |
+
# OCR
|
| 41 |
+
TaskType.OCR: "image-to-text",
|
| 42 |
+
TaskType.DOCUMENT_UNDERSTANDING: "document-question-answering",
|
| 43 |
+
|
| 44 |
+
#Legacy
|
| 45 |
+
TaskType.TEXT_CLASSIFICATION: "text-classification",
|
| 46 |
+
TaskType.NAMED_ENTITY_RECOGNITION: "token-classification",
|
| 47 |
+
TaskType.IMAGE_CLASSIFICATION: "image-classification",
|
| 48 |
+
TaskType.OBJECT_DETECTION: "object-detection",
|
| 49 |
+
TaskType.ZERO_SHOT_CLASSIFICATION: "zero-shot-classification"
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
def __init__(self):
|
| 53 |
+
self.api = HfApi()
|
| 54 |
+
|
| 55 |
+
async def search_models(self, requirements: UserRequirements, top_k: int = 20) -> List[ModelMetadata]:
|
| 56 |
+
"""
|
| 57 |
+
Search for models matching the requirements.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
requirements: UserRequirements object
|
| 61 |
+
top_k: Maximum number of models to return
|
| 62 |
+
|
| 63 |
+
Returns:
|
| 64 |
+
List of ModelMetadata objects
|
| 65 |
+
"""
|
| 66 |
+
task = requirements.task_type
|
| 67 |
+
pipeline_tag = self.PIPELINE_TAGS.get(task)
|
| 68 |
+
|
| 69 |
+
print(f" Searching for {task.value} models (pipeline: {pipeline_tag})...")
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
models = []
|
| 73 |
+
|
| 74 |
+
# Method 1: Search by pipeline_tag filter (current API)
|
| 75 |
+
if pipeline_tag:
|
| 76 |
+
try:
|
| 77 |
+
print(f" Method 1: Using pipeline_tag filter...")
|
| 78 |
+
models = list(self.api.list_models(
|
| 79 |
+
filter=f"pipeline_tag:{pipeline_tag}",
|
| 80 |
+
sort="downloads",
|
| 81 |
+
limit=top_k * 2
|
| 82 |
+
))
|
| 83 |
+
print(f" Found {len(models)} models")
|
| 84 |
+
except Exception as e:
|
| 85 |
+
print(f" Method 1 failed: {e}")
|
| 86 |
+
models = []
|
| 87 |
+
|
| 88 |
+
# Method 1.5: For translation, try specific translation tags
|
| 89 |
+
if not models and task == TaskType.TRANSLATION:
|
| 90 |
+
try:
|
| 91 |
+
print(f" Method 1.5: Trying specific translation tags...")
|
| 92 |
+
# Try common translation model patterns
|
| 93 |
+
search_terms = ["translation", "mbart", "nllb", "m2m", "opus"]
|
| 94 |
+
for term in search_terms:
|
| 95 |
+
try:
|
| 96 |
+
term_models = list(self.api.list_models(
|
| 97 |
+
search=term,
|
| 98 |
+
sort="downloads",
|
| 99 |
+
limit=top_k
|
| 100 |
+
))
|
| 101 |
+
models.extend(term_models)
|
| 102 |
+
print(f" Found {len(term_models)} models with '{term}'")
|
| 103 |
+
except:
|
| 104 |
+
continue
|
| 105 |
+
# Remove duplicates
|
| 106 |
+
unique_ids = set()
|
| 107 |
+
unique_models = []
|
| 108 |
+
for m in models:
|
| 109 |
+
if m.modelId not in unique_ids:
|
| 110 |
+
unique_ids.add(m.modelId)
|
| 111 |
+
unique_models.append(m)
|
| 112 |
+
models = unique_models
|
| 113 |
+
print(f" Total unique models after search: {len(models)}")
|
| 114 |
+
except Exception as e:
|
| 115 |
+
print(f" Method 1.5 failed: {e}")
|
| 116 |
+
|
| 117 |
+
# Method 2: Try without filter (get popular models)
|
| 118 |
+
if not models:
|
| 119 |
+
try:
|
| 120 |
+
print(f" Method 2: Getting popular models...")
|
| 121 |
+
models = list(self.api.list_models(
|
| 122 |
+
sort="downloads",
|
| 123 |
+
limit=top_k * 2
|
| 124 |
+
))
|
| 125 |
+
print(f" Found {len(models)} models")
|
| 126 |
+
except Exception as e:
|
| 127 |
+
print(f" Method 2 failed: {e}")
|
| 128 |
+
models = []
|
| 129 |
+
|
| 130 |
+
# Method 3: Try with search parameter
|
| 131 |
+
if not models and pipeline_tag:
|
| 132 |
+
try:
|
| 133 |
+
print(f" Method 3: Using search parameter...")
|
| 134 |
+
models = list(self.api.list_models(
|
| 135 |
+
search=pipeline_tag,
|
| 136 |
+
sort="downloads",
|
| 137 |
+
limit=top_k * 2
|
| 138 |
+
))
|
| 139 |
+
print(f" Found {len(models)} models")
|
| 140 |
+
except Exception as e:
|
| 141 |
+
print(f" Method 3 failed: {e}")
|
| 142 |
+
models = []
|
| 143 |
+
|
| 144 |
+
print(f" Total candidate models: {len(models)}")
|
| 145 |
+
|
| 146 |
+
if not models:
|
| 147 |
+
print(" No models found from HuggingFace API")
|
| 148 |
+
return []
|
| 149 |
+
|
| 150 |
+
# Fetch detailed metadata
|
| 151 |
+
model_details = []
|
| 152 |
+
for i, model in enumerate(models[:top_k]):
|
| 153 |
+
try:
|
| 154 |
+
print(f" Processing {i+1}/{min(len(models), top_k)}: {model.modelId}")
|
| 155 |
+
|
| 156 |
+
metadata = await self._fetch_model_details(model, requirements)
|
| 157 |
+
if metadata:
|
| 158 |
+
model_details.append(metadata)
|
| 159 |
+
print(f" Added to candidates")
|
| 160 |
+
|
| 161 |
+
await asyncio.sleep(0.1)
|
| 162 |
+
|
| 163 |
+
except Exception as e:
|
| 164 |
+
print(f" Error: {e}")
|
| 165 |
+
continue
|
| 166 |
+
|
| 167 |
+
print(f" Found {len(model_details)} suitable models")
|
| 168 |
+
return model_details
|
| 169 |
+
|
| 170 |
+
except Exception as e:
|
| 171 |
+
print(f" Error searching models: {e}")
|
| 172 |
+
import traceback
|
| 173 |
+
traceback.print_exc()
|
| 174 |
+
return []
|
| 175 |
+
|
| 176 |
+
async def _fetch_model_details(self, model, requirements: UserRequirements) -> Optional[ModelMetadata]:
|
| 177 |
+
"""Fetch detailed information for a specific model"""
|
| 178 |
+
try:
|
| 179 |
+
# Get model info from HuggingFace
|
| 180 |
+
model_info = self.api.model_info(model.modelId)
|
| 181 |
+
|
| 182 |
+
# Fetch model card content (README.md)
|
| 183 |
+
model_card = await self._fetch_model_card(model.modelId)
|
| 184 |
+
|
| 185 |
+
# Extract performance metrics from model card
|
| 186 |
+
performance_metrics = self._extract_performance_metrics(model_card)
|
| 187 |
+
|
| 188 |
+
# Estimate model size
|
| 189 |
+
model_size = self._estimate_model_size(model_info)
|
| 190 |
+
|
| 191 |
+
# Filter by size constraint if specified
|
| 192 |
+
if requirements.max_model_size_gb and model_size:
|
| 193 |
+
if model_size > requirements.max_model_size_gb:
|
| 194 |
+
return None
|
| 195 |
+
|
| 196 |
+
# Extract language tags
|
| 197 |
+
languages = ["en"] # Default
|
| 198 |
+
if hasattr(model_info, 'tags'):
|
| 199 |
+
for tag in model_info.tags:
|
| 200 |
+
if tag.startswith("language:"):
|
| 201 |
+
languages = [tag.replace("language:", "")]
|
| 202 |
+
break
|
| 203 |
+
|
| 204 |
+
# Get license from card data
|
| 205 |
+
license_info = "unknown"
|
| 206 |
+
if hasattr(model_info, 'cardData') and model_info.cardData:
|
| 207 |
+
license_info = model_info.cardData.get("license", "unknown")
|
| 208 |
+
|
| 209 |
+
# Get tags
|
| 210 |
+
tags = getattr(model_info, 'tags', [])
|
| 211 |
+
|
| 212 |
+
# Get pipeline tag
|
| 213 |
+
pipeline_tag = getattr(model_info, 'pipeline_tag', None)
|
| 214 |
+
|
| 215 |
+
# Get LLM-specific info
|
| 216 |
+
llm_info = self._extract_llm_info(model_info, model_card)
|
| 217 |
+
|
| 218 |
+
# Get OCR-specific info
|
| 219 |
+
ocr_info = self._extract_ocr_info(model_info, model_card)
|
| 220 |
+
|
| 221 |
+
# Determine task type from pipeline tag
|
| 222 |
+
task_type = self._map_pipeline_to_task(pipeline_tag) or requirements.task_type
|
| 223 |
+
|
| 224 |
+
# Extract source and target languages for translation
|
| 225 |
+
source_languages = self._extract_source_languages(model_info, model_card)
|
| 226 |
+
target_languages = self._extract_target_languages(model_info, model_card)
|
| 227 |
+
|
| 228 |
+
# Extract voice count for TTS
|
| 229 |
+
voice_count = self._extract_voice_count(model_card)
|
| 230 |
+
|
| 231 |
+
# Extract sample rate for audio models
|
| 232 |
+
sample_rate = self._extract_sample_rate(model_card)
|
| 233 |
+
|
| 234 |
+
# Extract WER for STT
|
| 235 |
+
wer_score = self._extract_wer(model_card)
|
| 236 |
+
|
| 237 |
+
return ModelMetadata(
|
| 238 |
+
model_id=model.modelId,
|
| 239 |
+
task_type=task_type,
|
| 240 |
+
downloads=getattr(model_info, 'downloads', 0) or 0,
|
| 241 |
+
likes=getattr(model_info, 'likes', 0) or 0,
|
| 242 |
+
last_modified=getattr(model_info, 'lastModified', datetime.now()),
|
| 243 |
+
license=license_info,
|
| 244 |
+
model_size=model_size,
|
| 245 |
+
languages=languages,
|
| 246 |
+
tags=tags,
|
| 247 |
+
pipeline_tag=pipeline_tag,
|
| 248 |
+
base_model=getattr(model_info, 'base_model', None),
|
| 249 |
+
finetuned_from=getattr(model_info, 'finetuned_from', None),
|
| 250 |
+
performance_metrics=performance_metrics,
|
| 251 |
+
hardware_requirements=self._extract_hardware_info(model_card),
|
| 252 |
+
model_card_content=model_card,
|
| 253 |
+
# Additional fields
|
| 254 |
+
source_languages=source_languages,
|
| 255 |
+
target_languages=target_languages,
|
| 256 |
+
voice_count=voice_count,
|
| 257 |
+
sample_rate=sample_rate,
|
| 258 |
+
wer_score=wer_score,
|
| 259 |
+
context_length=llm_info.get("context_length"),
|
| 260 |
+
has_chat_template=llm_info.get("has_chat_template", False),
|
| 261 |
+
supports_function_calling=llm_info.get("supports_function_calling", False),
|
| 262 |
+
supports_code=llm_info.get("supports_code", False),
|
| 263 |
+
supports_instruction=llm_info.get("supports_instruction", False),
|
| 264 |
+
quantization_supported=llm_info.get("quantization_supported", []),
|
| 265 |
+
supports_handwriting=ocr_info.get("supports_handwriting", False),
|
| 266 |
+
supports_layout=ocr_info.get("supports_layout", False),
|
| 267 |
+
supports_tables=ocr_info.get("supports_tables", False),
|
| 268 |
+
supports_formulas=ocr_info.get("supports_formulas", False),
|
| 269 |
+
supported_image_formats=ocr_info.get("supported_image_formats", ["jpg", "png"])
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
except Exception as e:
|
| 273 |
+
print(f" Error fetching details: {e}")
|
| 274 |
+
return None
|
| 275 |
+
|
| 276 |
+
async def _fetch_model_card(self, model_id: str) -> str:
|
| 277 |
+
"""Fetch model card content from HuggingFace"""
|
| 278 |
+
try:
|
| 279 |
+
card_url = f"https://huggingface.co/{model_id}/raw/main/README.md"
|
| 280 |
+
|
| 281 |
+
async with aiohttp.ClientSession() as session:
|
| 282 |
+
async with session.get(card_url, timeout=10) as response:
|
| 283 |
+
if response.status == 200:
|
| 284 |
+
return await response.text()
|
| 285 |
+
return ""
|
| 286 |
+
except Exception:
|
| 287 |
+
return ""
|
| 288 |
+
|
| 289 |
+
def _extract_performance_metrics(self, model_card: str) -> dict:
|
| 290 |
+
"""Extract performance metrics from model card text"""
|
| 291 |
+
metrics = {}
|
| 292 |
+
|
| 293 |
+
# Common metrics to look for
|
| 294 |
+
metric_patterns = {
|
| 295 |
+
"accuracy": r"accuracy[\s:]*([\d\.]+)%?",
|
| 296 |
+
"f1": r"f1[\s:]*([\d\.]+)",
|
| 297 |
+
"bleu": r"bleu[\s:]*([\d\.]+)",
|
| 298 |
+
"rouge": r"rouge[\s:]*([\d\.]+)",
|
| 299 |
+
"perplexity": r"perplexity[\s:]*([\d\.]+)",
|
| 300 |
+
"precision": r"precision[\s:]*([\d\.]+)",
|
| 301 |
+
"recall": r"recall[\s:]*([\d\.]+)"
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
for metric, pattern in metric_patterns.items():
|
| 305 |
+
matches = re.findall(pattern, model_card.lower())
|
| 306 |
+
if matches:
|
| 307 |
+
try:
|
| 308 |
+
metrics[metric] = float(matches[0])
|
| 309 |
+
except ValueError:
|
| 310 |
+
pass
|
| 311 |
+
|
| 312 |
+
return metrics
|
| 313 |
+
|
| 314 |
+
def _estimate_model_size(self, model_info) -> Optional[float]:
|
| 315 |
+
"""Estimate model size in GB"""
|
| 316 |
+
try:
|
| 317 |
+
# Try to get from config
|
| 318 |
+
if hasattr(model_info, 'config') and model_info.config:
|
| 319 |
+
param_size = model_info.config.get("num_parameters", 0)
|
| 320 |
+
if param_size:
|
| 321 |
+
# Rough estimate: 4 bytes per parameter (float32)
|
| 322 |
+
size_gb = (param_size * 4) / (1024 ** 3)
|
| 323 |
+
return size_gb
|
| 324 |
+
|
| 325 |
+
# Alternative: look for safetensors files
|
| 326 |
+
if hasattr(model_info, 'siblings'):
|
| 327 |
+
total_size = 0
|
| 328 |
+
for sibling in model_info.siblings:
|
| 329 |
+
if hasattr(sibling, 'rfilename') and sibling.rfilename.endswith(('.safetensors', '.bin')):
|
| 330 |
+
if hasattr(sibling, 'size'):
|
| 331 |
+
total_size += sibling.size
|
| 332 |
+
if total_size > 0:
|
| 333 |
+
return total_size / (1024 ** 3) # Convert to GB
|
| 334 |
+
except Exception:
|
| 335 |
+
pass
|
| 336 |
+
|
| 337 |
+
# Default size based on model name patterns
|
| 338 |
+
model_id = model_info.modelId.lower() if hasattr(model_info, 'modelId') else ""
|
| 339 |
+
|
| 340 |
+
if any(x in model_id for x in ['tiny', 'mini', 'albert']):
|
| 341 |
+
return 0.05 # 50MB
|
| 342 |
+
elif any(x in model_id for x in ['small', 'distilbert']):
|
| 343 |
+
return 0.2 # 200MB
|
| 344 |
+
elif any(x in model_id for x in ['base', 'bert-base']):
|
| 345 |
+
return 0.5 # 500MB
|
| 346 |
+
elif any(x in model_id for x in ['large', 'bert-large']):
|
| 347 |
+
return 1.5 # 1.5GB
|
| 348 |
+
elif any(x in model_id for x in ['xl', 'gpt2-xl']):
|
| 349 |
+
return 3.0 # 3GB
|
| 350 |
+
|
| 351 |
+
return None
|
| 352 |
+
|
| 353 |
+
def _extract_hardware_info(self, model_card: str) -> dict:
|
| 354 |
+
"""Extract hardware requirements from model card"""
|
| 355 |
+
hardware = {
|
| 356 |
+
"cpu_compatible": True, # Assume CPU compatible by default
|
| 357 |
+
"gpu_required": False,
|
| 358 |
+
"tpu_compatible": False,
|
| 359 |
+
"min_ram_gb": 4 # Default assumption
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
card_lower = model_card.lower()
|
| 363 |
+
|
| 364 |
+
if "gpu" in card_lower and "no gpu" not in card_lower:
|
| 365 |
+
hardware["gpu_required"] = True
|
| 366 |
+
if "tpu" in card_lower:
|
| 367 |
+
hardware["tpu_compatible"] = True
|
| 368 |
+
|
| 369 |
+
# Look for RAM requirements
|
| 370 |
+
ram_match = re.search(r'(\d+)\s*gb?\s*ram', card_lower)
|
| 371 |
+
if ram_match:
|
| 372 |
+
hardware["min_ram_gb"] = int(ram_match.group(1))
|
| 373 |
+
|
| 374 |
+
return hardware
|
| 375 |
+
|
| 376 |
+
def _extract_source_languages(self, model_info, model_card: str) -> List[str]:
|
| 377 |
+
"""Extract source languages for translation models"""
|
| 378 |
+
languages = []
|
| 379 |
+
if hasattr(model_info, 'cardData') and model_info.cardData:
|
| 380 |
+
src_langs = model_info.cardData.get("src_lang", [])
|
| 381 |
+
if src_langs:
|
| 382 |
+
if isinstance(src_langs, str):
|
| 383 |
+
languages = [src_langs]
|
| 384 |
+
elif isinstance(src_langs, list):
|
| 385 |
+
languages = src_langs
|
| 386 |
+
return languages or ["en"]
|
| 387 |
+
|
| 388 |
+
def _extract_target_languages(self, model_info, model_card: str) -> List[str]:
|
| 389 |
+
"""Extract target languages for translation models"""
|
| 390 |
+
languages = []
|
| 391 |
+
if hasattr(model_info, 'cardData') and model_info.cardData:
|
| 392 |
+
tgt_langs = model_info.cardData.get("tgt_lang", [])
|
| 393 |
+
if tgt_langs:
|
| 394 |
+
if isinstance(tgt_langs, str):
|
| 395 |
+
languages = [tgt_langs]
|
| 396 |
+
elif isinstance(tgt_langs, list):
|
| 397 |
+
languages = tgt_langs
|
| 398 |
+
return languages or ["en"]
|
| 399 |
+
|
| 400 |
+
def _extract_voice_count(self, model_card: str) -> int:
|
| 401 |
+
"""Extract number of voices for TTS models"""
|
| 402 |
+
patterns = [
|
| 403 |
+
r'(\d+)\s+voices?',
|
| 404 |
+
r'voices?:?\s*(\d+)',
|
| 405 |
+
r'multi-voice.*?(\d+)',
|
| 406 |
+
]
|
| 407 |
+
|
| 408 |
+
for pattern in patterns:
|
| 409 |
+
match = re.search(pattern, model_card.lower())
|
| 410 |
+
if match:
|
| 411 |
+
try:
|
| 412 |
+
return int(match.group(1))
|
| 413 |
+
except:
|
| 414 |
+
pass
|
| 415 |
+
return 0
|
| 416 |
+
|
| 417 |
+
def _extract_sample_rate(self, model_card: str) -> Optional[int]:
|
| 418 |
+
"""Extract sample rate for audio models"""
|
| 419 |
+
patterns = [
|
| 420 |
+
r'(\d+)\s*[kK]?[hH][zZ]',
|
| 421 |
+
r'sample rate:?\s*(\d+)',
|
| 422 |
+
]
|
| 423 |
+
|
| 424 |
+
for pattern in patterns:
|
| 425 |
+
match = re.search(pattern, model_card.lower())
|
| 426 |
+
if match:
|
| 427 |
+
try:
|
| 428 |
+
rate = int(match.group(1))
|
| 429 |
+
if 'k' in match.group(0).lower():
|
| 430 |
+
rate *= 1000
|
| 431 |
+
return rate
|
| 432 |
+
except:
|
| 433 |
+
pass
|
| 434 |
+
return None
|
| 435 |
+
|
| 436 |
+
def _extract_wer(self, model_card: str) -> Optional[float]:
|
| 437 |
+
"""Extract Word Error Rate for STT models"""
|
| 438 |
+
patterns = [
|
| 439 |
+
r'wer:?\s*([\d.]+)%?',
|
| 440 |
+
r'word error rate:?\s*([\d.]+)%?',
|
| 441 |
+
r'wer[\s=]+([\d.]+)',
|
| 442 |
+
]
|
| 443 |
+
|
| 444 |
+
for pattern in patterns:
|
| 445 |
+
match = re.search(pattern, model_card.lower())
|
| 446 |
+
if match:
|
| 447 |
+
try:
|
| 448 |
+
return float(match.group(1))
|
| 449 |
+
except:
|
| 450 |
+
pass
|
| 451 |
+
return None
|
| 452 |
+
|
| 453 |
+
def _extract_llm_info(self, model_info, model_card: str) -> dict:
|
| 454 |
+
"""Extract LLM-specific information"""
|
| 455 |
+
info = {
|
| 456 |
+
"context_length": 2048, # default
|
| 457 |
+
"has_chat_template": False,
|
| 458 |
+
"supports_function_calling": False,
|
| 459 |
+
"supports_code": False,
|
| 460 |
+
"supports_instruction": False,
|
| 461 |
+
"quantization_supported": []
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
card_lower = model_card.lower()
|
| 465 |
+
|
| 466 |
+
# Check context length
|
| 467 |
+
context_patterns = [
|
| 468 |
+
r'context length:?\s*(\d+)[kK]?',
|
| 469 |
+
r'max[ _]?length:?\s*(\d+)[kK]?',
|
| 470 |
+
r'(\d+)[kK]\s*(?:context|tokens)'
|
| 471 |
+
]
|
| 472 |
+
|
| 473 |
+
for pattern in context_patterns:
|
| 474 |
+
match = re.search(pattern, card_lower)
|
| 475 |
+
if match:
|
| 476 |
+
val = int(match.group(1))
|
| 477 |
+
if 'k' in match.group(0).lower():
|
| 478 |
+
info["context_length"] = val * 1024
|
| 479 |
+
else:
|
| 480 |
+
info["context_length"] = val
|
| 481 |
+
break
|
| 482 |
+
|
| 483 |
+
# Check for chat template
|
| 484 |
+
info["has_chat_template"] = "chat template" in card_lower or "conversation" in card_lower
|
| 485 |
+
|
| 486 |
+
# Check for function calling
|
| 487 |
+
info["supports_function_calling"] = any(x in card_lower for x in
|
| 488 |
+
["function calling", "tools", "function call", "tool use"])
|
| 489 |
+
|
| 490 |
+
# Check for code generation
|
| 491 |
+
info["supports_code"] = any(x in card_lower for x in
|
| 492 |
+
["code generation", "programming", "python", "javascript"])
|
| 493 |
+
|
| 494 |
+
# Check for instruction following
|
| 495 |
+
info["supports_instruction"] = "instruction" in card_lower
|
| 496 |
+
|
| 497 |
+
# Check quantization support
|
| 498 |
+
if "4bit" in card_lower or "4-bit" in card_lower:
|
| 499 |
+
info["quantization_supported"].append("4bit")
|
| 500 |
+
if "8bit" in card_lower or "8-bit" in card_lower:
|
| 501 |
+
info["quantization_supported"].append("8bit")
|
| 502 |
+
|
| 503 |
+
return info
|
| 504 |
+
|
| 505 |
+
def _extract_ocr_info(self, model_info, model_card: str) -> dict:
|
| 506 |
+
"""Extract OCR-specific information"""
|
| 507 |
+
info = {
|
| 508 |
+
"supports_handwriting": False,
|
| 509 |
+
"supports_layout": False,
|
| 510 |
+
"supports_tables": False,
|
| 511 |
+
"supports_formulas": False,
|
| 512 |
+
"supported_image_formats": ["jpg", "png"] # default
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
card_lower = model_card.lower()
|
| 516 |
+
|
| 517 |
+
# Check for handwriting
|
| 518 |
+
info["supports_handwriting"] = "handwriting" in card_lower or "handwritten" in card_lower
|
| 519 |
+
|
| 520 |
+
# Check for layout analysis
|
| 521 |
+
info["supports_layout"] = any(x in card_lower for x in
|
| 522 |
+
["layout", "paragraph", "document structure"])
|
| 523 |
+
|
| 524 |
+
# Check for table extraction
|
| 525 |
+
info["supports_tables"] = any(x in card_lower for x in
|
| 526 |
+
["table", "spreadsheet", "tabular"])
|
| 527 |
+
|
| 528 |
+
# Check for formula recognition
|
| 529 |
+
info["supports_formulas"] = any(x in card_lower for x in
|
| 530 |
+
["formula", "equation", "math", "latex"])
|
| 531 |
+
|
| 532 |
+
# Check image formats
|
| 533 |
+
formats = []
|
| 534 |
+
for fmt in ["jpg", "jpeg", "png", "tiff", "bmp", "pdf"]:
|
| 535 |
+
if fmt in card_lower:
|
| 536 |
+
formats.append(fmt)
|
| 537 |
+
if formats:
|
| 538 |
+
info["supported_image_formats"] = formats
|
| 539 |
+
|
| 540 |
+
return info
|
| 541 |
+
|
| 542 |
+
def _map_pipeline_to_task(self, pipeline_tag: Optional[str]) -> Optional[TaskType]:
|
| 543 |
+
"""Map HuggingFace pipeline tag to our TaskType"""
|
| 544 |
+
mapping = {
|
| 545 |
+
"translation": TaskType.TRANSLATION,
|
| 546 |
+
"text-to-speech": TaskType.TEXT_TO_SPEECH,
|
| 547 |
+
"automatic-speech-recognition": TaskType.SPEECH_TO_TEXT,
|
| 548 |
+
"text-generation": TaskType.TEXT_GENERATION,
|
| 549 |
+
"question-answering": TaskType.QUESTION_ANSWERING,
|
| 550 |
+
"summarization": TaskType.SUMMARIZATION,
|
| 551 |
+
"image-to-text": TaskType.OCR,
|
| 552 |
+
|
| 553 |
+
"document-question-answering": TaskType.DOCUMENT_UNDERSTANDING,
|
| 554 |
+
"text-classification": TaskType.TEXT_CLASSIFICATION,
|
| 555 |
+
"token-classification": TaskType.NAMED_ENTITY_RECOGNITION,
|
| 556 |
+
"image-classification": TaskType.IMAGE_CLASSIFICATION,
|
| 557 |
+
"object-detection": TaskType.OBJECT_DETECTION,
|
| 558 |
+
"zero-shot-classification": TaskType.ZERO_SHOT_CLASSIFICATION
|
| 559 |
+
}
|
| 560 |
+
return mapping.get(pipeline_tag) if pipeline_tag else None
|
src/config/__init__.py
ADDED
|
File without changes
|
src/main.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Main orchestrator for the HuggingFace Model Selector
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
# Add the project root to Python path
|
| 10 |
+
project_root = str(Path(__file__).parent.parent.absolute())
|
| 11 |
+
if project_root not in sys.path:
|
| 12 |
+
sys.path.insert(0, project_root)
|
| 13 |
+
|
| 14 |
+
import asyncio
|
| 15 |
+
from typing import Optional
|
| 16 |
+
|
| 17 |
+
from src.agents.input_agent import InputAgent
|
| 18 |
+
from src.agents.research_agent import ResearchAgent
|
| 19 |
+
from src.agents.evaluation_agent import EvaluationAgent
|
| 20 |
+
from src.agents.benchmarking_agent import BenchmarkingAgent
|
| 21 |
+
from src.agents.deployment_agent import DeploymentAgent
|
| 22 |
+
from src.models.schemas import DeploymentType, SelectionResult, TaskType
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class HuggingFaceModelSelector:
|
| 26 |
+
"""
|
| 27 |
+
Main orchestrator that coordinates all agents.
|
| 28 |
+
|
| 29 |
+
This class:
|
| 30 |
+
1. Takes a task description
|
| 31 |
+
2. Parses requirements
|
| 32 |
+
3. Searches for models
|
| 33 |
+
4. Evaluates and scores them
|
| 34 |
+
5. Benchmarks top models
|
| 35 |
+
6. Generates deployment code
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __init__(self):
|
| 39 |
+
self.input_agent = InputAgent()
|
| 40 |
+
self.research_agent = ResearchAgent()
|
| 41 |
+
self.evaluation_agent = EvaluationAgent()
|
| 42 |
+
self.benchmarking_agent = BenchmarkingAgent()
|
| 43 |
+
self.deployment_agent = DeploymentAgent()
|
| 44 |
+
|
| 45 |
+
async def select_and_deploy(self,
|
| 46 |
+
task_description: str,
|
| 47 |
+
deployment_type: DeploymentType = DeploymentType.FASTAPI,
|
| 48 |
+
benchmark: bool = True,
|
| 49 |
+
top_k: int = 5) -> SelectionResult:
|
| 50 |
+
"""
|
| 51 |
+
Complete pipeline: select best model and generate deployment code.
|
| 52 |
+
|
| 53 |
+
Args:
|
| 54 |
+
task_description: Natural language task (e.g., "translate english to french")
|
| 55 |
+
deployment_type: Type of deployment to generate
|
| 56 |
+
benchmark: Whether to run performance benchmarks
|
| 57 |
+
top_k: Number of top models to consider
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
SelectionResult with all details
|
| 61 |
+
"""
|
| 62 |
+
try:
|
| 63 |
+
print("\n" + "=" * 60)
|
| 64 |
+
print(" HuggingFace Model Selector")
|
| 65 |
+
print("=" * 60)
|
| 66 |
+
|
| 67 |
+
# Step 1: Parse requirements
|
| 68 |
+
print("\n Step 1: Analyzing requirements...")
|
| 69 |
+
requirements = self.input_agent.parse_requirements(task_description)
|
| 70 |
+
print(f" Task: {requirements.task_type.value}")
|
| 71 |
+
|
| 72 |
+
# Display task-specific requirements
|
| 73 |
+
if requirements.task_type == TaskType.TRANSLATION and requirements.translation_reqs:
|
| 74 |
+
req = requirements.translation_reqs
|
| 75 |
+
print(f" Translation: {req.source_language.value} β {req.target_language.value}")
|
| 76 |
+
if req.domain:
|
| 77 |
+
print(f" Domain: {req.domain}")
|
| 78 |
+
|
| 79 |
+
elif requirements.task_type == TaskType.TEXT_TO_SPEECH and requirements.tts_reqs:
|
| 80 |
+
req = requirements.tts_reqs
|
| 81 |
+
print(f" TTS Language: {req.language.value}")
|
| 82 |
+
print(f" Voice: {req.voice_type.value}")
|
| 83 |
+
|
| 84 |
+
elif requirements.task_type == TaskType.SPEECH_TO_TEXT and requirements.stt_reqs:
|
| 85 |
+
req = requirements.stt_reqs
|
| 86 |
+
print(f" STT Language: {req.language.value}")
|
| 87 |
+
if req.domain:
|
| 88 |
+
print(f" Domain: {req.domain}")
|
| 89 |
+
|
| 90 |
+
elif requirements.task_type in [TaskType.TEXT_GENERATION, TaskType.CHAT,
|
| 91 |
+
TaskType.INSTRUCTION_FOLLOWING, TaskType.CODE_GENERATION,
|
| 92 |
+
TaskType.QUESTION_ANSWERING, TaskType.SUMMARIZATION] and requirements.llm_reqs:
|
| 93 |
+
req = requirements.llm_reqs
|
| 94 |
+
print(f" LLM Size: {req.model_size.value}")
|
| 95 |
+
print(f" Context Length: {req.context_length}")
|
| 96 |
+
|
| 97 |
+
elif requirements.task_type in [TaskType.OCR, TaskType.DOCUMENT_UNDERSTANDING] and requirements.ocr_reqs:
|
| 98 |
+
req = requirements.ocr_reqs
|
| 99 |
+
langs = [lang.value for lang in req.languages]
|
| 100 |
+
print(f" OCR Languages: {langs}")
|
| 101 |
+
if req.handwritten:
|
| 102 |
+
print(f" Handwriting: Yes")
|
| 103 |
+
|
| 104 |
+
print(f" Hardware: {[c.value for c in requirements.hardware_constraints]}")
|
| 105 |
+
|
| 106 |
+
# Step 2: Search for models
|
| 107 |
+
print("\n Step 2: Searching HuggingFace...")
|
| 108 |
+
models = await self.research_agent.search_models(requirements, top_k=top_k*2)
|
| 109 |
+
|
| 110 |
+
if not models:
|
| 111 |
+
return SelectionResult(
|
| 112 |
+
status="error",
|
| 113 |
+
error="No models found matching your requirements"
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# Step 3: Score models
|
| 117 |
+
print("\n Step 3: Evaluating models...")
|
| 118 |
+
scored_models = self.evaluation_agent.score_models(models, requirements)
|
| 119 |
+
|
| 120 |
+
# Display top models
|
| 121 |
+
print("\n Top Models:")
|
| 122 |
+
for i, scored in enumerate(scored_models[:5]):
|
| 123 |
+
print(f" {i+1}. {scored.model_id} (Score: {scored.total_score:.3f})")
|
| 124 |
+
# Show top 3 component scores
|
| 125 |
+
top_metrics = sorted(scored.component_scores.items(), key=lambda x: x[1], reverse=True)[:3]
|
| 126 |
+
for metric, score in top_metrics:
|
| 127 |
+
print(f" - {metric}: {score:.2f}")
|
| 128 |
+
|
| 129 |
+
# Step 4: Select best model
|
| 130 |
+
best_model = scored_models[0]
|
| 131 |
+
print(f"\n Step 4: Selected model: {best_model.model_id}")
|
| 132 |
+
|
| 133 |
+
# Step 5: Benchmark (optional)
|
| 134 |
+
benchmark_results = []
|
| 135 |
+
if benchmark:
|
| 136 |
+
print("\n Step 5: Running benchmarks...")
|
| 137 |
+
benchmark_results = await self.benchmarking_agent.benchmark_models(
|
| 138 |
+
[m.model_id for m in scored_models[:3]],
|
| 139 |
+
requirements.task_type,
|
| 140 |
+
requirements
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
if benchmark_results and not benchmark_results[0].error:
|
| 144 |
+
print(f"\n Benchmark Results for {best_model.model_id}:")
|
| 145 |
+
print(f" Latency: {benchmark_results[0].latency_ms:.2f} ms")
|
| 146 |
+
print(f" Memory: {benchmark_results[0].memory_usage_mb:.2f} MB")
|
| 147 |
+
if benchmark_results[0].throughput:
|
| 148 |
+
print(f" Throughput: {benchmark_results[0].throughput:.2f} samples/sec")
|
| 149 |
+
|
| 150 |
+
# Step 6: Generate deployment code
|
| 151 |
+
print("\n Step 6: Generating deployment code...")
|
| 152 |
+
deployment_files = self.deployment_agent.generate_deployment(
|
| 153 |
+
model_id=best_model.model_id,
|
| 154 |
+
task_type=requirements.task_type,
|
| 155 |
+
deployment_type=deployment_type,
|
| 156 |
+
benchmark_results=benchmark_results[0] if benchmark_results else None,
|
| 157 |
+
requirements=requirements
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
# Save files
|
| 161 |
+
output_folder = self.deployment_agent.save_deployment_files(deployment_files)
|
| 162 |
+
print(f"\n Deployment files saved to: {output_folder}")
|
| 163 |
+
|
| 164 |
+
return SelectionResult(
|
| 165 |
+
status="success",
|
| 166 |
+
selected_model=best_model.model_id,
|
| 167 |
+
task_type=requirements.task_type,
|
| 168 |
+
requirements=requirements,
|
| 169 |
+
all_scores=scored_models,
|
| 170 |
+
benchmark_results=benchmark_results,
|
| 171 |
+
deployment_files=deployment_files
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
except Exception as e:
|
| 175 |
+
print(f"\n Error: {e}")
|
| 176 |
+
import traceback
|
| 177 |
+
traceback.print_exc()
|
| 178 |
+
return SelectionResult(
|
| 179 |
+
status="error",
|
| 180 |
+
error=str(e)
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
|
src/models/__init__.py
ADDED
|
File without changes
|
src/models/schemas.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data models and schemas for the HuggingFace Model Selector
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from pydantic import BaseModel, Field
|
| 6 |
+
from typing import List, Dict, Any, Optional
|
| 7 |
+
from enum import Enum
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TaskType(str, Enum):
|
| 12 |
+
"""Supported ML tasks"""
|
| 13 |
+
TRANSLATION = "translation"
|
| 14 |
+
TEXT_TO_SPEECH = "text-to-speech"
|
| 15 |
+
SPEECH_TO_TEXT = "automatic-speech-recognition"
|
| 16 |
+
QUESTION_ANSWERING = "question-answering"
|
| 17 |
+
TEXT_GENERATION = "text-generation"
|
| 18 |
+
CHAT = "text-generation" # Same pipeline tag
|
| 19 |
+
INSTRUCTION_FOLLOWING = "text-generation"
|
| 20 |
+
CODE_GENERATION = "text-generation"
|
| 21 |
+
SUMMARIZATION = "summarization"
|
| 22 |
+
OCR = "image-to-text" # Pipeline tag for OCR
|
| 23 |
+
DOCUMENT_UNDERSTANDING = "document-question-answering"
|
| 24 |
+
# Legacy/Other
|
| 25 |
+
TEXT_CLASSIFICATION = "text-classification"
|
| 26 |
+
NAMED_ENTITY_RECOGNITION = "token-classification"
|
| 27 |
+
IMAGE_CLASSIFICATION = "image-classification"
|
| 28 |
+
OBJECT_DETECTION = "object-detection"
|
| 29 |
+
ZERO_SHOT_CLASSIFICATION = "zero-shot-classification"
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class Language(str, Enum):
|
| 33 |
+
"""Common languages"""
|
| 34 |
+
ENGLISH = "en"
|
| 35 |
+
SPANISH = "es"
|
| 36 |
+
FRENCH = "fr"
|
| 37 |
+
GERMAN = "de"
|
| 38 |
+
ITALIAN = "it"
|
| 39 |
+
PORTUGUESE = "pt"
|
| 40 |
+
DUTCH = "nl"
|
| 41 |
+
RUSSIAN = "ru"
|
| 42 |
+
CHINESE = "zh"
|
| 43 |
+
JAPANESE = "ja"
|
| 44 |
+
KOREAN = "ko"
|
| 45 |
+
ARABIC = "ar"
|
| 46 |
+
HINDI = "hi"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class VoiceType(str, Enum):
|
| 50 |
+
"""Voice types for TTS models"""
|
| 51 |
+
MALE = "male"
|
| 52 |
+
FEMALE = "female"
|
| 53 |
+
NEUTRAL = "neutral"
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class HardwareConstraint(str, Enum):
|
| 57 |
+
"""Hardware constraints for model deployment"""
|
| 58 |
+
CPU = "cpu"
|
| 59 |
+
GPU_4GB = "gpu_4gb"
|
| 60 |
+
GPU_8GB = "gpu_8gb"
|
| 61 |
+
GPU_16GB = "gpu_16gb"
|
| 62 |
+
GPU_24GB = "gpu_24gb"
|
| 63 |
+
GPU_40GB = "gpu_40gb"
|
| 64 |
+
GPU_80GB = "gpu_80gb"
|
| 65 |
+
TPU = "tpu"
|
| 66 |
+
|
| 67 |
+
class ModelSize(str, Enum):
|
| 68 |
+
"""Model size categories for LLMs"""
|
| 69 |
+
TINY = "tiny" # < 1B parameters
|
| 70 |
+
SMALL = "small" # 1B-3B parameters
|
| 71 |
+
MEDIUM = "medium" # 3B-7B parameters
|
| 72 |
+
LARGE = "large" # 7B-13B parameters
|
| 73 |
+
XLARGE = "xlarge" # 13B-30B parameters
|
| 74 |
+
XXLARGE = "xxlarge" # 30B-70B parameters
|
| 75 |
+
MASSIVE = "massive" # 70B+ parameters
|
| 76 |
+
|
| 77 |
+
class TranslationRequirements(BaseModel):
|
| 78 |
+
"""Requirements for translation models"""
|
| 79 |
+
source_language: Language
|
| 80 |
+
target_language: Language
|
| 81 |
+
domain: Optional[str] = None # e.g., medical, legal, technical
|
| 82 |
+
quality_preference: str = "balanced" # "speed", "quality", "balanced"
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class TTSRequirements(BaseModel):
|
| 86 |
+
"""Requirements for text-to-speech models"""
|
| 87 |
+
language: Language
|
| 88 |
+
voice_type: VoiceType = VoiceType.NEUTRAL
|
| 89 |
+
speaking_rate: float = 1.0 # 0.5 to 2.0
|
| 90 |
+
pitch: float = 1.0 # 0.5 to 2.0
|
| 91 |
+
wants_multiple_voices: bool = False
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class STTRequirements(BaseModel):
|
| 95 |
+
"""Requirements for speech-to-text models"""
|
| 96 |
+
language: Language
|
| 97 |
+
domain: Optional[str] = None # e.g., general, medical, telephony
|
| 98 |
+
wants_word_timestamps: bool = False
|
| 99 |
+
wants_diarization: bool = False # Speaker diarization
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class LLMRequirements(BaseModel):
|
| 103 |
+
"""Requirements for Large Language Models"""
|
| 104 |
+
model_size: ModelSize = ModelSize.MEDIUM
|
| 105 |
+
context_length: int = 2048 # tokens
|
| 106 |
+
wants_chat_template: bool = False
|
| 107 |
+
wants_function_calling: bool = False
|
| 108 |
+
wants_code_generation: bool = False
|
| 109 |
+
wants_instruction_following: bool = False
|
| 110 |
+
wants_multilingual: bool = False
|
| 111 |
+
quantization: Optional[str] = None # "4bit", "8bit", None
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class OCRRequirements(BaseModel):
|
| 115 |
+
"""Requirements for OCR models"""
|
| 116 |
+
languages: List[Language] = Field(default_factory=lambda: [Language.ENGLISH])
|
| 117 |
+
handwritten: bool = False # Handwriting recognition
|
| 118 |
+
document_type: Optional[str] = None # "scanned", "photo", "document"
|
| 119 |
+
wants_layout_analysis: bool = False # Detect paragraphs, tables, etc.
|
| 120 |
+
wants_table_extraction: bool = False
|
| 121 |
+
wants_formula_recognition: bool = False # Math formulas
|
| 122 |
+
|
| 123 |
+
class UserRequirements(BaseModel):
|
| 124 |
+
"""Combined user requirements"""
|
| 125 |
+
task_type: TaskType
|
| 126 |
+
hardware_constraints: List[HardwareConstraint] = Field(default_factory=lambda: [HardwareConstraint.CPU])
|
| 127 |
+
max_model_size_gb: Optional[float] = None
|
| 128 |
+
|
| 129 |
+
# Task-specific requirements
|
| 130 |
+
translation_reqs: Optional[TranslationRequirements] = None
|
| 131 |
+
tts_reqs: Optional[TTSRequirements] = None
|
| 132 |
+
stt_reqs: Optional[STTRequirements] = None
|
| 133 |
+
llm_reqs: Optional[LLMRequirements] = None
|
| 134 |
+
ocr_reqs: Optional[OCRRequirements] = None
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
class ModelMetadata(BaseModel):
|
| 138 |
+
"""Metadata for a HuggingFace model"""
|
| 139 |
+
model_id: str
|
| 140 |
+
task_type: TaskType
|
| 141 |
+
downloads: int
|
| 142 |
+
likes: int
|
| 143 |
+
last_modified: datetime
|
| 144 |
+
license: str
|
| 145 |
+
model_size: Optional[float] = None # in GB
|
| 146 |
+
parameter_count: Optional[int] = None # Number of parameters
|
| 147 |
+
languages: List[str] = Field(default_factory=list)
|
| 148 |
+
framework: str = "pytorch" # pytorch, tensorflow, jax
|
| 149 |
+
pipeline_tag: Optional[str] = None
|
| 150 |
+
|
| 151 |
+
# Translation-specific
|
| 152 |
+
source_languages: List[str] = Field(default_factory=list)
|
| 153 |
+
target_languages: List[str] = Field(default_factory=list)
|
| 154 |
+
|
| 155 |
+
# TTS-specific
|
| 156 |
+
voice_count: int = 0
|
| 157 |
+
sample_rate: Optional[int] = None # Hz
|
| 158 |
+
|
| 159 |
+
# STT-specific
|
| 160 |
+
wer_score: Optional[float] = None # Word Error Rate
|
| 161 |
+
|
| 162 |
+
# LLM-specific
|
| 163 |
+
context_length: Optional[int] = None
|
| 164 |
+
has_chat_template: bool = False
|
| 165 |
+
supports_function_calling: bool = False
|
| 166 |
+
supports_code: bool = False
|
| 167 |
+
supports_instruction: bool = False
|
| 168 |
+
quantization_supported: List[str] = Field(default_factory=list)
|
| 169 |
+
|
| 170 |
+
# OCR-specific
|
| 171 |
+
supports_handwriting: bool = False
|
| 172 |
+
supports_layout: bool = False
|
| 173 |
+
supports_tables: bool = False
|
| 174 |
+
supports_formulas: bool = False
|
| 175 |
+
supported_image_formats: List[str] = Field(default_factory=list)
|
| 176 |
+
|
| 177 |
+
# General
|
| 178 |
+
performance_metrics: Dict[str, float] = Field(default_factory=dict)
|
| 179 |
+
hardware_requirements: Dict[str, Any] = Field(default_factory=dict)
|
| 180 |
+
model_card_content: str = ""
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
class ModelScore(BaseModel):
|
| 184 |
+
"""Scored model with component scores"""
|
| 185 |
+
model_id: str
|
| 186 |
+
task_type: TaskType
|
| 187 |
+
total_score: float
|
| 188 |
+
component_scores: Dict[str, float]
|
| 189 |
+
metadata: ModelMetadata
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
class BenchmarkResult(BaseModel):
|
| 193 |
+
"""Benchmark results for a model"""
|
| 194 |
+
model_id: str
|
| 195 |
+
task_type: TaskType
|
| 196 |
+
latency_ms: float
|
| 197 |
+
memory_usage_mb: float
|
| 198 |
+
accuracy: Optional[float] = None #oCR
|
| 199 |
+
throughput: Optional[float] = None
|
| 200 |
+
# Task-specific metrics
|
| 201 |
+
bleu_score: Optional[float] = None # Translation
|
| 202 |
+
wer_score: Optional[float] = None # STT
|
| 203 |
+
mos_score: Optional[float] = None # TTS Mean Opinion Score
|
| 204 |
+
perplexity: Optional[float] = None # LLMs
|
| 205 |
+
error: Optional[str] = None
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
class DeploymentType(str, Enum):
|
| 209 |
+
"""Supported deployment types"""
|
| 210 |
+
FASTAPI = "fastapi"
|
| 211 |
+
GRADIO = "gradio"
|
| 212 |
+
DOCKER = "docker"
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
class SelectionResult(BaseModel):
|
| 216 |
+
"""Final model selection result"""
|
| 217 |
+
status: str
|
| 218 |
+
selected_model: Optional[str] = None
|
| 219 |
+
task_type: Optional[TaskType] = None
|
| 220 |
+
requirements: Optional[UserRequirements] = None
|
| 221 |
+
all_scores: List[ModelScore] = Field(default_factory=list)
|
| 222 |
+
benchmark_results: List[BenchmarkResult] = Field(default_factory=list)
|
| 223 |
+
deployment_files: Optional[Dict[str, str]] = None
|
| 224 |
+
error: Optional[str] = None
|
src/utils/__init__.py
ADDED
|
File without changes
|
structure.txt
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# project_structure.txt
|
| 2 |
+
|
| 3 |
+
huggingface-model-selector/
|
| 4 |
+
βββ src/
|
| 5 |
+
β βββ agents/
|
| 6 |
+
β β βββ __init__.py
|
| 7 |
+
β β βββ input_agent.py # Parses user requirements
|
| 8 |
+
β β βββ research_agent.py # Searches HuggingFace
|
| 9 |
+
β β βββ evaluation_agent.py # Scores models
|
| 10 |
+
β β βββ benchmarking_agent.py # Tests performance
|
| 11 |
+
β β βββ deployment_agent.py # Generates deployment code
|
| 12 |
+
β βββ models/
|
| 13 |
+
β β βββ __init__.py
|
| 14 |
+
β β βββ schemas.py # Pydantic models/data classes
|
| 15 |
+
β βββ utils/
|
| 16 |
+
β β βββ __init__.py
|
| 17 |
+
β β βββ helpers.py # Helper functions
|
| 18 |
+
β βββ config/
|
| 19 |
+
β β βββ __init__.py
|
| 20 |
+
β β βββ settings.py # Configuration
|
| 21 |
+
β βββ main.py # Main orchestrator
|
| 22 |
+
βββ deployments/ # Generated deployment code
|
| 23 |
+
βββ tests/
|
| 24 |
+
β βββ __init__.py
|
| 25 |
+
β βββ test_agents.py
|
| 26 |
+
βββ docker/
|
| 27 |
+
β βββ Dockerfile
|
| 28 |
+
β βββ docker-compose.yml
|
| 29 |
+
βββ .github/
|
| 30 |
+
β βββ workflows/
|
| 31 |
+
β βββ ci.yml # GitHub Actions
|
| 32 |
+
βββ requirements.txt
|
| 33 |
+
βββ setup.py
|
| 34 |
+
βββ .env.example
|
| 35 |
+
βββ .gitignore
|
| 36 |
+
βββ README.md
|
| 37 |
+
βββ run.py # Entry point
|