honourjesus commited on
Commit
af2c3f6
Β·
0 Parent(s):

Agentic Model Selector

Browse files
.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