router-maestro 0.1.2__tar.gz → 0.1.3__tar.gz

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.
Files changed (72) hide show
  1. {router_maestro-0.1.2 → router_maestro-0.1.3}/PKG-INFO +48 -11
  2. {router_maestro-0.1.2 → router_maestro-0.1.3}/README.md +47 -10
  3. {router_maestro-0.1.2 → router_maestro-0.1.3}/pyproject.toml +1 -1
  4. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/__init__.py +1 -1
  5. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/cli/client.py +0 -32
  6. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/cli/main.py +1 -2
  7. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/cli/server.py +1 -1
  8. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/config/__init__.py +0 -2
  9. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/config/paths.py +0 -1
  10. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/routes/admin.py +1 -73
  11. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/schemas/__init__.py +0 -4
  12. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/schemas/admin.py +0 -19
  13. {router_maestro-0.1.2 → router_maestro-0.1.3}/uv.lock +1 -1
  14. router_maestro-0.1.2/src/router_maestro/cli/stats.py +0 -76
  15. router_maestro-0.1.2/src/router_maestro/stats/__init__.py +0 -14
  16. router_maestro-0.1.2/src/router_maestro/stats/heatmap.py +0 -154
  17. router_maestro-0.1.2/src/router_maestro/stats/storage.py +0 -228
  18. router_maestro-0.1.2/src/router_maestro/stats/tracker.py +0 -73
  19. router_maestro-0.1.2/tests/test_stats.py +0 -78
  20. {router_maestro-0.1.2 → router_maestro-0.1.3}/.env.example +0 -0
  21. {router_maestro-0.1.2 → router_maestro-0.1.3}/.gitignore +0 -0
  22. {router_maestro-0.1.2 → router_maestro-0.1.3}/CLAUDE.md +0 -0
  23. {router_maestro-0.1.2 → router_maestro-0.1.3}/Dockerfile +0 -0
  24. {router_maestro-0.1.2 → router_maestro-0.1.3}/LICENSE +0 -0
  25. {router_maestro-0.1.2 → router_maestro-0.1.3}/Makefile +0 -0
  26. {router_maestro-0.1.2 → router_maestro-0.1.3}/docker-compose.yml +0 -0
  27. {router_maestro-0.1.2 → router_maestro-0.1.3}/docs/deployment.md +0 -0
  28. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/__main__.py +0 -0
  29. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/auth/__init__.py +0 -0
  30. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/auth/github_oauth.py +0 -0
  31. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/auth/manager.py +0 -0
  32. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/auth/storage.py +0 -0
  33. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/cli/__init__.py +0 -0
  34. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/cli/auth.py +0 -0
  35. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/cli/config.py +0 -0
  36. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/cli/context.py +0 -0
  37. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/cli/model.py +0 -0
  38. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/config/contexts.py +0 -0
  39. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/config/priorities.py +0 -0
  40. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/config/providers.py +0 -0
  41. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/config/server.py +0 -0
  42. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/config/settings.py +0 -0
  43. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/providers/__init__.py +0 -0
  44. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/providers/anthropic.py +0 -0
  45. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/providers/base.py +0 -0
  46. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/providers/copilot.py +0 -0
  47. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/providers/openai.py +0 -0
  48. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/providers/openai_compat.py +0 -0
  49. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/routing/__init__.py +0 -0
  50. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/routing/router.py +0 -0
  51. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/__init__.py +0 -0
  52. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/app.py +0 -0
  53. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/middleware/__init__.py +0 -0
  54. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/middleware/auth.py +0 -0
  55. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/oauth_sessions.py +0 -0
  56. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/routes/__init__.py +0 -0
  57. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/routes/anthropic.py +0 -0
  58. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/routes/chat.py +0 -0
  59. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/routes/models.py +0 -0
  60. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/schemas/anthropic.py +0 -0
  61. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/schemas/openai.py +0 -0
  62. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/server/translation.py +0 -0
  63. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/utils/__init__.py +0 -0
  64. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/utils/logging.py +0 -0
  65. {router_maestro-0.1.2 → router_maestro-0.1.3}/src/router_maestro/utils/tokens.py +0 -0
  66. {router_maestro-0.1.2 → router_maestro-0.1.3}/tests/__init__.py +0 -0
  67. {router_maestro-0.1.2 → router_maestro-0.1.3}/tests/test_auth.py +0 -0
  68. {router_maestro-0.1.2 → router_maestro-0.1.3}/tests/test_config.py +0 -0
  69. {router_maestro-0.1.2 → router_maestro-0.1.3}/tests/test_providers.py +0 -0
  70. {router_maestro-0.1.2 → router_maestro-0.1.3}/tests/test_router.py +0 -0
  71. {router_maestro-0.1.2 → router_maestro-0.1.3}/tests/test_translation.py +0 -0
  72. {router_maestro-0.1.2 → router_maestro-0.1.3}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: router-maestro
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Multi-model routing and load balancing system with OpenAI-compatible API
5
5
  Author-email: Kanwen Li <likanwen@icloud.com>
6
6
  License-Expression: MIT
@@ -52,7 +52,6 @@ Router-Maestro acts as a proxy that gives you access to models from multiple pro
52
52
  - **Dual API compatibility**: Both OpenAI (`/v1/...`) and Anthropic (`/v1/messages`) API formats
53
53
  - **Cross-provider translation**: Seamlessly route OpenAI requests to Anthropic providers and vice versa
54
54
  - **Configuration hot-reload**: Auto-reload config files every 5 minutes without server restart
55
- - **Usage tracking**: Token usage statistics with heatmap visualization
56
55
  - **CLI management**: Full command-line interface for configuration and server control
57
56
  - **Docker ready**: Production-ready Docker images with Traefik integration
58
57
 
@@ -60,6 +59,11 @@ Router-Maestro acts as a proxy that gives you access to models from multiple pro
60
59
 
61
60
  - [Quick Start](#quick-start)
62
61
  - [Core Concepts](#core-concepts)
62
+ - [Model Identification](#model-identification)
63
+ - [Auto-Routing](#auto-routing)
64
+ - [Priority & Fallback](#priority--fallback)
65
+ - [Cross-Provider Translation](#cross-provider-translation)
66
+ - [Contexts](#contexts)
63
67
  - [CLI Reference](#cli-reference)
64
68
  - [API Reference](#api-reference)
65
69
  - [Configuration](#configuration)
@@ -70,20 +74,36 @@ Router-Maestro acts as a proxy that gives you access to models from multiple pro
70
74
 
71
75
  Get up and running in 4 steps:
72
76
 
73
- ### 1. Install
77
+ ### 1. Start the Server
78
+
79
+ #### Docker (recommended)
74
80
 
75
81
  ```bash
76
- pip install router-maestro
77
- # or
78
- uv pip install router-maestro
82
+ docker run -d -p 8080:8080 \
83
+ -v ~/.local/share/router-maestro:/home/maestro/.local/share/router-maestro \
84
+ -v ~/.config/router-maestro:/home/maestro/.config/router-maestro \
85
+ likanwen/router-maestro:latest
79
86
  ```
80
87
 
81
- ### 2. Start the Server
88
+ #### Install locally
82
89
 
83
90
  ```bash
91
+ pip install router-maestro
84
92
  router-maestro server start --port 8080
85
93
  ```
86
94
 
95
+ ### 2. Set Context (for Docker or Remote)
96
+
97
+ When running via Docker in remote VPS, set up a context to communicate with the containerized server:
98
+
99
+ ```bash
100
+ pip install router-maestro # Install CLI locally
101
+ router-maestro context add docker --endpoint http://localhost:8080
102
+ router-maestro context set docker
103
+ ```
104
+
105
+ > **What's a context?** A context is a named connection profile (endpoint + API key) that lets you manage local or remote Router-Maestro servers. See [Contexts](#contexts) for details.
106
+
87
107
  ### 3. Authenticate with GitHub Copilot
88
108
 
89
109
  ```bash
@@ -171,6 +191,27 @@ POST /v1/messages {"model": "openai/gpt-4o", ...}
171
191
  POST /v1/chat/completions {"model": "anthropic/claude-3-5-sonnet", ...}
172
192
  ```
173
193
 
194
+ ### Contexts
195
+
196
+ A **context** is a named connection profile that stores an endpoint URL and API key. Contexts let you manage multiple Router-Maestro deployments from a single CLI.
197
+
198
+ | Context | Use Case |
199
+ |---------|----------|
200
+ | `local` | Default context for `router-maestro server start` |
201
+ | `docker` | Connect to a local Docker container |
202
+ | `my-vps` | Connect to a remote VPS deployment |
203
+
204
+ ```bash
205
+ # Add a context
206
+ router-maestro context add my-vps --endpoint https://api.example.com --api-key xxx
207
+
208
+ # Switch contexts
209
+ router-maestro context set my-vps
210
+
211
+ # All CLI commands now target the remote server
212
+ router-maestro model list
213
+ ```
214
+
174
215
  ## CLI Reference
175
216
 
176
217
  ### Server
@@ -214,8 +255,6 @@ POST /v1/chat/completions {"model": "anthropic/claude-3-5-sonnet", ...}
214
255
  | Command | Description |
215
256
  |---------|-------------|
216
257
  | `config claude-code` | Generate Claude Code settings |
217
- | `stats --days 7` | Show usage statistics |
218
- | `stats --days 30 --heatmap` | Show heatmap visualization |
219
258
 
220
259
  ## API Reference
221
260
 
@@ -271,7 +310,6 @@ Following XDG Base Directory specification:
271
310
  | **Data** | `~/.local/share/router-maestro/` | |
272
311
  | | `auth.json` | OAuth tokens |
273
312
  | | `server.json` | Server state |
274
- | | `stats.db` | Usage statistics |
275
313
 
276
314
  ### Custom Providers
277
315
 
@@ -360,7 +398,6 @@ router-maestro context set my-vps
360
398
 
361
399
  # Now all commands target the VPS
362
400
  router-maestro model list
363
- router-maestro stats --days 7
364
401
  ```
365
402
 
366
403
  ### HTTPS with Traefik
@@ -15,7 +15,6 @@ Router-Maestro acts as a proxy that gives you access to models from multiple pro
15
15
  - **Dual API compatibility**: Both OpenAI (`/v1/...`) and Anthropic (`/v1/messages`) API formats
16
16
  - **Cross-provider translation**: Seamlessly route OpenAI requests to Anthropic providers and vice versa
17
17
  - **Configuration hot-reload**: Auto-reload config files every 5 minutes without server restart
18
- - **Usage tracking**: Token usage statistics with heatmap visualization
19
18
  - **CLI management**: Full command-line interface for configuration and server control
20
19
  - **Docker ready**: Production-ready Docker images with Traefik integration
21
20
 
@@ -23,6 +22,11 @@ Router-Maestro acts as a proxy that gives you access to models from multiple pro
23
22
 
24
23
  - [Quick Start](#quick-start)
25
24
  - [Core Concepts](#core-concepts)
25
+ - [Model Identification](#model-identification)
26
+ - [Auto-Routing](#auto-routing)
27
+ - [Priority & Fallback](#priority--fallback)
28
+ - [Cross-Provider Translation](#cross-provider-translation)
29
+ - [Contexts](#contexts)
26
30
  - [CLI Reference](#cli-reference)
27
31
  - [API Reference](#api-reference)
28
32
  - [Configuration](#configuration)
@@ -33,20 +37,36 @@ Router-Maestro acts as a proxy that gives you access to models from multiple pro
33
37
 
34
38
  Get up and running in 4 steps:
35
39
 
36
- ### 1. Install
40
+ ### 1. Start the Server
41
+
42
+ #### Docker (recommended)
37
43
 
38
44
  ```bash
39
- pip install router-maestro
40
- # or
41
- uv pip install router-maestro
45
+ docker run -d -p 8080:8080 \
46
+ -v ~/.local/share/router-maestro:/home/maestro/.local/share/router-maestro \
47
+ -v ~/.config/router-maestro:/home/maestro/.config/router-maestro \
48
+ likanwen/router-maestro:latest
42
49
  ```
43
50
 
44
- ### 2. Start the Server
51
+ #### Install locally
45
52
 
46
53
  ```bash
54
+ pip install router-maestro
47
55
  router-maestro server start --port 8080
48
56
  ```
49
57
 
58
+ ### 2. Set Context (for Docker or Remote)
59
+
60
+ When running via Docker in remote VPS, set up a context to communicate with the containerized server:
61
+
62
+ ```bash
63
+ pip install router-maestro # Install CLI locally
64
+ router-maestro context add docker --endpoint http://localhost:8080
65
+ router-maestro context set docker
66
+ ```
67
+
68
+ > **What's a context?** A context is a named connection profile (endpoint + API key) that lets you manage local or remote Router-Maestro servers. See [Contexts](#contexts) for details.
69
+
50
70
  ### 3. Authenticate with GitHub Copilot
51
71
 
52
72
  ```bash
@@ -134,6 +154,27 @@ POST /v1/messages {"model": "openai/gpt-4o", ...}
134
154
  POST /v1/chat/completions {"model": "anthropic/claude-3-5-sonnet", ...}
135
155
  ```
136
156
 
157
+ ### Contexts
158
+
159
+ A **context** is a named connection profile that stores an endpoint URL and API key. Contexts let you manage multiple Router-Maestro deployments from a single CLI.
160
+
161
+ | Context | Use Case |
162
+ |---------|----------|
163
+ | `local` | Default context for `router-maestro server start` |
164
+ | `docker` | Connect to a local Docker container |
165
+ | `my-vps` | Connect to a remote VPS deployment |
166
+
167
+ ```bash
168
+ # Add a context
169
+ router-maestro context add my-vps --endpoint https://api.example.com --api-key xxx
170
+
171
+ # Switch contexts
172
+ router-maestro context set my-vps
173
+
174
+ # All CLI commands now target the remote server
175
+ router-maestro model list
176
+ ```
177
+
137
178
  ## CLI Reference
138
179
 
139
180
  ### Server
@@ -177,8 +218,6 @@ POST /v1/chat/completions {"model": "anthropic/claude-3-5-sonnet", ...}
177
218
  | Command | Description |
178
219
  |---------|-------------|
179
220
  | `config claude-code` | Generate Claude Code settings |
180
- | `stats --days 7` | Show usage statistics |
181
- | `stats --days 30 --heatmap` | Show heatmap visualization |
182
221
 
183
222
  ## API Reference
184
223
 
@@ -234,7 +273,6 @@ Following XDG Base Directory specification:
234
273
  | **Data** | `~/.local/share/router-maestro/` | |
235
274
  | | `auth.json` | OAuth tokens |
236
275
  | | `server.json` | Server state |
237
- | | `stats.db` | Usage statistics |
238
276
 
239
277
  ### Custom Providers
240
278
 
@@ -323,7 +361,6 @@ router-maestro context set my-vps
323
361
 
324
362
  # Now all commands target the VPS
325
363
  router-maestro model list
326
- router-maestro stats --days 7
327
364
  ```
328
365
 
329
366
  ### HTTPS with Traefik
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "router-maestro"
3
- version = "0.1.2"
3
+ version = "0.1.3"
4
4
  description = "Multi-model routing and load balancing system with OpenAI-compatible API"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """Router-Maestro: Multi-model routing and load balancing system."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.1.3"
@@ -241,38 +241,6 @@ class AdminClient:
241
241
  self._handle_connection_error(e)
242
242
  return False
243
243
 
244
- async def get_stats(
245
- self, days: int = 7, provider: str | None = None, model: str | None = None
246
- ) -> dict:
247
- """Get usage statistics.
248
-
249
- Args:
250
- days: Number of days to query
251
- provider: Optional provider filter
252
- model: Optional model filter
253
-
254
- Returns:
255
- Stats dict with total_requests, total_tokens, by_provider, by_model
256
- """
257
- try:
258
- async with httpx.AsyncClient() as client:
259
- params: dict = {"days": days}
260
- if provider:
261
- params["provider"] = provider
262
- if model:
263
- params["model"] = model
264
-
265
- response = await client.get(
266
- f"{self.endpoint}/api/admin/stats",
267
- headers=self._get_headers(),
268
- params=params,
269
- )
270
- response.raise_for_status()
271
- return response.json()
272
- except httpx.HTTPError as e:
273
- self._handle_connection_error(e)
274
- return {}
275
-
276
244
  async def test_connection(self) -> dict:
277
245
  """Test connection to the server.
278
246
 
@@ -14,14 +14,13 @@ app = typer.Typer(
14
14
  console = Console()
15
15
 
16
16
  # Import and register sub-commands
17
- from router_maestro.cli import auth, config, context, model, server, stats # noqa: E402
17
+ from router_maestro.cli import auth, config, context, model, server # noqa: E402
18
18
 
19
19
  app.add_typer(server.app, name="server", help="Manage the API server")
20
20
  app.add_typer(auth.app, name="auth", help="Manage authentication for providers")
21
21
  app.add_typer(model.app, name="model", help="Manage models and priorities")
22
22
  app.add_typer(context.app, name="context", help="Manage deployment contexts")
23
23
  app.add_typer(config.app, name="config", help="Manage configuration")
24
- app.command(name="stats")(stats.stats)
25
24
 
26
25
 
27
26
  @app.callback(invoke_without_command=True)
@@ -98,7 +98,7 @@ def status() -> None:
98
98
 
99
99
  try:
100
100
  data = asyncio.run(client.test_connection())
101
- console.print(f"[green]Server is running[/green]")
101
+ console.print("[green]Server is running[/green]")
102
102
  console.print(f" Version: {data.get('version', 'unknown')}")
103
103
  console.print(f" Status: {data.get('status', 'unknown')}")
104
104
  except Exception as e:
@@ -8,7 +8,6 @@ from router_maestro.config.paths import (
8
8
  PRIORITIES_FILE,
9
9
  PROVIDERS_FILE,
10
10
  SERVER_CONFIG_FILE,
11
- STATS_DB_FILE,
12
11
  get_config_dir,
13
12
  get_data_dir,
14
13
  )
@@ -45,7 +44,6 @@ __all__ = [
45
44
  "PROVIDERS_FILE",
46
45
  "PRIORITIES_FILE",
47
46
  "CONTEXTS_FILE",
48
- "STATS_DB_FILE",
49
47
  "LOG_FILE",
50
48
  # Provider models
51
49
  "ModelConfig",
@@ -46,5 +46,4 @@ SERVER_CONFIG_FILE = get_data_dir() / "server.json"
46
46
  PROVIDERS_FILE = get_config_dir() / "providers.json"
47
47
  PRIORITIES_FILE = get_config_dir() / "priorities.json"
48
48
  CONTEXTS_FILE = get_config_dir() / "contexts.json"
49
- STATS_DB_FILE = get_data_dir() / "stats.db"
50
49
  LOG_FILE = get_data_dir() / "router-maestro.log"
@@ -1,9 +1,7 @@
1
1
  """Admin API routes for remote management."""
2
2
 
3
- from typing import Annotated
4
-
5
3
  import httpx
6
- from fastapi import APIRouter, BackgroundTasks, HTTPException, Query
4
+ from fastapi import APIRouter, BackgroundTasks, HTTPException
7
5
 
8
6
  from router_maestro.auth import AuthManager, AuthType
9
7
  from router_maestro.auth.github_oauth import (
@@ -29,9 +27,7 @@ from router_maestro.server.schemas.admin import (
29
27
  OAuthStatusResponse,
30
28
  PrioritiesResponse,
31
29
  PrioritiesUpdateRequest,
32
- StatsResponse,
33
30
  )
34
- from router_maestro.stats import StatsStorage
35
31
 
36
32
  router = APIRouter(prefix="/api/admin", tags=["admin"])
37
33
 
@@ -288,71 +284,3 @@ async def update_priorities(request: PrioritiesUpdateRequest) -> PrioritiesRespo
288
284
  priorities=config.priorities,
289
285
  fallback=config.fallback.model_dump(),
290
286
  )
291
-
292
-
293
- # ============================================================================
294
- # Stats endpoints
295
- # ============================================================================
296
-
297
-
298
- @router.get("/stats", response_model=StatsResponse)
299
- async def get_stats(
300
- days: Annotated[int, Query(ge=1, le=365)] = 7,
301
- provider: str | None = None,
302
- model: str | None = None,
303
- ) -> StatsResponse:
304
- """Get usage statistics."""
305
- storage = StatsStorage()
306
-
307
- # Get total stats
308
- total = storage.get_total_usage(days=days)
309
-
310
- # Get stats by model (which includes provider info)
311
- by_model_raw = storage.get_usage_by_model(days=days)
312
-
313
- # Aggregate by provider
314
- by_provider: dict[str, dict] = {}
315
- by_model: dict[str, dict] = {}
316
-
317
- for record in by_model_raw:
318
- provider_name = record["provider"]
319
- model_name = record["model"]
320
- model_key = f"{provider_name}/{model_name}"
321
-
322
- # Filter if requested
323
- if provider and provider_name != provider:
324
- continue
325
- if model and model_name != model:
326
- continue
327
-
328
- # Aggregate by provider
329
- if provider_name not in by_provider:
330
- by_provider[provider_name] = {
331
- "total_tokens": 0,
332
- "prompt_tokens": 0,
333
- "completion_tokens": 0,
334
- "request_count": 0,
335
- }
336
-
337
- by_provider[provider_name]["total_tokens"] += record.get("total_tokens", 0) or 0
338
- by_provider[provider_name]["prompt_tokens"] += record.get("prompt_tokens", 0) or 0
339
- by_provider[provider_name]["completion_tokens"] += record.get("completion_tokens", 0) or 0
340
- by_provider[provider_name]["request_count"] += record.get("request_count", 0) or 0
341
-
342
- # Store by model
343
- by_model[model_key] = {
344
- "total_tokens": record.get("total_tokens", 0) or 0,
345
- "prompt_tokens": record.get("prompt_tokens", 0) or 0,
346
- "completion_tokens": record.get("completion_tokens", 0) or 0,
347
- "request_count": record.get("request_count", 0) or 0,
348
- "avg_latency_ms": record.get("avg_latency_ms", 0) or 0,
349
- }
350
-
351
- return StatsResponse(
352
- total_requests=total.get("request_count", 0) or 0,
353
- total_tokens=total.get("total_tokens", 0) or 0,
354
- prompt_tokens=total.get("prompt_tokens", 0) or 0,
355
- completion_tokens=total.get("completion_tokens", 0) or 0,
356
- by_provider=by_provider,
357
- by_model=by_model,
358
- )
@@ -10,8 +10,6 @@ from router_maestro.server.schemas.admin import (
10
10
  OAuthStatusResponse,
11
11
  PrioritiesResponse,
12
12
  PrioritiesUpdateRequest,
13
- StatsQuery,
14
- StatsResponse,
15
13
  )
16
14
  from router_maestro.server.schemas.openai import (
17
15
  ChatCompletionChoice,
@@ -39,8 +37,6 @@ __all__ = [
39
37
  "OAuthStatusResponse",
40
38
  "PrioritiesResponse",
41
39
  "PrioritiesUpdateRequest",
42
- "StatsQuery",
43
- "StatsResponse",
44
40
  # OpenAI schemas
45
41
  "ChatCompletionChoice",
46
42
  "ChatCompletionChunk",
@@ -66,22 +66,3 @@ class PrioritiesUpdateRequest(BaseModel):
66
66
 
67
67
  priorities: list[str] = Field(..., description="New priority list")
68
68
  fallback: dict | None = Field(default=None, description="Optional fallback config update")
69
-
70
-
71
- class StatsQuery(BaseModel):
72
- """Query parameters for stats."""
73
-
74
- days: int = Field(default=7, ge=1, le=365, description="Number of days to query")
75
- provider: str | None = Field(default=None, description="Filter by provider")
76
- model: str | None = Field(default=None, description="Filter by model")
77
-
78
-
79
- class StatsResponse(BaseModel):
80
- """Response for usage statistics."""
81
-
82
- total_requests: int = Field(default=0)
83
- total_tokens: int = Field(default=0)
84
- prompt_tokens: int = Field(default=0)
85
- completion_tokens: int = Field(default=0)
86
- by_provider: dict[str, dict] = Field(default_factory=dict)
87
- by_model: dict[str, dict] = Field(default_factory=dict)
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "router-maestro"
928
- version = "0.1.1"
928
+ version = "0.1.3"
929
929
  source = { editable = "." }
930
930
  dependencies = [
931
931
  { name = "aiosqlite" },
@@ -1,76 +0,0 @@
1
- """Token usage statistics command."""
2
-
3
- import asyncio
4
-
5
- import typer
6
- from rich.console import Console
7
- from rich.table import Table
8
-
9
- from router_maestro.cli.client import AdminClientError, get_admin_client
10
-
11
- console = Console()
12
-
13
-
14
- def stats(
15
- days: int = typer.Option(7, "--days", "-d", help="Number of days to show"),
16
- provider: str = typer.Option(None, "--provider", "-p", help="Filter by provider"),
17
- model: str = typer.Option(None, "--model", "-m", help="Filter by model"),
18
- ) -> None:
19
- """Show token usage statistics."""
20
- client = get_admin_client()
21
-
22
- try:
23
- data = asyncio.run(client.get_stats(days=days, provider=provider, model=model))
24
- except AdminClientError as e:
25
- console.print(f"[red]{e}[/red]")
26
- raise typer.Exit(1)
27
- except Exception as e:
28
- console.print(f"[red]Failed to get stats: {e}[/red]")
29
- raise typer.Exit(1)
30
-
31
- if data.get("total_requests", 0) == 0:
32
- console.print("[dim]No usage data available.[/dim]")
33
- return
34
-
35
- # Summary table
36
- console.print(f"\n[bold]Token Usage Summary (Last {days} Days)[/bold]\n")
37
-
38
- summary_table = Table(show_header=False, box=None)
39
- summary_table.add_column("Metric", style="cyan")
40
- summary_table.add_column("Value", style="green", justify="right")
41
-
42
- summary_table.add_row("Total Requests", f"{data.get('total_requests', 0):,}")
43
- summary_table.add_row("Total Tokens", f"{data.get('total_tokens', 0):,}")
44
- summary_table.add_row(" Prompt", f"{data.get('prompt_tokens', 0):,}")
45
- summary_table.add_row(" Completion", f"{data.get('completion_tokens', 0):,}")
46
-
47
- console.print(summary_table)
48
-
49
- # By model table
50
- by_model = data.get("by_model", {})
51
- if by_model:
52
- console.print("\n[bold]Usage by Model[/bold]\n")
53
-
54
- model_table = Table()
55
- model_table.add_column("Model", style="cyan")
56
- model_table.add_column("Provider", style="magenta")
57
- model_table.add_column("Requests", justify="right")
58
- model_table.add_column("Total Tokens", justify="right", style="green")
59
- model_table.add_column("Avg Latency", justify="right")
60
-
61
- for model_key, record in by_model.items():
62
- parts = model_key.split("/", 1)
63
- provider_name = parts[0] if len(parts) > 1 else "-"
64
- model_name = parts[1] if len(parts) > 1 else model_key
65
-
66
- avg_latency = record.get("avg_latency_ms")
67
- latency = f"{avg_latency:.0f} ms" if avg_latency else "-"
68
- model_table.add_row(
69
- model_name,
70
- provider_name,
71
- f"{record.get('request_count', 0):,}",
72
- f"{record.get('total_tokens', 0):,}",
73
- latency,
74
- )
75
-
76
- console.print(model_table)
@@ -1,14 +0,0 @@
1
- """Stats module for router-maestro."""
2
-
3
- from router_maestro.stats.heatmap import display_stats_summary, generate_heatmap
4
- from router_maestro.stats.storage import StatsStorage, UsageRecord
5
- from router_maestro.stats.tracker import RequestTimer, UsageTracker
6
-
7
- __all__ = [
8
- "StatsStorage",
9
- "UsageRecord",
10
- "UsageTracker",
11
- "RequestTimer",
12
- "generate_heatmap",
13
- "display_stats_summary",
14
- ]