kundeshwar20 commited on
Commit
8393eae
·
1 Parent(s): 3ebe822

Move PNGs to Git LFS

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. .gitignore +45 -0
  3. Dockerfile +64 -0
  4. README.md +89 -6
  5. backend/Dockerfile.dev +25 -0
  6. backend/README.md +352 -0
  7. backend/__init__.py +0 -0
  8. backend/app/api/__init__.py +5 -0
  9. backend/app/api/dependencies.py +34 -0
  10. backend/app/api/endpoints/leaderboard.py +49 -0
  11. backend/app/api/endpoints/models.py +103 -0
  12. backend/app/api/endpoints/votes.py +105 -0
  13. backend/app/api/router.py +9 -0
  14. backend/app/asgi.py +106 -0
  15. backend/app/config/__init__.py +6 -0
  16. backend/app/config/base.py +38 -0
  17. backend/app/config/hf_config.py +34 -0
  18. backend/app/config/logging_config.py +38 -0
  19. backend/app/core/cache.py +109 -0
  20. backend/app/core/fastapi_cache.py +48 -0
  21. backend/app/main.py +18 -0
  22. backend/app/services/__init__.py +3 -0
  23. backend/app/services/hf_service.py +50 -0
  24. backend/app/services/leaderboard.py +382 -0
  25. backend/app/services/models.py +579 -0
  26. backend/app/services/rate_limiter.py +72 -0
  27. backend/app/services/votes.py +391 -0
  28. backend/app/utils/__init__.py +3 -0
  29. backend/app/utils/logging.py +105 -0
  30. backend/app/utils/model_validation.py +210 -0
  31. backend/poetry.lock +0 -0
  32. backend/pyproject.toml +32 -0
  33. backend/utils/analyze_prod_datasets.py +170 -0
  34. backend/utils/analyze_prod_models.py +106 -0
  35. backend/utils/fix_wrong_model_size.py +110 -0
  36. backend/utils/last_activity.py +164 -0
  37. backend/utils/sync_datasets_locally.py +130 -0
  38. backend/uv.lock +0 -0
  39. contents.py +6 -0
  40. docker-compose.yml +34 -0
  41. frontend/Dockerfile.dev +15 -0
  42. frontend/README.md +80 -0
  43. frontend/package.json +55 -0
  44. frontend/public/bglogo.png +3 -0
  45. frontend/public/iimlogo.png +0 -0
  46. frontend/public/iitblogo.png +3 -0
  47. frontend/public/index.html +96 -0
  48. frontend/public/logo256.png +0 -0
  49. frontend/public/logo32.png +0 -0
  50. frontend/public/og-image.jpg +0 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ frontend/public/bglogo.png filter=lfs diff=lfs merge=lfs -text
37
+ frontend/public/iitblogo.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ __pycache__
4
+ .cache/
5
+
6
+ # dependencies
7
+
8
+ frontend/node_modules
9
+ /.pnp
10
+ .pnp.js
11
+
12
+ # testing
13
+
14
+ /coverage
15
+
16
+ # production
17
+
18
+ /build
19
+
20
+ # misc
21
+
22
+ .DS_Store
23
+ .env.local
24
+ .env.development.local
25
+ .env.test.local
26
+ .env.production.local
27
+
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log\*
31
+
32
+ src/dataframe.json
33
+
34
+ yarn.lock
35
+ package-lock.json
36
+
37
+ /public
38
+
39
+ .claudesync/
40
+
41
+ # Environment variables
42
+ .env
43
+ .env.*
44
+ !.env.example
45
+
Dockerfile ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build frontend
2
+ FROM node:18 as frontend-build
3
+ WORKDIR /app
4
+ COPY frontend/package*.json ./
5
+ RUN npm install
6
+ COPY frontend/ ./
7
+
8
+ RUN npm run build
9
+
10
+ # Build backend
11
+ FROM python:3.12-slim
12
+ WORKDIR /app
13
+
14
+ # Create non-root user
15
+ RUN useradd -m -u 1000 user
16
+
17
+ # Install poetry
18
+ RUN pip install poetry
19
+
20
+ # Create and configure cache directory
21
+ RUN mkdir -p /app/.cache && \
22
+ chown -R user:user /app
23
+
24
+ # Copy and install backend dependencies
25
+ COPY backend/pyproject.toml backend/poetry.lock* ./
26
+ RUN poetry config virtualenvs.create false \
27
+ && poetry install --no-interaction --no-ansi --no-root --only main
28
+
29
+ # Copy backend code
30
+ COPY backend/ .
31
+
32
+ # Install Node.js and npm
33
+ RUN apt-get update && apt-get install -y \
34
+ curl \
35
+ netcat-openbsd \
36
+ && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
37
+ && apt-get install -y nodejs \
38
+ && rm -rf /var/lib/apt/lists/*
39
+
40
+ # Copy frontend server and build
41
+ COPY --from=frontend-build /app/build ./frontend/build
42
+ COPY --from=frontend-build /app/package*.json ./frontend/
43
+ COPY --from=frontend-build /app/server.js ./frontend/
44
+
45
+ # Install frontend production dependencies
46
+ WORKDIR /app/frontend
47
+ RUN npm install --production
48
+ WORKDIR /app
49
+
50
+ # Environment variables
51
+ ENV HF_HOME=/app/.cache \
52
+ TRANSFORMERS_CACHE=/app/.cache \
53
+ HF_DATASETS_CACHE=/app/.cache \
54
+ INTERNAL_API_PORT=7861 \
55
+ PORT=7860 \
56
+ NODE_ENV=production
57
+
58
+
59
+ # Note: HF_TOKEN should be provided at runtime, not build time
60
+ USER user
61
+ EXPOSE 7860
62
+
63
+ # Start both servers with wait-for
64
+ CMD ["sh", "-c", "env && uvicorn app.asgi:app --host 0.0.0.0 --port 7861 & while ! nc -z localhost 7861; do sleep 1; done && cd frontend && npm run serve"]
README.md CHANGED
@@ -1,10 +1,93 @@
1
  ---
2
- title: Bhashabench Leaderboard
3
- emoji: 👀
4
- colorFrom: gray
5
- colorTo: blue
6
  sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
 
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: BhashaBench Leaderboard
3
+ emoji: 🏆
4
+ colorFrom: blue
5
+ colorTo: red
6
  sdk: docker
7
+ hf_oauth: true
8
+ pinned: true
9
+ license: apache-2.0
10
+ duplicated_from: open-llm-leaderboard/open_llm_leaderboard
11
+ short_description: Evaluating LLMs on BhashaBench tasks
12
+ tags:
13
+ - leaderboard
14
+ - modality:text
15
+ - submission:manual
16
+ - test:public
17
+ - judge:function
18
+ - eval:generation
19
+ - language:English, Hindi
20
+ - domain:Ayur, Krishi, Finance, Legal
21
  ---
22
+ https://arxiv.org/bhashabench
23
 
24
+ # Open LLM Leaderboard
25
+
26
+ Modern React interface for comparing Large Language Models (LLMs) in an open and reproducible way.
27
+
28
+ ## Features
29
+
30
+ - 📊 Interactive table with advanced sorting and filtering
31
+ - 🔍 Semantic model search
32
+ - 📌 Pin models for comparison
33
+ - 📱 Responsive and modern interface
34
+ - 🎨 Dark/Light mode
35
+ - ⚡️ Optimized performance with virtualization
36
+
37
+ ## Architecture
38
+
39
+ The project is split into two main parts:
40
+
41
+ ### Frontend (React)
42
+
43
+ ```
44
+ frontend/
45
+ ├── src/
46
+ │ ├── components/ # Reusable UI components
47
+ │ ├── pages/ # Application pages
48
+ │ ├── hooks/ # Custom React hooks
49
+ │ ├── context/ # React contexts
50
+ │ └── constants/ # Constants and configurations
51
+ ├── public/ # Static assets
52
+ └── server.js # Express server for production
53
+ ```
54
+
55
+ ### Backend (FastAPI)
56
+
57
+ ```
58
+ backend/
59
+ ├── app/
60
+ │ ├── api/ # API router and endpoints
61
+ │ │ └── endpoints/ # Specific API endpoints
62
+ │ ├── core/ # Core functionality
63
+ │ ├── config/ # Configuration
64
+ │ └── services/ # Business logic services
65
+ │ ├── leaderboard.py
66
+ │ ├── models.py
67
+ │ ├── votes.py
68
+ │ └── hf_service.py
69
+ └── utils/ # Utility functions
70
+ ```
71
+
72
+ ## Technologies
73
+
74
+ ### Frontend
75
+
76
+ - React
77
+ - Material-UI
78
+ - TanStack Table & Virtual
79
+ - Express.js
80
+
81
+ ### Backend
82
+
83
+ - FastAPI
84
+ - Hugging Face API
85
+ - Docker
86
+
87
+ ## Development
88
+
89
+ The application is containerized using Docker and can be run using:
90
+
91
+ ```bash
92
+ docker-compose up
93
+ ```
backend/Dockerfile.dev ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install required system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Install poetry
11
+ RUN pip install poetry
12
+
13
+ # Copy Poetry configuration files
14
+ COPY pyproject.toml poetry.lock* ./
15
+
16
+ # Install dependencies
17
+ RUN poetry config virtualenvs.create false && \
18
+ poetry install --no-interaction --no-ansi --no-root
19
+
20
+ # Environment variables configuration for logs
21
+ ENV PYTHONUNBUFFERED=1
22
+ ENV LOG_LEVEL=INFO
23
+
24
+ # In dev, mount volume directly
25
+ CMD ["uvicorn", "app.asgi:app", "--host", "0.0.0.0", "--port", "7860", "--reload", "--log-level", "warning", "--no-access-log"]
backend/README.md ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Backend - Open LLM Leaderboard 🏆
2
+
3
+ FastAPI backend for the Open LLM Leaderboard. This service is part of a larger architecture that includes a React frontend. For complete project installation, see the [main README](../README.md).
4
+
5
+ ## ✨ Features
6
+
7
+ - 📊 REST API for LLM models leaderboard management
8
+ - 🗳️ Voting and ranking system
9
+ - 🔄 HuggingFace Hub integration
10
+ - 🚀 Caching and performance optimizations
11
+
12
+ ## 🏗 Architecture
13
+
14
+ ```mermaid
15
+ flowchart TD
16
+ Client(["**Frontend**<br><br>React Application"]) --> API["**API Server**<br><br>FastAPI REST Endpoints"]
17
+
18
+ subgraph Backend
19
+ API --> Core["**Core Layer**<br><br>• Middleware<br>• Cache<br>• Rate Limiting"]
20
+ Core --> Services["**Services Layer**<br><br>• Business Logic<br>• Data Processing"]
21
+
22
+ subgraph Services Layer
23
+ Services --> Models["**Model Service**<br><br>• Model Submission<br>• Evaluation Pipeline"]
24
+ Services --> Votes["**Vote Service**<br><br>• Vote Management<br>• Data Synchronization"]
25
+ Services --> Board["**Leaderboard Service**<br><br>• Rankings<br>• Performance Metrics"]
26
+ end
27
+
28
+ Models --> Cache["**Cache Layer**<br><br>• In-Memory Store<br>• Auto Invalidation"]
29
+ Votes --> Cache
30
+ Board --> Cache
31
+
32
+ Models --> HF["**HuggingFace Hub**<br><br>• Models Repository<br>• Datasets Access"]
33
+ Votes --> HF
34
+ Board --> HF
35
+ end
36
+
37
+ style Client fill:#f9f,stroke:#333,stroke-width:2px
38
+ style Models fill:#bbf,stroke:#333,stroke-width:2px
39
+ style Votes fill:#bbf,stroke:#333,stroke-width:2px
40
+ style Board fill:#bbf,stroke:#333,stroke-width:2px
41
+ style HF fill:#bfb,stroke:#333,stroke-width:2px
42
+ ```
43
+
44
+ ## 🛠️ HuggingFace Datasets
45
+
46
+ The application uses several datasets on the HuggingFace Hub:
47
+
48
+ ### 1. Requests Dataset (`{HF_ORGANIZATION}/requests`)
49
+
50
+ - **Operations**:
51
+ - 📤 `POST /api/models/submit`: Adds a JSON file for each new model submission
52
+ - 📥 `GET /api/models/status`: Reads files to get models status
53
+ - **Format**: One JSON file per model with submission details
54
+ - **Updates**: On each new model submission
55
+
56
+ ### 2. Votes Dataset (`{HF_ORGANIZATION}/votes`)
57
+
58
+ - **Operations**:
59
+ - 📤 `POST /api/votes/{model_id}`: Adds a new vote
60
+ - 📥 `GET /api/votes/model/{provider}/{model}`: Reads model votes
61
+ - 📥 `GET /api/votes/user/{user_id}`: Reads user votes
62
+ - **Format**: JSONL with one vote per line
63
+ - **Sync**: Bidirectional between local cache and Hub
64
+
65
+ ### 3. Contents Dataset (`{HF_ORGANIZATION}/contents`)
66
+
67
+ - **Operations**:
68
+ - 📥 `GET /api/leaderboard`: Reads raw data
69
+ - 📥 `GET /api/leaderboard/formatted`: Reads and formats data
70
+ - **Format**: Main dataset containing all scores and metrics
71
+ - **Updates**: Automatic after model evaluations
72
+
73
+ ### 4. Maintainers Highlight Dataset (`{HF_ORGANIZATION}/maintainers-highlight`)
74
+
75
+ - **Operations**:
76
+ - 📥 Read-only access for highlighted models
77
+ - **Format**: List of models selected by maintainers
78
+ - **Updates**: Manual by maintainers
79
+
80
+ ## 🛠 Local Development
81
+
82
+ ### Prerequisites
83
+
84
+ - Python 3.9+
85
+ - [Poetry](https://python-poetry.org/docs/#installation)
86
+
87
+ ### Standalone Installation (without Docker)
88
+
89
+ ```bash
90
+ # Install dependencies
91
+ poetry install
92
+
93
+ # Setup configuration
94
+ cp .env.example .env
95
+
96
+ # Start development server
97
+ poetry run uvicorn app.asgi:app --host 0.0.0.0 --port 7860 --reload
98
+ ```
99
+
100
+ Server will be available at http://localhost:7860
101
+
102
+ ## ⚙️ Configuration
103
+
104
+ | Variable | Description | Default |
105
+ | ------------ | ------------------------------------ | ----------- |
106
+ | ENVIRONMENT | Environment (development/production) | development |
107
+ | HF_TOKEN | HuggingFace authentication token | - |
108
+ | PORT | Server port | 7860 |
109
+ | LOG_LEVEL | Logging level (INFO/DEBUG/WARNING) | INFO |
110
+ | CORS_ORIGINS | Allowed CORS origins | ["*"] |
111
+ | CACHE_TTL | Cache Time To Live in seconds | 300 |
112
+
113
+ ## 🔧 Middleware
114
+
115
+ The backend uses several middleware layers for optimal performance and security:
116
+
117
+ - **CORS Middleware**: Handles Cross-Origin Resource Sharing
118
+ - **GZIP Middleware**: Compresses responses > 500 bytes
119
+ - **Rate Limiting**: Prevents API abuse
120
+ - **Caching**: In-memory caching with automatic invalidation
121
+
122
+ ## 📝 Logging
123
+
124
+ The application uses a structured logging system with:
125
+
126
+ - Formatted console output
127
+ - Different log levels per component
128
+ - Request/Response logging
129
+ - Performance metrics
130
+ - Error tracking
131
+
132
+ ## 📁 File Structure
133
+
134
+ ```
135
+ backend/
136
+ ├── app/ # Source code
137
+ │ ├── api/ # Routes and endpoints
138
+ │ │ └── endpoints/ # Endpoint handlers
139
+ │ ├── core/ # Configurations
140
+ │ ├── services/ # Business logic
141
+ │ └── utils/ # Utilities
142
+ └── tests/ # Tests
143
+ ```
144
+
145
+ ## 📚 API
146
+
147
+ Swagger documentation available at http://localhost:7860/docs
148
+
149
+ ### Main Endpoints & Data Structures
150
+
151
+ #### Leaderboard
152
+
153
+ - `GET /api/leaderboard/formatted` - Formatted data with computed fields and metadata
154
+
155
+ ```typescript
156
+ Response {
157
+ models: [{
158
+ id: string, // eval_name
159
+ model: {
160
+ name: string, // fullname
161
+ sha: string, // Model sha
162
+ precision: string, // e.g. "fp16", "int8"
163
+ type: string, // e.g. "fined-tuned-on-domain-specific-dataset"
164
+ weight_type: string,
165
+ architecture: string,
166
+ average_score: number,
167
+ has_chat_template: boolean
168
+ },
169
+ evaluations: {
170
+ ifeval: {
171
+ name: "IFEval",
172
+ value: number, // Raw score
173
+ normalized_score: number
174
+ },
175
+ bbh: {
176
+ name: "BBH",
177
+ value: number,
178
+ normalized_score: number
179
+ },
180
+ math: {
181
+ name: "MATH Level 5",
182
+ value: number,
183
+ normalized_score: number
184
+ },
185
+ gpqa: {
186
+ name: "GPQA",
187
+ value: number,
188
+ normalized_score: number
189
+ },
190
+ musr: {
191
+ name: "MUSR",
192
+ value: number,
193
+ normalized_score: number
194
+ },
195
+ mmlu_pro: {
196
+ name: "MMLU-PRO",
197
+ value: number,
198
+ normalized_score: number
199
+ }
200
+ },
201
+ features: {
202
+ is_not_available_on_hub: boolean,
203
+ is_merged: boolean,
204
+ is_moe: boolean,
205
+ is_flagged: boolean,
206
+ is_highlighted_by_maintainer: boolean
207
+ },
208
+ metadata: {
209
+ upload_date: string,
210
+ submission_date: string,
211
+ generation: string,
212
+ base_model: string,
213
+ hub_license: string,
214
+ hub_hearts: number,
215
+ params_billions: number,
216
+ co2_cost: number // CO₂ cost in kg
217
+ }
218
+ }]
219
+ }
220
+ ```
221
+
222
+ - `GET /api/leaderboard` - Raw data from the HuggingFace dataset
223
+ ```typescript
224
+ Response {
225
+ models: [{
226
+ eval_name: string,
227
+ Precision: string,
228
+ Type: string,
229
+ "Weight type": string,
230
+ Architecture: string,
231
+ Model: string,
232
+ fullname: string,
233
+ "Model sha": string,
234
+ "Average ⬆️": number,
235
+ "Hub License": string,
236
+ "Hub ❤️": number,
237
+ "#Params (B)": number,
238
+ "Available on the hub": boolean,
239
+ Merged: boolean,
240
+ MoE: boolean,
241
+ Flagged: boolean,
242
+ "Chat Template": boolean,
243
+ "CO₂ cost (kg)": number,
244
+ "IFEval Raw": number,
245
+ IFEval: number,
246
+ "BBH Raw": number,
247
+ BBH: number,
248
+ "MATH Lvl 5 Raw": number,
249
+ "MATH Lvl 5": number,
250
+ "GPQA Raw": number,
251
+ GPQA: number,
252
+ "MUSR Raw": number,
253
+ MUSR: number,
254
+ "MMLU-PRO Raw": number,
255
+ "MMLU-PRO": number,
256
+ "Maintainer's Highlight": boolean,
257
+ "Upload To Hub Date": string,
258
+ "Submission Date": string,
259
+ Generation: string,
260
+ "Base Model": string
261
+ }]
262
+ }
263
+ ```
264
+
265
+ #### Models
266
+
267
+ - `GET /api/models/status` - Get all models grouped by status
268
+ ```typescript
269
+ Response {
270
+ pending: [{
271
+ name: string,
272
+ submitter: string,
273
+ revision: string,
274
+ wait_time: string,
275
+ submission_time: string,
276
+ status: "PENDING" | "EVALUATING" | "FINISHED",
277
+ precision: string
278
+ }],
279
+ evaluating: Array<Model>,
280
+ finished: Array<Model>
281
+ }
282
+ ```
283
+ - `GET /api/models/pending` - Get pending models only
284
+ - `POST /api/models/submit` - Submit model
285
+
286
+ ```typescript
287
+ Request {
288
+ user_id: string,
289
+ model_id: string,
290
+ base_model?: string,
291
+ precision?: string,
292
+ model_type: string
293
+ }
294
+
295
+ Response {
296
+ status: string,
297
+ message: string
298
+ }
299
+ ```
300
+
301
+ - `GET /api/models/{model_id}/status` - Get model status
302
+
303
+ #### Votes
304
+
305
+ - `POST /api/votes/{model_id}` - Vote
306
+
307
+ ```typescript
308
+ Request {
309
+ vote_type: "up" | "down",
310
+ user_id: string // HuggingFace username
311
+ }
312
+
313
+ Response {
314
+ success: boolean,
315
+ message: string
316
+ }
317
+ ```
318
+
319
+ - `GET /api/votes/model/{provider}/{model}` - Get model votes
320
+ ```typescript
321
+ Response {
322
+ total_votes: number,
323
+ up_votes: number,
324
+ down_votes: number
325
+ }
326
+ ```
327
+ - `GET /api/votes/user/{user_id}` - Get user votes
328
+ ```typescript
329
+ Response Array<{
330
+ model_id: string,
331
+ vote_type: string,
332
+ timestamp: string
333
+ }>
334
+ ```
335
+
336
+ ## 🔒 Authentication
337
+
338
+ The backend uses HuggingFace token-based authentication for secure API access. Make sure to:
339
+
340
+ 1. Set your HF_TOKEN in the .env file
341
+ 2. Include the token in API requests via Bearer authentication
342
+ 3. Keep your token secure and never commit it to version control
343
+
344
+ ## 🚀 Performance
345
+
346
+ The backend implements several optimizations:
347
+
348
+ - In-memory caching with configurable TTL (Time To Live)
349
+ - Batch processing for model evaluations
350
+ - Rate limiting for API endpoints
351
+ - Efficient database queries with proper indexing
352
+ - Automatic cache invalidation for votes
backend/__init__.py ADDED
File without changes
backend/app/api/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """
2
+ API package initialization
3
+ """
4
+
5
+ __all__ = ["endpoints"]
backend/app/api/dependencies.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Depends, HTTPException
2
+ import logging
3
+ from app.services.models import ModelService
4
+ from app.services.votes import VoteService
5
+ from app.utils.logging import LogFormatter
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ model_service = ModelService()
10
+ vote_service = VoteService()
11
+
12
+ async def get_model_service() -> ModelService:
13
+ """Dependency to get ModelService instance"""
14
+ try:
15
+ logger.info(LogFormatter.info("Initializing model service dependency"))
16
+ await model_service.initialize()
17
+ logger.info(LogFormatter.success("Model service initialized"))
18
+ return model_service
19
+ except Exception as e:
20
+ error_msg = "Failed to initialize model service"
21
+ logger.error(LogFormatter.error(error_msg, e))
22
+ raise HTTPException(status_code=500, detail=str(e))
23
+
24
+ async def get_vote_service() -> VoteService:
25
+ """Dependency to get VoteService instance"""
26
+ try:
27
+ logger.info(LogFormatter.info("Initializing vote service dependency"))
28
+ await vote_service.initialize()
29
+ logger.info(LogFormatter.success("Vote service initialized"))
30
+ return vote_service
31
+ except Exception as e:
32
+ error_msg = "Failed to initialize vote service"
33
+ logger.error(LogFormatter.error(error_msg, e))
34
+ raise HTTPException(status_code=500, detail=str(e))
backend/app/api/endpoints/leaderboard.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from typing import List, Dict, Any
3
+ from app.services.leaderboard import LeaderboardService
4
+ from app.core.fastapi_cache import cached, build_cache_key
5
+ import logging
6
+ from app.utils.logging import LogFormatter
7
+
8
+ logger = logging.getLogger(__name__)
9
+ router = APIRouter()
10
+ leaderboard_service = LeaderboardService()
11
+
12
+ def leaderboard_key_builder(func, namespace: str = "leaderboard", **kwargs):
13
+ """Build cache key for leaderboard data"""
14
+ key_type = "raw" if func.__name__ == "get_leaderboard" else "formatted"
15
+ key = build_cache_key(namespace, key_type)
16
+ logger.debug(LogFormatter.info(f"Built leaderboard cache key: {key}"))
17
+ return key
18
+
19
+ @router.get("")
20
+ @cached(expire=300, key_builder=leaderboard_key_builder)
21
+ async def get_leaderboard() -> List[Dict[str, Any]]:
22
+ """
23
+ Get raw leaderboard data
24
+ Response will be automatically GZIP compressed if size > 500 bytes
25
+ """
26
+ try:
27
+ logger.info(LogFormatter.info("Fetching raw leaderboard data"))
28
+ data = await leaderboard_service.fetch_raw_data()
29
+ logger.info(LogFormatter.success(f"Retrieved {len(data)} leaderboard entries"))
30
+ return data
31
+ except Exception as e:
32
+ logger.error(LogFormatter.error("Failed to fetch raw leaderboard data", e))
33
+ raise
34
+
35
+ @router.get("/formatted")
36
+ @cached(expire=300, key_builder=leaderboard_key_builder)
37
+ async def get_formatted_leaderboard() -> List[Dict[str, Any]]:
38
+ """
39
+ Get formatted leaderboard data with restructured objects
40
+ Response will be automatically GZIP compressed if size > 500 bytes
41
+ """
42
+ try:
43
+ logger.info(LogFormatter.info("Fetching formatted leaderboard data"))
44
+ data = await leaderboard_service.get_formatted_data()
45
+ logger.info(LogFormatter.success(f"Retrieved {len(data)} formatted entries"))
46
+ return data
47
+ except Exception as e:
48
+ logger.error(LogFormatter.error("Failed to fetch formatted leaderboard data", e))
49
+ raise
backend/app/api/endpoints/models.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Depends
2
+ from typing import Dict, Any, List
3
+ import logging
4
+ from app.services.models import ModelService
5
+ from app.api.dependencies import get_model_service
6
+ from app.core.fastapi_cache import cached
7
+ from app.utils.logging import LogFormatter
8
+
9
+ logger = logging.getLogger(__name__)
10
+ router = APIRouter(tags=["models"])
11
+
12
+ @router.get("/status")
13
+ @cached(expire=300)
14
+ async def get_models_status(
15
+ model_service: ModelService = Depends(get_model_service)
16
+ ) -> Dict[str, List[Dict[str, Any]]]:
17
+ """Get all models grouped by status"""
18
+ try:
19
+ logger.info(LogFormatter.info("Fetching status for all models"))
20
+ result = await model_service.get_models()
21
+ stats = {
22
+ status: len(models) for status, models in result.items()
23
+ }
24
+ for line in LogFormatter.stats(stats, "Models by Status"):
25
+ logger.info(line)
26
+ return result
27
+ except Exception as e:
28
+ logger.error(LogFormatter.error("Failed to get models status", e))
29
+ raise HTTPException(status_code=500, detail=str(e))
30
+
31
+ @router.get("/pending")
32
+ @cached(expire=60)
33
+ async def get_pending_models(
34
+ model_service: ModelService = Depends(get_model_service)
35
+ ) -> List[Dict[str, Any]]:
36
+ """Get all models waiting for evaluation"""
37
+ try:
38
+ logger.info(LogFormatter.info("Fetching pending models"))
39
+ models = await model_service.get_models()
40
+ pending = models.get("pending", [])
41
+ logger.info(LogFormatter.success(f"Found {len(pending)} pending models"))
42
+ return pending
43
+ except Exception as e:
44
+ logger.error(LogFormatter.error("Failed to get pending models", e))
45
+ raise HTTPException(status_code=500, detail=str(e))
46
+
47
+ @router.post("/submit")
48
+ async def submit_model(
49
+ model_data: Dict[str, Any],
50
+ model_service: ModelService = Depends(get_model_service)
51
+ ) -> Dict[str, Any]:
52
+ try:
53
+ logger.info(LogFormatter.section("MODEL SUBMISSION"))
54
+
55
+ user_id = model_data.pop('user_id', None)
56
+ if not user_id:
57
+ error_msg = "user_id is required"
58
+ logger.error(LogFormatter.error("Validation failed", error_msg))
59
+ raise ValueError(error_msg)
60
+
61
+ # Log submission details
62
+ submission_info = {
63
+ "Model_ID": model_data.get("model_id"),
64
+ "User": user_id,
65
+ "Base_Model": model_data.get("base_model"),
66
+ "Precision": model_data.get("precision"),
67
+ "Model_Type": model_data.get("model_type")
68
+ }
69
+ for line in LogFormatter.tree(submission_info, "Submission Details"):
70
+ logger.info(line)
71
+
72
+ result = await model_service.submit_model(model_data, user_id)
73
+ logger.info(LogFormatter.success("Model submitted successfully"))
74
+ return result
75
+
76
+ except ValueError as e:
77
+ logger.error(LogFormatter.error("Invalid submission data", e))
78
+ raise HTTPException(status_code=400, detail=str(e))
79
+ except Exception as e:
80
+ logger.error(LogFormatter.error("Submission failed", e))
81
+ raise HTTPException(status_code=500, detail=str(e))
82
+
83
+ @router.get("/{model_id}/status")
84
+ async def get_model_status(
85
+ model_id: str,
86
+ model_service: ModelService = Depends(get_model_service)
87
+ ) -> Dict[str, Any]:
88
+ try:
89
+ logger.info(LogFormatter.info(f"Checking status for model: {model_id}"))
90
+ status = await model_service.get_model_status(model_id)
91
+
92
+ if status["status"] != "not_found":
93
+ logger.info(LogFormatter.success("Status found"))
94
+ for line in LogFormatter.tree(status, "Model Status"):
95
+ logger.info(line)
96
+ else:
97
+ logger.warning(LogFormatter.warning(f"No status found for model: {model_id}"))
98
+
99
+ return status
100
+
101
+ except Exception as e:
102
+ logger.error(LogFormatter.error("Failed to get model status", e))
103
+ raise HTTPException(status_code=500, detail=str(e))
backend/app/api/endpoints/votes.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Query, Depends
2
+ from typing import Dict, Any, List
3
+ from app.services.votes import VoteService
4
+ from app.core.fastapi_cache import cached, build_cache_key, invalidate_cache_key
5
+ import logging
6
+ from app.utils.logging import LogFormatter
7
+
8
+ logger = logging.getLogger(__name__)
9
+ router = APIRouter()
10
+ vote_service = VoteService()
11
+
12
+ def model_votes_key_builder(func, namespace: str = "model_votes", **kwargs):
13
+ """Build cache key for model votes"""
14
+ provider = kwargs.get('provider')
15
+ model = kwargs.get('model')
16
+ key = build_cache_key(namespace, provider, model)
17
+ logger.debug(LogFormatter.info(f"Built model votes cache key: {key}"))
18
+ return key
19
+
20
+ def user_votes_key_builder(func, namespace: str = "user_votes", **kwargs):
21
+ """Build cache key for user votes"""
22
+ user_id = kwargs.get('user_id')
23
+ key = build_cache_key(namespace, user_id)
24
+ logger.debug(LogFormatter.info(f"Built user votes cache key: {key}"))
25
+ return key
26
+
27
+ @router.post("/{model_id:path}")
28
+ async def add_vote(
29
+ model_id: str,
30
+ vote_type: str = Query(..., description="Type of vote (up/down)"),
31
+ user_id: str = Query(..., description="HuggingFace username")
32
+ ) -> Dict[str, Any]:
33
+ try:
34
+ logger.info(LogFormatter.section("ADDING VOTE"))
35
+ stats = {
36
+ "Model": model_id,
37
+ "User": user_id,
38
+ "Type": vote_type
39
+ }
40
+ for line in LogFormatter.tree(stats, "Vote Details"):
41
+ logger.info(line)
42
+
43
+ await vote_service.initialize()
44
+ result = await vote_service.add_vote(model_id, user_id, vote_type)
45
+
46
+ # Invalidate affected caches
47
+ try:
48
+ logger.info(LogFormatter.subsection("CACHE INVALIDATION"))
49
+ provider, model = model_id.split('/', 1)
50
+
51
+ # Build and invalidate cache keys
52
+ model_cache_key = build_cache_key("model_votes", provider, model)
53
+ user_cache_key = build_cache_key("user_votes", user_id)
54
+
55
+ invalidate_cache_key(model_cache_key)
56
+ invalidate_cache_key(user_cache_key)
57
+
58
+ cache_stats = {
59
+ "Model_Cache": model_cache_key,
60
+ "User_Cache": user_cache_key
61
+ }
62
+ for line in LogFormatter.tree(cache_stats, "Invalidated Caches"):
63
+ logger.info(line)
64
+
65
+ except Exception as e:
66
+ logger.error(LogFormatter.error("Failed to invalidate cache", e))
67
+
68
+ return result
69
+ except Exception as e:
70
+ logger.error(LogFormatter.error("Failed to add vote", e))
71
+ raise HTTPException(status_code=400, detail=str(e))
72
+
73
+ @router.get("/model/{provider}/{model}")
74
+ @cached(expire=60, key_builder=model_votes_key_builder)
75
+ async def get_model_votes(
76
+ provider: str,
77
+ model: str
78
+ ) -> Dict[str, Any]:
79
+ """Get all votes for a specific model"""
80
+ try:
81
+ logger.info(LogFormatter.info(f"Fetching votes for model: {provider}/{model}"))
82
+ await vote_service.initialize()
83
+ model_id = f"{provider}/{model}"
84
+ result = await vote_service.get_model_votes(model_id)
85
+ logger.info(LogFormatter.success(f"Found {result.get('total_votes', 0)} votes"))
86
+ return result
87
+ except Exception as e:
88
+ logger.error(LogFormatter.error("Failed to get model votes", e))
89
+ raise HTTPException(status_code=400, detail=str(e))
90
+
91
+ @router.get("/user/{user_id}")
92
+ @cached(expire=60, key_builder=user_votes_key_builder)
93
+ async def get_user_votes(
94
+ user_id: str
95
+ ) -> List[Dict[str, Any]]:
96
+ """Get all votes from a specific user"""
97
+ try:
98
+ logger.info(LogFormatter.info(f"Fetching votes for user: {user_id}"))
99
+ await vote_service.initialize()
100
+ votes = await vote_service.get_user_votes(user_id)
101
+ logger.info(LogFormatter.success(f"Found {len(votes)} votes"))
102
+ return votes
103
+ except Exception as e:
104
+ logger.error(LogFormatter.error("Failed to get user votes", e))
105
+ raise HTTPException(status_code=400, detail=str(e))
backend/app/api/router.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+
3
+ from app.api.endpoints import leaderboard, votes, models
4
+
5
+ router = APIRouter()
6
+
7
+ router.include_router(leaderboard.router, prefix="/leaderboard", tags=["leaderboard"])
8
+ router.include_router(votes.router, prefix="/votes", tags=["votes"])
9
+ router.include_router(models.router, prefix="/models", tags=["models"])
backend/app/asgi.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ASGI entry point for the BhashaBench LLM Leaderboard API.
3
+ """
4
+ import os
5
+ import uvicorn
6
+ import logging
7
+ import logging.config
8
+ from fastapi import FastAPI
9
+ from fastapi.middleware.cors import CORSMiddleware
10
+ from fastapi.middleware.gzip import GZipMiddleware
11
+ import sys
12
+
13
+ from app.api.router import router
14
+ from app.core.fastapi_cache import setup_cache
15
+ from app.utils.logging import LogFormatter
16
+ from app.config import hf_config
17
+
18
+ # Configure logging before anything else
19
+ LOGGING_CONFIG = {
20
+ "version": 1,
21
+ "disable_existing_loggers": True,
22
+ "formatters": {
23
+ "default": {
24
+ "format": "%(name)s - %(levelname)s - %(message)s",
25
+ }
26
+ },
27
+ "handlers": {
28
+ "default": {
29
+ "formatter": "default",
30
+ "class": "logging.StreamHandler",
31
+ "stream": "ext://sys.stdout",
32
+ }
33
+ },
34
+ "loggers": {
35
+ "uvicorn": {
36
+ "handlers": ["default"],
37
+ "level": "WARNING",
38
+ "propagate": False,
39
+ },
40
+ "uvicorn.error": {
41
+ "level": "WARNING",
42
+ "handlers": ["default"],
43
+ "propagate": False,
44
+ },
45
+ "uvicorn.access": {
46
+ "handlers": ["default"],
47
+ "level": "WARNING",
48
+ "propagate": False,
49
+ },
50
+ "app": {
51
+ "handlers": ["default"],
52
+ "level": "WARNING",
53
+ "propagate": False,
54
+ }
55
+ },
56
+ "root": {
57
+ "handlers": ["default"],
58
+ "level": "WARNING",
59
+ }
60
+ }
61
+
62
+ # Apply logging configuration
63
+ logging.config.dictConfig(LOGGING_CONFIG)
64
+ logger = logging.getLogger("app")
65
+
66
+ # Create FastAPI application
67
+ app = FastAPI(
68
+ title="BhashaBench Leaderboard",
69
+ version="1.0.0",
70
+ docs_url="/docs",
71
+ )
72
+
73
+ # Add CORS middleware
74
+ app.add_middleware(
75
+ CORSMiddleware,
76
+ allow_origins=["*"],
77
+ allow_credentials=True,
78
+ allow_methods=["*"],
79
+ allow_headers=["*"],
80
+ )
81
+
82
+ # Add GZIP compression
83
+ app.add_middleware(GZipMiddleware, minimum_size=500)
84
+
85
+ # Include API router
86
+ app.include_router(router, prefix="/api")
87
+
88
+ @app.on_event("startup")
89
+ async def startup_event():
90
+ """Initialize services on startup"""
91
+ logger.info("\n")
92
+ logger.info(LogFormatter.section("APPLICATION STARTUP"))
93
+
94
+ # Log HF configuration
95
+ logger.info(LogFormatter.section("HUGGING FACE CONFIGURATION"))
96
+ logger.info(LogFormatter.info(f"Organization: {hf_config.HF_ORGANIZATION}"))
97
+ logger.info(LogFormatter.info(f"Token Status: {'Present' if hf_config.HF_TOKEN else 'Missing'}"))
98
+ logger.info(LogFormatter.info(f"Using repositories:"))
99
+ logger.info(LogFormatter.info(f" - Queue: {hf_config.QUEUE_REPO}"))
100
+ logger.info(LogFormatter.info(f" - Aggregated: {hf_config.AGGREGATED_REPO}"))
101
+ logger.info(LogFormatter.info(f" - Votes: {hf_config.VOTES_REPO}"))
102
+ logger.info(LogFormatter.info(f" - Maintainers Highlight: {hf_config.MAINTAINERS_HIGHLIGHT_REPO}"))
103
+
104
+ # Setup cache
105
+ setup_cache()
106
+ logger.info(LogFormatter.success("FastAPI Cache initialized with in-memory backend"))
backend/app/config/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """
2
+ Configuration module for the Open LLM Leaderboard backend.
3
+ All configuration values are imported from base.py to avoid circular dependencies.
4
+ """
5
+
6
+ from .base import *
backend/app/config/base.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+
4
+ # Server configuration
5
+ HOST = "0.0.0.0"
6
+ PORT = 7860
7
+ WORKERS = 4
8
+ RELOAD = True if os.environ.get("ENVIRONMENT") == "development" else False
9
+
10
+ # CORS configuration
11
+ ORIGINS = ["http://localhost:3000"] if os.getenv("ENVIRONMENT") == "development" else ["*"]
12
+
13
+ # Cache configuration
14
+ CACHE_TTL = int(os.environ.get("CACHE_TTL", 300)) # 5 minutes default
15
+
16
+ # Rate limiting
17
+ RATE_LIMIT_PERIOD = 7 # days
18
+ RATE_LIMIT_QUOTA = 5
19
+ HAS_HIGHER_RATE_LIMIT = []
20
+
21
+ # HuggingFace configuration
22
+ HF_TOKEN = os.environ.get("HF_TOKEN")
23
+ HF_ORGANIZATION = "bharatgenai"
24
+ API = {
25
+ "INFERENCE": "https://api-inference.huggingface.co/models",
26
+ "HUB": "https://huggingface.co"
27
+ }
28
+
29
+ # Cache paths
30
+ CACHE_ROOT = Path(os.environ.get("HF_HOME", ".cache"))
31
+ DATASETS_CACHE = CACHE_ROOT / "datasets"
32
+ MODELS_CACHE = CACHE_ROOT / "models"
33
+ VOTES_CACHE = CACHE_ROOT / "votes"
34
+ EVAL_CACHE = CACHE_ROOT / "eval-queue"
35
+
36
+ # Repository configuration
37
+ QUEUE_REPO = f"{HF_ORGANIZATION}/requests"
38
+ EVAL_REQUESTS_PATH = EVAL_CACHE / "eval_requests.jsonl"
backend/app/config/hf_config.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hugging Face configuration module
3
+ """
4
+ import os
5
+ import logging
6
+ from typing import Optional
7
+ from huggingface_hub import HfApi
8
+ from pathlib import Path
9
+ from app.core.cache import cache_config
10
+ from app.utils.logging import LogFormatter
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Organization or user who owns the datasets
15
+ HF_ORGANIZATION = "bharatgenai"
16
+
17
+ # Get HF token directly from environment
18
+ HF_TOKEN = os.environ.get("HF_TOKEN")
19
+ if not HF_TOKEN:
20
+ logger.warning("HF_TOKEN not found in environment variables. Some features may be limited.")
21
+
22
+ # Initialize HF API
23
+ API = HfApi(token=HF_TOKEN)
24
+
25
+ # Repository configuration
26
+ QUEUE_REPO = f"{HF_ORGANIZATION}/requests"
27
+ AGGREGATED_REPO = f"{HF_ORGANIZATION}/results"
28
+ VOTES_REPO = f"{HF_ORGANIZATION}/votes"
29
+ MAINTAINERS_HIGHLIGHT_REPO = f"{HF_ORGANIZATION}/maintainers-highlight"
30
+
31
+ # File paths from cache config
32
+ VOTES_PATH = cache_config.votes_file
33
+ EVAL_REQUESTS_PATH = cache_config.eval_requests_file
34
+ MODEL_CACHE_DIR = cache_config.models_cache
backend/app/config/logging_config.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import sys
3
+ from tqdm import tqdm
4
+
5
+ def get_tqdm_handler():
6
+ """
7
+ Creates a special handler for tqdm that doesn't interfere with other logs.
8
+ """
9
+ class TqdmLoggingHandler(logging.Handler):
10
+ def emit(self, record):
11
+ try:
12
+ msg = self.format(record)
13
+ tqdm.write(msg)
14
+ self.flush()
15
+ except Exception:
16
+ self.handleError(record)
17
+
18
+ return TqdmLoggingHandler()
19
+
20
+ def setup_service_logger(service_name: str) -> logging.Logger:
21
+ """
22
+ Configure a specific logger for a given service.
23
+ """
24
+ logger = logging.getLogger(f"app.services.{service_name}")
25
+
26
+ # If the logger already has handlers, don't reconfigure it
27
+ if logger.handlers:
28
+ return logger
29
+
30
+ # Add tqdm handler for this service
31
+ tqdm_handler = get_tqdm_handler()
32
+ tqdm_handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
33
+ logger.addHandler(tqdm_handler)
34
+
35
+ # Don't propagate logs to parent loggers
36
+ logger.propagate = False
37
+
38
+ return logger
backend/app/core/cache.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+ from datetime import timedelta
5
+ import logging
6
+ from app.utils.logging import LogFormatter
7
+ from app.config.base import (
8
+ CACHE_ROOT,
9
+ DATASETS_CACHE,
10
+ MODELS_CACHE,
11
+ VOTES_CACHE,
12
+ EVAL_CACHE,
13
+ CACHE_TTL
14
+ )
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class CacheConfig:
19
+ def __init__(self):
20
+ # Get cache paths from config
21
+ self.cache_root = CACHE_ROOT
22
+ self.datasets_cache = DATASETS_CACHE
23
+ self.models_cache = MODELS_CACHE
24
+ self.votes_cache = VOTES_CACHE
25
+ self.eval_cache = EVAL_CACHE
26
+
27
+ # Specific files
28
+ self.votes_file = self.votes_cache / "votes_data.jsonl"
29
+ self.eval_requests_file = self.eval_cache / "eval_requests.jsonl"
30
+
31
+ # Cache TTL
32
+ self.cache_ttl = timedelta(seconds=CACHE_TTL)
33
+
34
+ self._initialize_cache_dirs()
35
+ self._setup_environment()
36
+
37
+ def _initialize_cache_dirs(self):
38
+ """Initialize all necessary cache directories"""
39
+ try:
40
+ logger.info(LogFormatter.section("CACHE INITIALIZATION"))
41
+
42
+ cache_dirs = {
43
+ "Root": self.cache_root,
44
+ "Datasets": self.datasets_cache,
45
+ "Models": self.models_cache,
46
+ "Votes": self.votes_cache,
47
+ "Eval": self.eval_cache
48
+ }
49
+
50
+ for name, cache_dir in cache_dirs.items():
51
+ cache_dir.mkdir(parents=True, exist_ok=True)
52
+ logger.info(LogFormatter.success(f"{name} cache directory: {cache_dir}"))
53
+
54
+ except Exception as e:
55
+ logger.error(LogFormatter.error("Failed to create cache directories", e))
56
+ raise
57
+
58
+ def _setup_environment(self):
59
+ """Configure HuggingFace environment variables"""
60
+ logger.info(LogFormatter.subsection("ENVIRONMENT SETUP"))
61
+
62
+ env_vars = {
63
+ "HF_HOME": str(self.cache_root),
64
+ "TRANSFORMERS_CACHE": str(self.models_cache),
65
+ "HF_DATASETS_CACHE": str(self.datasets_cache)
66
+ }
67
+
68
+ for var, value in env_vars.items():
69
+ os.environ[var] = value
70
+ logger.info(LogFormatter.info(f"Set {var}={value}"))
71
+
72
+ def get_cache_path(self, cache_type: str) -> Path:
73
+ """Returns the path for a specific cache type"""
74
+ cache_paths = {
75
+ "datasets": self.datasets_cache,
76
+ "models": self.models_cache,
77
+ "votes": self.votes_cache,
78
+ "eval": self.eval_cache
79
+ }
80
+ return cache_paths.get(cache_type, self.cache_root)
81
+
82
+ def flush_cache(self, cache_type: str = None):
83
+ """Flush specified cache or all caches if no type is specified"""
84
+ try:
85
+ if cache_type:
86
+ logger.info(LogFormatter.section(f"FLUSHING {cache_type.upper()} CACHE"))
87
+ cache_dir = self.get_cache_path(cache_type)
88
+ if cache_dir.exists():
89
+ stats = {
90
+ "Cache_Type": cache_type,
91
+ "Directory": str(cache_dir)
92
+ }
93
+ for line in LogFormatter.tree(stats, "Cache Details"):
94
+ logger.info(line)
95
+ shutil.rmtree(cache_dir)
96
+ cache_dir.mkdir(parents=True, exist_ok=True)
97
+ logger.info(LogFormatter.success("Cache cleared successfully"))
98
+ else:
99
+ logger.info(LogFormatter.section("FLUSHING ALL CACHES"))
100
+ for cache_type in ["datasets", "models", "votes", "eval"]:
101
+ self.flush_cache(cache_type)
102
+ logger.info(LogFormatter.success("All caches cleared successfully"))
103
+
104
+ except Exception as e:
105
+ logger.error(LogFormatter.error("Failed to flush cache", e))
106
+ raise
107
+
108
+ # Singleton instance of cache configuration
109
+ cache_config = CacheConfig()
backend/app/core/fastapi_cache.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi_cache import FastAPICache
2
+ from fastapi_cache.backends.inmemory import InMemoryBackend
3
+ from fastapi_cache.decorator import cache
4
+ from datetime import timedelta
5
+ from app.config import CACHE_TTL
6
+ import logging
7
+ from app.utils.logging import LogFormatter
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ def setup_cache():
12
+ """Initialize FastAPI Cache with in-memory backend"""
13
+ FastAPICache.init(
14
+ backend=InMemoryBackend(),
15
+ prefix="fastapi-cache",
16
+ expire=CACHE_TTL
17
+ )
18
+ logger.info(LogFormatter.success("FastAPI Cache initialized with in-memory backend"))
19
+
20
+ def invalidate_cache_key(key: str):
21
+ """Invalidate a specific cache key"""
22
+ try:
23
+ backend = FastAPICache.get_backend()
24
+ if hasattr(backend, 'delete'):
25
+ backend.delete(key)
26
+ logger.info(LogFormatter.success(f"Cache invalidated for key: {key}"))
27
+ else:
28
+ logger.warning(LogFormatter.warning("Cache backend does not support deletion"))
29
+ except Exception as e:
30
+ logger.error(LogFormatter.error(f"Failed to invalidate cache key: {key}", e))
31
+
32
+ def build_cache_key(namespace: str, *args) -> str:
33
+ """Build a consistent cache key"""
34
+ key = f"fastapi-cache:{namespace}:{':'.join(str(arg) for arg in args)}"
35
+ logger.debug(LogFormatter.info(f"Built cache key: {key}"))
36
+ return key
37
+
38
+ def cached(expire: int = CACHE_TTL, key_builder=None):
39
+ """Decorator for caching endpoint responses
40
+
41
+ Args:
42
+ expire (int): Cache TTL in seconds
43
+ key_builder (callable, optional): Custom key builder function
44
+ """
45
+ return cache(
46
+ expire=expire,
47
+ key_builder=key_builder
48
+ )
backend/app/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from app.config.logging_config import setup_logging
3
+ import logging
4
+
5
+ # Initialize logging configuration
6
+ setup_logging()
7
+ logger = logging.getLogger(__name__)
8
+
9
+ app = FastAPI(title="BhashaBench Leaderboard API")
10
+
11
+ @app.on_event("startup")
12
+ async def startup_event():
13
+ logger.info("Starting up the application...")
14
+
15
+ # Import and include routers after app initialization
16
+ from app.api import models, votes
17
+ app.include_router(models.router, prefix="/api", tags=["models"])
18
+ app.include_router(votes.router, prefix="/api", tags=["votes"])
backend/app/services/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from . import hf_service, leaderboard, votes, models
2
+
3
+ __all__ = ["hf_service", "leaderboard", "votes", "models"]
backend/app/services/hf_service.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from huggingface_hub import HfApi
3
+ from app.config import HF_TOKEN, API
4
+ from app.core.cache import cache_config
5
+ from app.utils.logging import LogFormatter
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class HuggingFaceService:
11
+ def __init__(self):
12
+ self.api = API
13
+ self.token = HF_TOKEN
14
+ self.cache_dir = cache_config.models_cache
15
+
16
+ async def check_authentication(self) -> bool:
17
+ """Check if the HF token is valid"""
18
+ if not self.token:
19
+ return False
20
+ try:
21
+ logger.info(LogFormatter.info("Checking HF token validity..."))
22
+ self.api.get_token_permission()
23
+ logger.info(LogFormatter.success("HF token is valid"))
24
+ return True
25
+ except Exception as e:
26
+ logger.error(LogFormatter.error("HF token validation failed", e))
27
+ return False
28
+
29
+ async def get_user_info(self) -> Optional[dict]:
30
+ """Get information about the authenticated user"""
31
+ try:
32
+ logger.info(LogFormatter.info("Fetching user information..."))
33
+ info = self.api.get_token_permission()
34
+ logger.info(LogFormatter.success(f"User info retrieved for: {info.get('user', 'Unknown')}"))
35
+ return info
36
+ except Exception as e:
37
+ logger.error(LogFormatter.error("Failed to get user info", e))
38
+ return None
39
+
40
+ def _log_repo_operation(self, operation: str, repo: str, details: str = None):
41
+ """Helper to log repository operations"""
42
+ logger.info(LogFormatter.section(f"HF REPOSITORY OPERATION - {operation.upper()}"))
43
+ stats = {
44
+ "Operation": operation,
45
+ "Repository": repo,
46
+ }
47
+ if details:
48
+ stats["Details"] = details
49
+ for line in LogFormatter.tree(stats):
50
+ logger.info(line)
backend/app/services/leaderboard.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.core.cache import cache_config
2
+ from datetime import datetime
3
+ from typing import List, Dict, Any
4
+ import datasets
5
+ from fastapi import HTTPException
6
+ import logging
7
+ from app.config.base import HF_ORGANIZATION
8
+ from app.utils.logging import LogFormatter
9
+ import pandas as pd
10
+ import random
11
+
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class LeaderboardService:
16
+ def __init__(self):
17
+ pass
18
+
19
+ def _generate_dummy_data(self) -> List[Dict[str, Any]]:
20
+ """Generate dummy leaderboard data for testing"""
21
+ from datetime import datetime, timedelta
22
+ import random
23
+
24
+ model_names = [
25
+ "meta-llama/Llama-3-70B", "mistralai/Mixtral-8x7B", "google/gemma-7b",
26
+ "microsoft/phi-3-medium", "databricks/dbrx-instruct", "01-ai/Yi-34B",
27
+ "Qwen/Qwen2-72B", "tiiuae/falcon-180B", "anthropic/claude-instant",
28
+ "cohere/command-r-plus", "alibaba/Qwen-VL", "baichuan-inc/Baichuan2-13B",
29
+ "THUDM/chatglm3-6b", "stabilityai/stablelm-3b", "EleutherAI/gpt-neox-20b"
30
+ ]
31
+
32
+ architectures = ["LlamaForCausalLM", "MixtralForCausalLM", "GemmaForCausalLM",
33
+ "Phi3ForCausalLM", "DbrxForCausalLM", "GPTNeoXForCausalLM"]
34
+
35
+ model_types = [
36
+ "pretrained", "fine-tuned", "finetuning", "fined-tuned-on-domain-specific-dataset",
37
+ "instruction-tuned", "chat-tuned"
38
+ ]
39
+
40
+ precisions = ["float16", "bfloat16", "float32", "8bit", "4bit"]
41
+ weight_types = ["Original", "Delta", "Adapter", "Merge"]
42
+ licenses = ["Apache-2.0", "MIT", "CC-BY-4.0", "Llama-2", "Custom"]
43
+
44
+ dummy_data = []
45
+ base_date = datetime.now() - timedelta(days=365)
46
+
47
+ for i, model_name in enumerate(model_names):
48
+ # Generate multiple variants per model (different precisions)
49
+ for precision in random.sample(precisions, k=random.randint(1, 3)):
50
+ upload_date = base_date + timedelta(days=random.randint(0, 365))
51
+ submission_date = upload_date + timedelta(days=random.randint(0, 30))
52
+
53
+ # Generate BBA scores (English and Hindi)
54
+ bba_english_raw = round(random.uniform(40, 95), 2)
55
+ bba_hindi_raw = round(random.uniform(35, 90), 2)
56
+ bba_raw = round((bba_english_raw + bba_hindi_raw) / 2, 2)
57
+
58
+ # Generate BBF scores (English and Hindi)
59
+ bbf_english_raw = round(random.uniform(45, 92), 2)
60
+ bbf_hindi_raw = round(random.uniform(38, 88), 2)
61
+ bbf_raw = round((bbf_english_raw + bbf_hindi_raw) / 2, 2)
62
+
63
+ # Generate BBK scores (English and Hindi)
64
+ bbk_english_raw = round(random.uniform(42, 90), 2)
65
+ bbk_hindi_raw = round(random.uniform(40, 87), 2)
66
+ bbk_raw = round((bbk_english_raw + bbk_hindi_raw) / 2, 2)
67
+
68
+ # Generate BBL scores (English and Hindi)
69
+ bbl_english_raw = round(random.uniform(43, 91), 2)
70
+ bbl_hindi_raw = round(random.uniform(41, 89), 2)
71
+ bbl_raw = round((bbl_english_raw + bbl_hindi_raw) / 2, 2)
72
+
73
+ # Normalized scores (slightly different from raw)
74
+ bba_norm = round(bba_raw * random.uniform(0.95, 1.05), 2)
75
+ bba_english_norm = round(bba_english_raw * random.uniform(0.95, 1.05), 2)
76
+ bba_hindi_norm = round(bba_hindi_raw * random.uniform(0.95, 1.05), 2)
77
+
78
+ bbf_norm = round(bbf_raw * random.uniform(0.95, 1.05), 2)
79
+ bbf_english_norm = round(bbf_english_raw * random.uniform(0.95, 1.05), 2)
80
+ bbf_hindi_norm = round(bbf_hindi_raw * random.uniform(0.95, 1.05), 2)
81
+
82
+ bbk_norm = round(bbk_raw * random.uniform(0.95, 1.05), 2)
83
+ bbk_english_norm = round(bbk_english_raw * random.uniform(0.95, 1.05), 2)
84
+ bbk_hindi_norm = round(bbk_hindi_raw * random.uniform(0.95, 1.05), 2)
85
+
86
+ bbl_norm = round(bbl_raw * random.uniform(0.95, 1.05), 2)
87
+ bbl_english_norm = round(bbl_english_raw * random.uniform(0.95, 1.05), 2)
88
+ bbl_hindi_norm = round(bbl_hindi_raw * random.uniform(0.95, 1.05), 2)
89
+
90
+ # Calculate average score from all normalized scores
91
+ average_score = round((bba_norm + bbf_norm + bbk_norm + bbl_norm) / 4, 2)
92
+
93
+ entry = {
94
+ "fullname": model_name,
95
+ "Model sha": f"sha256:{random.randbytes(32).hex()[:16]}",
96
+ "Precision": precision,
97
+ "Type": random.choice(model_types),
98
+ "Weight type": random.choice(weight_types),
99
+ "Architecture": random.choice(architectures),
100
+ "Average ⬆️": average_score,
101
+ "Chat Template": random.choice([True, False]),
102
+
103
+ # BBA Evaluation scores
104
+ "BBA Raw": bba_raw,
105
+ "BBA": bba_norm,
106
+ "BBA_English Raw": bba_english_raw,
107
+ "BBA_English": bba_english_norm,
108
+ "BBA_Hindi Raw": bba_hindi_raw,
109
+ "BBA_Hindi": bba_hindi_norm,
110
+
111
+ # BBF Evaluation scores
112
+ "BBF Raw": bbf_raw,
113
+ "BBF": bbf_norm,
114
+ "BBF_English Raw": bbf_english_raw,
115
+ "BBF_English": bbf_english_norm,
116
+ "BBF_Hindi Raw": bbf_hindi_raw,
117
+ "BBF_Hindi": bbf_hindi_norm,
118
+
119
+ # BBK Evaluation scores
120
+ "BBK Raw": bbk_raw,
121
+ "BBK": bbk_norm,
122
+ "BBK_English Raw": bbk_english_raw,
123
+ "BBK_English": bbk_english_norm,
124
+ "BBK_Hindi Raw": bbk_hindi_raw,
125
+ "BBK_Hindi": bbk_hindi_norm,
126
+
127
+ # BBL Evaluation scores
128
+ "BBL Raw": bbl_raw,
129
+ "BBL": bbl_norm,
130
+ "BBL_English Raw": bbl_english_raw,
131
+ "BBL_English": bbl_english_norm,
132
+ "BBL_Hindi Raw": bbl_hindi_raw,
133
+ "BBL_Hindi": bbl_hindi_norm,
134
+
135
+ # Features
136
+ "Available on the hub": random.choice([True, False]),
137
+ "Merged": random.choice([True, False]),
138
+ "MoE": random.choice([True, False]),
139
+ "Flagged": random.choice([True, False]),
140
+ "Official Providers": random.choice([True, False]),
141
+
142
+ # Metadata
143
+ "Upload To Hub Date": upload_date.strftime("%Y-%m-%d"),
144
+ "Submission Date": submission_date.strftime("%Y-%m-%d"),
145
+ "Generation": random.choice(["2", "3", "3.5", "4"]),
146
+ "Base Model": random.choice([model_name.split("/")[0] + "/base", "N/A"]),
147
+ "Hub License": random.choice(licenses),
148
+ "Hub ❤️": random.randint(10, 5000),
149
+ "#Params (B)": round(random.uniform(1, 180), 1),
150
+ "CO₂ cost (kg)": round(random.uniform(50, 500), 2)
151
+ }
152
+
153
+ dummy_data.append(entry)
154
+
155
+ return dummy_data
156
+
157
+ async def fetch_raw_data(self) -> List[Dict[str, Any]]:
158
+ """Fetch raw leaderboard data from HuggingFace dataset"""
159
+ try:
160
+ logger.info(LogFormatter.section("FETCHING LEADERBOARD DATA"))
161
+ logger.info(LogFormatter.info(f"Loading dataset from {HF_ORGANIZATION}/results"))
162
+
163
+ dataset = datasets.load_dataset(
164
+ f"{HF_ORGANIZATION}/results",
165
+ cache_dir=cache_config.get_cache_path("datasets")
166
+ )["train"]
167
+ df = dataset.to_pandas()
168
+ data = df.to_dict('records')
169
+
170
+ # data = self._generate_dummy_data()
171
+ # df = pd.DataFrame(data)
172
+ # data = df.to_dict('records')
173
+
174
+ stats = {
175
+ "Total_Entries": len(data),
176
+ "Dataset_Size": f"{df.memory_usage(deep=True).sum() / 1024 / 1024:.1f}MB"
177
+ }
178
+ for line in LogFormatter.stats(stats, "Dataset Statistics"):
179
+ logger.info(line)
180
+
181
+ return data
182
+
183
+ except Exception as e:
184
+ logger.error(LogFormatter.error("Failed to fetch leaderboard data", e))
185
+ raise HTTPException(status_code=500, detail=str(e))
186
+
187
+ async def get_formatted_data(self) -> List[Dict[str, Any]]:
188
+ """Get formatted leaderboard data"""
189
+ try:
190
+ logger.info(LogFormatter.section("FORMATTING LEADERBOARD DATA"))
191
+
192
+ raw_data = await self.fetch_raw_data()
193
+ formatted_data = []
194
+ type_counts = {}
195
+ error_count = 0
196
+
197
+ # Initialize progress tracking
198
+ total_items = len(raw_data)
199
+ logger.info(LogFormatter.info(f"Processing {total_items:,} entries..."))
200
+
201
+ for i, item in enumerate(raw_data, 1):
202
+ try:
203
+ formatted_item = await self.transform_data(item)
204
+ formatted_data.append(formatted_item)
205
+
206
+ # Count model types
207
+ model_type = formatted_item["model"]["type"]
208
+ type_counts[model_type] = type_counts.get(model_type, 0) + 1
209
+
210
+ except Exception as e:
211
+ error_count += 1
212
+ logger.error(LogFormatter.error(f"Failed to format entry {i}/{total_items}", e))
213
+ continue
214
+
215
+ # Log progress every 10%
216
+ if i % max(1, total_items // 10) == 0:
217
+ progress = (i / total_items) * 100
218
+ logger.info(LogFormatter.info(f"Progress: {LogFormatter.progress_bar(i, total_items)}"))
219
+
220
+ # Log final statistics
221
+ stats = {
222
+ "Total_Processed": total_items,
223
+ "Successful": len(formatted_data),
224
+ "Failed": error_count
225
+ }
226
+ logger.info(LogFormatter.section("PROCESSING SUMMARY"))
227
+ for line in LogFormatter.stats(stats, "Processing Statistics"):
228
+ logger.info(line)
229
+
230
+ # Log model type distribution
231
+ type_stats = {f"Type_{k}": v for k, v in type_counts.items()}
232
+ logger.info(LogFormatter.subsection("MODEL TYPE DISTRIBUTION"))
233
+ for line in LogFormatter.stats(type_stats):
234
+ logger.info(line)
235
+
236
+ return formatted_data
237
+
238
+ except Exception as e:
239
+ logger.error(LogFormatter.error("Failed to format leaderboard data", e))
240
+ raise HTTPException(status_code=500, detail=str(e))
241
+
242
+ async def transform_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
243
+ """Transform raw data into the format expected by the frontend"""
244
+ try:
245
+ # Extract model name for logging
246
+ model_name = data.get("fullname", "Unknown")
247
+ logger.debug(LogFormatter.info(f"Transforming data for model: {model_name}"))
248
+
249
+ # Create unique ID combining model name, precision, sha and chat template status
250
+ unique_id = f"{data.get('fullname', 'Unknown')}_{data.get('Precision', 'Unknown')}_{data.get('Model sha', 'Unknown')}_{str(data.get('Chat Template', False))}"
251
+
252
+ evaluations = {
253
+ "bba": {
254
+ "name": "BBA",
255
+ "value": data.get("BBA Raw", 0),
256
+ "normalized_score": data.get("BBA", 0)
257
+ },
258
+ "bba_english": {
259
+ "name": "BBA_English",
260
+ "value": data.get("BBA_English Raw", 0),
261
+ "normalized_score": data.get("BBA_English", 0)
262
+ },
263
+ "bba_hindi": {
264
+ "name": "BBA_Hindi",
265
+ "value": data.get("BBA_Hindi Raw", 0),
266
+ "normalized_score": data.get("BBA_Hindi", 0)
267
+ },
268
+ "bbf": {
269
+ "name": "BBF",
270
+ "value": data.get("BBF Raw", 0),
271
+ "normalized_score": data.get("BBF", 0)
272
+ },
273
+ "bbf_English": {
274
+ "name": "BBF_English",
275
+ "value": data.get("BBF_English Raw", 0),
276
+ "normalized_score": data.get("BBF_English", 0)
277
+ },
278
+ "bbf_Hindi": {
279
+ "name": "BBF_Hindi",
280
+ "value": data.get("BBF_Hindi Raw", 0),
281
+ "normalized_score": data.get("BBF_Hindi", 0)
282
+ },
283
+ "bbk": {
284
+ "name": "BBK",
285
+ "value": data.get("BBK Raw", 0),
286
+ "normalized_score": data.get("BBK", 0)
287
+ },
288
+ "bbk_english": {
289
+ "name": "BBK_English",
290
+ "value": data.get("BBK_English Raw", 0),
291
+ "normalized_score": data.get("BBK_English", 0)
292
+ },
293
+ "bbk_hindi": {
294
+ "name": "BBK_Hindi",
295
+ "value": data.get("BBK_Hindi Raw", 0),
296
+ "normalized_score": data.get("BBK_Hindi", 0)
297
+ },
298
+ "bbl": {
299
+ "name": "BBL",
300
+ "value": data.get("BBL Raw", 0),
301
+ "normalized_score": data.get("BBL", 0)
302
+ },
303
+ "bbl_english": {
304
+ "name": "BBL_English",
305
+ "value": data.get("BBL_English Raw", 0),
306
+ "normalized_score": data.get("BBL_English", 0)
307
+ },
308
+ "bbl_hindi": {
309
+ "name": "BBL_Hindi",
310
+ "value": data.get("BBL_Hindi Raw", 0),
311
+ "normalized_score": data.get("BBL_Hindi", 0)
312
+ }
313
+ }
314
+
315
+ features = {
316
+ "is_not_available_on_hub": data.get("Available on the hub", False),
317
+ "is_merged": data.get("Merged", False),
318
+ "is_moe": data.get("MoE", False),
319
+ "is_flagged": data.get("Flagged", False),
320
+ "is_highlighted_by_maintainer": data.get("Official Providers", False)
321
+ }
322
+
323
+ metadata = {
324
+ "upload_date": data.get("Upload To Hub Date"),
325
+ "submission_date": data.get("Submission Date"),
326
+ "generation": data.get("Generation"),
327
+ "base_model": data.get("Base Model"),
328
+ "hub_license": data.get("Hub License"),
329
+ "hub_hearts": data.get("Hub ❤️"),
330
+ "params_billions": data.get("#Params (B)"),
331
+ "co2_cost": data.get("CO₂ cost (kg)", 0)
332
+ }
333
+
334
+ # Clean model type by removing emojis if present
335
+ original_type = data.get("Type", "")
336
+ model_type = original_type.lower().strip()
337
+
338
+ # Remove emojis and parentheses
339
+ if "(" in model_type:
340
+ model_type = model_type.split("(")[0].strip()
341
+ model_type = ''.join(c for c in model_type if not c in '🔶🟢🟩💬🤝🌸 ')
342
+
343
+ # Map old model types to new ones
344
+ model_type_mapping = {
345
+ "fine-tuned": "fined-tuned-on-domain-specific-dataset",
346
+ "fine tuned": "fined-tuned-on-domain-specific-dataset",
347
+ "finetuned": "fined-tuned-on-domain-specific-dataset",
348
+ "fine_tuned": "fined-tuned-on-domain-specific-dataset",
349
+ "ft": "fined-tuned-on-domain-specific-dataset",
350
+ "finetuning": "fined-tuned-on-domain-specific-dataset",
351
+ "fine tuning": "fined-tuned-on-domain-specific-dataset",
352
+ "fine-tuning": "fined-tuned-on-domain-specific-dataset"
353
+ }
354
+
355
+ mapped_type = model_type_mapping.get(model_type.lower().strip(), model_type)
356
+
357
+ if mapped_type != model_type:
358
+ logger.debug(LogFormatter.info(f"Model type mapped: {original_type} -> {mapped_type}"))
359
+
360
+ transformed_data = {
361
+ "id": unique_id,
362
+ "model": {
363
+ "name": data.get("fullname"),
364
+ "sha": data.get("Model sha"),
365
+ "precision": data.get("Precision"),
366
+ "type": mapped_type,
367
+ "weight_type": data.get("Weight type"),
368
+ "architecture": data.get("Architecture"),
369
+ "average_score": data.get("Average ⬆️"),
370
+ "has_chat_template": data.get("Chat Template", False)
371
+ },
372
+ "evaluations": evaluations,
373
+ "features": features,
374
+ "metadata": metadata
375
+ }
376
+
377
+ logger.debug(LogFormatter.success(f"Successfully transformed data for {model_name}"))
378
+ return transformed_data
379
+
380
+ except Exception as e:
381
+ logger.error(LogFormatter.error(f"Failed to transform data for {data.get('fullname', 'Unknown')}", e))
382
+ raise
backend/app/services/models.py ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timezone
2
+ from typing import Dict, Any, Optional, List
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ import logging
7
+ import aiohttp
8
+ import asyncio
9
+ import time
10
+ from huggingface_hub import HfApi, CommitOperationAdd
11
+ from huggingface_hub.utils import build_hf_headers
12
+ from datasets import disable_progress_bar
13
+ import sys
14
+ import contextlib
15
+ from concurrent.futures import ThreadPoolExecutor
16
+ import tempfile
17
+
18
+ from app.config import (
19
+ QUEUE_REPO,
20
+ HF_TOKEN,
21
+ EVAL_REQUESTS_PATH
22
+ )
23
+ from app.config.hf_config import HF_ORGANIZATION
24
+ from app.services.hf_service import HuggingFaceService
25
+ from app.utils.model_validation import ModelValidator
26
+ from app.services.votes import VoteService
27
+ from app.core.cache import cache_config
28
+ from app.utils.logging import LogFormatter
29
+
30
+ # Disable datasets progress bars globally
31
+ disable_progress_bar()
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ # Context manager to temporarily disable stdout and stderr
36
+ @contextlib.contextmanager
37
+ def suppress_output():
38
+ stdout = sys.stdout
39
+ stderr = sys.stderr
40
+ devnull = open(os.devnull, 'w')
41
+ try:
42
+ sys.stdout = devnull
43
+ sys.stderr = devnull
44
+ yield
45
+ finally:
46
+ sys.stdout = stdout
47
+ sys.stderr = stderr
48
+ devnull.close()
49
+
50
+ class ProgressTracker:
51
+ def __init__(self, total: int, desc: str = "Progress", update_frequency: int = 10):
52
+ self.total = total
53
+ self.current = 0
54
+ self.desc = desc
55
+ self.start_time = time.time()
56
+ self.update_frequency = update_frequency # Percentage steps
57
+ self.last_update = -1
58
+
59
+ # Initial log with fancy formatting
60
+ logger.info(LogFormatter.section(desc))
61
+ logger.info(LogFormatter.info(f"Starting processing of {total:,} items..."))
62
+ sys.stdout.flush()
63
+
64
+ def update(self, n: int = 1):
65
+ self.current += n
66
+ current_percentage = (self.current * 100) // self.total
67
+
68
+ # Only update on frequency steps (e.g., 0%, 10%, 20%, etc.)
69
+ if current_percentage >= self.last_update + self.update_frequency or current_percentage == 100:
70
+ elapsed = time.time() - self.start_time
71
+ rate = self.current / elapsed if elapsed > 0 else 0
72
+ remaining = (self.total - self.current) / rate if rate > 0 else 0
73
+
74
+ # Create progress stats
75
+ stats = {
76
+ "Progress": LogFormatter.progress_bar(self.current, self.total),
77
+ "Items": f"{self.current:,}/{self.total:,}",
78
+ "Time": f"⏱️ {elapsed:.1f}s elapsed, {remaining:.1f}s remaining",
79
+ "Rate": f"🚀 {rate:.1f} items/s"
80
+ }
81
+
82
+ # Log progress using tree format
83
+ for line in LogFormatter.tree(stats):
84
+ logger.info(line)
85
+ sys.stdout.flush()
86
+
87
+ self.last_update = (current_percentage // self.update_frequency) * self.update_frequency
88
+
89
+ def close(self):
90
+ elapsed = time.time() - self.start_time
91
+ rate = self.total / elapsed if elapsed > 0 else 0
92
+
93
+ # Final summary with fancy formatting
94
+ logger.info(LogFormatter.section("COMPLETED"))
95
+ stats = {
96
+ "Total": f"{self.total:,} items",
97
+ "Time": f"{elapsed:.1f}s",
98
+ "Rate": f"{rate:.1f} items/s"
99
+ }
100
+ for line in LogFormatter.stats(stats):
101
+ logger.info(line)
102
+ logger.info("="*50)
103
+ sys.stdout.flush()
104
+
105
+ class ModelService(HuggingFaceService):
106
+ _instance: Optional['ModelService'] = None
107
+ _initialized = False
108
+
109
+ def __new__(cls):
110
+ if cls._instance is None:
111
+ logger.info(LogFormatter.info("Creating new ModelService instance"))
112
+ cls._instance = super(ModelService, cls).__new__(cls)
113
+ return cls._instance
114
+
115
+ def __init__(self):
116
+ if not hasattr(self, '_init_done'):
117
+ logger.info(LogFormatter.section("MODEL SERVICE INITIALIZATION"))
118
+ super().__init__()
119
+ self.validator = ModelValidator()
120
+ self.vote_service = VoteService()
121
+ self.eval_requests_path = cache_config.eval_requests_file
122
+ logger.info(LogFormatter.info(f"Using eval requests path: {self.eval_requests_path}"))
123
+
124
+ self.eval_requests_path.parent.mkdir(parents=True, exist_ok=True)
125
+ self.hf_api = HfApi(token=HF_TOKEN)
126
+ self.cached_models = None
127
+ self.last_cache_update = 0
128
+ self.cache_ttl = cache_config.cache_ttl.total_seconds()
129
+ self._init_done = True
130
+ logger.info(LogFormatter.success("Initialization complete"))
131
+
132
+ async def _download_and_process_file(self, file: str, session: aiohttp.ClientSession, progress: ProgressTracker) -> Optional[Dict]:
133
+ """Download and process a file asynchronously"""
134
+ try:
135
+ # Build file URL
136
+ url = f"https://huggingface.co/datasets/{QUEUE_REPO}/resolve/main/{file}"
137
+ headers = build_hf_headers(token=self.token)
138
+
139
+ # Download file
140
+ async with session.get(url, headers=headers) as response:
141
+ if response.status != 200:
142
+ logger.error(LogFormatter.error(f"Failed to download {file}", f"HTTP {response.status}"))
143
+ progress.update()
144
+ return None
145
+
146
+ try:
147
+ # First read content as text
148
+ text_content = await response.text()
149
+ # Then parse JSON
150
+ content = json.loads(text_content)
151
+ except json.JSONDecodeError as e:
152
+ logger.error(LogFormatter.error(f"Failed to decode JSON from {file}", e))
153
+ progress.update()
154
+ return None
155
+
156
+ # Get status and determine target status
157
+ status = content.get("status", "PENDING").upper()
158
+ target_status = None
159
+ status_map = {
160
+ "PENDING": ["PENDING"],
161
+ "EVALUATING": ["RUNNING"],
162
+ "FINISHED": ["FINISHED"]
163
+ }
164
+
165
+ for target, source_statuses in status_map.items():
166
+ if status in source_statuses:
167
+ target_status = target
168
+ break
169
+
170
+ if not target_status:
171
+ progress.update()
172
+ return None
173
+
174
+ # Calculate wait time
175
+ try:
176
+ submit_time = datetime.fromisoformat(content["submitted_time"].replace("Z", "+00:00"))
177
+ if submit_time.tzinfo is None:
178
+ submit_time = submit_time.replace(tzinfo=timezone.utc)
179
+ current_time = datetime.now(timezone.utc)
180
+ wait_time = current_time - submit_time
181
+
182
+ model_info = {
183
+ "name": content["model"],
184
+ "submitter": content.get("sender", "Unknown"),
185
+ "revision": content["revision"],
186
+ "wait_time": f"{wait_time.total_seconds():.1f}s",
187
+ "submission_time": content["submitted_time"],
188
+ "status": target_status,
189
+ "precision": content.get("precision", "Unknown")
190
+ }
191
+
192
+ progress.update()
193
+ return model_info
194
+
195
+ except (ValueError, TypeError) as e:
196
+ logger.error(LogFormatter.error(f"Failed to process {file}", e))
197
+ progress.update()
198
+ return None
199
+
200
+ except Exception as e:
201
+ logger.error(LogFormatter.error(f"Failed to load {file}", e))
202
+ progress.update()
203
+ return None
204
+
205
+ async def _refresh_models_cache(self):
206
+ """Refresh the models cache"""
207
+ try:
208
+ logger.info(LogFormatter.section("CACHE REFRESH"))
209
+ self._log_repo_operation("read", f"{HF_ORGANIZATION}/requests", "Refreshing models cache")
210
+
211
+ # Initialize models dictionary
212
+ models = {
213
+ "finished": [],
214
+ "evaluating": [],
215
+ "pending": []
216
+ }
217
+
218
+ try:
219
+ logger.info(LogFormatter.subsection("DATASET LOADING"))
220
+ logger.info(LogFormatter.info("Loading dataset files..."))
221
+
222
+ # List files in repository
223
+ with suppress_output():
224
+ files = self.hf_api.list_repo_files(
225
+ repo_id=QUEUE_REPO,
226
+ repo_type="dataset",
227
+ token=self.token
228
+ )
229
+
230
+ # Filter JSON files
231
+ json_files = [f for f in files if f.endswith('.json')]
232
+ total_files = len(json_files)
233
+
234
+ # Log repository stats
235
+ stats = {
236
+ "Total_Files": len(files),
237
+ "JSON_Files": total_files,
238
+ }
239
+ for line in LogFormatter.stats(stats, "Repository Statistics"):
240
+ logger.info(line)
241
+
242
+ if not json_files:
243
+ raise Exception("No JSON files found in repository")
244
+
245
+ # Initialize progress tracker
246
+ progress = ProgressTracker(total_files, "PROCESSING FILES")
247
+
248
+ try:
249
+ # Create aiohttp session to reuse connections
250
+ async with aiohttp.ClientSession() as session:
251
+ # Process files in chunks
252
+ chunk_size = 50
253
+
254
+ for i in range(0, len(json_files), chunk_size):
255
+ chunk = json_files[i:i + chunk_size]
256
+ chunk_tasks = [
257
+ self._download_and_process_file(file, session, progress)
258
+ for file in chunk
259
+ ]
260
+ results = await asyncio.gather(*chunk_tasks)
261
+
262
+ # Process results
263
+ for result in results:
264
+ if result:
265
+ status = result.pop("status")
266
+ models[status.lower()].append(result)
267
+
268
+ finally:
269
+ progress.close()
270
+
271
+ # Final summary with fancy formatting
272
+ logger.info(LogFormatter.section("CACHE SUMMARY"))
273
+ stats = {
274
+ "Finished": len(models["finished"]),
275
+ "Evaluating": len(models["evaluating"]),
276
+ "Pending": len(models["pending"])
277
+ }
278
+ for line in LogFormatter.stats(stats, "Models by Status"):
279
+ logger.info(line)
280
+ logger.info("="*50)
281
+
282
+ except Exception as e:
283
+ logger.error(LogFormatter.error("Error processing files", e))
284
+ raise
285
+
286
+ # Update cache
287
+ self.cached_models = models
288
+ self.last_cache_update = time.time()
289
+ logger.info(LogFormatter.success("Cache updated successfully"))
290
+
291
+ return models
292
+
293
+ except Exception as e:
294
+ logger.error(LogFormatter.error("Cache refresh failed", e))
295
+ raise
296
+
297
+ async def initialize(self):
298
+ """Initialize the model service"""
299
+ if self._initialized:
300
+ logger.info(LogFormatter.info("Service already initialized, using cached data"))
301
+ return
302
+
303
+ try:
304
+ logger.info(LogFormatter.section("MODEL SERVICE INITIALIZATION"))
305
+
306
+ # Check if cache already exists
307
+ cache_path = cache_config.get_cache_path("datasets")
308
+ if not cache_path.exists() or not any(cache_path.iterdir()):
309
+ logger.info(LogFormatter.info("No existing cache found, initializing datasets cache..."))
310
+ cache_config.flush_cache("datasets")
311
+ else:
312
+ logger.info(LogFormatter.info("Using existing datasets cache"))
313
+
314
+ # Ensure eval requests directory exists
315
+ self.eval_requests_path.parent.mkdir(parents=True, exist_ok=True)
316
+ logger.info(LogFormatter.info(f"Eval requests directory: {self.eval_requests_path}"))
317
+
318
+ # List existing files
319
+ if self.eval_requests_path.exists():
320
+ files = list(self.eval_requests_path.glob("**/*.json"))
321
+ stats = {
322
+ "Total_Files": len(files),
323
+ "Directory": str(self.eval_requests_path)
324
+ }
325
+ for line in LogFormatter.stats(stats, "Eval Requests"):
326
+ logger.info(line)
327
+
328
+ # Load initial cache
329
+ await self._refresh_models_cache()
330
+
331
+ self._initialized = True
332
+ logger.info(LogFormatter.success("Model service initialization complete"))
333
+
334
+ except Exception as e:
335
+ logger.error(LogFormatter.error("Initialization failed", e))
336
+ raise
337
+
338
+ async def get_models(self) -> Dict[str, List[Dict[str, Any]]]:
339
+ """Get all models with their status"""
340
+ if not self._initialized:
341
+ logger.info(LogFormatter.info("Service not initialized, initializing now..."))
342
+ await self.initialize()
343
+
344
+ current_time = time.time()
345
+ cache_age = current_time - self.last_cache_update
346
+
347
+ # Check if cache needs refresh
348
+ if not self.cached_models:
349
+ logger.info(LogFormatter.info("No cached data available, refreshing cache..."))
350
+ return await self._refresh_models_cache()
351
+ elif cache_age > self.cache_ttl:
352
+ logger.info(LogFormatter.info(f"Cache expired ({cache_age:.1f}s old, TTL: {self.cache_ttl}s)"))
353
+ return await self._refresh_models_cache()
354
+ else:
355
+ logger.info(LogFormatter.info(f"Using cached data ({cache_age:.1f}s old)"))
356
+ return self.cached_models
357
+
358
+ async def submit_model(
359
+ self,
360
+ model_data: Dict[str, Any],
361
+ user_id: str
362
+ ) -> Dict[str, Any]:
363
+ logger.info(LogFormatter.section("MODEL SUBMISSION"))
364
+ self._log_repo_operation("write", f"{HF_ORGANIZATION}/requests", f"Submitting model {model_data['model_id']} by {user_id}")
365
+ stats = {
366
+ "Model": model_data["model_id"],
367
+ "User": user_id,
368
+ "Revision": model_data["revision"],
369
+ "Precision": model_data["precision"],
370
+ "Type": model_data["model_type"]
371
+ }
372
+ for line in LogFormatter.tree(stats, "Submission Details"):
373
+ logger.info(line)
374
+
375
+ # Validate required fields
376
+ required_fields = [
377
+ "model_id", "base_model", "revision", "precision",
378
+ "weight_type", "model_type", "use_chat_template"
379
+ ]
380
+ for field in required_fields:
381
+ if field not in model_data:
382
+ raise ValueError(f"Missing required field: {field}")
383
+
384
+ # Get model info and validate it exists on HuggingFace
385
+ try:
386
+ logger.info(LogFormatter.subsection("MODEL VALIDATION"))
387
+
388
+ # Get the model info to check if it exists
389
+ model_info = self.hf_api.model_info(
390
+ model_data["model_id"],
391
+ revision=model_data["revision"],
392
+ token=self.token
393
+ )
394
+
395
+ if not model_info:
396
+ raise Exception(f"Model {model_data['model_id']} not found on HuggingFace Hub")
397
+
398
+ logger.info(LogFormatter.success("Model exists on HuggingFace Hub"))
399
+
400
+ except Exception as e:
401
+ logger.error(LogFormatter.error("Model validation failed", e))
402
+ raise
403
+
404
+ # Update model revision with commit sha
405
+ model_data["revision"] = model_info.sha
406
+
407
+ # Check if model already exists in the system
408
+ try:
409
+ logger.info(LogFormatter.subsection("CHECKING EXISTING SUBMISSIONS"))
410
+ existing_models = await self.get_models()
411
+
412
+ # Check in all statuses (pending, evaluating, finished)
413
+ for status, models in existing_models.items():
414
+ for model in models:
415
+ if model["name"] == model_data["model_id"] and model["revision"] == model_data["revision"]:
416
+ error_msg = f"Model {model_data['model_id']} revision {model_data['revision']} is already in the system with status: {status}"
417
+ logger.error(LogFormatter.error("Submission rejected", error_msg))
418
+ raise ValueError(error_msg)
419
+
420
+ logger.info(LogFormatter.success("No existing submission found"))
421
+ except ValueError:
422
+ raise
423
+ except Exception as e:
424
+ logger.error(LogFormatter.error("Failed to check existing submissions", e))
425
+ raise
426
+
427
+ # Check that model on hub and valid
428
+ valid, error, model_config = await self.validator.is_model_on_hub(
429
+ model_data["model_id"],
430
+ model_data["revision"],
431
+ test_tokenizer=True
432
+ )
433
+ if not valid:
434
+ logger.error(LogFormatter.error("Model on hub validation failed", error))
435
+ raise Exception(error)
436
+ logger.info(LogFormatter.success("Model on hub validation passed"))
437
+
438
+ # Validate model card
439
+ valid, error, model_card = await self.validator.check_model_card(
440
+ model_data["model_id"]
441
+ )
442
+ if not valid:
443
+ logger.error(LogFormatter.error("Model card validation failed", error))
444
+ raise Exception(error)
445
+ logger.info(LogFormatter.success("Model card validation passed"))
446
+
447
+ # Check size limits
448
+ model_size, error = await self.validator.get_model_size(
449
+ model_info,
450
+ model_data["precision"],
451
+ model_data["base_model"],
452
+ revision=model_data["revision"]
453
+ )
454
+ if model_size is None:
455
+ logger.error(LogFormatter.error("Model size validation failed", error))
456
+ raise Exception(error)
457
+ logger.info(LogFormatter.success(f"Model size validation passed: {model_size:.1f}B"))
458
+
459
+ # Size limits based on precision
460
+ if model_data["precision"] in ["float16", "bfloat16"] and model_size > 100:
461
+ error_msg = f"Model too large for {model_data['precision']} (limit: 100B)"
462
+ logger.error(LogFormatter.error("Size limit exceeded", error_msg))
463
+ raise Exception(error_msg)
464
+
465
+ # Chat template validation if requested
466
+ if model_data["use_chat_template"]:
467
+ valid, error = await self.validator.check_chat_template(
468
+ model_data["model_id"],
469
+ model_data["revision"]
470
+ )
471
+ if not valid:
472
+ logger.error(LogFormatter.error("Chat template validation failed", error))
473
+ raise Exception(error)
474
+ logger.info(LogFormatter.success("Chat template validation passed"))
475
+
476
+
477
+ architectures = model_info.config.get("architectures", "")
478
+ if architectures:
479
+ architectures = ";".join(architectures)
480
+
481
+ # Create eval entry
482
+ eval_entry = {
483
+ "model": model_data["model_id"],
484
+ "base_model": model_data["base_model"],
485
+ "revision": model_info.sha,
486
+ "precision": model_data["precision"],
487
+ "params": model_size,
488
+ "architectures": architectures,
489
+ "weight_type": model_data["weight_type"],
490
+ "status": "PENDING",
491
+ "submitted_time": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
492
+ "model_type": model_data["model_type"],
493
+ "job_id": -1,
494
+ "job_start_time": None,
495
+ "use_chat_template": model_data["use_chat_template"],
496
+ "sender": user_id
497
+ }
498
+
499
+ logger.info(LogFormatter.subsection("EVALUATION ENTRY"))
500
+ for line in LogFormatter.tree(eval_entry):
501
+ logger.info(line)
502
+
503
+ # Upload to HF dataset
504
+ try:
505
+ logger.info(LogFormatter.subsection("UPLOADING TO HUGGINGFACE"))
506
+ logger.info(LogFormatter.info(f"Uploading to {HF_ORGANIZATION}/requests..."))
507
+
508
+ # Construct the path in the dataset
509
+ org_or_user = model_data["model_id"].split("/")[0] if "/" in model_data["model_id"] else ""
510
+ model_path = model_data["model_id"].split("/")[-1]
511
+ relative_path = f"{org_or_user}/{model_path}_eval_request_False_{model_data['precision']}_{model_data['weight_type']}.json"
512
+
513
+ # Create a temporary file with the request
514
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as temp_file:
515
+ json.dump(eval_entry, temp_file, indent=2)
516
+ temp_file.flush()
517
+ temp_path = temp_file.name
518
+
519
+ # Upload file directly
520
+ self.hf_api.upload_file(
521
+ path_or_fileobj=temp_path,
522
+ path_in_repo=relative_path,
523
+ repo_id=f"{HF_ORGANIZATION}/requests",
524
+ repo_type="dataset",
525
+ commit_message=f"Add {model_data['model_id']} to eval queue",
526
+ token=self.token
527
+ )
528
+
529
+ # Clean up temp file
530
+ os.unlink(temp_path)
531
+
532
+ logger.info(LogFormatter.success("Upload successful"))
533
+
534
+ except Exception as e:
535
+ logger.error(LogFormatter.error("Upload failed", e))
536
+ raise
537
+
538
+ # Add automatic vote
539
+ try:
540
+ logger.info(LogFormatter.subsection("AUTOMATIC VOTE"))
541
+ logger.info(LogFormatter.info(f"Adding upvote for {model_data['model_id']} by {user_id}"))
542
+ await self.vote_service.add_vote(
543
+ model_data["model_id"],
544
+ user_id,
545
+ "up"
546
+ )
547
+ logger.info(LogFormatter.success("Vote recorded successfully"))
548
+ except Exception as e:
549
+ logger.error(LogFormatter.error("Failed to record vote", e))
550
+ # Don't raise here as the main submission was successful
551
+
552
+ return {
553
+ "status": "success",
554
+ "message": "The model was submitted successfully, and the vote has been recorded"
555
+ }
556
+
557
+ async def get_model_status(self, model_id: str) -> Dict[str, Any]:
558
+ """Get evaluation status of a model"""
559
+ logger.info(LogFormatter.info(f"Checking status for model: {model_id}"))
560
+ eval_path = self.eval_requests_path
561
+
562
+ for user_folder in eval_path.iterdir():
563
+ if user_folder.is_dir():
564
+ for file in user_folder.glob("*.json"):
565
+ with open(file, "r") as f:
566
+ data = json.load(f)
567
+ if data["model"] == model_id:
568
+ status = {
569
+ "status": data["status"],
570
+ "submitted_time": data["submitted_time"],
571
+ "job_id": data.get("job_id", -1)
572
+ }
573
+ logger.info(LogFormatter.success("Status found"))
574
+ for line in LogFormatter.tree(status, "Model Status"):
575
+ logger.info(line)
576
+ return status
577
+
578
+ logger.warning(LogFormatter.warning(f"No status found for model: {model_id}"))
579
+ return {"status": "not_found"}
backend/app/services/rate_limiter.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ import logging
3
+ from datetime import datetime, timedelta, timezone
4
+ from typing import Tuple, Dict, List
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ class RateLimiter:
9
+ def __init__(self, period_days: int = 7, quota: int = 5):
10
+ self.period_days = period_days
11
+ self.quota = quota
12
+ self.submission_history: Dict[str, List[datetime]] = {}
13
+ self.higher_quota_users = set() # Users with higher quotas
14
+ self.unlimited_users = set() # Users with no quota limits
15
+
16
+ def add_unlimited_user(self, user_id: str):
17
+ """Add a user to the unlimited users list"""
18
+ self.unlimited_users.add(user_id)
19
+
20
+ def add_higher_quota_user(self, user_id: str):
21
+ """Add a user to the higher quota users list"""
22
+ self.higher_quota_users.add(user_id)
23
+
24
+ def record_submission(self, user_id: str):
25
+ """Record a new submission for a user"""
26
+ current_time = datetime.now(timezone.utc)
27
+ if user_id not in self.submission_history:
28
+ self.submission_history[user_id] = []
29
+ self.submission_history[user_id].append(current_time)
30
+
31
+ def clean_old_submissions(self, user_id: str):
32
+ """Remove submissions older than the period"""
33
+ if user_id not in self.submission_history:
34
+ return
35
+
36
+ current_time = datetime.now(timezone.utc)
37
+ cutoff_time = current_time - timedelta(days=self.period_days)
38
+
39
+ self.submission_history[user_id] = [
40
+ time for time in self.submission_history[user_id]
41
+ if time > cutoff_time
42
+ ]
43
+
44
+ async def check_rate_limit(self, user_id: str) -> Tuple[bool, str]:
45
+ """Check if a user has exceeded their rate limit
46
+
47
+ Returns:
48
+ Tuple[bool, str]: (is_allowed, error_message)
49
+ """
50
+ # Unlimited users bypass all checks
51
+ if user_id in self.unlimited_users:
52
+ return True, ""
53
+
54
+ # Clean old submissions
55
+ self.clean_old_submissions(user_id)
56
+
57
+ # Get current submission count
58
+ submission_count = len(self.submission_history.get(user_id, []))
59
+
60
+ # Calculate user's quota
61
+ user_quota = self.quota * 2 if user_id in self.higher_quota_users else self.quota
62
+
63
+ # Check if user has exceeded their quota
64
+ if submission_count >= user_quota:
65
+ error_msg = (
66
+ f"User '{user_id}' has reached the limit of {user_quota} submissions "
67
+ f"in the last {self.period_days} days. Please wait before submitting again."
68
+ )
69
+ return False, error_msg
70
+
71
+ return True, ""
72
+ """
backend/app/services/votes.py ADDED
@@ -0,0 +1,391 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timezone
2
+ from typing import Dict, Any, List, Set, Tuple, Optional
3
+ import json
4
+ import logging
5
+ import asyncio
6
+ from pathlib import Path
7
+ import os
8
+ import aiohttp
9
+ from huggingface_hub import HfApi
10
+ import datasets
11
+
12
+ from app.services.hf_service import HuggingFaceService
13
+ from app.config import HF_TOKEN, API
14
+ from app.config.hf_config import HF_ORGANIZATION
15
+ from app.core.cache import cache_config
16
+ from app.utils.logging import LogFormatter
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class VoteService(HuggingFaceService):
21
+ _instance: Optional['VoteService'] = None
22
+ _initialized = False
23
+
24
+ def __new__(cls):
25
+ if cls._instance is None:
26
+ cls._instance = super(VoteService, cls).__new__(cls)
27
+ return cls._instance
28
+
29
+ def __init__(self):
30
+ if not hasattr(self, '_init_done'):
31
+ super().__init__()
32
+ self.votes_file = cache_config.votes_file
33
+ self.votes_to_upload: List[Dict[str, Any]] = []
34
+ self.vote_check_set: Set[Tuple[str, str, str]] = set()
35
+ self._votes_by_model: Dict[str, List[Dict[str, Any]]] = {}
36
+ self._votes_by_user: Dict[str, List[Dict[str, Any]]] = {}
37
+ self._upload_lock = asyncio.Lock()
38
+ self._last_sync = None
39
+ self._sync_interval = 300 # 5 minutes
40
+ self._total_votes = 0
41
+ self._last_vote_timestamp = None
42
+ self._max_retries = 3
43
+ self._retry_delay = 1 # seconds
44
+ self._upload_batch_size = 10
45
+ self.hf_api = HfApi(token=HF_TOKEN)
46
+ self._init_done = True
47
+
48
+ async def initialize(self):
49
+ """Initialize the vote service"""
50
+ if self._initialized:
51
+ await self._check_for_new_votes()
52
+ return
53
+
54
+ try:
55
+ logger.info(LogFormatter.section("VOTE SERVICE INITIALIZATION"))
56
+
57
+ # Ensure votes directory exists
58
+ self.votes_file.parent.mkdir(parents=True, exist_ok=True)
59
+
60
+ # Load existing votes if file exists
61
+ local_vote_count = 0
62
+ if self.votes_file.exists():
63
+ logger.info(LogFormatter.info(f"Loading votes from {self.votes_file}"))
64
+ local_vote_count = await self._count_local_votes()
65
+ logger.info(LogFormatter.info(f"Found {local_vote_count:,} local votes"))
66
+
67
+ # Check remote votes count
68
+ remote_vote_count = await self._count_remote_votes()
69
+ logger.info(LogFormatter.info(f"Found {remote_vote_count:,} remote votes"))
70
+
71
+ if remote_vote_count > local_vote_count:
72
+ logger.info(LogFormatter.info(f"Fetching {remote_vote_count - local_vote_count:,} new votes"))
73
+ await self._sync_with_hub()
74
+ elif remote_vote_count < local_vote_count:
75
+ logger.warning(LogFormatter.warning(f"Local votes ({local_vote_count:,}) > Remote votes ({remote_vote_count:,})"))
76
+ await self._load_existing_votes()
77
+ else:
78
+ logger.info(LogFormatter.success("Local and remote votes are in sync"))
79
+ if local_vote_count > 0:
80
+ await self._load_existing_votes()
81
+ else:
82
+ logger.info(LogFormatter.info("No votes found"))
83
+
84
+ self._initialized = True
85
+ self._last_sync = datetime.now(timezone.utc)
86
+
87
+ # Final summary
88
+ stats = {
89
+ "Total_Votes": self._total_votes,
90
+ "Last_Sync": self._last_sync.strftime("%Y-%m-%d %H:%M:%S UTC")
91
+ }
92
+ logger.info(LogFormatter.section("INITIALIZATION COMPLETE"))
93
+ for line in LogFormatter.stats(stats):
94
+ logger.info(line)
95
+
96
+ except Exception as e:
97
+ logger.error(LogFormatter.error("Initialization failed", e))
98
+ raise
99
+
100
+ async def _count_local_votes(self) -> int:
101
+ """Count votes in local file"""
102
+ if not self.votes_file.exists():
103
+ return 0
104
+
105
+ count = 0
106
+ try:
107
+ with open(self.votes_file, 'r') as f:
108
+ for _ in f:
109
+ count += 1
110
+ return count
111
+ except Exception as e:
112
+ logger.error(f"Error counting local votes: {str(e)}")
113
+ return 0
114
+
115
+ async def _count_remote_votes(self) -> int:
116
+ """Count votes in remote file"""
117
+ url = f"https://huggingface.co/datasets/{HF_ORGANIZATION}/votes/raw/main/votes_data.jsonl"
118
+ headers = {"Authorization": f"Bearer {HF_TOKEN}"} if HF_TOKEN else {}
119
+
120
+ try:
121
+ async with aiohttp.ClientSession() as session:
122
+ async with session.get(url, headers=headers) as response:
123
+ if response.status == 200:
124
+ count = 0
125
+ async for line in response.content:
126
+ if line.strip(): # Skip empty lines
127
+ count += 1
128
+ return count
129
+ else:
130
+ logger.error(f"Failed to get remote votes: HTTP {response.status}")
131
+ return 0
132
+ except Exception as e:
133
+ logger.error(f"Error counting remote votes: {str(e)}")
134
+ return 0
135
+
136
+ async def _sync_with_hub(self):
137
+ """Sync votes with HuggingFace hub using datasets"""
138
+ try:
139
+ logger.info(LogFormatter.section("VOTE SYNC"))
140
+ self._log_repo_operation("sync", f"{HF_ORGANIZATION}/votes", "Syncing local votes with HF hub")
141
+ logger.info(LogFormatter.info("Syncing with HuggingFace hub..."))
142
+
143
+ # Load votes from HF dataset
144
+ dataset = datasets.load_dataset(
145
+ f"{HF_ORGANIZATION}/votes",
146
+ split="train",
147
+ cache_dir=cache_config.get_cache_path("datasets")
148
+ )
149
+
150
+ remote_votes = len(dataset)
151
+ logger.info(LogFormatter.info(f"Dataset loaded with {remote_votes:,} votes"))
152
+
153
+ # Convert to list of dictionaries
154
+ df = dataset.to_pandas()
155
+ if 'timestamp' in df.columns:
156
+ df['timestamp'] = df['timestamp'].dt.strftime('%Y-%m-%dT%H:%M:%SZ')
157
+ remote_votes = df.to_dict('records')
158
+
159
+ # If we have more remote votes than local
160
+ if len(remote_votes) > self._total_votes:
161
+ new_votes = len(remote_votes) - self._total_votes
162
+ logger.info(LogFormatter.info(f"Processing {new_votes:,} new votes..."))
163
+
164
+ # Save votes to local file
165
+ with open(self.votes_file, 'w') as f:
166
+ for vote in remote_votes:
167
+ f.write(json.dumps(vote) + '\n')
168
+
169
+ # Reload votes in memory
170
+ await self._load_existing_votes()
171
+ logger.info(LogFormatter.success("Sync completed successfully"))
172
+ else:
173
+ logger.info(LogFormatter.success("Local votes are up to date"))
174
+
175
+ self._last_sync = datetime.now(timezone.utc)
176
+
177
+ except Exception as e:
178
+ logger.error(LogFormatter.error("Sync failed", e))
179
+ raise
180
+
181
+ async def _check_for_new_votes(self):
182
+ """Check for new votes on the hub"""
183
+ try:
184
+ self._log_repo_operation("check", f"{HF_ORGANIZATION}/votes", "Checking for new votes")
185
+ # Load only dataset metadata
186
+ dataset_info = datasets.load_dataset(f"{HF_ORGANIZATION}/votes", split="train")
187
+ remote_vote_count = len(dataset_info)
188
+
189
+ if remote_vote_count > self._total_votes:
190
+ logger.info(f"Found {remote_vote_count - self._total_votes} new votes on hub")
191
+ await self._sync_with_hub()
192
+ else:
193
+ logger.info("No new votes found on hub")
194
+
195
+ except Exception as e:
196
+ logger.error(f"Error checking for new votes: {str(e)}")
197
+
198
+ async def _load_existing_votes(self):
199
+ """Load existing votes from file"""
200
+ if not self.votes_file.exists():
201
+ logger.warning(LogFormatter.warning("No votes file found"))
202
+ return
203
+
204
+ try:
205
+ logger.info(LogFormatter.section("LOADING VOTES"))
206
+
207
+ # Clear existing data structures
208
+ self.vote_check_set.clear()
209
+ self._votes_by_model.clear()
210
+ self._votes_by_user.clear()
211
+
212
+ vote_count = 0
213
+ latest_timestamp = None
214
+
215
+ with open(self.votes_file, "r") as f:
216
+ for line in f:
217
+ try:
218
+ vote = json.loads(line.strip())
219
+ vote_count += 1
220
+
221
+ # Track latest timestamp
222
+ try:
223
+ vote_timestamp = datetime.fromisoformat(vote["timestamp"].replace("Z", "+00:00"))
224
+ if not latest_timestamp or vote_timestamp > latest_timestamp:
225
+ latest_timestamp = vote_timestamp
226
+ vote["timestamp"] = vote_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
227
+ except (KeyError, ValueError) as e:
228
+ logger.warning(LogFormatter.warning(f"Invalid timestamp in vote: {str(e)}"))
229
+ continue
230
+
231
+ if vote_count % 1000 == 0:
232
+ logger.info(LogFormatter.info(f"Processed {vote_count:,} votes..."))
233
+
234
+ self._add_vote_to_memory(vote)
235
+
236
+ except json.JSONDecodeError as e:
237
+ logger.error(LogFormatter.error("Vote parsing failed", e))
238
+ continue
239
+ except Exception as e:
240
+ logger.error(LogFormatter.error("Vote processing failed", e))
241
+ continue
242
+
243
+ self._total_votes = vote_count
244
+ self._last_vote_timestamp = latest_timestamp
245
+
246
+ # Final summary
247
+ stats = {
248
+ "Total_Votes": vote_count,
249
+ "Latest_Vote": latest_timestamp.strftime("%Y-%m-%d %H:%M:%S UTC") if latest_timestamp else "None",
250
+ "Unique_Models": len(self._votes_by_model),
251
+ "Unique_Users": len(self._votes_by_user)
252
+ }
253
+
254
+ logger.info(LogFormatter.section("VOTE SUMMARY"))
255
+ for line in LogFormatter.stats(stats):
256
+ logger.info(line)
257
+
258
+ except Exception as e:
259
+ logger.error(LogFormatter.error("Failed to load votes", e))
260
+ raise
261
+
262
+ def _add_vote_to_memory(self, vote: Dict[str, Any]):
263
+ """Add vote to memory structures"""
264
+ try:
265
+ check_tuple = (vote["model"], vote["revision"], vote["username"])
266
+
267
+ # Skip if we already have this vote
268
+ if check_tuple in self.vote_check_set:
269
+ return
270
+
271
+ self.vote_check_set.add(check_tuple)
272
+
273
+ # Update model votes
274
+ if vote["model"] not in self._votes_by_model:
275
+ self._votes_by_model[vote["model"]] = []
276
+ self._votes_by_model[vote["model"]].append(vote)
277
+
278
+ # Update user votes
279
+ if vote["username"] not in self._votes_by_user:
280
+ self._votes_by_user[vote["username"]] = []
281
+ self._votes_by_user[vote["username"]].append(vote)
282
+
283
+ except KeyError as e:
284
+ logger.error(f"Malformed vote data, missing key: {str(e)}")
285
+ except Exception as e:
286
+ logger.error(f"Error adding vote to memory: {str(e)}")
287
+
288
+ async def get_user_votes(self, user_id: str) -> List[Dict[str, Any]]:
289
+ """Get all votes from a specific user"""
290
+ logger.info(LogFormatter.info(f"Fetching votes for user: {user_id}"))
291
+ votes = self._votes_by_user.get(user_id, [])
292
+ logger.info(LogFormatter.success(f"Found {len(votes):,} votes"))
293
+ return votes
294
+
295
+ async def get_model_votes(self, model_id: str) -> Dict[str, Any]:
296
+ """Get all votes for a specific model"""
297
+ logger.info(LogFormatter.info(f"Fetching votes for model: {model_id}"))
298
+ votes = self._votes_by_model.get(model_id, [])
299
+
300
+ # Group votes by revision
301
+ votes_by_revision = {}
302
+ for vote in votes:
303
+ revision = vote["revision"]
304
+ if revision not in votes_by_revision:
305
+ votes_by_revision[revision] = 0
306
+ votes_by_revision[revision] += 1
307
+
308
+ stats = {
309
+ "Total_Votes": len(votes),
310
+ **{f"Revision_{k}": v for k, v in votes_by_revision.items()}
311
+ }
312
+
313
+ logger.info(LogFormatter.section("VOTE STATISTICS"))
314
+ for line in LogFormatter.stats(stats):
315
+ logger.info(line)
316
+
317
+ return {
318
+ "total_votes": len(votes),
319
+ "votes_by_revision": votes_by_revision,
320
+ "votes": votes
321
+ }
322
+
323
+ async def _get_model_revision(self, model_id: str) -> str:
324
+ """Get current revision of a model with retries"""
325
+ logger.info(f"Getting revision for model: {model_id}")
326
+ for attempt in range(self._max_retries):
327
+ try:
328
+ model_info = await asyncio.to_thread(self.hf_api.model_info, model_id)
329
+ logger.info(f"Successfully got revision {model_info.sha} for model {model_id}")
330
+ return model_info.sha
331
+ except Exception as e:
332
+ logger.error(f"Error getting model revision for {model_id} (attempt {attempt + 1}): {str(e)}")
333
+ if attempt < self._max_retries - 1:
334
+ retry_delay = self._retry_delay * (attempt + 1)
335
+ logger.info(f"Retrying in {retry_delay} seconds...")
336
+ await asyncio.sleep(retry_delay)
337
+ else:
338
+ logger.warning(f"Using 'main' as fallback revision for {model_id} after {self._max_retries} failed attempts")
339
+ return "main"
340
+
341
+ async def add_vote(self, model_id: str, user_id: str, vote_type: str) -> Dict[str, Any]:
342
+ """Add a vote for a model"""
343
+ try:
344
+ self._log_repo_operation("add", f"{HF_ORGANIZATION}/votes", f"Adding {vote_type} vote for {model_id} by {user_id}")
345
+ logger.info(LogFormatter.section("NEW VOTE"))
346
+ stats = {
347
+ "Model": model_id,
348
+ "User": user_id,
349
+ "Type": vote_type
350
+ }
351
+ for line in LogFormatter.tree(stats, "Vote Details"):
352
+ logger.info(line)
353
+
354
+ revision = await self._get_model_revision(model_id)
355
+ check_tuple = (model_id, revision, user_id)
356
+
357
+ if check_tuple in self.vote_check_set:
358
+ raise ValueError("Vote already recorded for this model")
359
+
360
+ vote = {
361
+ "model": model_id,
362
+ "revision": revision,
363
+ "username": user_id,
364
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
365
+ "vote_type": vote_type
366
+ }
367
+
368
+ # Update local storage
369
+ with open(self.votes_file, "a") as f:
370
+ f.write(json.dumps(vote) + "\n")
371
+
372
+ self._add_vote_to_memory(vote)
373
+ self.votes_to_upload.append(vote)
374
+
375
+ stats = {
376
+ "Status": "Success",
377
+ "Queue_Size": len(self.votes_to_upload)
378
+ }
379
+ for line in LogFormatter.stats(stats):
380
+ logger.info(line)
381
+
382
+ # Try to upload if batch size reached
383
+ if len(self.votes_to_upload) >= self._upload_batch_size:
384
+ logger.info(LogFormatter.info(f"Upload batch size reached ({self._upload_batch_size}), triggering sync"))
385
+ await self._sync_with_hub()
386
+
387
+ return {"status": "success", "message": "Vote added successfully"}
388
+
389
+ except Exception as e:
390
+ logger.error(LogFormatter.error("Failed to add vote", e))
391
+ raise
backend/app/utils/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from . import model_validation
2
+
3
+ __all__ = ["model_validation"]
backend/app/utils/logging.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import sys
3
+ from typing import Dict, Any, List, Optional
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ class LogFormatter:
8
+ """Utility class for consistent log formatting across the application"""
9
+
10
+ @staticmethod
11
+ def section(title: str) -> str:
12
+ """Create a section header"""
13
+ return f"\n{'='*20} {title.upper()} {'='*20}"
14
+
15
+ @staticmethod
16
+ def subsection(title: str) -> str:
17
+ """Create a subsection header"""
18
+ return f"\n{'─'*20} {title} {'─'*20}"
19
+
20
+ @staticmethod
21
+ def tree(items: Dict[str, Any], title: str = None) -> List[str]:
22
+ """Create a tree view of dictionary data"""
23
+ lines = []
24
+ if title:
25
+ lines.append(f"📊 {title}:")
26
+
27
+ # Get the maximum length for alignment
28
+ max_key_length = max(len(str(k)) for k in items.keys())
29
+
30
+ # Format each item
31
+ for i, (key, value) in enumerate(items.items()):
32
+ prefix = "└──" if i == len(items) - 1 else "├──"
33
+ if isinstance(value, (int, float)):
34
+ value = f"{value:,}" # Add thousand separators
35
+ lines.append(f"{prefix} {str(key):<{max_key_length}}: {value}")
36
+
37
+ return lines
38
+
39
+ @staticmethod
40
+ def stats(stats: Dict[str, int], title: str = None) -> List[str]:
41
+ """Format statistics with icons"""
42
+ lines = []
43
+ if title:
44
+ lines.append(f"📊 {title}:")
45
+
46
+ # Get the maximum length for alignment
47
+ max_key_length = max(len(str(k)) for k in stats.keys())
48
+
49
+ # Format each stat with an appropriate icon
50
+ icons = {
51
+ "total": "📌",
52
+ "success": "✅",
53
+ "error": "❌",
54
+ "pending": "⏳",
55
+ "processing": "⚙️",
56
+ "finished": "✨",
57
+ "evaluating": "🔄",
58
+ "downloads": "⬇️",
59
+ "files": "📁",
60
+ "cached": "💾",
61
+ "size": "📏",
62
+ "time": "⏱️",
63
+ "rate": "🚀"
64
+ }
65
+
66
+ # Format each item
67
+ for i, (key, value) in enumerate(stats.items()):
68
+ prefix = "└──" if i == len(stats) - 1 else "├──"
69
+ icon = icons.get(key.lower().split('_')[0], "•")
70
+ if isinstance(value, (int, float)):
71
+ value = f"{value:,}" # Add thousand separators
72
+ lines.append(f"{prefix} {icon} {str(key):<{max_key_length}}: {value}")
73
+
74
+ return lines
75
+
76
+ @staticmethod
77
+ def progress_bar(current: int, total: int, width: int = 20) -> str:
78
+ """Create a progress bar"""
79
+ percentage = (current * 100) // total
80
+ filled = "█" * (percentage * width // 100)
81
+ empty = "░" * (width - len(filled))
82
+ return f"{filled}{empty} {percentage:3d}%"
83
+
84
+ @staticmethod
85
+ def error(message: str, error: Optional[Exception] = None) -> str:
86
+ """Format error message"""
87
+ error_msg = f"\n❌ Error: {message}"
88
+ if error:
89
+ error_msg += f"\n └── Details: {str(error)}"
90
+ return error_msg
91
+
92
+ @staticmethod
93
+ def success(message: str) -> str:
94
+ """Format success message"""
95
+ return f"✅ {message}"
96
+
97
+ @staticmethod
98
+ def warning(message: str) -> str:
99
+ """Format warning message"""
100
+ return f"⚠️ {message}"
101
+
102
+ @staticmethod
103
+ def info(message: str) -> str:
104
+ """Format info message"""
105
+ return f"ℹ️ {message}"
backend/app/utils/model_validation.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import logging
3
+ import asyncio
4
+ import re
5
+ from typing import Tuple, Optional, Dict, Any
6
+ import aiohttp
7
+ from huggingface_hub import HfApi, ModelCard, hf_hub_download
8
+ from huggingface_hub import hf_api
9
+ from transformers import AutoConfig, AutoTokenizer
10
+ from app.config.base import HF_TOKEN, API
11
+ from app.utils.logging import LogFormatter
12
+
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class ModelValidator:
17
+ def __init__(self):
18
+ self.token = HF_TOKEN
19
+ self.api = HfApi(token=self.token)
20
+ self.headers = {"Authorization": f"Bearer {self.token}"} if self.token else {}
21
+
22
+ async def check_model_card(self, model_id: str) -> Tuple[bool, str, Optional[Dict[str, Any]]]:
23
+ """Check if model has a valid model card"""
24
+ try:
25
+ logger.info(LogFormatter.info(f"Checking model card for {model_id}"))
26
+
27
+ # Get model card content using ModelCard.load
28
+ try:
29
+ model_card = await asyncio.to_thread(
30
+ ModelCard.load,
31
+ model_id
32
+ )
33
+ logger.info(LogFormatter.success("Model card found"))
34
+ except Exception as e:
35
+ error_msg = "Please add a model card to your model to explain how you trained/fine-tuned it."
36
+ logger.error(LogFormatter.error(error_msg, e))
37
+ return False, error_msg, None
38
+
39
+ # Check license in model card data
40
+ if model_card.data.license is None and not ("license_name" in model_card.data and "license_link" in model_card.data):
41
+ error_msg = "License not found. Please add a license to your model card using the `license` metadata or a `license_name`/`license_link` pair."
42
+ logger.warning(LogFormatter.warning(error_msg))
43
+ return False, error_msg, None
44
+
45
+ # Enforce card content length
46
+ if len(model_card.text) < 200:
47
+ error_msg = "Please add a description to your model card, it is too short."
48
+ logger.warning(LogFormatter.warning(error_msg))
49
+ return False, error_msg, None
50
+
51
+ logger.info(LogFormatter.success("Model card validation passed"))
52
+ return True, "", model_card
53
+
54
+ except Exception as e:
55
+ error_msg = "Failed to validate model card"
56
+ logger.error(LogFormatter.error(error_msg, e))
57
+ return False, str(e), None
58
+
59
+ async def get_safetensors_metadata(self, model_id: str, is_adapter: bool = False, revision: str = "main") -> Optional[Dict]:
60
+ """Get metadata from a safetensors file"""
61
+ try:
62
+ if is_adapter:
63
+ metadata = await asyncio.to_thread(
64
+ hf_api.parse_safetensors_file_metadata,
65
+ model_id,
66
+ "adapter_model.safetensors",
67
+ token=self.token,
68
+ revision=revision,
69
+ )
70
+ else:
71
+ metadata = await asyncio.to_thread(
72
+ hf_api.get_safetensors_metadata,
73
+ repo_id=model_id,
74
+ token=self.token,
75
+ revision=revision,
76
+ )
77
+ return metadata
78
+
79
+ except Exception as e:
80
+ logger.error(f"Failed to get safetensors metadata: {str(e)}")
81
+ return None
82
+
83
+ async def get_model_size(
84
+ self,
85
+ model_info: Any,
86
+ precision: str,
87
+ base_model: str,
88
+ revision: str
89
+ ) -> Tuple[Optional[float], Optional[str]]:
90
+ """Get model size in billions of parameters"""
91
+ try:
92
+ logger.info(LogFormatter.info(f"Checking model size for {model_info.modelId}"))
93
+
94
+ # Check if model is adapter
95
+ is_adapter = any(s.rfilename == "adapter_config.json" for s in model_info.siblings if hasattr(s, 'rfilename'))
96
+
97
+ # Try to get size from safetensors first
98
+ model_size = None
99
+
100
+ if is_adapter and base_model:
101
+ # For adapters, we need both adapter and base model sizes
102
+ adapter_meta = await self.get_safetensors_metadata(model_info.id, is_adapter=True, revision=revision)
103
+ base_meta = await self.get_safetensors_metadata(base_model, revision="main")
104
+
105
+ if adapter_meta and base_meta:
106
+ adapter_size = sum(adapter_meta.parameter_count.values())
107
+ base_size = sum(base_meta.parameter_count.values())
108
+ model_size = adapter_size + base_size
109
+ else:
110
+ # For regular models, just get the model size
111
+ meta = await self.get_safetensors_metadata(model_info.id, revision=revision)
112
+ if meta:
113
+ model_size = sum(meta.parameter_count.values()) # total params
114
+
115
+ if model_size is None:
116
+ # If model size could not be determined, return an error
117
+ return None, "Model size could not be determined"
118
+
119
+ # Adjust size for GPTQ models
120
+ size_factor = 8 if (precision == "GPTQ" or "gptq" in model_info.id.lower()) else 1
121
+ model_size = model_size / 1e9 # Convert to billions, assuming float16
122
+ model_size = round(size_factor * model_size, 3)
123
+
124
+ logger.info(LogFormatter.success(f"Model size: {model_size}B parameters"))
125
+ return model_size, None
126
+
127
+ except Exception as e:
128
+ logger.error(LogFormatter.error(f"Error while determining model size: {e}"))
129
+ return None, str(e)
130
+
131
+
132
+ async def check_chat_template(
133
+ self,
134
+ model_id: str,
135
+ revision: str
136
+ ) -> Tuple[bool, Optional[str]]:
137
+ """Check if model has a valid chat template"""
138
+ try:
139
+ logger.info(LogFormatter.info(f"Checking chat template for {model_id}"))
140
+
141
+ try:
142
+ config_file = await asyncio.to_thread(
143
+ hf_hub_download,
144
+ repo_id=model_id,
145
+ filename="tokenizer_config.json",
146
+ revision=revision,
147
+ repo_type="model"
148
+ )
149
+
150
+ with open(config_file, 'r') as f:
151
+ tokenizer_config = json.load(f)
152
+
153
+ if 'chat_template' not in tokenizer_config:
154
+ error_msg = f"The model {model_id} doesn't have a chat_template in its tokenizer_config.json. Please add a chat_template before submitting or submit without it."
155
+ logger.error(LogFormatter.error(error_msg))
156
+ return False, error_msg
157
+
158
+ logger.info(LogFormatter.success("Valid chat template found"))
159
+ return True, None
160
+
161
+ except Exception as e:
162
+ error_msg = f"Error checking chat_template: {str(e)}"
163
+ logger.error(LogFormatter.error(error_msg))
164
+ return False, error_msg
165
+
166
+ except Exception as e:
167
+ error_msg = "Failed to check chat template"
168
+ logger.error(LogFormatter.error(error_msg, e))
169
+ return False, str(e)
170
+
171
+ async def is_model_on_hub(
172
+ self,
173
+ model_name: str,
174
+ revision: str,
175
+ test_tokenizer: bool = False,
176
+ trust_remote_code: bool = False
177
+ ) -> Tuple[bool, Optional[str], Optional[Any]]:
178
+ """Check if model exists and is properly configured on the Hub"""
179
+ try:
180
+ config = await asyncio.to_thread(
181
+ AutoConfig.from_pretrained,
182
+ model_name,
183
+ revision=revision,
184
+ trust_remote_code=trust_remote_code,
185
+ token=self.token,
186
+ force_download=True
187
+ )
188
+
189
+ if test_tokenizer:
190
+ try:
191
+ await asyncio.to_thread(
192
+ AutoTokenizer.from_pretrained,
193
+ model_name,
194
+ revision=revision,
195
+ trust_remote_code=trust_remote_code,
196
+ token=self.token
197
+ )
198
+ except ValueError as e:
199
+ return False, f"The tokenizer is not available in an official Transformers release: {e}", None
200
+ except Exception:
201
+ return False, "The tokenizer cannot be loaded. Ensure the tokenizer class is part of a stable Transformers release and correctly configured.", None
202
+
203
+ return True, None, config
204
+
205
+ except ValueError:
206
+ return False, "The model requires `trust_remote_code=True` to launch, and for safety reasons, we don't accept such models automatically.", None
207
+ except Exception as e:
208
+ if "You are trying to access a gated repo." in str(e):
209
+ return True, "The model is gated and requires special access permissions.", None
210
+ return False, f"The model was not found or is misconfigured on the Hub. Error: {e.args[0]}", None
backend/poetry.lock ADDED
The diff for this file is too large to render. See raw diff
 
backend/pyproject.toml ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "llm-leaderboard-backend"
3
+ version = "0.1.0"
4
+ description = "Backend for the Open LLM Leaderboard"
5
+ authors = ["Your Name <[email protected]>"]
6
+
7
+ [tool.poetry.dependencies]
8
+ python = "^3.12"
9
+ fastapi = "^0.115.6"
10
+ uvicorn = {extras = ["standard"], version = "^0.34.0"}
11
+ numpy = "^2.2.0"
12
+ pandas = "^2.2.3"
13
+ datasets = "^3.2.0"
14
+ pyarrow = "^18.1.0"
15
+ python-multipart = "^0.0.20"
16
+ huggingface-hub = "^0.27.0"
17
+ transformers = "^4.47.0"
18
+ safetensors = "^0.4.5"
19
+ aiofiles = "^24.1.0"
20
+ fastapi-cache2 = "^0.2.1"
21
+ python-dotenv = "^1.0.1"
22
+ pydantic = "^2.10.4"
23
+
24
+ [tool.poetry.group.dev.dependencies]
25
+ pytest = "^8.3.4"
26
+ black = "^24.10.0"
27
+ isort = "^5.13.2"
28
+ flake8 = "^6.1.0"
29
+
30
+ [build-system]
31
+ requires = ["poetry-core>=1.0.0"]
32
+ build-backend = "poetry.core.masonry.api"
backend/utils/analyze_prod_datasets.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Dict, Any, List
7
+ from huggingface_hub import HfApi
8
+ from dotenv import load_dotenv
9
+ from app.config.hf_config import HF_ORGANIZATION
10
+
11
+ # Get the backend directory path
12
+ BACKEND_DIR = Path(__file__).parent.parent
13
+ ROOT_DIR = BACKEND_DIR.parent
14
+
15
+ # Load environment variables from .env file in root directory
16
+ load_dotenv(ROOT_DIR / ".env")
17
+
18
+ # Configure logging
19
+ logging.basicConfig(
20
+ level=logging.INFO,
21
+ format='%(message)s'
22
+ )
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Initialize Hugging Face API
26
+ HF_TOKEN = os.getenv("HF_TOKEN")
27
+ if not HF_TOKEN:
28
+ raise ValueError("HF_TOKEN not found in environment variables")
29
+ api = HfApi(token=HF_TOKEN)
30
+
31
+ def analyze_dataset(repo_id: str) -> Dict[str, Any]:
32
+ """Analyze a dataset and return statistics"""
33
+ try:
34
+ # Get dataset info
35
+ dataset_info = api.dataset_info(repo_id=repo_id)
36
+
37
+ # Get file list
38
+ files = api.list_repo_files(repo_id, repo_type="dataset")
39
+
40
+ # Get last commit info
41
+ commits = api.list_repo_commits(repo_id, repo_type="dataset")
42
+ last_commit = next(commits, None)
43
+
44
+ # Count lines in jsonl files
45
+ total_entries = 0
46
+ for file in files:
47
+ if file.endswith('.jsonl'):
48
+ try:
49
+ # Download file content
50
+ content = api.hf_hub_download(
51
+ repo_id=repo_id,
52
+ filename=file,
53
+ repo_type="dataset"
54
+ )
55
+
56
+ # Count lines
57
+ with open(content, 'r') as f:
58
+ for _ in f:
59
+ total_entries += 1
60
+
61
+ except Exception as e:
62
+ logger.error(f"Error processing file {file}: {str(e)}")
63
+ continue
64
+
65
+ # Special handling for requests dataset
66
+ if repo_id == f"{HF_ORGANIZATION}/requests":
67
+ pending_count = 0
68
+ completed_count = 0
69
+
70
+ try:
71
+ content = api.hf_hub_download(
72
+ repo_id=repo_id,
73
+ filename="eval_requests.jsonl",
74
+ repo_type="dataset"
75
+ )
76
+
77
+ with open(content, 'r') as f:
78
+ for line in f:
79
+ try:
80
+ entry = json.loads(line)
81
+ if entry.get("status") == "pending":
82
+ pending_count += 1
83
+ elif entry.get("status") == "completed":
84
+ completed_count += 1
85
+ except json.JSONDecodeError:
86
+ continue
87
+
88
+ except Exception as e:
89
+ logger.error(f"Error analyzing requests: {str(e)}")
90
+
91
+ # Build response
92
+ response = {
93
+ "id": repo_id,
94
+ "last_modified": last_commit.created_at if last_commit else None,
95
+ "total_entries": total_entries,
96
+ "file_count": len(files),
97
+ "size_bytes": dataset_info.size_in_bytes,
98
+ "downloads": dataset_info.downloads,
99
+ }
100
+
101
+ # Add request-specific info if applicable
102
+ if repo_id == f"{HF_ORGANIZATION}/requests":
103
+ response.update({
104
+ "pending_requests": pending_count,
105
+ "completed_requests": completed_count
106
+ })
107
+
108
+ return response
109
+
110
+ except Exception as e:
111
+ logger.error(f"Error analyzing dataset {repo_id}: {str(e)}")
112
+ return {
113
+ "id": repo_id,
114
+ "error": str(e)
115
+ }
116
+
117
+ def main():
118
+ """Main function to analyze all datasets"""
119
+ try:
120
+ # List of datasets to analyze
121
+ datasets = [
122
+ {
123
+ "id": f"{HF_ORGANIZATION}/contents",
124
+ "description": "Aggregated results"
125
+ },
126
+ {
127
+ "id": f"{HF_ORGANIZATION}/requests",
128
+ "description": "Evaluation requests"
129
+ },
130
+ {
131
+ "id": f"{HF_ORGANIZATION}/votes",
132
+ "description": "User votes"
133
+ },
134
+ {
135
+ "id": f"{HF_ORGANIZATION}/maintainers-highlight",
136
+ "description": "Highlighted models"
137
+ }
138
+ ]
139
+
140
+ # Analyze each dataset
141
+ results = []
142
+ for dataset in datasets:
143
+ logger.info(f"\nAnalyzing {dataset['description']} ({dataset['id']})...")
144
+ result = analyze_dataset(dataset['id'])
145
+ results.append(result)
146
+
147
+ if 'error' in result:
148
+ logger.error(f"❌ Error: {result['error']}")
149
+ else:
150
+ logger.info(f"✓ {result['total_entries']} entries")
151
+ logger.info(f"✓ {result['file_count']} files")
152
+ logger.info(f"✓ {result['size_bytes'] / 1024:.1f} KB")
153
+ logger.info(f"✓ {result['downloads']} downloads")
154
+
155
+ if 'pending_requests' in result:
156
+ logger.info(f"✓ {result['pending_requests']} pending requests")
157
+ logger.info(f"✓ {result['completed_requests']} completed requests")
158
+
159
+ if result['last_modified']:
160
+ last_modified = datetime.fromisoformat(result['last_modified'].replace('Z', '+00:00'))
161
+ logger.info(f"✓ Last modified: {last_modified.strftime('%Y-%m-%d %H:%M:%S')}")
162
+
163
+ return results
164
+
165
+ except Exception as e:
166
+ logger.error(f"Global error: {str(e)}")
167
+ return []
168
+
169
+ if __name__ == "__main__":
170
+ main()
backend/utils/analyze_prod_models.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from huggingface_hub import HfApi
7
+ from dotenv import load_dotenv
8
+ from app.config.hf_config import HF_ORGANIZATION
9
+
10
+ # Get the backend directory path
11
+ BACKEND_DIR = Path(__file__).parent.parent
12
+ ROOT_DIR = BACKEND_DIR.parent
13
+
14
+ # Load environment variables from .env file in root directory
15
+ load_dotenv(ROOT_DIR / ".env")
16
+
17
+ # Configure logging
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format='%(message)s'
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Initialize Hugging Face API
25
+ HF_TOKEN = os.getenv("HF_TOKEN")
26
+ if not HF_TOKEN:
27
+ raise ValueError("HF_TOKEN not found in environment variables")
28
+ api = HfApi(token=HF_TOKEN)
29
+
30
+ def count_evaluated_models():
31
+ """Count the number of evaluated models"""
32
+ try:
33
+ # Get dataset info
34
+ dataset_info = api.dataset_info(repo_id=f"{HF_ORGANIZATION}/results", repo_type="dataset")
35
+
36
+ # Get file list
37
+ files = api.list_repo_files(f"{HF_ORGANIZATION}/results", repo_type="dataset")
38
+
39
+ # Get last commit info
40
+ commits = api.list_repo_commits(f"{HF_ORGANIZATION}/results", repo_type="dataset")
41
+ last_commit = next(commits, None)
42
+
43
+ # Count lines in jsonl files
44
+ total_entries = 0
45
+ for file in files:
46
+ if file.endswith('.jsonl'):
47
+ try:
48
+ # Download file content
49
+ content = api.hf_hub_download(
50
+ repo_id=f"{HF_ORGANIZATION}/results",
51
+ filename=file,
52
+ repo_type="dataset"
53
+ )
54
+
55
+ # Count lines
56
+ with open(content, 'r') as f:
57
+ for _ in f:
58
+ total_entries += 1
59
+
60
+ except Exception as e:
61
+ logger.error(f"Error processing file {file}: {str(e)}")
62
+ continue
63
+
64
+ # Build response
65
+ response = {
66
+ "total_models": total_entries,
67
+ "last_modified": last_commit.created_at if last_commit else None,
68
+ "file_count": len(files),
69
+ "size_bytes": dataset_info.size_in_bytes,
70
+ "downloads": dataset_info.downloads
71
+ }
72
+
73
+ return response
74
+
75
+ except Exception as e:
76
+ logger.error(f"Error counting evaluated models: {str(e)}")
77
+ return {
78
+ "error": str(e)
79
+ }
80
+
81
+ def main():
82
+ """Main function to count evaluated models"""
83
+ try:
84
+ logger.info("\nAnalyzing evaluated models...")
85
+ result = count_evaluated_models()
86
+
87
+ if 'error' in result:
88
+ logger.error(f"❌ Error: {result['error']}")
89
+ else:
90
+ logger.info(f"✓ {result['total_models']} models evaluated")
91
+ logger.info(f"✓ {result['file_count']} files")
92
+ logger.info(f"✓ {result['size_bytes'] / 1024:.1f} KB")
93
+ logger.info(f"✓ {result['downloads']} downloads")
94
+
95
+ if result['last_modified']:
96
+ last_modified = datetime.fromisoformat(result['last_modified'].replace('Z', '+00:00'))
97
+ logger.info(f"✓ Last modified: {last_modified.strftime('%Y-%m-%d %H:%M:%S')}")
98
+
99
+ return result
100
+
101
+ except Exception as e:
102
+ logger.error(f"Global error: {str(e)}")
103
+ return {"error": str(e)}
104
+
105
+ if __name__ == "__main__":
106
+ main()
backend/utils/fix_wrong_model_size.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import pytz
4
+ import logging
5
+ import asyncio
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ import huggingface_hub
9
+ from huggingface_hub.errors import RepositoryNotFoundError, RevisionNotFoundError
10
+ from dotenv import load_dotenv
11
+ from git import Repo
12
+ from datetime import datetime
13
+ from tqdm.auto import tqdm
14
+ from tqdm.contrib.logging import logging_redirect_tqdm
15
+
16
+ from app.config.hf_config import HF_TOKEN, QUEUE_REPO, API, EVAL_REQUESTS_PATH
17
+
18
+ from app.utils.model_validation import ModelValidator
19
+
20
+ huggingface_hub.logging.set_verbosity_error()
21
+ huggingface_hub.utils.disable_progress_bars()
22
+
23
+ logging.basicConfig(
24
+ level=logging.ERROR,
25
+ format='%(message)s'
26
+ )
27
+ logger = logging.getLogger(__name__)
28
+ load_dotenv()
29
+
30
+ validator = ModelValidator()
31
+
32
+ def get_changed_files(repo_path, start_date, end_date):
33
+ repo = Repo(repo_path)
34
+ start = datetime.strptime(start_date, '%Y-%m-%d')
35
+ end = datetime.strptime(end_date, '%Y-%m-%d')
36
+
37
+ changed_files = set()
38
+ pbar = tqdm(repo.iter_commits(), desc=f"Reading commits from {end_date} to {start_date}")
39
+ for commit in pbar:
40
+ commit_date = datetime.fromtimestamp(commit.committed_date)
41
+ pbar.set_postfix_str(f"Commit date: {commit_date}")
42
+ if start <= commit_date <= end:
43
+ changed_files.update(item.a_path for item in commit.diff(commit.parents[0]))
44
+
45
+ if commit_date < start:
46
+ break
47
+
48
+ return changed_files
49
+
50
+
51
+ def read_json(repo_path, file):
52
+ with open(f"{repo_path}/{file}") as file:
53
+ return json.load(file)
54
+
55
+
56
+ def write_json(repo_path, file, content):
57
+ with open(f"{repo_path}/{file}", "w") as file:
58
+ json.dump(content, file, indent=2)
59
+
60
+
61
+ def main():
62
+ requests_path = "/Users/lozowski/Developer/requests"
63
+ start_date = "2024-12-09"
64
+ end_date = "2025-01-07"
65
+
66
+ changed_files = get_changed_files(requests_path, start_date, end_date)
67
+
68
+ for file in tqdm(changed_files):
69
+ try:
70
+ request_data = read_json(requests_path, file)
71
+ except FileNotFoundError as e:
72
+ tqdm.write(f"File {file} not found")
73
+ continue
74
+
75
+ try:
76
+ model_info = API.model_info(
77
+ repo_id=request_data["model"],
78
+ revision=request_data["revision"],
79
+ token=HF_TOKEN
80
+ )
81
+ except (RepositoryNotFoundError, RevisionNotFoundError) as e:
82
+ tqdm.write(f"Model info for {request_data["model"]} not found")
83
+ continue
84
+
85
+ with logging_redirect_tqdm():
86
+ new_model_size, error = asyncio.run(validator.get_model_size(
87
+ model_info=model_info,
88
+ precision=request_data["precision"],
89
+ base_model=request_data["base_model"],
90
+ revision=request_data["revision"]
91
+ ))
92
+
93
+ if error:
94
+ tqdm.write(f"Error getting model size info for {request_data["model"]}, {error}")
95
+ continue
96
+
97
+ old_model_size = request_data["params"]
98
+ if old_model_size != new_model_size:
99
+ if new_model_size > 100:
100
+ tqdm.write(f"Model: {request_data["model"]}, size is more 100B: {new_model_size}")
101
+
102
+ tqdm.write(f"Model: {request_data["model"]}, old size: {request_data["params"]} new size: {new_model_size}")
103
+ tqdm.write(f"Updating request file {file}")
104
+
105
+ request_data["params"] = new_model_size
106
+ write_json(requests_path, file, content=request_data)
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
backend/utils/last_activity.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Dict, Any, List, Tuple
7
+ from huggingface_hub import HfApi
8
+ from dotenv import load_dotenv
9
+
10
+ # Get the backend directory path
11
+ BACKEND_DIR = Path(__file__).parent.parent
12
+ ROOT_DIR = BACKEND_DIR.parent
13
+
14
+ # Load environment variables from .env file in root directory
15
+ load_dotenv(ROOT_DIR / ".env")
16
+
17
+ # Configure logging
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format='%(message)s'
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Initialize Hugging Face API
25
+ HF_TOKEN = os.getenv("HF_TOKEN")
26
+ if not HF_TOKEN:
27
+ raise ValueError("HF_TOKEN not found in environment variables")
28
+ api = HfApi(token=HF_TOKEN)
29
+
30
+ # Default organization
31
+ HF_ORGANIZATION = os.getenv('HF_ORGANIZATION', 'bharatgenai')
32
+
33
+ def get_last_votes(limit: int = 5) -> List[Dict]:
34
+ """Get the last votes from the votes dataset"""
35
+ try:
36
+ logger.info("\nFetching last votes...")
37
+
38
+ # Download and read votes file
39
+ logger.info("Downloading votes file...")
40
+ votes_file = api.hf_hub_download(
41
+ repo_id=f"{HF_ORGANIZATION}/votes",
42
+ filename="votes_data.jsonl",
43
+ repo_type="dataset"
44
+ )
45
+
46
+ logger.info("Reading votes file...")
47
+ votes = []
48
+ with open(votes_file, 'r') as f:
49
+ for line in f:
50
+ try:
51
+ vote = json.loads(line)
52
+ votes.append(vote)
53
+ except json.JSONDecodeError:
54
+ continue
55
+
56
+ # Sort by timestamp and get last n votes
57
+ logger.info("Sorting votes...")
58
+ votes.sort(key=lambda x: x.get('timestamp', ''), reverse=True)
59
+ last_votes = votes[:limit]
60
+
61
+ logger.info(f"✓ Found {len(last_votes)} recent votes")
62
+ return last_votes
63
+
64
+ except Exception as e:
65
+ logger.error(f"Error reading votes: {str(e)}")
66
+ return []
67
+
68
+ def get_last_models(limit: int = 5) -> List[Dict]:
69
+ """Get the last models from the requests dataset using commit history"""
70
+ try:
71
+ logger.info("\nFetching last model submissions...")
72
+
73
+ # Get commit history
74
+ logger.info("Getting commit history...")
75
+ commits = list(api.list_repo_commits(
76
+ repo_id=f"{HF_ORGANIZATION}/requests",
77
+ repo_type="dataset"
78
+ ))
79
+ logger.info(f"Found {len(commits)} commits")
80
+
81
+ # Track processed files to avoid duplicates
82
+ processed_files = set()
83
+ models = []
84
+
85
+ # Process commits until we have enough models
86
+ for i, commit in enumerate(commits):
87
+ logger.info(f"Processing commit {i+1}/{len(commits)} ({commit.created_at})")
88
+
89
+ # Look at added/modified files in this commit
90
+ files_to_process = [f for f in (commit.added + commit.modified) if f.endswith('.json')]
91
+ if files_to_process:
92
+ logger.info(f"Found {len(files_to_process)} JSON files in commit")
93
+
94
+ for file in files_to_process:
95
+ if file in processed_files:
96
+ continue
97
+
98
+ processed_files.add(file)
99
+ logger.info(f"Downloading {file}...")
100
+
101
+ try:
102
+ # Download and read the file
103
+ content = api.hf_hub_download(
104
+ repo_id=f"{HF_ORGANIZATION}/requests",
105
+ filename=file,
106
+ repo_type="dataset"
107
+ )
108
+
109
+ with open(content, 'r') as f:
110
+ model_data = json.load(f)
111
+ models.append(model_data)
112
+ logger.info(f"✓ Added model {model_data.get('model', 'Unknown')}")
113
+
114
+ if len(models) >= limit:
115
+ logger.info("Reached desired number of models")
116
+ break
117
+
118
+ except Exception as e:
119
+ logger.error(f"Error reading file {file}: {str(e)}")
120
+ continue
121
+
122
+ if len(models) >= limit:
123
+ break
124
+
125
+ logger.info(f"✓ Found {len(models)} recent model submissions")
126
+ return models
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error reading models: {str(e)}")
130
+ return []
131
+
132
+ def main():
133
+ """Display last activities from the leaderboard"""
134
+ try:
135
+ # Get last votes
136
+ logger.info("\n=== Last Votes ===")
137
+ last_votes = get_last_votes()
138
+ if last_votes:
139
+ for vote in last_votes:
140
+ logger.info(f"\nModel: {vote.get('model')}")
141
+ logger.info(f"User: {vote.get('username')}")
142
+ logger.info(f"Timestamp: {vote.get('timestamp')}")
143
+ else:
144
+ logger.info("No votes found")
145
+
146
+ # Get last model submissions
147
+ logger.info("\n=== Last Model Submissions ===")
148
+ last_models = get_last_models()
149
+ if last_models:
150
+ for model in last_models:
151
+ logger.info(f"\nModel: {model.get('model')}")
152
+ logger.info(f"Submitter: {model.get('sender', 'Unknown')}")
153
+ logger.info(f"Status: {model.get('status', 'Unknown')}")
154
+ logger.info(f"Submission Time: {model.get('submitted_time', 'Unknown')}")
155
+ logger.info(f"Precision: {model.get('precision', 'Unknown')}")
156
+ logger.info(f"Weight Type: {model.get('weight_type', 'Unknown')}")
157
+ else:
158
+ logger.info("No models found")
159
+
160
+ except Exception as e:
161
+ logger.error(f"Global error: {str(e)}")
162
+
163
+ if __name__ == "__main__":
164
+ main()
backend/utils/sync_datasets_locally.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import logging
5
+ from pathlib import Path
6
+ from huggingface_hub import HfApi, snapshot_download, upload_folder, create_repo
7
+ from dotenv import load_dotenv
8
+
9
+ # Configure source and destination usernames
10
+ SOURCE_USERNAME = "bharatgenai"
11
+ DESTINATION_USERNAME = "tfrere"
12
+
13
+ # Get the backend directory path
14
+ BACKEND_DIR = Path(__file__).parent.parent
15
+ ROOT_DIR = BACKEND_DIR.parent
16
+
17
+ # Load environment variables from .env file in root directory
18
+ load_dotenv(ROOT_DIR / ".env")
19
+
20
+ # Configure logging
21
+ logging.basicConfig(
22
+ level=logging.INFO,
23
+ format='%(message)s'
24
+ )
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # List of dataset names to sync
28
+ DATASET_NAMES = [
29
+ "votes",
30
+ "results",
31
+ "requests",
32
+ "results",
33
+ "maintainers-highlight",
34
+ ]
35
+
36
+ # Build list of datasets with their source and destination paths
37
+ DATASETS = [
38
+ (name, f"{SOURCE_USERNAME}/{name}", f"{DESTINATION_USERNAME}/{name}")
39
+ for name in DATASET_NAMES
40
+ ]
41
+
42
+ # Initialize Hugging Face API
43
+ api = HfApi()
44
+
45
+ def ensure_repo_exists(repo_id, token):
46
+ """Ensure the repository exists, create it if it doesn't"""
47
+ try:
48
+ api.repo_info(repo_id=repo_id, repo_type="dataset")
49
+ logger.info(f"✓ Repository {repo_id} already exists")
50
+ except Exception:
51
+ logger.info(f"Creating repository {repo_id}...")
52
+ create_repo(
53
+ repo_id=repo_id,
54
+ repo_type="dataset",
55
+ token=token,
56
+ private=True
57
+ )
58
+ logger.info(f"✓ Repository {repo_id} created")
59
+
60
+ def process_dataset(dataset_info, token):
61
+ """Process a single dataset"""
62
+ name, source_dataset, destination_dataset = dataset_info
63
+ try:
64
+ logger.info(f"\n📥 Processing dataset: {name}")
65
+
66
+ # Ensure destination repository exists
67
+ ensure_repo_exists(destination_dataset, token)
68
+
69
+ # Create a temporary directory for this dataset
70
+ with tempfile.TemporaryDirectory() as temp_dir:
71
+ try:
72
+ # List files in source dataset
73
+ logger.info(f"Listing files in {source_dataset}...")
74
+ files = api.list_repo_files(source_dataset, repo_type="dataset")
75
+ logger.info(f"Detected structure: {len(files)} files")
76
+
77
+ # Download dataset
78
+ logger.info(f"Downloading from {source_dataset}...")
79
+ local_dir = snapshot_download(
80
+ repo_id=source_dataset,
81
+ repo_type="dataset",
82
+ local_dir=temp_dir,
83
+ token=token
84
+ )
85
+ logger.info(f"✓ Download complete")
86
+
87
+ # Upload to destination while preserving structure
88
+ logger.info(f"📤 Uploading to {destination_dataset}...")
89
+ api.upload_folder(
90
+ folder_path=local_dir,
91
+ repo_id=destination_dataset,
92
+ repo_type="dataset",
93
+ token=token
94
+ )
95
+ logger.info(f"✅ {name} copied successfully!")
96
+ return True
97
+
98
+ except Exception as e:
99
+ logger.error(f"❌ Error processing {name}: {str(e)}")
100
+ return False
101
+
102
+ except Exception as e:
103
+ logger.error(f"❌ Error for {name}: {str(e)}")
104
+ return False
105
+
106
+ def copy_datasets():
107
+ try:
108
+ logger.info("🔑 Checking authentication...")
109
+ # Get token from .env file
110
+ token = os.getenv("HF_TOKEN")
111
+ if not token:
112
+ raise ValueError("HF_TOKEN not found in .env file")
113
+
114
+ # Process datasets sequentially
115
+ results = []
116
+ for dataset_info in DATASETS:
117
+ success = process_dataset(dataset_info, token)
118
+ results.append((dataset_info[0], success))
119
+
120
+ # Print final summary
121
+ logger.info("\n📊 Final summary:")
122
+ for dataset, success in results:
123
+ status = "✅ Success" if success else "❌ Failure"
124
+ logger.info(f"{dataset}: {status}")
125
+
126
+ except Exception as e:
127
+ logger.error(f"❌ Global error: {str(e)}")
128
+
129
+ if __name__ == "__main__":
130
+ copy_datasets()
backend/uv.lock ADDED
The diff for this file is too large to render. See raw diff
 
contents.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+
3
+ ds = load_dataset("open-llm-leaderboard/contents")
4
+ ds["train"] = ds["train"].select([0])
5
+ print (ds["train"][0])
6
+ # ds.push_to_hub("bharatgenai/results")
docker-compose.yml ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ backend:
3
+ build:
4
+ context: ./backend
5
+ dockerfile: Dockerfile.dev
6
+ args:
7
+ - HF_TOKEN=${HF_TOKEN}
8
+ ports:
9
+ - "${BACKEND_PORT:-8000}:${BACKEND_PORT:-8000}"
10
+ volumes:
11
+ - ./backend:/app
12
+ environment:
13
+ - ENVIRONMENT=${ENVIRONMENT:-development}
14
+ - HF_TOKEN=${HF_TOKEN}
15
+ - HF_HOME=${HF_HOME:-/.cache}
16
+ - BACKEND_PORT=${BACKEND_PORT:-8000}
17
+ command: uvicorn app.asgi:app --host 0.0.0.0 --port ${BACKEND_PORT:-8000} --reload
18
+
19
+ frontend:
20
+ build:
21
+ context: ./frontend
22
+ dockerfile: Dockerfile.dev
23
+ ports:
24
+ - "${FRONTEND_PORT:-7860}:7860"
25
+ volumes:
26
+ - ./frontend:/app
27
+ - /app/node_modules
28
+ environment:
29
+ - NODE_ENV=${ENVIRONMENT:-development}
30
+ - CHOKIDAR_USEPOLLING=true
31
+ - PORT=${FRONTEND_PORT:-7860}
32
+ command: npm start
33
+ stdin_open: true
34
+ tty: true
frontend/Dockerfile.dev ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18
2
+
3
+ WORKDIR /app
4
+
5
+ # Install required global dependencies
6
+ RUN npm install -g react-scripts
7
+
8
+ # Copy package.json and package-lock.json
9
+ COPY package*.json ./
10
+
11
+ # Install project dependencies
12
+ RUN npm install
13
+
14
+ # Volume will be mounted here, no need for COPY
15
+ CMD ["npm", "start"]
frontend/README.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Frontend - Open LLM Leaderboard 🏆
2
+
3
+ React interface for exploring and comparing open-source language models.
4
+
5
+ ## 🏗 Architecture
6
+
7
+ ```mermaid
8
+ flowchart TD
9
+ Client(["User Browser"]) --> Components["React Components"]
10
+
11
+ subgraph Frontend
12
+ Components --> Context["Context Layer<br>• LeaderboardContext<br>• Global State"]
13
+
14
+ API["API Layer<br>• /api/leaderboard/formatted<br>• TanStack Query"] --> |Data Feed| Context
15
+
16
+ Context --> Hooks["Hooks Layer<br>• Data Processing<br>• Filtering<br>• Caching"]
17
+
18
+ Hooks --> Features["Features<br>• Table Management<br>• Search & Filters<br>• Display Options"]
19
+ Features --> Cache["Cache Layer<br>• LocalStorage<br>• URL State"]
20
+ end
21
+
22
+ API --> Backend["Backend Server"]
23
+
24
+ style Backend fill:#f96,stroke:#333,stroke-width:2px
25
+ ```
26
+
27
+ ## ✨ Core Features
28
+
29
+ - 🔍 **Search & Filters**: Real-time filtering, regex search, advanced filters
30
+ - 📊 **Data Visualization**: Interactive table, customizable columns, sorting
31
+ - 🔄 **State Management**: URL sync, client-side caching (5min TTL)
32
+ - 📱 **Responsive Design**: Mobile-friendly, dark/light themes
33
+
34
+ ## 🛠 Tech Stack
35
+
36
+ - React 18 + Material-UI
37
+ - TanStack Query & Table
38
+ - React Router v6
39
+
40
+ ## 📁 Project Structure
41
+
42
+ ```
43
+ src/
44
+ ├── pages/
45
+ │ └── LeaderboardPage/
46
+ │ ├── components/ # UI Components
47
+ │ ├── context/ # Global State
48
+ │ └── hooks/ # Data Processing
49
+ ├── components/ # Shared Components
50
+ └── utils/ # Helper Functions
51
+ ```
52
+
53
+ ## 🚀 Development
54
+
55
+ ```bash
56
+ # Install dependencies
57
+ npm install
58
+
59
+ # Start development server
60
+ npm start
61
+
62
+ # Production build
63
+ npm run build
64
+ ```
65
+
66
+ ## 🔧 Environment Variables
67
+
68
+ ```env
69
+ # API Configuration
70
+ REACT_APP_API_URL=http://localhost:8000
71
+ REACT_APP_CACHE_DURATION=300000 # 5 minutes
72
+ ```
73
+
74
+ ## 🔄 Data Flow
75
+
76
+ 1. API fetches leaderboard data from backend
77
+ 2. Context stores and manages global state
78
+ 3. Hooks handle data processing and filtering
79
+ 4. Components render based on processed data
80
+ 5. Cache maintains user preferences and URL state
frontend/package.json ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "bharatgenai-bhashabench-leaderboard",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@emotion/react": "^11.13.3",
7
+ "@emotion/styled": "^11.13.0",
8
+ "@huggingface/hub": "^0.14.0",
9
+ "@mui/icons-material": "^6.1.7",
10
+ "@mui/lab": "^6.0.0-beta.16",
11
+ "@mui/material": "^6.1.6",
12
+ "@mui/x-data-grid": "^7.22.2",
13
+ "@tanstack/react-query": "^5.62.2",
14
+ "@tanstack/react-table": "^8.20.5",
15
+ "@tanstack/react-virtual": "^3.10.9",
16
+ "@testing-library/jest-dom": "^5.17.0",
17
+ "@testing-library/react": "^13.4.0",
18
+ "@testing-library/user-event": "^13.5.0",
19
+ "compression": "^1.7.4",
20
+ "cors": "^2.8.5",
21
+ "express": "^4.18.2",
22
+ "react": "^18.3.1",
23
+ "react-dom": "^18.3.1",
24
+ "react-router-dom": "^6.28.0",
25
+ "react-scripts": "5.0.1",
26
+ "serve-static": "^1.15.0",
27
+ "web-vitals": "^2.1.4"
28
+ },
29
+ "scripts": {
30
+ "start": "react-scripts start",
31
+ "build": "react-scripts build",
32
+ "test": "react-scripts test",
33
+ "eject": "react-scripts eject",
34
+ "serve": "node server.js"
35
+ },
36
+ "eslintConfig": {
37
+ "extends": [
38
+ "react-app",
39
+ "react-app/jest"
40
+ ]
41
+ },
42
+ "browserslist": {
43
+ "production": [
44
+ ">0.2%",
45
+ "not dead",
46
+ "not op_mini all"
47
+ ],
48
+ "development": [
49
+ "last 1 chrome version",
50
+ "last 1 firefox version",
51
+ "last 1 safari version"
52
+ ]
53
+ },
54
+ "proxy": "http://backend:9999"
55
+ }
frontend/public/bglogo.png ADDED

Git LFS Details

  • SHA256: 97f6d2e95c5249e3c4007e3a2c871fdd0fa4b535f352b52c69d4fa14b3f96887
  • Pointer size: 131 Bytes
  • Size of remote file: 219 kB
frontend/public/iimlogo.png ADDED
frontend/public/iitblogo.png ADDED

Git LFS Details

  • SHA256: 6a6192820b0966dafad3f3e277ac59d0e2f5b47ac41ed46897d01a2329abc5ce
  • Pointer size: 131 Bytes
  • Size of remote file: 605 kB
frontend/public/index.html ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%PUBLIC_URL%/logo32.png" />
6
+ <meta
7
+ name="viewport"
8
+ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
9
+ />
10
+ <meta
11
+ name="description"
12
+ content="Interactive leaderboard for comparing LLM performance across BhashaBench benchmarks."
13
+ />
14
+
15
+ <!-- Open Graph / Facebook -->
16
+ <meta property="og:type" content="website" />
17
+ <meta
18
+ property="og:url"
19
+ content="https://huggingface.co/spaces/bharatgenai/bhashabench_leaderboard"
20
+ />
21
+ <meta
22
+ property="og:title"
23
+ content="BhashaBench Leaderboard - Compare Large Language Models on BhashaBench Benchmarks"
24
+ />
25
+ <meta
26
+ property="og:description"
27
+ content="Interactive leaderboard for comparing LLM performance across BhashaBench benchmarks."
28
+ />
29
+ <meta property="og:image" content="%PUBLIC_URL%/og-image.png" />
30
+
31
+ <!-- Twitter -->
32
+ <meta property="twitter:card" content="summary_large_image" />
33
+ <meta
34
+ property="twitter:url"
35
+ content="https://huggingface.co/spaces/bharatgenai/bhashabench_leaderboard"
36
+ />
37
+ <meta
38
+ property="twitter:title"
39
+ content="BhashaBench Leaderboard - Compare Large Language Models on BhashaBench Benchmarks"
40
+ />
41
+ <meta
42
+ property="twitter:description"
43
+ content="Interactive leaderboard for comparing LLM performance across BhashaBench benchmarks."
44
+ />
45
+ <meta property="twitter:image" content="%PUBLIC_URL%/og-image.png" />
46
+ <!--
47
+ Notice the use of %PUBLIC_URL% in the tags above.
48
+ It will be replaced with the URL of the `public` folder during the build.
49
+ Only files inside the `public` folder can be referenced from the HTML.
50
+
51
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
52
+ work correctly both with client-side routing and a non-root public URL.
53
+ Learn how to configure a non-root public URL by running `npm run build`.
54
+ -->
55
+ <title>
56
+ BhashaBench Leaderboard - Compare Large Language Models on BhashaBench Benchmarks
57
+ </title>
58
+ <link
59
+ href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"
60
+ rel="stylesheet"
61
+ />
62
+ <style>
63
+ html,
64
+ body {
65
+ position: fixed;
66
+ width: 100%;
67
+ height: 100%;
68
+ overflow: hidden;
69
+ -webkit-overflow-scrolling: touch;
70
+ }
71
+ #root {
72
+ position: absolute;
73
+ top: 0;
74
+ left: 0;
75
+ right: 0;
76
+ bottom: 0;
77
+ overflow-y: auto;
78
+ -webkit-overflow-scrolling: touch;
79
+ }
80
+ </style>
81
+ </head>
82
+ <body>
83
+ <noscript>You need to enable JavaScript to run this app.</noscript>
84
+ <div id="root"></div>
85
+ <!--
86
+ This HTML file is a template.
87
+ If you open it directly in the browser, you will see an empty page.
88
+
89
+ You can add webfonts, meta tags, or analytics to this file.
90
+ The build step will place the bundled scripts into the <body> tag.
91
+
92
+ To begin the development, run `npm start` or `yarn start`.
93
+ To create a production bundle, use `npm run build` or `yarn build`.
94
+ -->
95
+ </body>
96
+ </html>
frontend/public/logo256.png ADDED
frontend/public/logo32.png ADDED
frontend/public/og-image.jpg ADDED