model-router-cli 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
app/storage/models.py ADDED
@@ -0,0 +1,202 @@
1
+ import datetime
2
+ from sqlalchemy import (
3
+ Column,
4
+ String,
5
+ Float,
6
+ Integer,
7
+ Boolean,
8
+ DateTime,
9
+ Text,
10
+ JSON,
11
+ ForeignKey,
12
+ )
13
+ from sqlalchemy.orm import declarative_base, relationship
14
+
15
+ Base = declarative_base()
16
+
17
+
18
+ class ModelRecord(Base):
19
+ __tablename__ = "models"
20
+
21
+ id = Column(String(100), primary_key=True)
22
+ name = Column(String(200), nullable=False)
23
+ provider = Column(String(50), nullable=False) # mock, ollama, openai, anthropic, gemini
24
+ type = Column(String(50), default="LOCAL") # LOCAL, CLOUD, MOCK
25
+ context_window = Column(Integer, default=8192)
26
+
27
+ supports_coding = Column(Boolean, default=False)
28
+ supports_reasoning = Column(Boolean, default=False)
29
+ supports_vision = Column(Boolean, default=False)
30
+ supports_tools = Column(Boolean, default=False)
31
+
32
+ quality_score = Column(Float, default=0.7)
33
+ speed_score = Column(Float, default=0.7)
34
+ reliability_score = Column(Float, default=0.95)
35
+
36
+ cost_per_input_token = Column(Float, default=0.0)
37
+ cost_per_output_token = Column(Float, default=0.0)
38
+
39
+ availability = Column(String(50), default="AVAILABLE")
40
+ is_active = Column(Boolean, default=True)
41
+ tier = Column(String(50), default="BALANCED") # FAST, BALANCED, POWER
42
+
43
+ created_at = Column(DateTime, default=datetime.datetime.utcnow)
44
+ updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
45
+
46
+
47
+ class ProviderRecord(Base):
48
+ __tablename__ = "providers"
49
+
50
+ id = Column(String(50), primary_key=True)
51
+ name = Column(String(100), nullable=False)
52
+ status = Column(String(50), default="READY") # READY, CONNECTED, NOT_CONFIGURED, ERROR
53
+ is_enabled = Column(Boolean, default=True)
54
+ base_url = Column(String(500), nullable=True)
55
+ updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
56
+
57
+
58
+ class RoutingPolicyRecord(Base):
59
+ __tablename__ = "routing_policies"
60
+
61
+ id = Column(String(50), primary_key=True)
62
+ name = Column(String(100), nullable=False)
63
+ description = Column(String(500), nullable=True)
64
+ quality_weight = Column(Float, default=0.35)
65
+ cost_weight = Column(Float, default=0.25)
66
+ speed_weight = Column(Float, default=0.20)
67
+ capability_weight = Column(Float, default=0.15)
68
+ reliability_weight = Column(Float, default=0.05)
69
+ is_default = Column(Boolean, default=False)
70
+
71
+
72
+ class RoutingRuleRecord(Base):
73
+ __tablename__ = "routing_rules"
74
+
75
+ id = Column(String(100), primary_key=True)
76
+ name = Column(String(200), nullable=False)
77
+ description = Column(String(500), nullable=True)
78
+ priority = Column(Integer, default=0)
79
+ is_enabled = Column(Boolean, default=True)
80
+
81
+ condition_field = Column(String(100), nullable=False) # task_type, complexity, budget_percent, context_size
82
+ condition_operator = Column(String(50), nullable=False) # ==, !=, >, <, >=, <=, contains
83
+ condition_value = Column(String(200), nullable=False)
84
+
85
+ action_type = Column(String(50), nullable=False) # ROUTE_TO, PREFER, FORCE_TIER, SET_POLICY
86
+ action_target = Column(String(100), nullable=False)
87
+
88
+ created_at = Column(DateTime, default=datetime.datetime.utcnow)
89
+
90
+
91
+ class RequestRecord(Base):
92
+ __tablename__ = "requests"
93
+
94
+ request_id = Column(String(100), primary_key=True)
95
+ timestamp = Column(DateTime, default=datetime.datetime.utcnow)
96
+ prompt = Column(Text, nullable=False)
97
+ task_type = Column(String(50), nullable=False)
98
+ complexity = Column(Float, default=0.5)
99
+ context_size = Column(Integer, default=0)
100
+ reasoning_required = Column(Boolean, default=False)
101
+ coding_required = Column(Boolean, default=False)
102
+
103
+ routing_policy = Column(String(50), default="balanced")
104
+ selected_model = Column(String(100), nullable=False)
105
+ provider = Column(String(50), nullable=False)
106
+ status = Column(String(50), default="SUCCESS") # SUCCESS, FAILED, FALLBACK
107
+
108
+ fallback_used = Column(Boolean, default=False)
109
+ original_model = Column(String(100), nullable=True)
110
+ fallback_reason = Column(String(500), nullable=True)
111
+
112
+ input_tokens = Column(Integer, default=0)
113
+ output_tokens = Column(Integer, default=0)
114
+ total_tokens = Column(Integer, default=0)
115
+ estimated_cost = Column(Float, default=0.0)
116
+ baseline_cost = Column(Float, default=0.0)
117
+ cost_saved = Column(Float, default=0.0)
118
+
119
+ routing_latency_ms = Column(Float, default=0.0)
120
+ provider_latency_ms = Column(Float, default=0.0)
121
+ total_latency_ms = Column(Float, default=0.0)
122
+ time_to_first_token_ms = Column(Float, nullable=True)
123
+
124
+
125
+ class RoutingDecisionRecord(Base):
126
+ __tablename__ = "routing_decisions"
127
+
128
+ decision_id = Column(String(100), primary_key=True)
129
+ request_id = Column(String(100), ForeignKey("requests.request_id"), nullable=False)
130
+ timestamp = Column(DateTime, default=datetime.datetime.utcnow)
131
+
132
+ selected_model = Column(String(100), nullable=False)
133
+ confidence = Column(Float, default=0.9)
134
+ reasons = Column(JSON, default=list)
135
+ candidate_scores = Column(JSON, default=dict)
136
+ rejected_candidates = Column(JSON, default=dict)
137
+ policy_used = Column(String(50), default="balanced")
138
+
139
+
140
+ class ResponseRecord(Base):
141
+ __tablename__ = "responses"
142
+
143
+ response_id = Column(String(100), primary_key=True)
144
+ request_id = Column(String(100), ForeignKey("requests.request_id"), nullable=False)
145
+ timestamp = Column(DateTime, default=datetime.datetime.utcnow)
146
+ model_id = Column(String(100), nullable=False)
147
+ provider = Column(String(50), nullable=False)
148
+ content = Column(Text, nullable=False)
149
+ finish_reason = Column(String(50), default="stop")
150
+ is_mock = Column(Boolean, default=False)
151
+
152
+
153
+ class FeedbackRecord(Base):
154
+ __tablename__ = "feedback"
155
+
156
+ id = Column(String(100), primary_key=True)
157
+ request_id = Column(String(100), ForeignKey("requests.request_id"), nullable=False)
158
+ model_id = Column(String(100), nullable=False)
159
+ task_type = Column(String(50), nullable=False)
160
+ rating = Column(Integer, nullable=False) # 1 for thumbs up, -1 for thumbs down
161
+ comment = Column(Text, nullable=True)
162
+ timestamp = Column(DateTime, default=datetime.datetime.utcnow)
163
+
164
+
165
+ class BudgetRecord(Base):
166
+ __tablename__ = "budgets"
167
+
168
+ id = Column(String(50), primary_key=True, default="default")
169
+ daily_limit = Column(Float, default=10.0)
170
+ monthly_limit = Column(Float, default=100.0)
171
+ per_request_limit = Column(Float, default=1.0)
172
+ current_daily_spend = Column(Float, default=0.0)
173
+ current_monthly_spend = Column(Float, default=0.0)
174
+ intervention_mode = Column(String(50), default="OPTIMIZE") # NORMAL, OPTIMIZE_80, CHEAP_95, BLOCK_100
175
+ updated_at = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
176
+
177
+
178
+ class ExperimentRecord(Base):
179
+ __tablename__ = "experiments"
180
+
181
+ id = Column(String(100), primary_key=True)
182
+ name = Column(String(200), nullable=False)
183
+ description = Column(String(500), nullable=True)
184
+ policy_a = Column(String(50), nullable=False)
185
+ policy_b = Column(String(50), nullable=False)
186
+ status = Column(String(50), default="ACTIVE") # ACTIVE, PAUSED, COMPLETED
187
+ sample_count = Column(Integer, default=0)
188
+ created_at = Column(DateTime, default=datetime.datetime.utcnow)
189
+
190
+
191
+ class ExperimentRunRecord(Base):
192
+ __tablename__ = "experiment_runs"
193
+
194
+ id = Column(String(100), primary_key=True)
195
+ experiment_id = Column(String(100), ForeignKey("experiments.id"), nullable=False)
196
+ request_id = Column(String(100), ForeignKey("requests.request_id"), nullable=False)
197
+ assigned_policy = Column(String(50), nullable=False)
198
+ selected_model = Column(String(100), nullable=False)
199
+ cost = Column(Float, default=0.0)
200
+ latency_ms = Column(Float, default=0.0)
201
+ feedback_rating = Column(Integer, nullable=True)
202
+ timestamp = Column(DateTime, default=datetime.datetime.utcnow)
@@ -0,0 +1,343 @@
1
+ Metadata-Version: 2.4
2
+ Name: model-router-cli
3
+ Version: 1.0.0
4
+ Summary: Intelligent, explainable LLM request routing platform & AI Traffic Control Room
5
+ Author-email: PicadoLabs <picadolabs@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://picadolabs.me
8
+ Project-URL: Repository, https://github.com/PicadoLabs/ai-model-router
9
+ Project-URL: Issues, https://github.com/PicadoLabs/ai-model-router/issues
10
+ Keywords: llm,ai,router,fastapi,ollama,openai,cost-optimization
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: fastapi>=0.110.0
24
+ Requires-Dist: uvicorn[standard]>=0.28.0
25
+ Requires-Dist: pydantic>=2.6.0
26
+ Requires-Dist: pydantic-settings>=2.2.0
27
+ Requires-Dist: sqlalchemy>=2.0.28
28
+ Requires-Dist: aiosqlite>=0.20.0
29
+ Requires-Dist: httpx>=0.27.0
30
+ Requires-Dist: typer>=0.12.0
31
+ Requires-Dist: rich>=13.7.0
32
+ Requires-Dist: sse-starlette>=2.0.0
33
+ Requires-Dist: python-multipart>=0.0.9
34
+ Requires-Dist: python-dotenv>=1.0.1
35
+ Requires-Dist: tiktoken>=0.6.0
36
+ Dynamic: license-file
37
+
38
+ # Model Router
39
+
40
+ Intelligent, explainable, cost- and latency-aware LLM request routing platform and AI Traffic Control Room.
41
+
42
+ ---
43
+
44
+ ## Overview
45
+
46
+ Model Router intercepts incoming AI requests, analyzes their task type and continuous complexity, evaluates available models against a configurable multi-criteria scoring objective, selects the optimal candidate, and dispatches the request with automatic fallback handling and budget guards.
47
+
48
+ The platform is designed local-first, allowing full local development and testing using Mock models or local Ollama instances without requiring paid external API keys.
49
+
50
+ ---
51
+
52
+ ## Key Features
53
+
54
+ - **Dual-Mode Request Analyzer**: Deterministic heuristics (<3ms latency overhead) for 12 task types, continuous complexity scoring (0.05 to 0.99), and requirement detection, plus an optional LLM classifier mode.
55
+ - **Explainable Routing Engine**: Multi-criteria weighted scoring across Quality, Cost Efficiency, Speed, Capabilities, and Reliability with transparent decision factor reports and candidate rejection logs.
56
+ - **Provider Abstraction**: Decoupled adapters for Mock (simulation), Ollama (local), OpenAI, Anthropic, and Google Gemini.
57
+ - **Resilience and Tiered Fallback**: Automated retry classification for transient errors (timeouts, HTTP 429, 503) and tiered fallback to local/mock alternatives.
58
+ - **Budget Control Guards**: Real-time spend tracking with automated threshold interventions (80% cost optimization, 95% local-only saver, 100% block).
59
+ - **Traffic Control Room UI**: Real-time operational interface with seamless dark/light mode switching, featuring live topology graphs, playground inspector, SSE live request stream, telemetry export as CSV/JSON, visual rules builder, and cost savings simulator.
60
+ - **Developer CLI**: Terminal diagnostics (`doctor`), routing dry-run (`route`), execution (`run`), model catalog (`models`), and analytics (`analytics`).
61
+
62
+ ---
63
+
64
+ ## Architecture & Workflow
65
+
66
+ ```text
67
+ [ Client / SDK / Typer CLI ]
68
+ │
69
+ ▼
70
+ [ FastAPI Gateway (Port 8000) ]
71
+ │
72
+ ┌────────┴──────────────────────────┐
73
+ │ 1. Request Analyzer (<3ms) │ --> Task Type, Complexity, Context Size
74
+ │ 2. Priority Rules Evaluation │ --> Conditional Overrides
75
+ │ 3. Candidate Hard Pruning │ --> Filter Ineligible Models (Context / Caps)
76
+ │ 4. Multi-Criteria Scoring │ --> Normalized 0-100 Score across 5 Dimensions
77
+ │ 5. Decision Factor Generator │ --> Itemized Explainability Breakdown
78
+ └────────┬──────────────────────────┘
79
+ │
80
+ ▼
81
+ [ Fallback Supervisor & Provider Layer ]
82
+ ├── Local: Ollama Provider (qwen2.5-coder, llama3.2, deepseek-r1)
83
+ ├── Simulated: In-Memory Mock Provider (Zero Cost)
84
+ └── Cloud: OpenAI, Anthropic, Google Gemini (Optional)
85
+ │
86
+ ▼
87
+ [ Storage & Observability Engine ]
88
+ ├── Asynchronous SQLite WAL Database (`model_router.db`)
89
+ └── Server-Sent Events (SSE) Stream -> React Control Room (Port 5173)
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Supported Platforms & Prerequisites
95
+
96
+ ### Supported Platforms
97
+ - Linux (Ubuntu 20.04+, Debian 11+, Fedora)
98
+ - macOS (macOS 12+ / Apple Silicon & Intel)
99
+ - Windows (Windows 10, Windows 11 / PowerShell & WSL2)
100
+
101
+ ### Prerequisites
102
+ - Python 3.10, 3.11, or 3.12
103
+ - Node.js 18+ and npm
104
+ - (Optional) [Ollama](https://ollama.com/) for local model inference
105
+
106
+ ---
107
+
108
+ ## Installation & Setup
109
+
110
+ ### 1. Clone the Repository
111
+ ```bash
112
+ git clone https://github.com/PicadoLabs/AI-Model-Router.git
113
+ cd AI-Model-Router
114
+ ```
115
+
116
+ ### 2. Backend Installation
117
+ ```bash
118
+ # Create and activate virtual environment
119
+ python -m venv venv
120
+ # On Linux/macOS:
121
+ source venv/bin/activate
122
+ # On Windows PowerShell:
123
+ .\venv\Scripts\Activate.ps1
124
+
125
+ # Install Python dependencies
126
+ pip install -r requirements.txt
127
+
128
+ # Create environment file from template
129
+ cp .env.example .env
130
+ ```
131
+
132
+ ### 3. Frontend Installation
133
+ ```bash
134
+ cd frontend
135
+ npm install
136
+ cd ..
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Configuration & Environment Variables
142
+
143
+ Configuration is loaded via Pydantic Settings from the `.env` file:
144
+
145
+ | Variable | Default | Description |
146
+ | :--- | :--- | :--- |
147
+ | `APP_ENV` | `development` | Application environment (`development`, `production`, `test`) |
148
+ | `PORT` | `8000` | FastAPI server port |
149
+ | `HOST` | `0.0.0.0` | FastAPI server host |
150
+ | `DATABASE_URL` | `sqlite+aiosqlite:///./model_router.db` | SQLAlchemy database connection URI |
151
+ | `ROUTER_ANALYZER` | `rules` | Default analyzer mode (`rules` for heuristics, `llm` for model classifier) |
152
+ | `DEFAULT_ROUTING_POLICY` | `balanced` | Default routing weights (`balanced`, `lowest_cost`, `lowest_latency`, `highest_quality`) |
153
+ | `BASELINE_MODEL_ID` | `mock-power` | Reference model ID for calculating baseline cost savings |
154
+ | `DEFAULT_PROVIDER` | `mock` | Default execution provider (`mock`, `ollama`) |
155
+ | `OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama HTTP endpoint |
156
+ | `OPENAI_API_KEY` | *(empty)* | Optional OpenAI API Key |
157
+ | `ANTHROPIC_API_KEY` | *(empty)* | Optional Anthropic API Key |
158
+ | `GEMINI_API_KEY` | *(empty)* | Optional Google Gemini API Key |
159
+ | `DAILY_BUDGET` | `10.00` | Daily spend limit in USD |
160
+ | `MONTHLY_BUDGET` | `100.00` | Monthly spend limit in USD |
161
+ | `MAX_RETRIES` | `2` | Maximum retries before triggering cascading fallback |
162
+ | `PROVIDER_TIMEOUT_SECONDS` | `30.0` | Provider HTTP timeout in seconds |
163
+
164
+ ---
165
+
166
+ ## Quickstart
167
+
168
+ ### 1. Run System Diagnostics
169
+ ```bash
170
+ python backend/app/cli/main.py doctor
171
+ ```
172
+
173
+ ### 2. Start the Backend API Server
174
+ ```bash
175
+ python backend/main.py
176
+ # API server running at http://127.0.0.1:8000
177
+ # Interactive API docs available at http://127.0.0.1:8000/docs
178
+ ```
179
+
180
+ ### 3. Start the Control Room UI
181
+ In a separate terminal:
182
+ ```bash
183
+ cd frontend
184
+ npm run dev
185
+ # Access UI at http://localhost:5173
186
+ ```
187
+
188
+ ---
189
+
190
+ ## CLI Usage
191
+
192
+ The built-in Typer CLI provides terminal commands for inspection, diagnostics, and testing:
193
+
194
+ ```bash
195
+ # Run system diagnostics & provider health checks
196
+ python backend/app/cli/main.py doctor
197
+
198
+ # Inspect routing decision for a prompt without executing (Dry Run)
199
+ python backend/app/cli/main.py route "Write a Python function to parse JSON"
200
+
201
+ # Route and execute a query through the selected model
202
+ python backend/app/cli/main.py run "Debug this distributed async deadlock in worker pool"
203
+
204
+ # List all registered models in the catalog
205
+ python backend/app/cli/main.py models
206
+
207
+ # View system-wide routing performance and cost savings analytics
208
+ python backend/app/cli/main.py analytics
209
+ ```
210
+
211
+ ---
212
+
213
+ ## REST API Usage
214
+
215
+ ### 1. Dry-Run Routing (`POST /api/route`)
216
+ ```bash
217
+ curl -X POST http://127.0.0.1:8000/api/route \
218
+ -H "Content-Type: application/json" \
219
+ -d '{"prompt": "Write a quicksort algorithm in Python", "policy": "balanced"}'
220
+ ```
221
+
222
+ ### 2. End-to-End Routed Generation (`POST /api/generate`)
223
+ ```bash
224
+ curl -X POST http://127.0.0.1:8000/api/generate \
225
+ -H "Content-Type: application/json" \
226
+ -d '{"prompt": "Explain the difference between TCP and UDP", "policy": "lowest_cost"}'
227
+ ```
228
+
229
+ ### 3. Fetch Registered Models (`GET /api/models`)
230
+ ```bash
231
+ curl http://127.0.0.1:8000/api/models
232
+ ```
233
+
234
+ ### 4. Export Historical Traffic (`GET /api/traffic/export`)
235
+ Export all persisted traffic records for auditing, accounting, or latency analysis:
236
+
237
+ ```bash
238
+ # Export as JSON
239
+ curl -OJ "http://127.0.0.1:8000/api/traffic/export?format=json"
240
+
241
+ # Export as CSV
242
+ curl -OJ "http://127.0.0.1:8000/api/traffic/export?format=csv"
243
+ ```
244
+
245
+ Each export includes the timestamp, request ID, prompt preview, task type, complexity, selected model, input/output/total tokens, cost saved, and total latency. The `format` query parameter accepts only `csv` or `json`.
246
+
247
+ The same export is available in the frontend under **Traffic**. Select `CSV` or `JSON` beside **Export Telemetry**, then click the button to download the complete historical traffic dataset.
248
+
249
+ ---
250
+
251
+ ## Running Tests
252
+
253
+ The test suite includes 18 automated unit, integration, and end-to-end tests covering prompt heuristics, candidate pruning, scoring weights, provider execution, error fallbacks, REST endpoints, and CSV/JSON traffic exports:
254
+
255
+ ```bash
256
+ # Run the backend test suite from the repository root
257
+ pytest backend/tests
258
+
259
+ # Or run it from the backend directory
260
+ cd backend
261
+ python -m pytest tests
262
+
263
+ # Run frontend production build test
264
+ cd frontend
265
+ npm run build
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Project Structure
271
+
272
+ ```text
273
+ AI-Model-Router/
274
+ ├── .github/
275
+ │ ├── ISSUE_TEMPLATE/
276
+ │ │ ├── bug_report.md
277
+ │ │ └── feature_request.md
278
+ │ ├── pull_request_template.md
279
+ │ └── workflows/
280
+ │ └── ci.yml
281
+ ├── backend/
282
+ │ ├── app/
283
+ │ │ ├── analytics/ # Cost savings and aggregate analytics service
284
+ │ │ ├── analyzer/ # Dual-mode request analyzer (heuristics & LLM)
285
+ │ │ ├── api/ # FastAPI REST endpoints and request handlers
286
+ │ │ ├── budgets/ # Spend tracking and automated threshold manager
287
+ │ │ ├── cli/ # Typer CLI application (doctor, route, run, etc.)
288
+ │ │ ├── config/ # Pydantic Settings environment configuration
289
+ │ │ ├── experiments/ # A/B policy experimentation service
290
+ │ │ ├── fallback/ # Error classifier and tiered fallback supervisor
291
+ │ │ ├── models/ # Pydantic schemas (RequestAnalysis, RoutingDecision)
292
+ │ │ ├── observability/ # Redacted structured JSON event logger
293
+ │ │ ├── providers/ # Decoupled adapters (Mock, Ollama, Cloud)
294
+ │ │ ├── router/ # Core scoring matrix, pruner, and rules engine
295
+ │ │ └── storage/ # SQLAlchemy models and SQLite async database
296
+ │ ├── tests/ # Pytest test suite (15 passing tests)
297
+ │ ├── main.py # FastAPI application entrypoint
298
+ │ └── requirements.txt # Python backend dependencies
299
+ ├── frontend/
300
+ │ ├── src/
301
+ │ │ ├── components/ # UI components (Navbar, RoutingMap topology graph)
302
+ │ │ ├── pages/ # Control Room pages (Dashboard, Playground, Rules, etc.)
303
+ │ │ └── types/ # TypeScript data interfaces
304
+ │ └── package.json # Node.js dependencies
305
+ ├── .env.example # Configuration template
306
+ ├── .gitignore # Git exclusions
307
+ ├── CODE_OF_CONDUCT.md # Contributor Covenant Code of Conduct
308
+ ├── CONTRIBUTING.md # Contribution guidelines and workflow
309
+ ├── LICENSE # MIT License
310
+ ├── README.md # Project documentation
311
+ ├── requirements.txt # Root Python dependencies
312
+ └── SECURITY.md # Vulnerability reporting and security policy
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Contributing
318
+
319
+ We welcome contributions from the community. Please review [CONTRIBUTING.md](CONTRIBUTING.md) for details on our development setup, coding standards, branch conventions, and pull request process.
320
+
321
+ Please note that this project is released with a [Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms.
322
+
323
+ ---
324
+
325
+ ## Security
326
+
327
+ Security and privacy are core to Model Router. For vulnerability reporting procedures and our zero-secret-exposure policy, please refer to [SECURITY.md](SECURITY.md).
328
+
329
+ ---
330
+
331
+ ## License
332
+
333
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
334
+
335
+ ---
336
+
337
+ ## PicadoLabs
338
+
339
+ Maintained and architected by **PicadoLabs**.
340
+
341
+ - **Organization**: [PicadoLabs](https://github.com/PicadoLabs)
342
+ - **Website**: [https://picadolabs.me](https://picadolabs.me)
343
+ - **Contact**: [picadolabs@gmail.com](mailto:picadolabs@gmail.com)
@@ -0,0 +1,38 @@
1
+ app/analytics/service.py,sha256=_GO-LPARnbJf6VrvAja2ceMLhASBZDt4TFf8cYRSWG0,4748
2
+ app/analyzer/analyzer.py,sha256=Lk-W-AZBMIrTK7pDEUP-DzfuW-pk-sgmyE9KkbQsulw,3441
3
+ app/analyzer/heuristics.py,sha256=S_IfUkHcwfYXppUg54g5qqSxIoKNH645_aDJywPbc2k,6955
4
+ app/api/routes.py,sha256=8L4mCKSHUNzIU9Oqb_daIeHmqTIfSac3AhuhcQUaNng,21164
5
+ app/budgets/manager.py,sha256=rNofzi6fvqazK13z6wN6QniICOInza7ZSd_jw-Bzf_0,1177
6
+ app/cli/main.py,sha256=4d7L6qf5gHHO-yRT52fGzwyPyYe0N5LCnCspzbxsx-s,12329
7
+ app/config/settings.py,sha256=GgGnDg4I2iRmFQOeZ-HiXOa8kBD-q_FUARmr5SeF3_c,1141
8
+ app/experiments/service.py,sha256=qhIPIabn3aqKSbmpLZipzlBuW2yWtpE0EPUP9myXZmI,2553
9
+ app/fallback/handler.py,sha256=gpUhnFkUWL6V0MCnxIr5mNBZBPkaTw7GaoyslA6sa74,3790
10
+ app/models/schemas.py,sha256=8VabYcm9TSqSS2SLxMA-xwgm3TvXC_VYKBiceaShtC4,3513
11
+ app/observability/events.py,sha256=v05Kkj6RekQoVkOSoT8mkHUm8RO1xkucOi-f-qsdxaA,1352
12
+ app/providers/base.py,sha256=gZfKy02kdOEH3vYDz0Ba4oIRY29XCtOxRfi_HXvGyqM,1357
13
+ app/providers/external_providers.py,sha256=QaVOXiL8UktZf5nYcMyfEaFIa9oEZg7dDVic_6eC6D0,11943
14
+ app/providers/mock_provider.py,sha256=0Sfhx3ZlWd82Es4N3SWnIbzpFz3MuXfwtY4d1K4d0Ss,4436
15
+ app/providers/ollama_provider.py,sha256=zWzOvFFe72O2-z45bFtDyWdSLc6qJFEtY2_WjqcaKtE,5470
16
+ app/providers/registry.py,sha256=WqxTGZGbqcvhEku4dWtY8w_HV8tzJ2AphjauVmYi63k,1266
17
+ app/router/engine.py,sha256=C8W5YE3lwsSDN3Dl7NVwfbhEgqbNL51BHOk1y_sN4PY,6195
18
+ app/router/rules_engine.py,sha256=beLmOv1WcJEiK4h6LReGxdV1AeFnALHS1LTo6x67wus,2577
19
+ app/router/scoring.py,sha256=va-xN4x1Wni_ws7LY8wgxi6Esup4q8tKMLUzQzuZwoQ,5753
20
+ app/static/favicon.png,sha256=0u27ceoPWx2R8aaNO89vVtXFDHUZU6vI1mKF3oK59Yo,155750
21
+ app/static/favicon.svg,sha256=YbyaFh3lgkgojmkFQl1xgPBiTChlAHuX12P9rBIEOmY,9522
22
+ app/static/icons.svg,sha256=tF-lBhlc_N70BrqfDHezbdwafCJAQJJuxwq8L96nuTo,5031
23
+ app/static/index.html,sha256=YYE4tnZ3gy7wJKsLbxBCEOfaHSA7QjiYojL_rBpP5II,839
24
+ app/static/logo.png,sha256=0u27ceoPWx2R8aaNO89vVtXFDHUZU6vI1mKF3oK59Yo,155750
25
+ app/static/assets/index-CQFztymk.js,sha256=mxQ-1WmUmDpHWuplZHcTwdX4d_puaab15O60H_RlTfU,650186
26
+ app/static/assets/index-DWa3sE4Y.css,sha256=1pucPHMy-Sfbssn_Nh2QoczvfmACtZAXXm_ebuBj4KI,51647
27
+ app/storage/database.py,sha256=5rmfnGoe6dJnEvTwfWfRf4V-RiaOa3KER-Urh0GDqVM,14586
28
+ app/storage/models.py,sha256=UhJG2J1Kar76eyvsbM2zVdMypuds7LCmWYY9gmyR-bQ,7930
29
+ model_router_cli-1.0.0.dist-info/licenses/LICENSE,sha256=TP8beNKdm-mrwHR5hsEoZbvW1BFJkbqoR8LXaR1SH_Y,1112
30
+ tests/test_analyzer.py,sha256=Fs3l1-9kOR6mkzumXdXA2cbT4EuRjCKH2sDso6tL6YA,1692
31
+ tests/test_e2e.py,sha256=NIl4KtvZ2sd4bmTNgwQkl7avw6mZjj-Hca6lGmInYeU,4541
32
+ tests/test_providers.py,sha256=sXL7jR31Fuc1LyD7fa2tR7RPnz1FyFErv_bGv4YS3Uc,669
33
+ tests/test_router.py,sha256=iCK5KYdVO81fPStag6tuCSUQ3yBuie7UHvodwGA27Jo,2832
34
+ model_router_cli-1.0.0.dist-info/METADATA,sha256=29bC-LWtKiOFrmQVZQrYX5TPGPzHfz9UNRPlJyoYlt4,13698
35
+ model_router_cli-1.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
36
+ model_router_cli-1.0.0.dist-info/entry_points.txt,sha256=aUfDy8XCf6UTra8rCjeYvObpjyovPYfFo3fR__8I61I,49
37
+ model_router_cli-1.0.0.dist-info/top_level.txt,sha256=MJXn5pCZl0XSEeWNKzshxzMMANr3AymeoMYBO1JNPyU,10
38
+ model_router_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ modelrouter = app.cli.main:app
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Model Router Contributors
4
+ Copyright (c) 2026 PicadoLabs
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ app
2
+ tests
tests/test_analyzer.py ADDED
@@ -0,0 +1,41 @@
1
+ import pytest
2
+ from app.analyzer.heuristics import analyze_request_heuristics, estimate_tokens
3
+ from app.models.schemas import TaskType, PriorityLevel
4
+
5
+
6
+ def test_token_estimation():
7
+ text = "Hello world from Model Router platform!"
8
+ tokens = estimate_tokens(text)
9
+ assert tokens > 0
10
+
11
+
12
+ def test_heuristic_classification_coding():
13
+ prompt = "Write a python function to calculate fibonacci sequence using dynamic programming"
14
+ analysis = analyze_request_heuristics(prompt)
15
+ assert analysis.task_type == TaskType.CODING
16
+ assert analysis.coding_required is True
17
+ assert analysis.complexity >= 0.40
18
+
19
+
20
+ def test_heuristic_classification_debugging_complexity():
21
+ prompt = "Debug this distributed async deadlock issue in the microservices cluster and explain the root cause"
22
+ analysis = analyze_request_heuristics(prompt)
23
+ assert analysis.task_type == TaskType.DEBUGGING
24
+ assert analysis.reasoning_required is True
25
+ assert analysis.coding_required is True
26
+ assert analysis.complexity_label == PriorityLevel.HIGH
27
+ assert analysis.complexity >= 0.75
28
+
29
+
30
+ def test_heuristic_classification_summarization():
31
+ prompt = "Provide a summary and key bullet points of this meeting transcript"
32
+ analysis = analyze_request_heuristics(prompt)
33
+ assert analysis.task_type == TaskType.SUMMARIZATION
34
+ assert analysis.complexity_label in (PriorityLevel.LOW, PriorityLevel.MEDIUM)
35
+
36
+
37
+ def test_heuristic_classification_math():
38
+ prompt = "Calculate the derivative and integral of matrix eigenvalue differential equation"
39
+ analysis = analyze_request_heuristics(prompt)
40
+ assert analysis.task_type == TaskType.MATH
41
+ assert analysis.reasoning_required is True